diff --git a/.ci.yaml b/.ci.yaml index 239cce546881e..1687b69e18963 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -52,6 +52,8 @@ targets: recipe: engine_v2/engine_v2 properties: config_name: local_engine + # local_engine schedules a bunch of other builds, so it's likely to timeout waiting for those builds to run. + timeout: 180 - name: Linux linux_android_emulator_tests bringup: true @@ -231,7 +233,7 @@ targets: - name: Linux linux_fuchsia recipe: engine_v2/engine_v2 - timeout: 60 + timeout: 120 properties: release_build: "true" config_name: linux_fuchsia @@ -406,7 +408,7 @@ targets: - name: Linux clangd recipe: engine_v2/builder properties: - config_name: linux_unopt_debug_no_rbe + config_name: linux_clangd - name: Linux linux_unopt recipe: engine_v2/engine_v2 @@ -434,7 +436,7 @@ targets: shard: web_tests subshards: >- ["0", "1", "2", "3", "4", "5", "6", "7_last"] - timeout: 60 + timeout: 120 runIf: - DEPS - .ci.yaml @@ -503,22 +505,19 @@ targets: drone_dimensions: - os=Mac-13|Mac-14 - # Avoid using a Mac orchestrator to save ~5 minutes of Mac host time. - - name: Linux mac_clangd - recipe: engine_v2/engine_v2 - timeout: 90 - # Do not remove(https://github.com/flutter/flutter/issues/144644) - # Scheduler will fail to get the platform - drone_dimensions: - - os=Linux + - name: Mac clangd + recipe: engine_v2/builder properties: - config_name: mac_unopt_debug_no_rbe + config_name: mac_clangd + cpu: arm64 - name: Mac mac_unopt recipe: engine_v2/engine_v2 properties: config_name: mac_unopt add_recipes_cq: "true" + # Retry for flakes caused by https://github.com/flutter/flutter/issues/157636 + presubmit_max_attempts: "2" timeout: 120 - name: Mac mac_ios_engine diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c1c1d3d05f37b..ce1c48a3dcf3f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -18,13 +18,13 @@ If you need help, consider asking for advice on the #hackers-new channel on [Discord]. -[Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview -[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene -[test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests -[Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo +[Contributor Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview +[Tree Hygiene]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md +[test-exempt]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests +[Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style -[testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine +[testing the engine]: https://github.com/flutter/engine/blob/main/docs/testing/Testing-the-engine.md [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests -[breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes -[Discord]: https://github.com/flutter/flutter/wiki/Chat +[breaking change policy]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#handling-breaking-changes +[Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9688ddae25af1..79d1163182c5b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,10 +11,14 @@ updates: timezone: "America/Los_Angeles" labels: - "autosubmit" + groups: + all-github-actions: + patterns: [ "*" ] ignore: # ignore patch versions, just rely on minor in order to update fewer times - dependency-name: "github/codeql-action" update-types: ["version-update:semver-minor"] + - package-ecosystem: "pub" directory: "/lib/web_ui" schedule: diff --git a/.github/workflows/engine-cp.yml b/.github/workflows/engine-cp.yml index 028ad97245fe6..5f308f5317284 100644 --- a/.github/workflows/engine-cp.yml +++ b/.github/workflows/engine-cp.yml @@ -30,7 +30,7 @@ jobs: run: | echo "COMMIT_SHA=$(echo ${{ github.event.pull_request.merge_commit_sha }})" >> $GITHUB_ENV - name: Checkout Flutter Engine Repo - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: flutteractionsbot/engine path: engine diff --git a/.github/workflows/third_party_scan.yml b/.github/workflows/third_party_scan.yml index 6689fee43e9e4..a2ed8fb0cd1b7 100644 --- a/.github/workflows/third_party_scan.yml +++ b/.github/workflows/third_party_scan.yml @@ -21,17 +21,17 @@ jobs: contents: read steps: - name: "Checkout code" - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: persist-credentials: false - name: "setup python" - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b with: python-version: '3.7.7' # install the python version needed - name: "extract deps, find commit hash, pass to osv-scanner" run: python ci/scan_deps.py --output osv-lockfile-${{github.sha}}.json - name: "upload osv-scanner deps" - uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 + uses: actions/upload-artifact@v4 with: # use github.ref in name to avoid duplicated artifacts name: osv-lockfile-${{github.sha}} diff --git a/BUILD.gn b/BUILD.gn index 4eb42dfa45070..b5720381d1148 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -183,10 +183,10 @@ group("unittests") { # Compile all unittests targets if enabled. if (enable_unittests) { public_deps += [ + "//flutter/assets:assets_unittests", "//flutter/display_list:display_list_rendertests", "//flutter/display_list:display_list_unittests", "//flutter/flow:flow_unittests", - "//flutter/fml:fml_arc_unittests", "//flutter/fml:fml_unittests", "//flutter/lib/ui:ui_unittests", "//flutter/runtime:dart_plugin_registrant_unittests", @@ -196,6 +196,7 @@ group("unittests") { "//flutter/shell/platform/embedder:embedder_a11y_unittests", "//flutter/shell/platform/embedder:embedder_proctable_unittests", "//flutter/shell/platform/embedder:embedder_unittests", + "//flutter/testing:testing_unittests", "//flutter/testing/dart", "//flutter/testing/smoke_test_failure", "//flutter/third_party/tonic/tests:tonic_unittests", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6871fbd71d1c9..1fe14d36403df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -243,6 +243,7 @@ $ ./run_tests.py --variant=host_debug_unopt_arm64 --type=engine | Name | run_tests.py type | Description | | ---------------------------------------- | ------------------- | --------------------------------------------------------------- | | accessibility_unittests | engine | | +| assets_unittests | engine | | | client_wrapper_glfw_unittests | engine | | | client_wrapper_unittests | engine | | | common_cpp_core_unittests | engine | | @@ -256,7 +257,6 @@ $ ./run_tests.py --variant=host_debug_unopt_arm64 --type=engine | felt | n/a | The test runner for flutter web. See //lib/web_ui | | flow_unittests | engine | | | flutter_tester | dart | Launcher for engine dart tests. | -| fml_arc_unittests | engine | | | fml_unittests | engine | Unit tests for //fml | | framework_common_unittests | engine(mac) | | | gpu_surface_metal_unittests | engine(mac) | | diff --git a/DEPS b/DEPS index d272147a907c1..d7bde6d4372c3 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': '3a081993e2a740118839621e0b0fd206e045987e', + 'skia_revision': 'bd7d952398d511dbba69346c912eccd445126d5d', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. @@ -56,28 +56,26 @@ 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': '86ac62245577068c94e6f88b1595c33ff314fad6', + 'dart_revision': '1a28e6c86b09f1c83365f54388c32ed97c9e9b31', # 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': '93883fde36ac158fd415dcd6dbd387dcfd928d3c', - 'dart_boringssl_gen_rev': 'fef055e8d2749b82c79c8f043be1cbe5e8e4b40c', - 'dart_boringssl_rev': '2db0eb3f96a5756298dcd7f9319e56a98585bd10', - 'dart_browser_launcher_rev': 'e5fc5d488eb5038bfec2a6690c72ab8dd353e101', - 'dart_clock_rev': '7956d60042f4ea979c4554d43eeb57d087627869', - 'dart_collection_rev': '887b826b50f48d6a9cd2c0684aa353e8e3a0fad0', - 'dart_devtools_rev': 'dcef4f6efe986f110f55dbbe7f3731802e86690a', + 'dart_boringssl_rev': 'bb13a96931dd90d6570fa6151e19ef426d5c641f', + 'dart_core_rev': '7f9f597e64fa52faebd3c0a2214f61a7b081174d', + 'dart_devtools_rev': '3e5327a02693b1405359dc5322d7f0a40151b9b7', + 'dart_http_rev': '79470d014b467f01b0e7c5b63ab6c86b22dec8db', 'dart_libprotobuf_rev': '24487dd1045c7f3d64a21f38a3f0c06cc4cf2edb', 'dart_perfetto_rev': '13ce0c9e13b0940d2476cd0cff2301708a9a2e2b', 'dart_protobuf_gn_rev': 'ca669f79945418f6229e4fef89b666b2a88cbb10', - 'dart_protobuf_rev': 'ccf104dbc36929c0f8708285d5f3a8fae206343e', - 'dart_pub_rev': '1d7b0d9a35be9cff5d071f6cf17fcdcde2b7ecc5', - 'dart_tools_rev': 'f882de9ba86712003728d4663e1b73a620d352b1', - 'dart_watcher_rev': '3b850778ad0b62db3aa2cfe48832870c2461db30', - 'dart_web_rev': '8478cd27d574249eca3d41f9135458dfda2762c8', + 'dart_protobuf_rev': 'da7279c56734cffed4deb1e3a6f93bdcefccf6b8', + 'dart_pub_rev': '30bfc439fedba1ee3daadcf542f1483479bc4909', + 'dart_tools_rev': '223daf5300a5e3f6e09b7e24a5fda1009d6708f7', + 'dart_watcher_rev': 'bc44e6f6b85972e516c44b9ae6c042b4cbd7c72b', + 'dart_web_rev': 'bdf112ec64d28285cc29c6c78274ff3a827bb79b', 'dart_webdev_rev': '5f30c560dc4e3df341356c43ec1a766ee6b74a7c', - 'dart_webkit_inspection_protocol_rev': 'b459c427b74bf5e0919a083a97a167fb74d8bff1', - 'dart_yaml_edit_rev': '35f4248c7bbba289b3899fa55486e2f31ef1a8c5', + 'dart_webkit_inspection_protocol_rev': 'effa75205516757795683d527c3dea9546eb0c32', + 'dart_yaml_edit_rev': 'fbdc70acc164af187772e013a2e1364cd05b88dc', 'ocmock_rev': 'c4ec0e3a7a9f56cfdbd0aa01f4f97bb4b75c5ef8', # v3.7.1 @@ -151,27 +149,14 @@ vars = { "upstream_abseil-cpp": "https://github.com/abseil/abseil-cpp.git", "upstream_angle": "https://github.com/google/angle.git", "upstream_archive": "https://github.com/brendan-duncan/archive.git", - "upstream_args": "https://github.com/dart-lang/args.git", - "upstream_async": "https://github.com/dart-lang/async.git", - "upstream_bazel_worker": "https://github.com/dart-lang/bazel_worker.git", "upstream_benchmark": "https://github.com/google/benchmark.git", - "upstream_boolean_selector": "https://github.com/dart-lang/boolean_selector.git", - "upstream_boringssl_gen": "https://github.com/dart-lang/boringssl_gen.git", "upstream_boringssl": "https://github.com/openssl/openssl.git", "upstream_brotli": "https://github.com/google/brotli.git", - "upstream_browser_launcher": "https://github.com/dart-lang/browser_launcher.git", "upstream_buildroot": "https://github.com/flutter/buildroot.git", - "upstream_cli_util": "https://github.com/dart-lang/cli_util.git", - "upstream_clock": "https://github.com/dart-lang/clock.git", - "upstream_collection": "https://github.com/dart-lang/collection.git", - "upstream_convert": "https://github.com/dart-lang/convert.git", - "upstream_crypto": "https://github.com/dart-lang/crypto.git", - "upstream_csslib": "https://github.com/dart-lang/csslib.git", "upstream_dart_style": "https://github.com/dart-lang/dart_style.git", "upstream_dartdoc": "https://github.com/dart-lang/dartdoc.git", "upstream_equatable": "https://github.com/felangel/equatable.git", "upstream_ffi": "https://github.com/dart-lang/ffi.git", - "upstream_fixnum": "https://github.com/dart-lang/fixnum.git", "upstream_flatbuffers": "https://github.com/google/flatbuffers.git", "upstream_freetype2": "https://gitlab.freedesktop.org/freetype/freetype.git", "upstream_gcloud": "https://github.com/dart-lang/gcloud.git", @@ -181,16 +166,13 @@ vars = { "upstream_googletest": "https://github.com/google/googletest.git", "upstream_gtest-parallel": "https://github.com/google/gtest-parallel.git", "upstream_harfbuzz": "https://github.com/harfbuzz/harfbuzz.git", - "upstream_html": "https://github.com/dart-lang/html.git", "upstream_http_multi_server": "https://github.com/dart-lang/http_multi_server.git", - "upstream_http_parser": "https://github.com/dart-lang/http_parser.git", "upstream_http": "https://github.com/dart-lang/http.git", "upstream_icu": "https://github.com/unicode-org/icu.git", "upstream_intl": "https://github.com/dart-lang/intl.git", "upstream_imgui": "https://github.com/ocornut/imgui.git", "upstream_inja": "https://github.com/pantor/inja.git", "upstream_json": "https://github.com/nlohmann/json.git", - "upstream_json_rpc_2": "https://github.com/dart-lang/json_rpc_2.git", "upstream_libcxx": "https://github.com/llvm-mirror/libcxx.git", "upstream_libcxxabi": "https://github.com/llvm-mirror/libcxxabi.git", "upstream_libexpat": "https://github.com/libexpat/libexpat.git", @@ -199,15 +181,11 @@ vars = { "upstream_libtess2": "https://github.com/memononen/libtess2.git", "upstream_libwebp": "https://chromium.googlesource.com/webm/libwebp.git", "upstream_leak_tracker": "https://github.com/dart-lang/leak_tracker.git", - "upstream_logging": "https://github.com/dart-lang/logging.git", "upstream_markdown": "https://github.com/dart-lang/markdown.git", - "upstream_matcher": "https://github.com/dart-lang/matcher.git", "upstream_mockito": "https://github.com/dart-lang/mockito.git", "upstream_ocmock": "https://github.com/erikdoe/ocmock.git", "upstream_package_config": "https://github.com/dart-lang/package_config.git", "upstream_packages": "https://github.com/flutter/packages.git", - "upstream_path": "https://github.com/dart-lang/path.git", - "upstream_platform": "https://github.com/google/platform.dart.git", "upstream_pool": "https://github.com/dart-lang/pool.git", "upstream_process_runner": "https://github.com/google/process_runner.git", "upstream_process": "https://github.com/google/process.dart.git", @@ -233,7 +211,6 @@ vars = { "upstream_term_glyph": "https://github.com/dart-lang/term_glyph.git", "upstream_test_reflective_loader": "https://github.com/dart-lang/test_reflective_loader.git", "upstream_test": "https://github.com/dart-lang/test.git", - "upstream_typed_data": "https://github.com/dart-lang/typed_data.git", "upstream_usage": "https://github.com/dart-lang/usage.git", "upstream_vector_math": "https://github.com/google/vector_math.dart.git", "upstream_VulkanMemoryAllocator": "https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git", @@ -250,7 +227,7 @@ vars = { # The version / instance id of the cipd:chromium/fuchsia/test-scripts which # will be used altogether with fuchsia-sdk to setup the build / test # environment. - 'fuchsia_test_scripts_version': '_fkA2GjLQH4bc_n2pbwmUEsGfjReXWXHt0zA1jm8EEkC', + 'fuchsia_test_scripts_version': 'r9Dc5VRF6sE3pJH20k2d1Yko3xSlwljH_nuw7O8vcb4C', # The version / instance id of the cipd:chromium/fuchsia/gn-sdk which will be # used altogether with fuchsia-sdk to generate gn based build rules. @@ -276,7 +253,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'c8f93f25a19cefaaeb64d4323e2fc8c9ccd20479', + 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'cc9bcddf1524812c80ef741191d5db7469e705de', 'src/flutter/third_party/depot_tools': Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '580b4ff3f5cd0dcaa2eacda28cefe0f45320e8f7', @@ -306,7 +283,7 @@ deps = { Var('chromium_git') + '/external/github.com/google/flatbuffers' + '@' + '0a80646371179f8a7a5c1f42c31ee1d44dcf6709', 'src/flutter/third_party/icu': - Var('chromium_git') + '/chromium/deps/icu.git' + '@' + '9408c6fd4a39e6fef0e1c4077602e1c83b15f3fb', + Var('chromium_git') + '/chromium/deps/icu.git' + '@' + '4239b1559d11d4fa66c100543eda4161e060311e', 'src/flutter/third_party/gtest-parallel': Var('chromium_git') + '/external/github.com/google/gtest-parallel' + '@' + '38191e2733d7cbaeaef6a3f1a942ddeb38a2ad14', @@ -317,9 +294,6 @@ deps = { 'src/flutter/third_party/googletest': Var('chromium_git') + '/external/github.com/google/googletest' + '@' + '7f036c5563af7d0329f20e8bb42effb04629f0c0', - 'src/flutter/third_party/boringssl': - Var('dart_git') + '/boringssl_gen.git' + '@' + Var('dart_boringssl_gen_rev'), - 'src/flutter/third_party/brotli': Var('skia_git') + '/external/github.com/google/brotli.git' + '@' + '350100a5bb9d9671aca85213b2ec7a70a361b0cd', @@ -349,97 +323,43 @@ deps = { Var('chromium_git') + '/external/github.com/WebAssembly/binaryen.git@93883fde36ac158fd415dcd6dbd387dcfd928d3c', 'src/flutter/third_party/dart/third_party/devtools': - {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:dcef4f6efe986f110f55dbbe7f3731802e86690a'}]}, - - 'src/flutter/third_party/dart/third_party/pkg/args': - Var('dart_git') + '/args.git@09c0fca1785c9df39288a48f767994eed80bed40', - - 'src/flutter/third_party/dart/third_party/pkg/async': - Var('dart_git') + '/async.git@5f70a996f673d625e3502597084653686c3e754c', - - 'src/flutter/third_party/dart/third_party/pkg/bazel_worker': - Var('dart_git') + '/bazel_worker.git@aa3cc9e826350b960e0c5a67e6065bcedba8b0ac', - - 'src/flutter/third_party/dart/third_party/pkg/boolean_selector': - Var('dart_git') + '/boolean_selector.git@d6c7c36ae1111f11cc24306d71d3ab2deea8fa68', - - 'src/flutter/third_party/dart/third_party/pkg/browser_launcher': - Var('dart_git') + '/browser_launcher.git' + '@' + Var('dart_browser_launcher_rev'), - - 'src/flutter/third_party/dart/third_party/pkg/cli_util': - Var('dart_git') + '/cli_util.git@c36b3941e38092d6d6f87ac27d9e88f153d3ac38', - - 'src/flutter/third_party/dart/third_party/pkg/clock': - Var('dart_git') + '/clock.git' + '@' + Var('dart_clock_rev'), - - 'src/flutter/third_party/dart/third_party/pkg/collection': - Var('dart_git') + '/collection.git' + '@' + Var('dart_collection_rev'), - - 'src/flutter/third_party/dart/third_party/pkg/convert': - Var('dart_git') + '/convert.git@d361833e117cb2438d2a2a6d0b0acb28ff0910fb', - - 'src/flutter/third_party/dart/third_party/pkg/crypto': - Var('dart_git') + '/crypto.git@3d26ef4cf22d4b218ba30e616544ad3cf52f64a1', + {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:3e5327a02693b1405359dc5322d7f0a40151b9b7'}]}, - 'src/flutter/third_party/dart/third_party/pkg/csslib': - Var('dart_git') + '/csslib.git@a3700b05bbcc42782e8a7024790dbf019d89c249', + 'src/flutter/third_party/dart/third_party/pkg/core': + Var('dart_git') + '/core.git' + '@' + Var('dart_core_rev'), 'src/flutter/third_party/dart/third_party/pkg/dart_style': - Var('dart_git') + '/dart_style.git@5d35f4d829ffb8532d345d95d3e9504ae6cd839e', + Var('dart_git') + '/dart_style.git@1de89eb3bd340315f9ff5f2afc319cc1d6131b8d', 'src/flutter/third_party/dart/third_party/pkg/dartdoc': - Var('dart_git') + '/dartdoc.git@80c6f18f34b387d4b9ce89ddd2e3049093335f9d', - - 'src/flutter/third_party/dart/third_party/pkg/fixnum': - Var('dart_git') + '/fixnum.git@83293b8ed86ccd574a94fcf4a2da43f31c1b43e0', + Var('dart_git') + '/dartdoc.git@c7f11603effa88ddacabfd555993f322fca8b3fe', 'src/flutter/third_party/dart/third_party/pkg/glob': - Var('dart_git') + '/glob.git@00a9c82d31c01ae88ec9ae4021d842e9b832aa52', - - 'src/flutter/third_party/dart/third_party/pkg/html': - Var('dart_git') + '/html.git@6d3bc86cf2ab530ef3fa5f84b5980dc318a02af4', + Var('dart_git') + '/glob.git@994191a107b99a1911a3ef52ca238cd9305c8d37', 'src/flutter/third_party/dart/third_party/pkg/http': - Var('dart_git') + '/http.git@5e2281edd25f9addbf26242a0658d8f2dfa1134b', + Var('dart_git') + '/http.git' + '@' + Var('dart_http_rev'), 'src/flutter/third_party/dart/third_party/pkg/http_multi_server': - Var('dart_git') + '/http_multi_server.git@e7515b5896b83d522189802a1e14e103e19426c0', - - 'src/flutter/third_party/dart/third_party/pkg/http_parser': - Var('dart_git') + '/http_parser.git@23d775898ee90be9daf3297e298a8869bc755d84', + Var('dart_git') + '/http_multi_server.git@f6a748819139b8cbf513d5fc36b10676b0cb066f', 'src/flutter/third_party/dart/third_party/pkg/intl': Var('dart_git') + '/intl.git@5d65e3808ce40e6282e40881492607df4e35669f', - 'src/flutter/third_party/dart/third_party/pkg/json_rpc_2': - Var('dart_git') + '/json_rpc_2.git@c9b616bded8cdb5bfdc836ba7648afa6aba40062', - 'src/flutter/third_party/dart/third_party/pkg/leak_tracker': Var('dart_git') + '/leak_tracker.git@f5620600a5ce1c44f65ddaa02001e200b096e14c', - 'src/flutter/third_party/dart/third_party/pkg/logging': - Var('dart_git') + '/logging.git@6fa056098ceca03d399bff64592822b2ae5dee6e', - 'src/flutter/third_party/dart/third_party/pkg/markdown': - Var('dart_git') + '/markdown.git@d53feae0760a4f0aae5ffdfb12d8e6acccf14b40', - - 'src/flutter/third_party/dart/third_party/pkg/matcher': - Var('dart_git') + '/matcher.git@31f13583630e093731c8cf2b843c14196d748c5c', - - 'src/flutter/third_party/dart/third_party/pkg/mockito': - Var('dart_git') + '/mockito.git@57d484f9b8e7f6a504966a901174358a42fa932a', + Var('dart_git') + '/markdown.git@4d5dbc659955973902f2585c54e94d453532db70', 'src/flutter/third_party/dart/third_party/pkg/native': - Var('dart_git') + '/native.git@659511886501bcce638c3966590df04984909ef0', + Var('dart_git') + '/native.git@8ea1a9db0af42933eb22334c4506ca464d7237e9', 'src/flutter/third_party/dart/third_party/pkg/package_config': - Var('dart_git') + '/package_config.git@bafff8e90be25e1985f7e3ee40ea1d22571a93e6', - - 'src/flutter/third_party/dart/third_party/pkg/path': - Var('dart_git') + '/path.git@e969f42ed112dd702a9453beb9df6c12ae2d3805', + Var('dart_git') + '/package_config.git@76f2f6c245451da1fa24d7bbb00251b909e729a5', 'src/flutter/third_party/dart/third_party/pkg/pool': - Var('dart_git') + '/pool.git@7bfc71b39742753a88688e56e55a828a2f5dc0bf', + Var('dart_git') + '/pool.git@f85209d83cb0aa3c5612ed80de32df51ba580abd', 'src/flutter/third_party/dart/third_party/pkg/protobuf': Var('dart_git') + '/protobuf.git' + '@' + Var('dart_protobuf_rev'), @@ -448,47 +368,44 @@ deps = { Var('dart_git') + '/pub.git' + '@' + Var('dart_pub_rev'), 'src/flutter/third_party/dart/third_party/pkg/pub_semver': - Var('dart_git') + '/pub_semver.git@8cce9d00431b6653026cdfcf6cf8548078c56f02', + Var('dart_git') + '/pub_semver.git@8e9fcb9d3f89f06022387f906da4d380688f935c', 'src/flutter/third_party/dart/third_party/pkg/shelf': - Var('dart_git') + '/shelf.git@f5600534e3e49ebed02e1e14ec82553958d86f36', + Var('dart_git') + '/shelf.git@2b5b683e78f5cc84e479a43297fd7b5489d7db02', 'src/flutter/third_party/dart/third_party/pkg/source_maps': - Var('dart_git') + '/source_maps.git@17695e81d9ad129d20effd3d5c4f1cfa03f5add8', + Var('dart_git') + '/source_maps.git@198d32bbde2f5736c04dfbab306a17096fd1648b', 'src/flutter/third_party/dart/third_party/pkg/source_span': - Var('dart_git') + '/source_span.git@ec100b7f12e9d36d2cdb3c369fefde736de4a550', + Var('dart_git') + '/source_span.git@22a243eb50d926935a8a1300a49af6d2988c3ae6', 'src/flutter/third_party/dart/third_party/pkg/sse': - Var('dart_git') + '/sse.git@af2c5c572a8da6d2f7551b80d75121f2a38a4c79', + Var('dart_git') + '/sse.git@b97dc3ad9a58a454dc5ffe5b3ec814dadc389e32', 'src/flutter/third_party/dart/third_party/pkg/stack_trace': - Var('dart_git') + '/stack_trace.git@115bcd9591d251dab7a5ad518655c2124a1cc525', + Var('dart_git') + '/stack_trace.git@b660cfa444d46bcceb3a0eacbd6a2a7829da11de', 'src/flutter/third_party/dart/third_party/pkg/stream_channel': - Var('dart_git') + '/stream_channel.git@f4407168b275fcde9187baefd7dbce76d0992825', + Var('dart_git') + '/stream_channel.git@71fe6dd315e7f451466da80f7af2661cd28aaaea', 'src/flutter/third_party/dart/third_party/pkg/string_scanner': - Var('dart_git') + '/string_scanner.git@084b201c54b168aced178fff41fce71e3869ae42', + Var('dart_git') + '/string_scanner.git@77de235c061a09264cae920e52939787325c8cae', 'src/flutter/third_party/dart/third_party/pkg/tar': Var('dart_git') + '/external/github.com/simolus3/tar.git@5a1ea943e70cdf3fa5e1102cdbb9418bd9b4b81a', 'src/flutter/third_party/dart/third_party/pkg/term_glyph': - Var('dart_git') + '/term_glyph.git@19d8c08a4e81122639129c62049896021910c932', + Var('dart_git') + '/term_glyph.git@9ed8ed96fdd84cb7b72ee1be3e86010969fa95d4', 'src/flutter/third_party/dart/third_party/pkg/test': - Var('dart_git') + '/test.git@8e8a83607d90a7a6813fa378b2d1962a2fc0d44b', + Var('dart_git') + '/test.git@dc0f8ea4d09aabb0fed16daea3d4653c8f967b02', 'src/flutter/third_party/dart/third_party/pkg/test_reflective_loader': - Var('dart_git') + '/test_reflective_loader.git@598af2f503955020af0eaa82558d574a03934078', + Var('dart_git') + '/test_reflective_loader.git@faade6299d1823f0d062eb5e98f3b440ddcea7c6', 'src/flutter/third_party/dart/third_party/pkg/tools': Var('dart_git') + '/tools.git' + '@' + Var('dart_tools_rev'), - 'src/flutter/third_party/dart/third_party/pkg/typed_data': - Var('dart_git') + '/typed_data.git@6abfafdcf661cd8a814619d7e2a3e99edb3a3862', - 'src/flutter/third_party/dart/third_party/pkg/watcher': Var('dart_git') + '/watcher.git' + '@' + Var('dart_watcher_rev'), @@ -496,7 +413,7 @@ deps = { Var('dart_git') + '/web.git' + '@' + Var('dart_web_rev'), 'src/flutter/third_party/dart/third_party/pkg/web_socket_channel': - Var('dart_git') + '/web_socket_channel.git@40aa29f1d2167467f5934d755891a8beb62a1239', + Var('dart_git') + '/web_socket_channel.git@7a2039fd7c4c656ab27850926f89103b5f188dab', 'src/flutter/third_party/dart/third_party/pkg/webdev': Var('dart_git') + '/webdev.git' + '@' + Var('dart_webdev_rev'), @@ -505,7 +422,7 @@ deps = { Var('dart_git') + '/external/github.com/google/webkit_inspection_protocol.dart.git' + '@' + Var('dart_webkit_inspection_protocol_rev'), 'src/flutter/third_party/dart/third_party/pkg/yaml': - Var('dart_git') + '/yaml.git@e773005ab84e1b4d24132b0a687be7f9a3bfda15', + Var('dart_git') + '/yaml.git@2a3727288a9336b6f9b7c5236657414ce1ee5d8a', 'src/flutter/third_party/dart/third_party/pkg/yaml_edit': Var('dart_git') + '/yaml_edit.git' + '@' + Var('dart_yaml_edit_rev'), @@ -661,17 +578,17 @@ deps = { 'src/flutter/third_party/pyyaml': Var('flutter_git') + '/third_party/pyyaml.git' + '@' + '03c67afd452cdff45b41bfe65e19a2fb5b80a0e8', - 'src/flutter/third_party/swiftshader': - Var('swiftshader_git') + '/SwiftShader.git' + '@' + '2fa7e9b99ae4e70ea5ae2cc9c8d3afb43391384f', + 'src/flutter/third_party/swiftshader': + Var('swiftshader_git') + '/SwiftShader.git' + '@' + '2fa7e9b99ae4e70ea5ae2cc9c8d3afb43391384f', - 'src/flutter/third_party/angle': - Var('chromium_git') + '/angle/angle.git' + '@' + '6a09e41ce6ea8c93524faae1a925eb01562f53b1', + 'src/flutter/third_party/angle': + Var('chromium_git') + '/angle/angle.git' + '@' + '6a09e41ce6ea8c93524faae1a925eb01562f53b1', - 'src/flutter/third_party/vulkan_memory_allocator': - Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator' + '@' + '7de5cc00de50e71a3aab22dea52fbb7ff4efceb6', + 'src/flutter/third_party/vulkan_memory_allocator': + Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator' + '@' + '7de5cc00de50e71a3aab22dea52fbb7ff4efceb6', - 'src/flutter/third_party/abseil-cpp': - Var('flutter_git') + '/third_party/abseil-cpp.git' + '@' + 'ff6504dc527b25fef0f3c531e7dba0ed6b69c162', + 'src/flutter/third_party/abseil-cpp': + Var('flutter_git') + '/third_party/abseil-cpp.git' + '@' + 'ff6504dc527b25fef0f3c531e7dba0ed6b69c162', # Dart packages 'src/flutter/third_party/pkg/archive': @@ -698,9 +615,6 @@ deps = { 'src/flutter/third_party/pkg/node_preamble': Var('flutter_git') + '/third_party/node_preamble.dart.git' + '@' + '47245865175929ec452d8058e563c267b64c3d64', # 2.0.2 - 'src/flutter/third_party/pkg/platform': - Var('dart_git') + '/platform.dart' + '@' + '1ffad63428bbd1b3ecaa15926bacfb724023648c', # 3.1.0 - 'src/flutter/third_party/pkg/process': Var('dart_git') + '/process.dart' + '@' + '0c9aeac86dcc4e3a6cf760b76fed507107e244d5', # 4.2.1 @@ -810,7 +724,7 @@ deps = { }, 'src/flutter/prebuilts/emsdk': { - 'url': Var('skia_git') + '/external/github.com/emscripten-core/emsdk.git' + '@' + 'a896e3d066448b3530dbcaa48869fafefd738f57', + 'url': Var('skia_git') + '/external/github.com/emscripten-core/emsdk.git' + '@' + '2514ec738de72cebbba7f4fdba0cf2fabcb779a5', 'condition': 'download_emsdk', }, @@ -956,7 +870,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'avSAUwQTf5xVGuZQUn5L9VBcj4Vow6PtZTQz2GEB4aUC' + 'version': '5taAI9-tnFN84ZJvrs2E-sXIhxas7Bw7jxCrLtzcnaEC' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', @@ -1006,7 +920,7 @@ deps = { 'packages': [ { 'package': 'flutter/flutter_font_fallbacks', - 'version': '91f2de34d4a309816a182ce7552201d95388e2e0bceea951a6dd04693ed6fe30' + 'version': '44bd38be0bc8c189a397ca6dd6f737746a9e0c6117b96a8f84f1edf6acd1206b' } ], 'dep_type': 'cipd', diff --git a/analysis_options.yaml b/analysis_options.yaml index 771f8790dde4e..f06dc36175322 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -23,6 +23,9 @@ analyzer: - third_party - shell/platform/fuchsia +formatter: + page_width: 100 + linter: rules: # This list is derived from the list of all available lints located at @@ -221,7 +224,6 @@ linter: - unnecessary_to_list_in_spreads - unreachable_from_main - unrelated_type_equality_checks - - unsafe_html - use_build_context_synchronously - use_colored_box # - use_decorated_box # leads to bugs: DecoratedBox and Container are not equivalent (Container inserts extra padding) diff --git a/assets/BUILD.gn b/assets/BUILD.gn index eefdaf5eb02c7..c470cb6971c3f 100644 --- a/assets/BUILD.gn +++ b/assets/BUILD.gn @@ -2,6 +2,13 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//flutter/common/config.gni") +import("//flutter/testing/testing.gni") + +if (is_fuchsia) { + import("//flutter/tools/fuchsia/gn-sdk/src/gn_configs.gni") +} + source_set("assets") { sources = [ "asset_manager.cc", @@ -9,12 +16,43 @@ source_set("assets") { "asset_resolver.h", "directory_asset_bundle.cc", "directory_asset_bundle.h", + "native_assets.cc", + "native_assets.h", ] deps = [ "//flutter/common", "//flutter/fml", + "//flutter/third_party/rapidjson", ] public_configs = [ "//flutter:config" ] } + +test_fixtures("assets_fixtures") { + fixtures = [] +} + +if (enable_unittests) { + executable("assets_unittests") { + testonly = true + + sources = [ "native_assets_unittests.cc" ] + + deps = [ + ":assets", + ":assets_fixtures", + "//flutter/testing", + ] + + if (!defined(defines)) { + defines = [] + } + + # This is needed for //flutter/third_party/googletest for linking zircon + # symbols. + if (is_fuchsia) { + libs = [ "${fuchsia_arch_root}/sysroot/lib/libzircon.so" ] + } + } +} diff --git a/assets/native_assets.cc b/assets/native_assets.cc new file mode 100644 index 0000000000000..afdf7ad86f549 --- /dev/null +++ b/assets/native_assets.cc @@ -0,0 +1,136 @@ +// 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. + +#include "assets/native_assets.h" + +#include "flutter/fml/build_config.h" +#include "rapidjson/document.h" + +namespace flutter { + +#if defined(FML_ARCH_CPU_ARMEL) +#define kTargetArchitectureName "arm" +#elif defined(FML_ARCH_CPU_ARM64) +#define kTargetArchitectureName "arm64" +#elif defined(FML_ARCH_CPU_X86) +#define kTargetArchitectureName "ia32" +#elif defined(FML_ARCH_CPU_X86_64) +#define kTargetArchitectureName "x64" +#else +#error Target architecture detection failed. +#endif + +#if defined(FML_OS_ANDROID) +#define kTargetOperatingSystemName "android" +#elif defined(OS_FUCHSIA) +#define kTargetOperatingSystemName "fuchsia" +#elif defined(FML_OS_LINUX) +#define kTargetOperatingSystemName "linux" +#elif defined(FML_OS_IOS) || defined(FML_OS_IOS_SIMULATOR) +#define kTargetOperatingSystemName "ios" +#elif defined(FML_OS_MACOSX) +#define kTargetOperatingSystemName "macos" +#elif defined(FML_OS_WIN) +#define kTargetOperatingSystemName "windows" +#else +#error Target operating system detection failed. +#endif + +#define kTarget kTargetOperatingSystemName "_" kTargetArchitectureName + +void NativeAssetsManager::RegisterNativeAssets(const uint8_t* manifest, + size_t manifest_size) { + parsed_mapping_.clear(); + + rapidjson::Document document; + static_assert(sizeof(decltype(document)::Ch) == sizeof(uint8_t), ""); + document.Parse(reinterpret_cast(manifest), + manifest_size); + if (document.HasParseError()) { + FML_DLOG(WARNING) << "NativeAssetsManifest.json is malformed."; + return; + } + if (!document.IsObject()) { + FML_DLOG(WARNING) << "NativeAssetsManifest.json is malformed."; + return; + } + auto native_assets = document.FindMember("native-assets"); + if (native_assets == document.MemberEnd() || + !native_assets->value.IsObject()) { + FML_DLOG(WARNING) << "NativeAssetsManifest.json is malformed."; + return; + } + auto mapping = native_assets->value.FindMember(kTarget); + if (mapping == native_assets->value.MemberEnd() || + !mapping->value.IsObject()) { + FML_DLOG(WARNING) << "NativeAssetsManifest.json is malformed."; + return; + } + for (auto entry = mapping->value.MemberBegin(); + entry != mapping->value.MemberEnd(); entry++) { + std::vector parsed_path; + entry->name.GetString(); + auto& value = entry->value; + if (!value.IsArray()) { + FML_DLOG(WARNING) << "NativeAssetsManifest.json is malformed."; + continue; + } + for (const auto& element : value.GetArray()) { + if (!element.IsString()) { + FML_DLOG(WARNING) << "NativeAssetsManifest.json is malformed."; + continue; + } + parsed_path.push_back(element.GetString()); + } + parsed_mapping_[entry->name.GetString()] = std::move(parsed_path); + } +} + +void NativeAssetsManager::RegisterNativeAssets( + const std::shared_ptr& asset_manager) { + std::unique_ptr manifest_mapping = + asset_manager->GetAsMapping("NativeAssetsManifest.json"); + if (manifest_mapping == nullptr) { + FML_DLOG(WARNING) + << "Could not find NativeAssetsManifest.json in the asset store."; + return; + } + + RegisterNativeAssets(manifest_mapping->GetMapping(), + manifest_mapping->GetSize()); +} + +std::vector NativeAssetsManager::LookupNativeAsset( + std::string_view asset_id) { + // Cpp17 does not support unordered_map lookup with std::string_view on a + // std::string key. + std::string as_string = std::string(asset_id); + if (parsed_mapping_.find(as_string) == parsed_mapping_.end()) { + return std::vector(); + } + return parsed_mapping_[as_string]; +} + +std::string NativeAssetsManager::AvailableNativeAssets() { + if (parsed_mapping_.empty()) { + return std::string("No available native assets."); + } + + std::string result; + result.append("Available native assets: "); + bool first = true; + for (const auto& n : parsed_mapping_) { + if (first) { + first = false; + } else { + result.append(", "); + } + result.append(n.first); + } + + result.append("."); + return result; +} + +} // namespace flutter diff --git a/assets/native_assets.h b/assets/native_assets.h new file mode 100644 index 0000000000000..4e3524948496e --- /dev/null +++ b/assets/native_assets.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef FLUTTER_ASSETS_NATIVE_ASSETS_H_ +#define FLUTTER_ASSETS_NATIVE_ASSETS_H_ + +#include +#include + +#include "flutter/assets/asset_manager.h" + +namespace flutter { + +// Parses the `NativeAssetsManifest.json` and provides a way to look up assets +// and the available assets for the callbacks that are registered to the Dart VM +// via the dart_api.h. +// +// The engine eagerly populates a native assets manager on startup. This native +// assets manager is stored in the `IsolateGroupData` so it can be accessed on +// the native assets callbacks registered in `InitDartFFIForIsolateGroup`. +class NativeAssetsManager { + public: + NativeAssetsManager() = default; + ~NativeAssetsManager() = default; + + // Reads the `NativeAssetsManifest.json` bundled in the Flutter application. + void RegisterNativeAssets(const uint8_t* manifest, size_t manifest_size); + void RegisterNativeAssets(const std::shared_ptr& asset_manager); + + // Looks up the asset path for [asset_id]. + // + // The asset path consists of a type, and an optional path. For example: + // `["system", "libsqlite3.so"]`. + std::vector LookupNativeAsset(std::string_view asset_id); + + // Lists the available asset ids. + // + // Used when a user tries to look up an asset with an ID that does not exist + // to report the list of available asset ids. + std::string AvailableNativeAssets(); + + private: + std::unordered_map> parsed_mapping_; + + NativeAssetsManager(const NativeAssetsManager&) = delete; + NativeAssetsManager(NativeAssetsManager&&) = delete; + NativeAssetsManager& operator=(const NativeAssetsManager&) = delete; + NativeAssetsManager& operator=(NativeAssetsManager&&) = delete; +}; + +} // namespace flutter + +#endif // FLUTTER_ASSETS_NATIVE_ASSETS_H_ diff --git a/assets/native_assets_unittests.cc b/assets/native_assets_unittests.cc new file mode 100644 index 0000000000000..006f72437c0d0 --- /dev/null +++ b/assets/native_assets_unittests.cc @@ -0,0 +1,90 @@ +// 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. + +#include "flutter/assets/native_assets.h" + +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +// Manifest containing all hosts on which this test is run. +// In practise, a manifest will only contain a single OS. +// A manifest might contain multiple architectures if the OS supports +// multi-arch apps. +const char* kTestManifest = R"({ + "format-version": [ + 1, + 0, + 0 + ], + "native-assets": { + "linux_arm64": { + "package:my_package/my_package_bindings_generated.dart": [ + "absolute", + "libmy_package.so" + ] + }, + "linux_x64": { + "package:my_package/my_package_bindings_generated.dart": [ + "absolute", + "libmy_package.so" + ] + }, + "macos_arm64": { + "package:my_package/my_package_bindings_generated.dart": [ + "absolute", + "my_package.framework/my_package" + ] + }, + "macos_x64": { + "package:my_package/my_package_bindings_generated.dart": [ + "absolute", + "my_package.framework/my_package" + ] + }, + "windows_x64": { + "package:my_package/my_package_bindings_generated.dart": [ + "absolute", + "my_package.dll" + ] + } + } +})"; + +TEST(NativeAssetsManagerTest, NoAvailableAssets) { + NativeAssetsManager manager; + std::string available_assets = manager.AvailableNativeAssets(); + ASSERT_EQ(available_assets, "No available native assets."); +} + +TEST(NativeAssetsManagerTest, NativeAssetsManifestParsing) { + NativeAssetsManager manager; + manager.RegisterNativeAssets(reinterpret_cast(kTestManifest), + strlen(kTestManifest)); + + std::string available_assets = manager.AvailableNativeAssets(); + ASSERT_EQ(available_assets, + "Available native assets: " + "package:my_package/my_package_bindings_generated.dart."); + + std::vector existing_asset = manager.LookupNativeAsset( + "package:my_package/my_package_bindings_generated.dart"); + ASSERT_EQ(existing_asset.size(), 2u); + ASSERT_EQ(existing_asset[0], "absolute"); +#if defined(FML_OS_MACOSX) + ASSERT_EQ(existing_asset[1], "my_package.framework/my_package"); +#elif defined(FML_OS_LINUX) + ASSERT_EQ(existing_asset[1], "libmy_package.so"); +#elif defined(FML_OS_WIN) + ASSERT_EQ(existing_asset[1], "my_package.dll"); +#endif + + std::vector non_existing_asset = + manager.LookupNativeAsset("non_existing_asset"); + ASSERT_EQ(non_existing_asset.size(), 0u); +} + +} // namespace testing +} // namespace flutter diff --git a/build_overrides/angle.gni b/build_overrides/angle.gni index 9fa8f46c0238a..2ee36b37e9593 100644 --- a/build_overrides/angle.gni +++ b/build_overrides/angle.gni @@ -31,8 +31,7 @@ angle_spirv_headers_dir = "//flutter/third_party/vulkan-deps/spirv-headers/src" angle_spirv_tools_dir = "//flutter/third_party/vulkan-deps/spirv-tools/src" angle_spirv_cross_dir = "//flutter/third_party/vulkan-deps/spirv-cross/src" angle_spirv_headers_dir = "//flutter/third_party/vulkan-deps/spirv-headers/src" -angle_vulkan_memory_allocator_dir = - "//flutter/third_party/vulkan_memory_allocator" +angle_vulkan_memory_allocator_dir = "//flutter/flutter_vma" # This is a general Chromium flag, but in the Flutter build only ANGLE needs it # so it is defined here. diff --git a/ci/bin/format.dart b/ci/bin/format.dart index b165dd5a7854c..88390bd826256 100644 --- a/ci/bin/format.dart +++ b/ci/bin/format.dart @@ -6,6 +6,7 @@ // // Run with --help for usage. +import 'dart:ffi'; import 'dart:io'; import 'package:args/args.dart'; @@ -312,17 +313,15 @@ class ClangFormatChecker extends FormatChecker { super.allFiles, super.messageCallback, }) { - /*late*/ String clangOs; - if (Platform.isLinux) { - clangOs = 'linux-x64'; - } else if (Platform.isMacOS) { - clangOs = 'mac-x64'; - } else if (Platform.isWindows) { - clangOs = 'windows-x64'; - } else { - throw FormattingException( - "Unknown operating system: don't know how to run clang-format here."); - } + final clangOs = switch (Abi.current()) { + Abi.linuxArm64 => 'linux-arm64', + Abi.linuxX64 => 'linux-x64', + Abi.macosArm64 => 'mac-arm64', + Abi.macosX64 => 'mac-x64', + Abi.windowsX64 => 'windows-x64', + (_) => throw FormattingException( + "Unknown operating system: don't know how to run clang-format here.") + }; clangFormat = File( path.join( srcDir.absolute.path, diff --git a/ci/builders/linux_android_debug_engine.json b/ci/builders/linux_android_debug_engine.json index 9dc92cbcf6a98..33f6efce3eb3e 100644 --- a/ci/builders/linux_android_debug_engine.json +++ b/ci/builders/linux_android_debug_engine.json @@ -8,46 +8,6 @@ "definition files." ], "builds": [ - { - "archives": [ - { - "name": "ci/android_jit_release_x86", - "type": "gcs", - "base_path": "out/ci/android_jit_release_x86/zip_archives/", - "include_paths": [ - "out/ci/android_jit_release_x86/zip_archives/android-x86-jit-release/artifacts.zip", - "out/ci/android_jit_release_x86/zip_archives/download.flutter.io" - ], - "realm": "production" - } - ], - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "use_rbe": true - }, - "gn": [ - "--target-dir", - "ci/android_jit_release_x86", - "--android", - "--android-cpu=x86", - "--runtime-mode=jit_release", - "--rbe", - "--no-goma" - ], - "name": "ci/android_jit_release_x86", - "description": "Produces jit-release mode artifacts to target x86 Android from a Linux host.", - "ninja": { - "config": "ci/android_jit_release_x86", - "targets": [ - "flutter", - "flutter/shell/platform/android:embedding_jars", - "flutter/shell/platform/android:abi_jars" - ] - } - }, { "archives": [ { diff --git a/ci/builders/linux_android_emulator.json b/ci/builders/linux_android_emulator.json index 3a1745ec18619..b60f608d11d2d 100644 --- a/ci/builders/linux_android_emulator.json +++ b/ci/builders/linux_android_emulator.json @@ -46,7 +46,7 @@ }, { "dependency": "avd_cipd_version", - "version": "build_id:8735216702926189361" + "version": "build_id:8733065022087935185" } ], "contexts": [ @@ -78,7 +78,7 @@ }, { "dependency": "avd_cipd_version", - "version": "build_id:8735216702926189361" + "version": "build_id:8733065022087935185" } ], "contexts": [ diff --git a/ci/builders/linux_android_emulator_34.json b/ci/builders/linux_android_emulator_34.json index a9f7fb3fcb8d5..0a4f1f3aa3185 100644 --- a/ci/builders/linux_android_emulator_34.json +++ b/ci/builders/linux_android_emulator_34.json @@ -46,7 +46,7 @@ }, { "dependency": "avd_cipd_version", - "version": "build_id:8740267484269553649" + "version": "build_id:8733065022087935185" } ], "contexts": [ @@ -78,7 +78,7 @@ }, { "dependency": "avd_cipd_version", - "version": "build_id:8740267484269553649" + "version": "build_id:8733065022087935185" } ], "contexts": [ @@ -156,4 +156,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/ci/builders/linux_android_emulator_opengles_34.json b/ci/builders/linux_android_emulator_opengles_34.json index 8d3bfb423a105..e251d97e85514 100644 --- a/ci/builders/linux_android_emulator_opengles_34.json +++ b/ci/builders/linux_android_emulator_opengles_34.json @@ -46,7 +46,7 @@ }, { "dependency": "avd_cipd_version", - "version": "build_id:8740267484269553649" + "version": "build_id:8733065022087935185" } ], "contexts": [ @@ -78,7 +78,7 @@ }, { "dependency": "avd_cipd_version", - "version": "build_id:8740267484269553649" + "version": "build_id:8733065022087935185" } ], "contexts": [ @@ -94,4 +94,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/ci/builders/linux_unopt.json b/ci/builders/linux_unopt.json index 0835c0f16e167..3d7d1d5aa109e 100644 --- a/ci/builders/linux_unopt.json +++ b/ci/builders/linux_unopt.json @@ -270,52 +270,6 @@ } ] }, - { - "cas_archive": false, - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "use_rbe": true - }, - "gn": [ - "--target-dir", - "ci/android_jit_release_x86_test", - "--android", - "--android-cpu=x86", - "--runtime-mode=jit_release", - "--rbe", - "--no-goma" - ], - "name": "ci/android_jit_release_x86_test", - "description": "Produces jit-release mode artifacts to target x86 Android from a Linux host.", - "ninja": { - "config": "ci/android_jit_release_x86_test", - "targets": [ - "flutter", - "flutter/shell/platform/android:embedding_jars", - "flutter/shell/platform/android:abi_jars", - "flutter/shell/platform/android:robolectric_tests" - ] - }, - "tests": [ - { - "language": "python3", - "name": "Host Tests for android_jit_release_x86_test", - "script": "flutter/testing/run_tests.py", - "parameters": [ - "--variant", - "ci/android_jit_release_x86_test", - "--type", - "java", - "--engine-capture-core-dump", - "--android-variant", - "ci/android_jit_release_x86_test" - ] - } - ] - }, { "cas_archive": false, "drone_dimensions": [ diff --git a/ci/builders/linux_web_engine.json b/ci/builders/linux_web_engine.json index 7c350e1250f20..a47980e6dd244 100644 --- a/ci/builders/linux_web_engine.json +++ b/ci/builders/linux_web_engine.json @@ -201,94 +201,6 @@ ] } }, - { - "name": "web_tests/test_bundles/dart2wasm-html-html", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "generators": { - "tasks": [ - { - "name": "compile bundle dart2wasm-html-html", - "parameters": [ - "test", - "--compile", - "--bundle=dart2wasm-html-html" - ], - "scripts": [ - "flutter/lib/web_ui/dev/felt" - ] - } - ] - } - }, - { - "name": "web_tests/test_bundles/dart2wasm-html-ui", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "generators": { - "tasks": [ - { - "name": "compile bundle dart2wasm-html-ui", - "parameters": [ - "test", - "--compile", - "--bundle=dart2wasm-html-ui" - ], - "scripts": [ - "flutter/lib/web_ui/dev/felt" - ] - } - ] - } - }, - { - "name": "web_tests/test_bundles/dart2wasm-canvaskit-canvaskit", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "generators": { - "tasks": [ - { - "name": "compile bundle dart2wasm-canvaskit-canvaskit", - "parameters": [ - "test", - "--compile", - "--bundle=dart2wasm-canvaskit-canvaskit" - ], - "scripts": [ - "flutter/lib/web_ui/dev/felt" - ] - } - ] - } - }, - { - "name": "web_tests/test_bundles/dart2wasm-canvaskit-ui", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "generators": { - "tasks": [ - { - "name": "compile bundle dart2wasm-canvaskit-ui", - "parameters": [ - "test", - "--compile", - "--bundle=dart2wasm-canvaskit-ui" - ], - "scripts": [ - "flutter/lib/web_ui/dev/felt" - ] - } - ] - } - }, { "name": "web_tests/test_bundles/dart2wasm-skwasm-ui", "drone_dimensions": [ @@ -336,7 +248,7 @@ ], "tests": [ { - "name": "Linux run chrome-dart2js-html-engine suite", + "name": "Linux run chrome suites", "recipe": "engine_v2/tester_engine", "drone_dimensions": [ "device_type=none", @@ -348,7 +260,14 @@ }, "dependencies": [ "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-engine" + "web_tests/test_bundles/dart2js-html-engine", + "web_tests/test_bundles/dart2js-html-html", + "web_tests/test_bundles/dart2js-html-ui", + "web_tests/test_bundles/dart2js-canvaskit-canvaskit", + "web_tests/test_bundles/dart2js-canvaskit-ui", + "web_tests/test_bundles/dart2wasm-html-engine", + "web_tests/test_bundles/dart2wasm-skwasm-ui", + "web_tests/test_bundles/fallbacks" ], "test_dependencies": [ { @@ -369,35 +288,7 @@ "--suite=chrome-dart2js-html-engine" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-dart2js-html-html suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-html" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ { "name": "run suite chrome-dart2js-html-html", "parameters": [ @@ -406,35 +297,7 @@ "--suite=chrome-dart2js-html-html" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-dart2js-html-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ { "name": "run suite chrome-dart2js-html-ui", "parameters": [ @@ -443,35 +306,7 @@ "--suite=chrome-dart2js-html-ui" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-dart2js-canvaskit-canvaskit suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-canvaskit" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ { "name": "run suite chrome-dart2js-canvaskit-canvaskit", "parameters": [ @@ -480,35 +315,7 @@ "--suite=chrome-dart2js-canvaskit-canvaskit" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-dart2js-canvaskit-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ { "name": "run suite chrome-dart2js-canvaskit-ui", "parameters": [ @@ -517,11 +324,83 @@ "--suite=chrome-dart2js-canvaskit-ui" ], "script": "flutter/lib/web_ui/dev/felt" + }, + { + "name": "run suite chrome-full-dart2js-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2js-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, + { + "name": "run suite chrome-full-dart2js-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2js-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, + { + "name": "run suite chrome-dart2wasm-html-engine", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-html-engine" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, + { + "name": "run suite chrome-coi-dart2wasm-skwasm-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-coi-dart2wasm-skwasm-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, + { + "name": "run suite chrome-force-st-dart2wasm-skwasm-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-force-st-dart2wasm-skwasm-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, + { + "name": "run suite chrome-fallbacks", + "parameters": [ + "test", + "--run", + "--suite=chrome-fallbacks" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, + { + "name": "run suite chrome-coi-fallbacks", + "parameters": [ + "test", + "--run", + "--suite=chrome-coi-fallbacks" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, + { + "name": "run suite chrome-force-st-fallbacks", + "parameters": [ + "test", + "--run", + "--suite=chrome-force-st-fallbacks" + ], + "script": "flutter/lib/web_ui/dev/felt" } ] }, { - "name": "Linux run chrome-full-dart2js-canvaskit-canvaskit suite", + "name": "Linux run firefox suites", "recipe": "engine_v2/tester_engine", "drone_dimensions": [ "device_type=none", @@ -533,7 +412,12 @@ }, "dependencies": [ "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-canvaskit" + "web_tests/test_bundles/dart2js-html-engine", + "web_tests/test_bundles/dart2js-html-html", + "web_tests/test_bundles/dart2js-html-ui", + "web_tests/test_bundles/dart2js-canvaskit-canvaskit", + "web_tests/test_bundles/dart2js-canvaskit-ui", + "web_tests/test_bundles/fallbacks" ], "test_dependencies": [ { @@ -541,122 +425,20 @@ "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" + "dependency": "firefox", + "version": "version:132.0" } ], "tasks": [ { - "name": "run suite chrome-full-dart2js-canvaskit-canvaskit", + "name": "run suite firefox-dart2js-html-engine", "parameters": [ "test", "--run", - "--suite=chrome-full-dart2js-canvaskit-canvaskit" + "--suite=firefox-dart2js-html-engine" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-full-dart2js-canvaskit-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-full-dart2js-canvaskit-ui", - "parameters": [ - "test", - "--run", - "--suite=chrome-full-dart2js-canvaskit-ui" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run firefox-dart2js-html-engine suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-engine" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "firefox", - "version": "version:106.0" - } - ], - "tasks": [ - { - "name": "run suite firefox-dart2js-html-engine", - "parameters": [ - "test", - "--run", - "--suite=firefox-dart2js-html-engine" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run firefox-dart2js-html-html suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-html" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, - { - "dependency": "firefox", - "version": "version:106.0" - } - ], - "tasks": [ { "name": "run suite firefox-dart2js-html-html", "parameters": [ @@ -665,35 +447,7 @@ "--suite=firefox-dart2js-html-html" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run firefox-dart2js-html-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, - { - "dependency": "firefox", - "version": "version:106.0" - } - ], - "tasks": [ { "name": "run suite firefox-dart2js-html-ui", "parameters": [ @@ -702,35 +456,7 @@ "--suite=firefox-dart2js-html-ui" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run firefox-dart2js-canvaskit-canvaskit suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-canvaskit" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, - { - "dependency": "firefox", - "version": "version:106.0" - } - ], - "tasks": [ { "name": "run suite firefox-dart2js-canvaskit-canvaskit", "parameters": [ @@ -739,35 +465,7 @@ "--suite=firefox-dart2js-canvaskit-canvaskit" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run firefox-dart2js-canvaskit-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, - { - "dependency": "firefox", - "version": "version:106.0" - } - ], - "tasks": [ { "name": "run suite firefox-dart2js-canvaskit-ui", "parameters": [ @@ -776,885 +474,25 @@ "--suite=firefox-dart2js-canvaskit-ui" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-dart2wasm-html-engine suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-html-engine" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2wasm-html-engine", - "parameters": [ - "test", - "--run", - "--suite=chrome-dart2wasm-html-engine" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-dart2wasm-html-html suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-html-html" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2wasm-html-html", - "parameters": [ - "test", - "--run", - "--suite=chrome-dart2wasm-html-html" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-dart2wasm-html-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-html-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2wasm-html-ui", - "parameters": [ - "test", - "--run", - "--suite=chrome-dart2wasm-html-ui" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-dart2wasm-canvaskit-canvaskit suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-canvaskit-canvaskit" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2wasm-canvaskit-canvaskit", - "parameters": [ - "test", - "--run", - "--suite=chrome-dart2wasm-canvaskit-canvaskit" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-dart2wasm-canvaskit-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-canvaskit-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2wasm-canvaskit-ui", - "parameters": [ - "test", - "--run", - "--suite=chrome-dart2wasm-canvaskit-ui" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-dart2wasm-skwasm-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-skwasm-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2wasm-skwasm-ui", - "parameters": [ - "test", - "--run", - "--suite=chrome-dart2wasm-skwasm-ui" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-full-dart2wasm-canvaskit-canvaskit suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-canvaskit-canvaskit" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-full-dart2wasm-canvaskit-canvaskit", - "parameters": [ - "test", - "--run", - "--suite=chrome-full-dart2wasm-canvaskit-canvaskit" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-full-dart2wasm-canvaskit-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-canvaskit-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-full-dart2wasm-canvaskit-ui", - "parameters": [ - "test", - "--run", - "--suite=chrome-full-dart2wasm-canvaskit-ui" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run chrome-fallbacks suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/fallbacks" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-fallbacks", - "parameters": [ - "test", - "--run", - "--suite=chrome-fallbacks" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Linux run firefox-fallbacks suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Linux" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/fallbacks" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "firefox", - "version": "version:106.0" - } - ], - "tasks": [ - { - "name": "run suite firefox-fallbacks", - "parameters": [ - "test", - "--run", - "--suite=firefox-fallbacks" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Mac run safari-dart2js-html-engine suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Mac-13", - "cpu=arm64" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-engine" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - } - ], - "tasks": [ - { - "name": "run suite safari-dart2js-html-engine", - "parameters": [ - "test", - "--run", - "--suite=safari-dart2js-html-engine" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Mac run safari-dart2js-html-html suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Mac-13", - "cpu=arm64" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-html" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - } - ], - "tasks": [ - { - "name": "run suite safari-dart2js-html-html", - "parameters": [ - "test", - "--run", - "--suite=safari-dart2js-html-html" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Mac run safari-dart2js-html-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Mac-13", - "cpu=arm64" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - } - ], - "tasks": [ - { - "name": "run suite safari-dart2js-html-ui", - "parameters": [ - "test", - "--run", - "--suite=safari-dart2js-html-ui" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Mac run safari-dart2js-canvaskit-canvaskit suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Mac-13", - "cpu=arm64" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-canvaskit" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - } - ], - "tasks": [ - { - "name": "run suite safari-dart2js-canvaskit-canvaskit", - "parameters": [ - "test", - "--run", - "--suite=safari-dart2js-canvaskit-canvaskit" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Mac run safari-dart2js-canvaskit-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Mac-13", - "cpu=arm64" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - } - ], - "tasks": [ - { - "name": "run suite safari-dart2js-canvaskit-ui", - "parameters": [ - "test", - "--run", - "--suite=safari-dart2js-canvaskit-ui" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Mac run safari-fallbacks suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Mac-13", - "cpu=arm64" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/fallbacks" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - } - ], - "tasks": [ - { - "name": "run suite safari-fallbacks", - "parameters": [ - "test", - "--run", - "--suite=safari-fallbacks" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-dart2js-html-engine suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-engine" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2js-html-engine", - "parameters": [ - "test", - "--run", - "--suite=chrome-dart2js-html-engine" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-dart2js-html-html suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-html" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2js-html-html", - "parameters": [ - "test", - "--run", - "--suite=chrome-dart2js-html-html" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-dart2js-html-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-html-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2js-html-ui", - "parameters": [ - "test", - "--run", - "--suite=chrome-dart2js-html-ui" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-dart2js-canvaskit-canvaskit suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-canvaskit" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2js-canvaskit-canvaskit", - "parameters": [ - "test", - "--run", - "--suite=chrome-dart2js-canvaskit-canvaskit" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-dart2js-canvaskit-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2js-canvaskit-ui", - "parameters": [ - "test", - "--run", - "--suite=chrome-dart2js-canvaskit-ui" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-full-dart2js-canvaskit-canvaskit suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-canvaskit" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-full-dart2js-canvaskit-canvaskit", - "parameters": [ - "test", - "--run", - "--suite=chrome-full-dart2js-canvaskit-canvaskit" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-full-dart2js-canvaskit-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2js-canvaskit-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-full-dart2js-canvaskit-ui", - "parameters": [ - "test", - "--run", - "--suite=chrome-full-dart2js-canvaskit-ui" - ], - "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-dart2wasm-html-engine suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-html-engine" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2wasm-html-engine", + "name": "run suite firefox-fallbacks", "parameters": [ "test", "--run", - "--suite=chrome-dart2wasm-html-engine" + "--suite=firefox-fallbacks" ], "script": "flutter/lib/web_ui/dev/felt" } ] }, { - "name": "Windows run chrome-dart2wasm-html-html suite", + "name": "Mac run safari suites", "recipe": "engine_v2/tester_engine", "drone_dimensions": [ "device_type=none", - "os=Windows" + "os=Mac-13", + "cpu=arm64" ], "gclient_variables": { "download_android_deps": false, @@ -1662,106 +500,78 @@ }, "dependencies": [ "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-html-html" + "web_tests/test_bundles/dart2js-html-engine", + "web_tests/test_bundles/dart2js-html-html", + "web_tests/test_bundles/dart2js-html-ui", + "web_tests/test_bundles/dart2js-canvaskit-canvaskit", + "web_tests/test_bundles/dart2js-canvaskit-ui", + "web_tests/test_bundles/fallbacks" ], "test_dependencies": [ { "dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - }, - { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" } ], "tasks": [ { - "name": "run suite chrome-dart2wasm-html-html", + "name": "run suite safari-dart2js-html-engine", "parameters": [ "test", "--run", - "--suite=chrome-dart2wasm-html-html" + "--suite=safari-dart2js-html-engine" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-dart2wasm-html-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-html-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ + "name": "run suite safari-dart2js-html-html", + "parameters": [ + "test", + "--run", + "--suite=safari-dart2js-html-html" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, { - "name": "run suite chrome-dart2wasm-html-ui", + "name": "run suite safari-dart2js-html-ui", "parameters": [ "test", "--run", - "--suite=chrome-dart2wasm-html-ui" + "--suite=safari-dart2js-html-ui" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-dart2wasm-canvaskit-canvaskit suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-canvaskit-canvaskit" - ], - "test_dependencies": [ + }, { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" + "name": "run suite safari-dart2js-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=safari-dart2js-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" }, { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ + "name": "run suite safari-dart2js-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=safari-dart2js-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, { - "name": "run suite chrome-dart2wasm-canvaskit-canvaskit", + "name": "run suite safari-fallbacks", "parameters": [ "test", "--run", - "--suite=chrome-dart2wasm-canvaskit-canvaskit" + "--suite=safari-fallbacks" ], "script": "flutter/lib/web_ui/dev/felt" } ] }, { - "name": "Windows run chrome-dart2wasm-canvaskit-ui suite", + "name": "Windows run chrome suites", "recipe": "engine_v2/tester_engine", "drone_dimensions": [ "device_type=none", @@ -1773,7 +583,14 @@ }, "dependencies": [ "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-canvaskit-ui" + "web_tests/test_bundles/dart2js-html-engine", + "web_tests/test_bundles/dart2js-html-html", + "web_tests/test_bundles/dart2js-html-ui", + "web_tests/test_bundles/dart2js-canvaskit-canvaskit", + "web_tests/test_bundles/dart2js-canvaskit-ui", + "web_tests/test_bundles/dart2wasm-html-engine", + "web_tests/test_bundles/dart2wasm-skwasm-ui", + "web_tests/test_bundles/fallbacks" ], "test_dependencies": [ { @@ -1787,153 +604,95 @@ ], "tasks": [ { - "name": "run suite chrome-dart2wasm-canvaskit-ui", + "name": "run suite chrome-dart2js-html-engine", "parameters": [ "test", "--run", - "--suite=chrome-dart2wasm-canvaskit-ui" + "--suite=chrome-dart2js-html-engine" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-dart2wasm-skwasm-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-skwasm-ui" - ], - "test_dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" }, { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ - { - "name": "run suite chrome-dart2wasm-skwasm-ui", + "name": "run suite chrome-dart2js-html-html", "parameters": [ "test", "--run", - "--suite=chrome-dart2wasm-skwasm-ui" + "--suite=chrome-dart2js-html-html" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-full-dart2wasm-canvaskit-canvaskit suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-canvaskit-canvaskit" - ], - "test_dependencies": [ + }, { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" + "name": "run suite chrome-dart2js-html-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-html-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" }, { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ + "name": "run suite chrome-dart2js-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, { - "name": "run suite chrome-full-dart2wasm-canvaskit-canvaskit", + "name": "run suite chrome-dart2js-canvaskit-ui", "parameters": [ "test", "--run", - "--suite=chrome-full-dart2wasm-canvaskit-canvaskit" + "--suite=chrome-dart2js-canvaskit-ui" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-full-dart2wasm-canvaskit-ui suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/dart2wasm-canvaskit-ui" - ], - "test_dependencies": [ + }, { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" + "name": "run suite chrome-full-dart2js-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2js-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" }, { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ + "name": "run suite chrome-full-dart2js-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2js-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, { - "name": "run suite chrome-full-dart2wasm-canvaskit-ui", + "name": "run suite chrome-dart2wasm-html-engine", "parameters": [ "test", "--run", - "--suite=chrome-full-dart2wasm-canvaskit-ui" + "--suite=chrome-dart2wasm-html-engine" ], "script": "flutter/lib/web_ui/dev/felt" - } - ] - }, - { - "name": "Windows run chrome-fallbacks suite", - "recipe": "engine_v2/tester_engine", - "drone_dimensions": [ - "device_type=none", - "os=Windows" - ], - "gclient_variables": { - "download_android_deps": false, - "download_jdk": false - }, - "dependencies": [ - "web_tests/artifacts", - "web_tests/test_bundles/fallbacks" - ], - "test_dependencies": [ + }, { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" + "name": "run suite chrome-coi-dart2wasm-skwasm-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-coi-dart2wasm-skwasm-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" }, { - "dependency": "chrome_and_driver", - "version": "125.0.6422.141" - } - ], - "tasks": [ + "name": "run suite chrome-force-st-dart2wasm-skwasm-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-force-st-dart2wasm-skwasm-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, { "name": "run suite chrome-fallbacks", "parameters": [ @@ -1942,6 +701,24 @@ "--suite=chrome-fallbacks" ], "script": "flutter/lib/web_ui/dev/felt" + }, + { + "name": "run suite chrome-coi-fallbacks", + "parameters": [ + "test", + "--run", + "--suite=chrome-coi-fallbacks" + ], + "script": "flutter/lib/web_ui/dev/felt" + }, + { + "name": "run suite chrome-force-st-fallbacks", + "parameters": [ + "test", + "--run", + "--suite=chrome-force-st-fallbacks" + ], + "script": "flutter/lib/web_ui/dev/felt" } ] } diff --git a/ci/builders/local_engine.json b/ci/builders/local_engine.json index 74ccf2e3f7175..f832fb4675cd7 100644 --- a/ci/builders/local_engine.json +++ b/ci/builders/local_engine.json @@ -288,6 +288,33 @@ "targets": [] } }, + { + "cas_archive": false, + "drone_dimensions": [ + "os=Linux", + "device_type=none" + ], + "gclient_variables": { + "use_rbe": true + }, + "gn": [ + "--runtime-mode", + "debug", + "--unoptimized", + "--android", + "--android-cpu=x64", + "--no-stripped", + "--no-lto", + "--rbe", + "--no-goma" + ], + "name": "linux/android_debug_unopt_x64", + "description": "Builds an unoptimized debug mode engine that targets 64-bit x64 Android from a Linux host.", + "ninja": { + "config": "android_debug_unopt_x64", + "targets": [] + } + }, { "cas_archive": false, "drone_dimensions": [ diff --git a/ci/builders/mac_unopt.json b/ci/builders/mac_unopt.json index 3a95e217f5670..3f58ce3601e60 100644 --- a/ci/builders/mac_unopt.json +++ b/ci/builders/mac_unopt.json @@ -5,8 +5,7 @@ "drone_dimensions": [ "device_type=none", "os=Mac-13|Mac-14", - "cpu=x86", - "mac_model=Macmini8,1" + "cpu=arm64" ], "gclient_variables": { "download_android_deps": false, @@ -15,9 +14,11 @@ }, "gn": [ "--target-dir", - "ci/host_debug_tests", + "ci/host_debug_arm64_tests", "--runtime-mode", "debug", + "--mac-cpu", + "arm64", "--no-lto", "--prebuilt-dart-sdk", "--build-embedder-examples", @@ -25,12 +26,11 @@ "--rbe", "--no-goma", "--xcode-symlinks" - ], - "name": "ci/host_debug_tests", - "description": "Produces debug mode x64 macOS host-side tooling and builds host-side unit tests for x64 macOS.", + "name": "ci/host_debug_arm64_tests", + "description": "Produces debug mode arm64 macOS host-side tooling and builds host-side unit tests for arm64 macOS.", "ninja": { - "config": "ci/host_debug_tests", + "config": "ci/host_debug_arm64_tests", "targets": [] }, "properties": { @@ -45,7 +45,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "ci/host_debug_tests", + "ci/host_debug_arm64_tests", "--type", "dart,dart-host,engine", "--engine-capture-core-dump" @@ -58,8 +58,7 @@ "drone_dimensions": [ "device_type=none", "os=Mac-13|Mac-14", - "cpu=x86", - "mac_model=Macmini8,1" + "cpu=arm64" ], "gclient_variables": { "download_android_deps": false, @@ -67,9 +66,11 @@ }, "gn": [ "--target-dir", - "ci/host_profile_tests", + "ci/host_profile_arm64_tests", "--runtime-mode", "profile", + "--mac-cpu", + "arm64", "--no-lto", "--prebuilt-dart-sdk", "--build-embedder-examples", @@ -77,10 +78,10 @@ "--no-goma", "--xcode-symlinks" ], - "name": "ci/host_profile_tests", - "description": "Produces profile mode x64 macOS host-side tooling and builds host-side unit tests for x64 macOS.", + "name": "ci/host_profile_arm64_tests", + "description": "Produces profile mode arm64 macOS host-side tooling and builds host-side unit tests for arm64 macOS.", "ninja": { - "config": "ci/host_profile_tests", + "config": "ci/host_profile_arm64_tests", "targets": [] }, "properties": { @@ -95,7 +96,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "ci/host_profile_tests", + "ci/host_profile_arm64_tests", "--type", "dart,dart-host,engine", "--engine-capture-core-dump" @@ -108,14 +109,7 @@ "drone_dimensions": [ "device_type=none", "os=Mac-13|Mac-14", - "cpu=x86", - "mac_model=Macmini8,1" - ], - "dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - } + "cpu=arm64" ], "gclient_variables": { "download_android_deps": false, @@ -123,9 +117,11 @@ }, "gn": [ "--target-dir", - "ci/host_release_tests", + "ci/host_release_arm64_tests", "--runtime-mode", "release", + "--mac-cpu", + "arm64", "--no-lto", "--prebuilt-dart-sdk", "--build-embedder-examples", @@ -134,10 +130,10 @@ "--no-goma", "--xcode-symlinks" ], - "name": "ci/host_release_tests", - "description": "Produces release mode x64 macOS host-side tooling and builds host-side unit tests for x64 macOS.", + "name": "ci/host_release_arm64_tests", + "description": "Produces release mode arm64 macOS host-side tooling and builds host-side unit tests for arm64 macOS.", "ninja": { - "config": "ci/host_release_tests", + "config": "ci/host_release_arm64_tests", "targets": [] }, "properties": { @@ -148,11 +144,11 @@ "tests": [ { "language": "python3", - "name": "Impeller-golden, dart and engine tests for host_release", + "name": "Dart and engine tests for host_release", "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "ci/host_release_tests", + "ci/host_release_arm64_tests", "--type", "dart,dart-host,engine" ] @@ -216,62 +212,6 @@ } ] }, - { - "cas_archive": false, - "drone_dimensions": [ - "device_type=none", - "os=Mac-13|Mac-14", - "cpu=x86", - "mac_model=Macmini8,1" - ], - "gclient_variables": { - "download_android_deps": false, - "use_rbe": true - }, - "gn": [ - "--target-dir", - "ci/host_debug_unopt", - "--runtime-mode", - "debug", - "--unoptimized", - "--no-lto", - "--prebuilt-dart-sdk", - "--enable-impeller-3d", - "--rbe", - "--no-goma", - "--xcode-symlinks" - ], - "name": "ci/host_debug_unopt", - "description": "Builds a debug mode unopt x64 macOS engine and runs host-side tests.", - "ninja": { - "config": "ci/host_debug_unopt", - "targets": [] - }, - "properties": { - "$flutter/osx_sdk": { - "sdk_version": "15a240d" - } - }, - "tests": [ - { - "language": "python3", - "name": "Host Tests for host_debug_unopt", - "script": "flutter/testing/run_tests.py", - "parameters": [ - "--variant", - "ci/host_debug_unopt", - "--type", - "dart,dart-host,engine", - "--engine-capture-core-dump" - ] - }, - { - "name": "Tests of tools/gn", - "language": "python3", - "script": "flutter/tools/gn_test.py" - } - ] - }, { "cas_archive": false, "properties": { @@ -297,6 +237,7 @@ "debug", "--simulator", "--no-lto", + "--unoptimized", "--rbe", "--no-goma", "--xcode-symlinks", @@ -358,6 +299,7 @@ "--prebuilt-dart-sdk", "--mac-cpu", "arm64", + "--enable-impeller-3d", "--rbe", "--no-goma", "--xcode-symlinks", @@ -388,6 +330,11 @@ "--engine-capture-core-dump", "--no-skia-gold" ] + }, + { + "name": "Tests of tools/gn", + "language": "python3", + "script": "flutter/tools/gn_test.py" } ] }, @@ -415,6 +362,7 @@ "--runtime-mode", "debug", "--simulator", + "--unoptimized", "--no-lto", "--simulator-cpu", "arm64", @@ -482,6 +430,7 @@ "--runtime-mode", "debug", "--simulator", + "--unoptimized", "--no-lto", "--simulator-cpu", "arm64", diff --git a/ci/builders/mac_unopt_debug_no_rbe.json b/ci/builders/mac_unopt_debug_no_rbe.json deleted file mode 100644 index 4b211e8eb3ba8..0000000000000 --- a/ci/builders/mac_unopt_debug_no_rbe.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "_comment": [ - "This build is only used to generate compile_commands.json for clangd ", - "and should not be used for any other purpose.", - "", - "Note the `flutter/tools/font_subset` target is only used because if no ", - "targets are specified, the build will fail, and if an empty list of ", - "targets are specified, all targets are built (wasteful/slow)" - ], - "builds": [ - { - "cas_archive": false, - "drone_dimensions": [ - "device_type=none", - "os=Mac-13|Mac-14", - "cpu=x86", - "mac_model=Macmini8,1" - ], - "gn": [ - "--runtime-mode", - "debug", - "--unoptimized", - "--prebuilt-dart-sdk", - "--no-lto", - "--no-rbe", - "--no-goma", - "--xcode-symlinks", - "--target-dir", - "ci/mac_unopt_debug_no_rbe" - ], - "name": "ci/mac_unopt_debug_no_rbe", - "description": "Tests clangd on macOS.", - "ninja": { - "config": "ci/mac_unopt_debug_no_rbe", - "targets": ["flutter/tools/font_subset"] - }, - "properties": { - "$flutter/osx_sdk": { - "sdk_version": "15a240d" - } - }, - "tests": [ - { - "language": "dart", - "name": "clangd", - "script": "flutter/tools/clangd_check/bin/main.dart", - "parameters": [ - "--clangd=buildtools/mac-x64/clang/bin/clangd", - "--compile-commands-dir=../out/ci/mac_unopt_debug_no_rbe" - ] - } - ] - } - ] -} diff --git a/ci/builders/standalone/linux_android_emulator_skia.json b/ci/builders/standalone/linux_android_emulator_skia.json index a4553271f5d3d..148552a96a66c 100644 --- a/ci/builders/standalone/linux_android_emulator_skia.json +++ b/ci/builders/standalone/linux_android_emulator_skia.json @@ -40,7 +40,7 @@ }, { "dependency": "avd_cipd_version", - "version": "build_id:8735216702926189361" + "version": "build_id:8733065022087935185" } ], "contexts": [ @@ -64,7 +64,7 @@ }, { "dependency": "avd_cipd_version", - "version": "build_id:8735216702926189361" + "version": "build_id:8733065022087935185" } ], "contexts": [ diff --git a/ci/builders/standalone/linux_android_emulator_skia_34.json b/ci/builders/standalone/linux_android_emulator_skia_34.json index cbaf9a95b7732..88e6c68cc6a9d 100644 --- a/ci/builders/standalone/linux_android_emulator_skia_34.json +++ b/ci/builders/standalone/linux_android_emulator_skia_34.json @@ -40,7 +40,7 @@ }, { "dependency": "avd_cipd_version", - "version": "build_id:8740267484269553649" + "version": "build_id:8733065022087935185" } ], "contexts": [ @@ -64,7 +64,7 @@ }, { "dependency": "avd_cipd_version", - "version": "build_id:8740267484269553649" + "version": "build_id:8733065022087935185" } ], "contexts": [ diff --git a/ci/builders/standalone/linux_unopt_debug_no_rbe.json b/ci/builders/standalone/linux_clangd.json similarity index 97% rename from ci/builders/standalone/linux_unopt_debug_no_rbe.json rename to ci/builders/standalone/linux_clangd.json index 05a775a1cab20..7dc297eb0abe8 100644 --- a/ci/builders/standalone/linux_unopt_debug_no_rbe.json +++ b/ci/builders/standalone/linux_clangd.json @@ -7,6 +7,7 @@ "targets are specified, the build will fail, and if an empty list of ", "targets are specified, all targets are built (wasteful/slow)" ], + "cas_archive": false, "gn": [ "--runtime-mode", "debug", diff --git a/ci/builders/standalone/mac_clangd.json b/ci/builders/standalone/mac_clangd.json new file mode 100644 index 0000000000000..010785997ef1d --- /dev/null +++ b/ci/builders/standalone/mac_clangd.json @@ -0,0 +1,40 @@ +{ + "_comment": [ + "This build is only used to generate compile_commands.json for clangd ", + "and should not be used for any other purpose.", + "", + "Note the `flutter/tools/font_subset` target is only used because if no ", + "targets are specified, the build will fail, and if an empty list of ", + "targets are specified, all targets are built (wasteful/slow)" + ], + "cas_archive": false, + "gn": [ + "--runtime-mode", + "debug", + "--unoptimized", + "--prebuilt-dart-sdk", + "--no-lto", + "--no-rbe", + "--no-goma", + "--xcode-symlinks", + "--target-dir", + "ci/mac_unopt_debug_no_rbe" + ], + "name": "ci/mac_unopt_debug_no_rbe", + "description": "Tests clangd on macOS.", + "ninja": { + "config": "ci/mac_unopt_debug_no_rbe", + "targets": ["flutter/tools/font_subset"] + }, + "tests": [ + { + "language": "dart", + "name": "clangd", + "script": "flutter/tools/clangd_check/bin/main.dart", + "parameters": [ + "--clangd=buildtools/mac-arm64/clang/bin/clangd", + "--compile-commands-dir=../out/ci/mac_unopt_debug_no_rbe" + ] + } + ] +} diff --git a/ci/format.sh b/ci/format.sh index 8bdd9424d010f..c402102d6c8a8 100755 --- a/ci/format.sh +++ b/ci/format.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index d8ad1bf5356d0..d5897249b3ae7 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -1,7 +1,6 @@ ../../../.clang-format ../../../.git ../../../.gitattributes -../../../.github ../../../.gitignore ../../../.gn ../../../AUTHORS @@ -25,6 +24,7 @@ ../../../flutter/Doxyfile ../../../flutter/README.md ../../../flutter/analysis_options.yaml +../../../flutter/assets/native_assets_unittests.cc ../../../flutter/build ../../../flutter/build_overrides ../../../flutter/buildtools @@ -34,6 +34,7 @@ ../../../flutter/display_list/display_list_unittests.cc ../../../flutter/display_list/dl_color_unittests.cc ../../../flutter/display_list/dl_paint_unittests.cc +../../../flutter/display_list/dl_storage_unittests.cc ../../../flutter/display_list/dl_vertices_unittests.cc ../../../flutter/display_list/effects/dl_color_filter_unittests.cc ../../../flutter/display_list/effects/dl_color_source_unittests.cc @@ -84,6 +85,7 @@ ../../../flutter/flow/texture_unittests.cc ../../../flutter/flow/view_slicer_unittests.cc ../../../flutter/flutter_frontend_server +../../../flutter/flutter_vma/include/README.md ../../../flutter/fml/ascii_trie_unittests.cc ../../../flutter/fml/backtrace_unittests.cc ../../../flutter/fml/base32_unittest.cc @@ -106,11 +108,7 @@ ../../../flutter/fml/message_loop_unittests.cc ../../../flutter/fml/paths_unittests.cc ../../../flutter/fml/platform/darwin/cf_utils_unittests.mm -../../../flutter/fml/platform/darwin/scoped_nsobject_arc_unittests.mm -../../../flutter/fml/platform/darwin/scoped_nsobject_unittests.mm ../../../flutter/fml/platform/darwin/string_range_sanitization_unittests.mm -../../../flutter/fml/platform/darwin/weak_nsobject_arc_unittests.mm -../../../flutter/fml/platform/darwin/weak_nsobject_unittests.mm ../../../flutter/fml/platform/fuchsia/log_interest_listener_unittests.cc ../../../flutter/fml/platform/win/file_win_unittests.cc ../../../flutter/fml/platform/win/wstring_conversion_unittests.cc @@ -136,6 +134,7 @@ ../../../flutter/impeller/compiler/shader_bundle_unittests.cc ../../../flutter/impeller/compiler/switches_unittests.cc ../../../flutter/impeller/core/allocator_unittests.cc +../../../flutter/impeller/core/buffer_view_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_atlas_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_basic_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_blend_unittests.cc @@ -157,7 +156,7 @@ ../../../flutter/impeller/display_list/dl_unittests.cc ../../../flutter/impeller/display_list/skia_conversions_unittests.cc ../../../flutter/impeller/docs -../../../flutter/impeller/entity/contents/clip_contents_unittests.cc +../../../flutter/impeller/entity/clip_stack_unittests.cc ../../../flutter/impeller/entity/contents/filters/blend_filter_contents_unittests.cc ../../../flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc ../../../flutter/impeller/entity/contents/filters/inputs/filter_input_unittests.cc @@ -167,7 +166,6 @@ ../../../flutter/impeller/entity/contents/tiled_texture_contents_unittests.cc ../../../flutter/impeller/entity/draw_order_resolver_unittests.cc ../../../flutter/impeller/entity/entity_pass_target_unittests.cc -../../../flutter/impeller/entity/entity_pass_unittests.cc ../../../flutter/impeller/entity/entity_unittests.cc ../../../flutter/impeller/entity/geometry/geometry_unittests.cc ../../../flutter/impeller/entity/render_target_cache_unittests.cc @@ -184,7 +182,10 @@ ../../../flutter/impeller/geometry/trig_unittests.cc ../../../flutter/impeller/golden_tests/README.md ../../../flutter/impeller/playground +../../../flutter/impeller/renderer/backend/gles/buffer_bindings_gles_unittests.cc +../../../flutter/impeller/renderer/backend/gles/device_buffer_gles_unittests.cc ../../../flutter/impeller/renderer/backend/gles/test +../../../flutter/impeller/renderer/backend/gles/unique_handle_gles_unittests.cc ../../../flutter/impeller/renderer/backend/metal/allocator_mtl_unittests.mm ../../../flutter/impeller/renderer/backend/metal/texture_mtl_unittests.mm ../../../flutter/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc @@ -194,6 +195,7 @@ ../../../flutter/impeller/renderer/backend/vulkan/descriptor_pool_vk_unittests.cc ../../../flutter/impeller/renderer/backend/vulkan/driver_info_vk_unittests.cc ../../../flutter/impeller/renderer/backend/vulkan/fence_waiter_vk_unittests.cc +../../../flutter/impeller/renderer/backend/vulkan/formats_vk_unittests.cc ../../../flutter/impeller/renderer/backend/vulkan/pipeline_cache_data_vk_unittests.cc ../../../flutter/impeller/renderer/backend/vulkan/render_pass_builder_vk_unittests.cc ../../../flutter/impeller/renderer/backend/vulkan/render_pass_cache_unittests.cc @@ -437,6 +439,7 @@ ../../../flutter/sky/packages/sky_engine/README.md ../../../flutter/sky/packages/sky_engine/lib/_embedder.yaml ../../../flutter/sky/packages/sky_engine/pubspec.yaml +../../../flutter/sky/tools/cp.py ../../../flutter/sky/tools/create_embedder_framework.py ../../../flutter/sky/tools/create_ios_framework.py ../../../flutter/sky/tools/create_macos_binary.py @@ -946,12 +949,9 @@ ../../../flutter/third_party/angle/tools ../../../flutter/third_party/angle/util ../../../flutter/third_party/benchmark -../../../flutter/third_party/boringssl/.git -../../../flutter/third_party/boringssl/.gitignore -../../../flutter/third_party/boringssl/OWNERS -../../../flutter/third_party/boringssl/README -../../../flutter/third_party/boringssl/codereview.settings ../../../flutter/third_party/boringssl/src/.bazelrc +../../../flutter/third_party/boringssl/src/.bazelversion +../../../flutter/third_party/boringssl/src/.bcr/README.md ../../../flutter/third_party/boringssl/src/.clang-format ../../../flutter/third_party/boringssl/src/.git ../../../flutter/third_party/boringssl/src/.github @@ -1016,11 +1016,8 @@ ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256_test.cc ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdsa/ecdsa_test.cc ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/hkdf/hkdf_test.cc -../../../flutter/third_party/boringssl/src/crypto/fipsmodule/md5/md5_test.cc ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/gcm_test.cc ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/ctrdrbg_test.cc -../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/fork_detect_test.cc -../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/urandom_test.cc ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/service_indicator/service_indicator_test.cc ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha_test.cc ../../../flutter/third_party/boringssl/src/crypto/hmac_extra/hmac_test.cc @@ -1031,6 +1028,9 @@ ../../../flutter/third_party/boringssl/src/crypto/keccak/keccak_test.cc ../../../flutter/third_party/boringssl/src/crypto/kyber/kyber_test.cc ../../../flutter/third_party/boringssl/src/crypto/lhash/lhash_test.cc +../../../flutter/third_party/boringssl/src/crypto/md5/md5_test.cc +../../../flutter/third_party/boringssl/src/crypto/mldsa/mldsa_test.cc +../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem_test.cc ../../../flutter/third_party/boringssl/src/crypto/obj/README ../../../flutter/third_party/boringssl/src/crypto/obj/obj_test.cc ../../../flutter/third_party/boringssl/src/crypto/pem/pem_test.cc @@ -1041,13 +1041,15 @@ ../../../flutter/third_party/boringssl/src/crypto/pkcs8/test ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305_test.cc ../../../flutter/third_party/boringssl/src/crypto/pool/pool_test.cc +../../../flutter/third_party/boringssl/src/crypto/rand_extra/fork_detect_test.cc ../../../flutter/third_party/boringssl/src/crypto/rand_extra/getentropy_test.cc ../../../flutter/third_party/boringssl/src/crypto/rand_extra/rand_test.cc +../../../flutter/third_party/boringssl/src/crypto/rand_extra/urandom_test.cc ../../../flutter/third_party/boringssl/src/crypto/refcount_test.cc ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_test.cc ../../../flutter/third_party/boringssl/src/crypto/self_test.cc ../../../flutter/third_party/boringssl/src/crypto/siphash/siphash_test.cc -../../../flutter/third_party/boringssl/src/crypto/spx/spx_test.cc +../../../flutter/third_party/boringssl/src/crypto/slhdsa/slhdsa_test.cc ../../../flutter/third_party/boringssl/src/crypto/stack/stack_test.cc ../../../flutter/third_party/boringssl/src/crypto/test ../../../flutter/third_party/boringssl/src/crypto/thread_test.cc @@ -1068,6 +1070,7 @@ ../../../flutter/third_party/boringssl/src/gen/sources.bzl ../../../flutter/third_party/boringssl/src/gen/sources.cmake ../../../flutter/third_party/boringssl/src/gen/test_support +../../../flutter/third_party/boringssl/src/infra/config/README.md ../../../flutter/third_party/boringssl/src/pki/README.md ../../../flutter/third_party/boringssl/src/pki/cert_issuer_source_static_unittest.cc ../../../flutter/third_party/boringssl/src/pki/cert_issuer_source_sync_unittest.h @@ -1103,9 +1106,11 @@ ../../../flutter/third_party/boringssl/src/pki/verify_certificate_chain_unittest.cc ../../../flutter/third_party/boringssl/src/pki/verify_name_match_unittest.cc ../../../flutter/third_party/boringssl/src/pki/verify_signed_data_unittest.cc +../../../flutter/third_party/boringssl/src/pki/verify_unittest.cc ../../../flutter/third_party/boringssl/src/rust ../../../flutter/third_party/boringssl/src/ssl/span_test.cc ../../../flutter/third_party/boringssl/src/ssl/ssl_c_test.c +../../../flutter/third_party/boringssl/src/ssl/ssl_internal_test.cc ../../../flutter/third_party/boringssl/src/ssl/ssl_test.cc ../../../flutter/third_party/boringssl/src/ssl/test ../../../flutter/third_party/boringssl/src/third_party/fiat/METADATA @@ -1240,6 +1245,10 @@ ../../../flutter/third_party/boringssl/src/third_party/wycheproof_testvectors/kw_test.txt ../../../flutter/third_party/boringssl/src/third_party/wycheproof_testvectors/kwp_test.json ../../../flutter/third_party/boringssl/src/third_party/wycheproof_testvectors/kwp_test.txt +../../../flutter/third_party/boringssl/src/third_party/wycheproof_testvectors/mldsa_65_standard_sign_test.json +../../../flutter/third_party/boringssl/src/third_party/wycheproof_testvectors/mldsa_65_standard_sign_test.txt +../../../flutter/third_party/boringssl/src/third_party/wycheproof_testvectors/mldsa_65_standard_verify_test.json +../../../flutter/third_party/boringssl/src/third_party/wycheproof_testvectors/mldsa_65_standard_verify_test.txt ../../../flutter/third_party/boringssl/src/third_party/wycheproof_testvectors/primality_test.json ../../../flutter/third_party/boringssl/src/third_party/wycheproof_testvectors/primality_test.txt ../../../flutter/third_party/boringssl/src/third_party/wycheproof_testvectors/rsa_oaep_2048_sha1_mgf1sha1_test.json @@ -1656,6 +1665,7 @@ ../../../flutter/third_party/dart/third_party/OWNERS ../../../flutter/third_party/dart/third_party/binary_size ../../../flutter/third_party/dart/third_party/binaryen +../../../flutter/third_party/dart/third_party/boringssl/OWNERS ../../../flutter/third_party/dart/third_party/clang.tar.gz.sha1 ../../../flutter/third_party/dart/third_party/cpu_features/OWNERS ../../../flutter/third_party/dart/third_party/cpu_features/README.chromium @@ -2585,7 +2595,6 @@ ../../../flutter/third_party/pkg/node_preamble/example ../../../flutter/third_party/pkg/node_preamble/package.json ../../../flutter/third_party/pkg/node_preamble/pubspec.yaml -../../../flutter/third_party/pkg/platform ../../../flutter/third_party/pkg/process ../../../flutter/third_party/pkg/process_runner ../../../flutter/third_party/pkg/vector_math @@ -2796,6 +2805,7 @@ ../../../flutter/third_party/skia/modules/canvaskit/wasm_tools/SIMD/.gitignore ../../../flutter/third_party/skia/modules/jetski/BUILD.bazel ../../../flutter/third_party/skia/modules/jetski/README +../../../flutter/third_party/skia/modules/jsonreader/BUILD.bazel ../../../flutter/third_party/skia/modules/pathkit/.gitignore ../../../flutter/third_party/skia/modules/pathkit/BUILD.bazel ../../../flutter/third_party/skia/modules/pathkit/CHANGELOG.md @@ -3300,6 +3310,7 @@ ../../../fuchsia/sdk/linux/bind/fuchsia.gpio/meta.json ../../../fuchsia/sdk/linux/bind/fuchsia.i2c/meta.json ../../../fuchsia/sdk/linux/bind/fuchsia.khadas.platform/meta.json +../../../fuchsia/sdk/linux/bind/fuchsia.mailbox/meta.json ../../../fuchsia/sdk/linux/bind/fuchsia.nxp.platform/meta.json ../../../fuchsia/sdk/linux/bind/fuchsia.pci/meta.json ../../../fuchsia/sdk/linux/bind/fuchsia.platform/meta.json @@ -3366,7 +3377,6 @@ ../../../fuchsia/sdk/linux/fidl/fuchsia.diagnostics/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.development/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.framework/meta.json -../../../fuchsia/sdk/linux/fidl/fuchsia.driver.legacy/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.metadata/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.registrar/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.test/meta.json @@ -3389,6 +3399,7 @@ ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.i2c/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.i2cimpl/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.light/meta.json +../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.mailbox/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.network.driver/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.network/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.pci/meta.json @@ -3402,6 +3413,7 @@ ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.radar/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.registers/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.rtc/meta.json +../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.sdhci/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.sdio/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.sdmmc/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.serial/meta.json @@ -3412,7 +3424,11 @@ ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.spmi/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.temperature/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.trippoint/meta.json +../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.dci/meta.json +../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.descriptor/meta.json +../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.endpoint/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.phy/meta.json +../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.request/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.hwinfo/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.images/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.images2/meta.json @@ -3473,8 +3489,10 @@ ../../../fuchsia/sdk/linux/fidl/fuchsia.sysinfo/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/meta.json +../../../fuchsia/sdk/linux/fidl/fuchsia.system.state/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.test/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.thermal/meta.json +../../../fuchsia/sdk/linux/fidl/fuchsia.tracing.controller/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.tracing.perfetto/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.tracing.provider/meta.json ../../../fuchsia/sdk/linux/fidl/fuchsia.tracing/meta.json @@ -3519,15 +3537,9 @@ ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/dist/lib/hwasan/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/dist/lib/ld.so.1 -../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/dist/lib/asan/ld.so.1 -../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/dist/lib/hwasan/ld.so.1 -../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/dist/lib/hwasan/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/dist/lib/ld.so.1 -../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/dist/lib/asan/ld.so.1 -../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/dist/lib/hwasan/ld.so.1 -../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/dist/lib/hwasan/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/dist/lib/ld.so.1 @@ -3537,39 +3549,38 @@ ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/dist/lib/hwasan/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/dist/lib/ld.so.1 +../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/dist/lib/asan/ld.so.1 +../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/dist/lib/hwasan/ld.so.1 +../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/dist/lib/hwasan/ld.so.1 ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/dist/lib/ld.so.1 -../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/dist/lib/asan/ld.so.1 -../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/dist/lib/ld.so.1 -../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/dist/lib/asan/ld.so.1 -../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-23/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-23/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/dist/lib/ld.so.1 +../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/dist/lib/asan/ld.so.1 +../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/dist/lib/ld.so.1 -../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/dist/lib/asan/ld.so.1 -../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/dist/lib/ld.so.1 -../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/dist/lib/asan/ld.so.1 -../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-23/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-23/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/dist/lib/ld.so.1 +../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/dist/lib/asan/ld.so.1 +../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/dist/lib/asan/ld.so.1 ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/dist/lib/ld.so.1 ../../../fuchsia/sdk/linux/packages/blobs @@ -3599,6 +3610,10 @@ ../../../fuchsia/sdk/linux/pkg/component_outgoing_cpp/meta.json ../../../fuchsia/sdk/linux/pkg/driver_component_cpp/meta.json ../../../fuchsia/sdk/linux/pkg/driver_devfs_cpp/meta.json +../../../fuchsia/sdk/linux/pkg/driver_fake_bti_cpp/meta.json +../../../fuchsia/sdk/linux/pkg/driver_fake_object_cpp/meta.json +../../../fuchsia/sdk/linux/pkg/driver_fake_platform_device_cpp/meta.json +../../../fuchsia/sdk/linux/pkg/driver_fake_resource_cpp/meta.json ../../../fuchsia/sdk/linux/pkg/driver_incoming_cpp/meta.json ../../../fuchsia/sdk/linux/pkg/driver_logging_cpp/meta.json ../../../fuchsia/sdk/linux/pkg/driver_metadata_cpp/meta.json diff --git a/ci/licenses_golden/licenses_dart b/ci/licenses_golden/licenses_dart index f953d41a7a49c..23615dc9b005d 100644 --- a/ci/licenses_golden/licenses_dart +++ b/ci/licenses_golden/licenses_dart @@ -1,4 +1,4 @@ -Signature: 08e7e8bc94ce8cdff314783a0aa3acbe +Signature: 88b861f153b1524223e9c67bfdbe479d ==================================================================================================== LIBRARY: dart @@ -2084,7 +2084,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: dart -ORIGIN: ../../../flutter/third_party/dart/runtime/bin/address_sanitizer.cc + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/bin/dart_io_api_impl.cc + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/bin/observatory_assets_empty.cc + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/include/bin/dart_io_api.h + ../../../flutter/third_party/dart/LICENSE @@ -2163,7 +2162,6 @@ ORIGIN: ../../../flutter/third_party/dart/sdk/lib/js/_js_annotations.dart + ../. ORIGIN: ../../../flutter/third_party/dart/sdk/lib/vmservice/asset.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/vmservice/vmservice.dart + ../../../flutter/third_party/dart/LICENSE TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/dart/runtime/bin/address_sanitizer.cc FILE: ../../../flutter/third_party/dart/runtime/bin/dart_io_api_impl.cc FILE: ../../../flutter/third_party/dart/runtime/bin/observatory_assets_empty.cc FILE: ../../../flutter/third_party/dart/runtime/include/bin/dart_io_api.h @@ -3842,8 +3840,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: dart -ORIGIN: ../../../flutter/third_party/dart/runtime/bin/dart_precompiled_runtime_test_component.cml + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/bin/dart_test_component.cml + ../../../flutter/third_party/dart/LICENSE +ORIGIN: ../../../flutter/third_party/dart/runtime/bin/dartaotruntime_test_component.cml + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/bin/main_impl.h + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/bin/run_vm_tests_test_component.cml + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/lib/ffi_dynamic_library.h + ../../../flutter/third_party/dart/LICENSE @@ -3904,8 +3902,8 @@ ORIGIN: ../../../flutter/third_party/dart/sdk/lib/async/future_extensions.dart + ORIGIN: ../../../flutter/third_party/dart/sdk/lib/js_interop/js_interop.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/js_interop_unsafe/js_interop_unsafe.dart + ../../../flutter/third_party/dart/LICENSE TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/dart/runtime/bin/dart_precompiled_runtime_test_component.cml FILE: ../../../flutter/third_party/dart/runtime/bin/dart_test_component.cml +FILE: ../../../flutter/third_party/dart/runtime/bin/dartaotruntime_test_component.cml FILE: ../../../flutter/third_party/dart/runtime/bin/main_impl.h FILE: ../../../flutter/third_party/dart/runtime/bin/run_vm_tests_test_component.cml FILE: ../../../flutter/third_party/dart/runtime/lib/ffi_dynamic_library.h @@ -4068,6 +4066,8 @@ ORIGIN: ../../../flutter/third_party/dart/runtime/bin/uri.cc + ../../../flutter/ ORIGIN: ../../../flutter/third_party/dart/runtime/bin/uri.h + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/include/bin/native_assets_api.h + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/lib/concurrent.cc + ../../../flutter/third_party/dart/LICENSE +ORIGIN: ../../../flutter/third_party/dart/runtime/platform/no_tsan.cc + ../../../flutter/third_party/dart/LICENSE +ORIGIN: ../../../flutter/third_party/dart/runtime/platform/no_tsan.h + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/platform/synchronization.h + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/platform/synchronization_absl.cc + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/platform/synchronization_posix.cc + ../../../flutter/third_party/dart/LICENSE @@ -4092,6 +4092,7 @@ ORIGIN: ../../../flutter/third_party/dart/runtime/vm/heap/incremental_compactor. ORIGIN: ../../../flutter/third_party/dart/runtime/vm/interpreter.cc + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/vm/interpreter.h + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/vm/os.cc + ../../../flutter/third_party/dart/LICENSE +ORIGIN: ../../../flutter/third_party/dart/runtime/vm/simulator_memory.h + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/runtime/vm/stack_frame_kbc.h + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/js_dev_runtime/private/custom_hash_set.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_only.dart + ../../../flutter/third_party/dart/LICENSE @@ -4102,6 +4103,7 @@ ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/js_runtime/lib/synce ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/js_runtime/lib/synced/invocation_mirror_constants.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/js_shared/lib/date_time_patch.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/js_shared/lib/synced/async_status_codes.dart + ../../../flutter/third_party/dart/LICENSE +ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/vm/bin/resident_compiler_utils.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/vm/lib/concurrent_patch.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/wasm/lib/compact_hash.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/wasm/lib/ffi_patch.dart + ../../../flutter/third_party/dart/LICENSE @@ -4119,6 +4121,8 @@ FILE: ../../../flutter/third_party/dart/runtime/bin/uri.cc FILE: ../../../flutter/third_party/dart/runtime/bin/uri.h FILE: ../../../flutter/third_party/dart/runtime/include/bin/native_assets_api.h FILE: ../../../flutter/third_party/dart/runtime/lib/concurrent.cc +FILE: ../../../flutter/third_party/dart/runtime/platform/no_tsan.cc +FILE: ../../../flutter/third_party/dart/runtime/platform/no_tsan.h FILE: ../../../flutter/third_party/dart/runtime/platform/synchronization.h FILE: ../../../flutter/third_party/dart/runtime/platform/synchronization_absl.cc FILE: ../../../flutter/third_party/dart/runtime/platform/synchronization_posix.cc @@ -4143,6 +4147,7 @@ FILE: ../../../flutter/third_party/dart/runtime/vm/heap/incremental_compactor.h FILE: ../../../flutter/third_party/dart/runtime/vm/interpreter.cc FILE: ../../../flutter/third_party/dart/runtime/vm/interpreter.h FILE: ../../../flutter/third_party/dart/runtime/vm/os.cc +FILE: ../../../flutter/third_party/dart/runtime/vm/simulator_memory.h FILE: ../../../flutter/third_party/dart/runtime/vm/stack_frame_kbc.h FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/js_dev_runtime/private/custom_hash_set.dart FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_only.dart @@ -4153,6 +4158,7 @@ FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/js_runtime/lib/synced/ FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/js_runtime/lib/synced/invocation_mirror_constants.dart FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/js_shared/lib/date_time_patch.dart FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/js_shared/lib/synced/async_status_codes.dart +FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/vm/bin/resident_compiler_utils.dart FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/vm/lib/concurrent_patch.dart FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/wasm/lib/compact_hash.dart FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/wasm/lib/ffi_patch.dart @@ -4376,8 +4382,8 @@ FILE: ../../../flutter/third_party/dart/runtime/observatory/web/index.html FILE: ../../../flutter/third_party/dart/runtime/observatory/web/third_party/trace_viewer_full.html FILE: ../../../flutter/third_party/dart/runtime/observatory/web/timeline.html FILE: ../../../flutter/third_party/dart/runtime/tools/entitlements/dart.plist -FILE: ../../../flutter/third_party/dart/runtime/tools/entitlements/dart_precompiled_runtime.plist -FILE: ../../../flutter/third_party/dart/runtime/tools/entitlements/dart_precompiled_runtime_product.plist +FILE: ../../../flutter/third_party/dart/runtime/tools/entitlements/dartaotruntime.plist +FILE: ../../../flutter/third_party/dart/runtime/tools/entitlements/dartaotruntime_product.plist FILE: ../../../flutter/third_party/dart/runtime/tools/entitlements/gen_snapshot.plist FILE: ../../../flutter/third_party/dart/runtime/tools/entitlements/gen_snapshot_product.plist FILE: ../../../flutter/third_party/dart/runtime/tools/entitlements/run_vm_tests.plist @@ -4801,7 +4807,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. -You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/c8659684d34d242338cd1f3cb9a592b55fc63156 +You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/1a28e6c86b09f1c83365f54388c32ed97c9e9b31 /third_party/fallback_root_certificates/ ==================================================================================================== diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 35835f6bc35ff..6f989d5173dff 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -11563,163 +11563,162 @@ copied and put under another distribution licence ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_bitstr.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_bool.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_d2i_fp.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_dup.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_gentm.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_i2d_fp.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_int.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_mbstr.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_object.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_octet.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_strex.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_strnid.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_time.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_type.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_utctm.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn1_lib.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn1_par.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn_pack.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/f_int.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/f_string.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_dec.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_enc.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_fre.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_new.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_typ.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_utl.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/base64/base64.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/bio.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/bio_mem.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/connect.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/errno.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/fd.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/file.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/hexdump.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_bitstr.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_bool.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_d2i_fp.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_dup.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_gentm.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_i2d_fp.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_int.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_mbstr.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_object.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_octet.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_strex.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_strnid.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_time.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_type.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_utctm.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn1_lib.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn1_par.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn_pack.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/f_int.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/f_string.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_dec.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_enc.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_fre.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_new.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_typ.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_utl.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/base64/base64.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/bio.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/bio_mem.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/connect.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/errno.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/fd.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/file.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/hexdump.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/printf.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/socket.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bn_extra/convert.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/buf/buf.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/cipher_extra.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/derive_key.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_des.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_null.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_rc2.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_rc4.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/printf.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/socket.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bn_extra/convert.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/buf/buf.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/cipher_extra.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/derive_key.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_des.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_null.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_rc2.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_rc4.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/conf/conf.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/conf/conf_def.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_intel.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/des/des.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/conf/conf.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_intel.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/des/des.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/des/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/digest_extra/digest_extra.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/dsa/dsa.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/err/err.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/evp.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/evp_asn1.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/evp_ctx.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/digest_extra/digest_extra.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/dsa/dsa.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/err/err.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/evp.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/evp_asn1.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/evp_ctx.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/sign.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ex_data.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/add.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/bn.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/bytes.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/cmp.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/div.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/exponentiation.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/generic.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/mul.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/prime.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/random.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/shift.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/cipher.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/sign.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ex_data.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/add.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/bn.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/bytes.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/cmp.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/div.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/exponentiation.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/generic.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/mul.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/prime.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/random.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/shift.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/cipher.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/dh/check.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/dh/dh.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digest/digest.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digest/digests.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/dh/check.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/dh/dh.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digest/digest.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digest/digests.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digest/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/hmac/hmac.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/md4/md4.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/md5/md5.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/blinding.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/hmac/hmac.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/blinding.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/rsa.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/rsa_impl.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha1.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha256.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha512.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/rsa.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/rsa_impl.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha1.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha256.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha512.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/internal.h ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/lhash/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/lhash/lhash.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/mem.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/obj/obj.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/obj/obj_xref.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_all.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_info.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_lib.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_oth.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_pk8.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_pkey.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rc4/rc4.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/lhash/lhash.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/md4/md4.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/md5/md5.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/mem.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/obj/obj.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/obj/obj_xref.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_all.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_info.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_lib.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_oth.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_pk8.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_pkey.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rc4/rc4.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_crypt.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/stack/stack.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/thread.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/a_digest.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/a_sign.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/a_verify.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/algorithm.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/asn1_gen.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/by_dir.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/by_file.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/i2d_pr.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/name_print.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/t_crl.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/t_req.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/t_x509.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/t_x509a.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_att.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_cmp.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_d2.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_def.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_ext.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_lu.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_obj.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_req.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_set.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_txt.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_v3.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_vfy.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509name.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509rset.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_all.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_attrib.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_crl.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_exten.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_name.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_pubkey.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_req.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_sig.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_spki.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_val.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_x509.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/bio/base64_bio.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/blowfish/blowfish.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/cast/cast.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/cast/cast_tables.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_crypt.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/stack/stack.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/thread.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/a_digest.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/a_sign.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/a_verify.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/algorithm.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/asn1_gen.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/by_dir.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/by_file.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/i2d_pr.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/name_print.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/t_crl.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/t_req.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/t_x509.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/t_x509a.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_att.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_cmp.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_d2.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_def.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_ext.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_lu.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_obj.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_req.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_set.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_txt.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_v3.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_vfy.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509name.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509rset.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_all.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_attrib.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_crl.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_exten.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_name.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_pubkey.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_req.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_sig.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_spki.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_val.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_x509.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/bio/base64_bio.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/blowfish/blowfish.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/cast/cast.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/cast/cast_tables.cc ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/cast/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/des/cfb64ede.c +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/des/cfb64ede.cc ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/macros.h -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/rc4/rc4_decrepit.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/ripemd/ripemd.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/rsa/rsa_decrepit.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/ssl/ssl_decrepit.c +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/rc4/rc4_decrepit.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/ripemd/ripemd.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/rsa/rsa_decrepit.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/ssl/ssl_decrepit.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/asn1.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/base64.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/bio.h @@ -11779,163 +11778,162 @@ ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/t1_enc.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/tls_method.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/tls_record.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_bitstr.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_bool.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_d2i_fp.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_dup.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_gentm.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_i2d_fp.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_int.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_mbstr.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_object.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_octet.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_strex.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_strnid.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_time.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_type.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_utctm.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn1_lib.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn1_par.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn_pack.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/f_int.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/f_string.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_dec.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_enc.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_fre.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_new.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_typ.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_utl.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/base64/base64.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/bio.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/bio_mem.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/connect.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/errno.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/fd.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/file.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/hexdump.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_bitstr.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_bool.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_d2i_fp.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_dup.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_gentm.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_i2d_fp.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_int.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_mbstr.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_object.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_octet.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_strex.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_strnid.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_time.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_type.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/a_utctm.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn1_lib.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn1_par.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/asn_pack.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/f_int.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/f_string.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_dec.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_enc.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_fre.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_new.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_typ.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/tasn_utl.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/base64/base64.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/bio.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/bio_mem.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/connect.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/errno.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/fd.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/file.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/hexdump.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/printf.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/socket.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bn_extra/convert.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/buf/buf.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/cipher_extra.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/derive_key.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_des.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_null.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_rc2.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_rc4.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/printf.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/socket.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bn_extra/convert.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/buf/buf.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/cipher_extra.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/derive_key.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_des.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_null.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_rc2.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_rc4.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/conf/conf.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/conf/conf_def.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_intel.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/des/des.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/conf/conf.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_intel.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/des/des.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/des/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/digest_extra/digest_extra.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/dsa/dsa.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/err/err.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/evp.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/evp_asn1.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/evp_ctx.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/digest_extra/digest_extra.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/dsa/dsa.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/err/err.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/evp.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/evp_asn1.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/evp_ctx.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/sign.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/ex_data.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/add.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/bn.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/bytes.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/cmp.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/div.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/exponentiation.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/generic.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/mul.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/prime.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/random.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/shift.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/cipher.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/sign.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/ex_data.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/add.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/bn.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/bytes.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/cmp.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/div.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/exponentiation.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/generic.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/mul.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/prime.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/random.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/shift.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/cipher.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/dh/check.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/dh/dh.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digest/digest.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digest/digests.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/dh/check.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/dh/dh.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digest/digest.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digest/digests.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digest/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/hmac/hmac.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/md4/md4.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/md5/md5.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/blinding.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/hmac/hmac.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/blinding.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/rsa.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/rsa_impl.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha1.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha256.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha512.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/rsa.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/rsa_impl.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha1.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha256.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/sha512.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/internal.h FILE: ../../../flutter/third_party/boringssl/src/crypto/lhash/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/lhash/lhash.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/mem.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/obj/obj.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/obj/obj_xref.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_all.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_info.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_lib.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_oth.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_pk8.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_pkey.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/rc4/rc4.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/lhash/lhash.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/md4/md4.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/md5/md5.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/mem.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/obj/obj.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/obj/obj_xref.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_all.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_info.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_lib.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_oth.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_pk8.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_pkey.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rc4/rc4.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_crypt.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/stack/stack.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/thread.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/a_digest.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/a_sign.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/a_verify.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/algorithm.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/asn1_gen.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/by_dir.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/by_file.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/i2d_pr.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/name_print.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/t_crl.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/t_req.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/t_x509.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/t_x509a.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_att.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_cmp.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_d2.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_def.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_ext.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_lu.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_obj.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_req.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_set.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_txt.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_v3.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_vfy.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509name.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509rset.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_all.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_attrib.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_crl.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_exten.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_name.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_pubkey.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_req.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_sig.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_spki.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_val.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_x509.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/bio/base64_bio.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/blowfish/blowfish.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/cast/cast.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/cast/cast_tables.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_crypt.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/stack/stack.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/thread.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/a_digest.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/a_sign.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/a_verify.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/algorithm.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/asn1_gen.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/by_dir.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/by_file.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/i2d_pr.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/name_print.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/t_crl.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/t_req.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/t_x509.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/t_x509a.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_att.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_cmp.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_d2.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_def.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_ext.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_lu.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_obj.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_req.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_set.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_txt.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_v3.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_vfy.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509name.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509rset.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_all.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_attrib.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_crl.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_exten.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_name.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_pubkey.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_req.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_sig.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_spki.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_val.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_x509.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/bio/base64_bio.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/blowfish/blowfish.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/cast/cast.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/cast/cast_tables.cc FILE: ../../../flutter/third_party/boringssl/src/decrepit/cast/internal.h -FILE: ../../../flutter/third_party/boringssl/src/decrepit/des/cfb64ede.c +FILE: ../../../flutter/third_party/boringssl/src/decrepit/des/cfb64ede.cc FILE: ../../../flutter/third_party/boringssl/src/decrepit/macros.h -FILE: ../../../flutter/third_party/boringssl/src/decrepit/rc4/rc4_decrepit.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/ripemd/ripemd.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/rsa/rsa_decrepit.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/ssl/ssl_decrepit.c +FILE: ../../../flutter/third_party/boringssl/src/decrepit/rc4/rc4_decrepit.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/ripemd/ripemd.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/rsa/rsa_decrepit.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/ssl/ssl_decrepit.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/asn1.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/base64.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/bio.h @@ -32191,11 +32189,11 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/jacobi.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/sqrt.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/jacobi.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/sqrt.cc.inc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/jacobi.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/sqrt.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/jacobi.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/sqrt.cc.inc ---------------------------------------------------------------------------------------------------- Copyright (c) 1998-2000 The OpenSSL Project. All rights reserved. @@ -32246,18 +32244,18 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ex_data.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/prime.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/random.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ex_data.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/prime.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/random.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/internal.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/base.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/ex_data.h TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/ex_data.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/prime.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/random.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/ex_data.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/prime.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/random.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/internal.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/base.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ex_data.h @@ -32311,18 +32309,18 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_all.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/dh/dh_decrepit.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/dsa/dsa_decrepit.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_all.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/dh/dh_decrepit.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/dsa/dsa_decrepit.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/ssl3.h ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/handshake.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/s3_both.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/s3_pkt.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/tls_record.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_all.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/dh/dh_decrepit.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/dsa/dsa_decrepit.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_all.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/dh/dh_decrepit.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/dsa/dsa_decrepit.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ssl3.h FILE: ../../../flutter/third_party/boringssl/src/ssl/handshake.cc FILE: ../../../flutter/third_party/boringssl/src/ssl/s3_both.cc @@ -32378,9 +32376,9 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/pair.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/pair.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/pair.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/pair.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 1998-2003 The OpenSSL Project. All rights reserved. @@ -32431,9 +32429,9 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/ctx.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/ctx.cc.inc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/ctx.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/ctx.cc.inc ---------------------------------------------------------------------------------------------------- Copyright (c) 1998-2004 The OpenSSL Project. All rights reserved. @@ -32484,12 +32482,12 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ecdsa_extra/ecdsa_asn1.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdsa/ecdsa.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ecdsa_extra/ecdsa_asn1.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdsa/ecdsa.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/ecdsa.h TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/ecdsa_extra/ecdsa_asn1.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdsa/ecdsa.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/ecdsa_extra/ecdsa_asn1.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdsa/ecdsa.cc.inc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ecdsa.h ---------------------------------------------------------------------------------------------------- Copyright (c) 1998-2005 The OpenSSL Project. All rights reserved. @@ -32541,28 +32539,28 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/exponentiation.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_key.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_montgomery.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/exponentiation.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_key.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_montgomery.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/oct.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/wnaf.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/oct.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/wnaf.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/ec.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/ec_key.h ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/d1_both.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/d1_pkt.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/dtls_record.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/exponentiation.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_key.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_montgomery.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/exponentiation.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_key.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_montgomery.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/oct.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/wnaf.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/oct.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/wnaf.cc.inc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ec.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ec_key.h FILE: ../../../flutter/third_party/boringssl/src/ssl/d1_both.cc @@ -32618,20 +32616,20 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/err/err.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/err/err.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/blinding.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/blinding.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/bn.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/err.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/tls1.h ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/d1_srtp.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/ssl_session.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/err/err.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/err/err.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/blinding.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/blinding.cc.inc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/bn.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/err.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/tls1.h @@ -32687,8 +32685,8 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/tls/kdf.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/ssl/ssl_decrepit.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/tls/kdf.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/ssl/ssl_decrepit.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/ssl.h ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/extensions.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/handshake_client.cc @@ -32703,8 +32701,8 @@ ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/ssl_transcript.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/ssl_x509.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/t1_enc.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/tls/kdf.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/ssl/ssl_decrepit.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/tls/kdf.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/ssl/ssl_decrepit.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ssl.h FILE: ../../../flutter/third_party/boringssl/src/ssl/extensions.cc FILE: ../../../flutter/third_party/boringssl/src/ssl/handshake_client.cc @@ -32821,46 +32819,46 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/pbkdf.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/pbkdf.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/p5_pbev2.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/pkcs8.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/pkcs8_x509.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_akey.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_akeya.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_bcons.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_bitst.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_enum.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_extku.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ia5.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_info.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_lib.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_prn.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_skey.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_trs.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509spki.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_x509a.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/p5_pbev2.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/pkcs8.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/pkcs8_x509.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_akey.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_akeya.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_bcons.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_bitst.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_enum.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_extku.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ia5.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_info.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_lib.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_prn.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_skey.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_trs.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509spki.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_x509a.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/pkcs8.h TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/pbkdf.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/pbkdf.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/p5_pbev2.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/pkcs8.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/pkcs8_x509.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_akey.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_akeya.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_bcons.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_bitst.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_enum.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_extku.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ia5.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_info.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_lib.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_prn.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_skey.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_trs.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509spki.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_x509a.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/p5_pbev2.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/pkcs8.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs8/pkcs8_x509.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_akey.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_akeya.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_bcons.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_bitst.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_enum.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_extku.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ia5.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_info.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_lib.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_prn.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_skey.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_trs.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509spki.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_x509a.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/pkcs8.h ---------------------------------------------------------------------------------------------------- Copyright (c) 1999 The OpenSSL Project. All rights reserved. @@ -32946,9 +32944,9 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_conf.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_conf.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_conf.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_conf.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 1999-2002 The OpenSSL Project. All rights reserved. @@ -32999,11 +32997,11 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_alt.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_utl.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_alt.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_utl.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_alt.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_utl.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_alt.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_utl.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 1999-2003 The OpenSSL Project. All rights reserved. @@ -33055,15 +33053,15 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/ext_dat.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_cpols.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_int.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_purp.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_cpols.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_int.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_purp.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/x509v3_errors.h TYPE: LicenseType.bsd FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/ext_dat.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_cpols.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_int.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_purp.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_cpols.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_int.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_purp.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/x509v3_errors.h ---------------------------------------------------------------------------------------------------- Copyright (c) 1999-2004 The OpenSSL Project. All rights reserved. @@ -33223,11 +33221,11 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_crld.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_genn.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_crld.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_genn.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_crld.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_genn.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_crld.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_genn.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 1999-2008 The OpenSSL Project. All rights reserved. @@ -33418,9 +33416,9 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_algor.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x_algor.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_algor.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x_algor.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2000 The OpenSSL Project. All rights reserved. @@ -33471,12 +33469,12 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdh/ecdh.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdh/ecdh.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/ecdh.h TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdh/ecdh.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdh/ecdh.cc.inc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ecdh.h ---------------------------------------------------------------------------------------------------- Copyright (c) 2000-2002 The OpenSSL Project. All rights reserved. @@ -33528,9 +33526,9 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/ec_asn1.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/ec_asn1.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/ec_asn1.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/ec_asn1.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2000-2003 The OpenSSL Project. All rights reserved. @@ -33620,14 +33618,14 @@ authorization of the copyright holder. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/dh_extra/dh_asn1.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/dsa/dsa_asn1.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_asn1.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/dh_extra/dh_asn1.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/dsa/dsa_asn1.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_asn1.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/asn1t.h TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/dh_extra/dh_asn1.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/dsa/dsa_asn1.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_asn1.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/dh_extra/dh_asn1.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/dsa/dsa_asn1.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_asn1.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/asn1t.h ---------------------------------------------------------------------------------------------------- Copyright (c) 2000-2005 The OpenSSL Project. All rights reserved. @@ -33757,13 +33755,13 @@ authorization of the copyright holder. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_x509.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_xaux.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509cset.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_x509.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_xaux.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509cset.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_x509.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_xaux.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509cset.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_x509.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/pem/pem_xaux.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509cset.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2001 The OpenSSL Project. All rights reserved. @@ -33938,11 +33936,11 @@ authorization of the copyright holder. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/key_wrap.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/e_aes.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/key_wrap.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/e_aes.cc.inc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/key_wrap.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/e_aes.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/key_wrap.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/e_aes.cc.inc ---------------------------------------------------------------------------------------------------- Copyright (c) 2001-2011 The OpenSSL Project. All rights reserved. @@ -34717,12 +34715,12 @@ freely, subject to the following restrictions: ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/aes.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/mode_wrappers.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/aes.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/mode_wrappers.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/aes.h TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/aes.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/mode_wrappers.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/aes.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/mode_wrappers.cc.inc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/aes.h ---------------------------------------------------------------------------------------------------- Copyright (c) 2002-2006 The OpenSSL Project. All rights reserved. @@ -35342,13 +35340,13 @@ authorization of the copyright holder. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ncons.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_pcons.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_pmaps.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ncons.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_pcons.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_pmaps.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ncons.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_pcons.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_pmaps.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ncons.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_pcons.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_pmaps.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2003 The OpenSSL Project. All rights reserved. @@ -35673,9 +35671,9 @@ authorization of the copyright holder. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_vpm.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_vpm.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_vpm.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/x509_vpm.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2004 The OpenSSL Project. All rights reserved. @@ -36046,9 +36044,9 @@ authorization of the copyright holder. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/padding.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/padding.cc.inc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/padding.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rsa/padding.cc.inc ---------------------------------------------------------------------------------------------------- Copyright (c) 2005 The OpenSSL Project. All rights reserved. @@ -36100,22 +36098,22 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dsa_asn1.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ec.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ec_asn1.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_rsa.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_rsa_asn1.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/print.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/rsa_pss.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dsa_asn1.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ec.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ec_asn1.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_rsa.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_rsa_asn1.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/print.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/rsa_pss.cc TYPE: LicenseType.bsd FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dsa_asn1.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ec.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ec_asn1.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_rsa.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_rsa_asn1.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/print.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/rsa_pss.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dsa_asn1.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ec.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ec_asn1.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_rsa.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_rsa_asn1.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/print.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/rsa_pss.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2006 The OpenSSL Project. All rights reserved. @@ -36166,9 +36164,9 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digestsign/digestsign.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digestsign/digestsign.cc.inc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digestsign/digestsign.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/digestsign/digestsign.cc.inc ---------------------------------------------------------------------------------------------------- Copyright (c) 2006,2007 The OpenSSL Project. All rights reserved. @@ -36539,21 +36537,21 @@ SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/e_aesccm.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/cbc.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/cfb.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/ctr.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/gcm.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/e_aesccm.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/cbc.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/cfb.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/ctr.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/gcm.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/ofb.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/ofb.cc.inc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/e_aesccm.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/cbc.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/cfb.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/ctr.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/gcm.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/e_aesccm.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/cbc.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/cfb.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/ctr.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/gcm.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/ofb.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/ofb.cc.inc ---------------------------------------------------------------------------------------------------- Copyright (c) 2008 The OpenSSL Project. All rights reserved. @@ -37236,9 +37234,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cmac/cmac.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cmac/cmac.cc.inc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cmac/cmac.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cmac/cmac.cc.inc ---------------------------------------------------------------------------------------------------- Copyright (c) 2010 The OpenSSL Project. All rights reserved. @@ -37436,9 +37434,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/dh_extra/params.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/dh_extra/params.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/dh_extra/params.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/dh_extra/params.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2011 The OpenSSL Project. All rights reserved. @@ -37489,9 +37487,9 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/xts/xts.c +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/xts/xts.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/decrepit/xts/xts.c +FILE: ../../../flutter/third_party/boringssl/src/decrepit/xts/xts.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2011 The OpenSSL Project. All rights reserved. @@ -37677,9 +37675,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/tls_cbc.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/tls_cbc.cc TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/tls_cbc.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/tls_cbc.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2012 The OpenSSL Project. All rights reserved. @@ -38152,25 +38150,25 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/socket_helper.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bytestring/ber.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bytestring/cbb.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bytestring/cbs.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bio/socket_helper.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bytestring/ber.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bytestring/cbb.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bytestring/cbs.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bytestring/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/chacha/chacha.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_chacha20poly1305.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_tls.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/crypto.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/engine/engine.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/aead.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/hkdf/hkdf.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/rand.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/urandom.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs7/pkcs7.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305_arm.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305_vec.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/windows.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/chacha/chacha.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_chacha20poly1305.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_tls.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/crypto.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/engine/engine.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/aead.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/hkdf/hkdf.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/rand.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs7/pkcs7.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305_arm.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305_vec.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/urandom.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/windows.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/aead.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/bytestring.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/chacha.h @@ -38199,25 +38197,25 @@ ORIGIN: ../../../flutter/third_party/boringssl/src/tool/tool.cc ORIGIN: ../../../flutter/third_party/boringssl/src/tool/transport_common.cc ORIGIN: ../../../flutter/third_party/boringssl/src/tool/transport_common.h TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/socket_helper.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bytestring/ber.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bytestring/cbb.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/bytestring/cbs.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/bio/socket_helper.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bytestring/ber.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bytestring/cbb.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/bytestring/cbs.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/bytestring/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/chacha/chacha.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_chacha20poly1305.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_tls.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/crypto.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/engine/engine.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/aead.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/hkdf/hkdf.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/rand.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/urandom.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs7/pkcs7.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305_arm.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305_vec.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/windows.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/chacha/chacha.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_chacha20poly1305.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_tls.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/crypto.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/engine/engine.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cipher/aead.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/hkdf/hkdf.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/rand.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs7/pkcs7.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305_arm.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305_vec.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/urandom.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/windows.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/aead.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/bytestring.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/chacha.h @@ -38580,17 +38578,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bn_extra/bn_asn1.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bn_extra/bn_asn1.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/conf/internal.h ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/curve25519/asm/x25519-asm-arm.S -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p224-64.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/util.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p224-64.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/util.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/refcount.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/thread_none.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/thread_pthread.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/thread_win.c -ORIGIN: ../../../flutter/third_party/boringssl/src/gen/crypto/err_data.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/refcount.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/thread_none.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/thread_pthread.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/thread_win.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/gen/crypto/err_data.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/buffer.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/cmac.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/curve25519.h @@ -38604,17 +38602,17 @@ ORIGIN: ../../../flutter/third_party/boringssl/src/tool/generate_ed25519.cc ORIGIN: ../../../flutter/third_party/boringssl/src/tool/genrsa.cc ORIGIN: ../../../flutter/third_party/boringssl/src/tool/rand.cc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/bn_extra/bn_asn1.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/bn_extra/bn_asn1.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/conf/internal.h FILE: ../../../flutter/third_party/boringssl/src/crypto/curve25519/asm/x25519-asm-arm.S -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p224-64.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/util.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p224-64.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/util.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/refcount.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/thread_none.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/thread_pthread.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/thread_win.c -FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/err_data.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/refcount.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/thread_none.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/thread_pthread.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/thread_win.cc +FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/err_data.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/buffer.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/cmac.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/curve25519.h @@ -38822,20 +38820,20 @@ freely, subject to the following restrictions: ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bytestring/asn1_compat.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_linux.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_arm_linux.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/curve25519/spake25519.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/polyval.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bytestring/asn1_compat.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_linux.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_arm_linux.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/curve25519/spake25519.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/polyval.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/obj/objects.go ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/poly1305/internal.h ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pool/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pool/pool.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/deterministic.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/evp/dss1.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/evp/evp_do_all.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/obj/obj_decrepit.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/x509/x509_decrepit.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pool/pool.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/deterministic.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/evp/dss1.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/evp/evp_do_all.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/obj/obj_decrepit.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/x509/x509_decrepit.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/asn1_mac.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/obj_mac.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/pool.h @@ -38844,20 +38842,20 @@ ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/tls13_client.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/tls13_enc.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/tls13_server.cc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/bytestring/asn1_compat.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_linux.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_arm_linux.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/curve25519/spake25519.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/polyval.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/bytestring/asn1_compat.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_linux.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_arm_linux.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/curve25519/spake25519.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/polyval.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/obj/objects.go FILE: ../../../flutter/third_party/boringssl/src/crypto/poly1305/internal.h FILE: ../../../flutter/third_party/boringssl/src/crypto/pool/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/pool/pool.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/deterministic.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/evp/dss1.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/evp/evp_do_all.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/obj/obj_decrepit.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/x509/x509_decrepit.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/pool/pool.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/deterministic.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/evp/dss1.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/evp/evp_do_all.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/obj/obj_decrepit.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/x509/x509_decrepit.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/asn1_mac.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/obj_mac.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/pool.h @@ -38911,44 +38909,44 @@ freely, subject to the following restrictions: ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_aesctrhmac.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_aesgcmsiv.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_aesctrhmac.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_aesgcmsiv.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/err/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ed25519.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ed25519_asn1.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ed25519.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ed25519_asn1.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bcm.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bcm.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/delocate.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/ctrdrbg.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/self_check/fips.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/self_check/self_check.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/ctrdrbg.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/self_check/fips.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/self_check/self_check.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs7/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs7/pkcs7_x509.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/forkunsafe.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/rand_extra.c -ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/cfb/cfb.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/pkcs7/pkcs7_x509.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/forkunsafe.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/rand_extra.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/decrepit/cfb/cfb.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/is_boringssl.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/span.h ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/ssl_versions.cc ORIGIN: ../../../flutter/third_party/boringssl/src/tool/file.cc ORIGIN: ../../../flutter/third_party/boringssl/src/tool/sign.cc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_aesctrhmac.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_aesgcmsiv.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_aesctrhmac.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cipher_extra/e_aesgcmsiv.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/err/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ed25519.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ed25519_asn1.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ed25519.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_ed25519_asn1.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bcm.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bcm.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/delocate.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/ctrdrbg.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/self_check/fips.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/self_check/self_check.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/ctrdrbg.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/self_check/fips.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/self_check/self_check.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs7/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs7/pkcs7_x509.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/forkunsafe.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/rand_extra.c -FILE: ../../../flutter/third_party/boringssl/src/decrepit/cfb/cfb.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/pkcs7/pkcs7_x509.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/forkunsafe.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/rand_extra.cc +FILE: ../../../flutter/third_party/boringssl/src/decrepit/cfb/cfb.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/is_boringssl.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/span.h FILE: ../../../flutter/third_party/boringssl/src/ssl/ssl_versions.cc @@ -39135,38 +39133,38 @@ IN THE MATERIALS. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bytestring/unicode.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bytestring/unicode.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/chacha/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_fuchsia.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_fuchsia.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_arm_linux.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/div_extra.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd_extra.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/felem.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/scalar.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple_mul.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/md5/internal.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/div_extra.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd_extra.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/felem.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/scalar.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple_mul.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/internal.h ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/tls/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/hrss/hrss.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/hrss/hrss.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/hrss/internal.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/md5/internal.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/e_os2.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/hrss.h ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/handoff.cc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/bytestring/unicode.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/bytestring/unicode.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/chacha/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_fuchsia.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_fuchsia.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_arm_linux.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/div_extra.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd_extra.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/felem.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/scalar.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple_mul.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/md5/internal.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/div_extra.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/gcd_extra.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/felem.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/scalar.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple_mul.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/sha/internal.h FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/tls/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/hrss/hrss.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/hrss/hrss.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/hrss/internal.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/md5/internal.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/e_os2.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/hrss.h FILE: ../../../flutter/third_party/boringssl/src/ssl/handoff.cc @@ -39188,9 +39186,9 @@ CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_win.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_win.cc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_win.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_win.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2018, Google Inc. Copyright (c) 2020, Arm Ltd. @@ -39275,26 +39273,26 @@ SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/ec_derive.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_x25519.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_x25519_asn1.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/aes_nohw.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/fips_shared_support.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/gcm_nohw.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/siphash/siphash.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/ec_derive.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_x25519.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_x25519_asn1.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/aes_nohw.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/fips_shared_support.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/gcm_nohw.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/siphash/siphash.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/trust_token/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/trust_token/trust_token.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/trust_token/trust_token.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/siphash.h TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/ec_derive.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_x25519.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_x25519_asn1.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/aes_nohw.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/fips_shared_support.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/gcm_nohw.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/siphash/siphash.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/ec_derive.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_x25519.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_x25519_asn1.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/aes_nohw.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/fips_shared_support.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/gcm_nohw.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/siphash/siphash.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/trust_token/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/trust_token/trust_token.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/trust_token/trust_token.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/siphash.h ---------------------------------------------------------------------------------------------------- Copyright (c) 2019, Google Inc. @@ -39433,40 +39431,38 @@ MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/curve25519/curve25519.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/curve25519/curve25519.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/curve25519/curve25519_tables.h ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/curve25519/internal.h ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/dsa/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/hash_to_curve.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/hash_to_curve.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256_table.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/fork_detect.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/fork_detect.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/getrandom_fillin.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/hpke/hpke.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/passive.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/trust_token/pmbtoken.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/trust_token/voprf.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/hpke/hpke.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/fork_detect.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/getrandom_fillin.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/passive.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/trust_token/pmbtoken.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/trust_token/voprf.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/hpke.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/trust_token.h ORIGIN: ../../../flutter/third_party/boringssl/src/tool/fd.cc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/curve25519/curve25519.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/curve25519/curve25519.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/curve25519/curve25519_tables.h FILE: ../../../flutter/third_party/boringssl/src/crypto/curve25519/internal.h FILE: ../../../flutter/third_party/boringssl/src/crypto/dsa/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/hash_to_curve.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/hash_to_curve.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/ec_extra/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256_table.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/fork_detect.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/fork_detect.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/rand/getrandom_fillin.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/hpke/hpke.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/passive.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/trust_token/pmbtoken.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/trust_token/voprf.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/hpke/hpke.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/fork_detect.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/getrandom_fillin.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/passive.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/trust_token/pmbtoken.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/trust_token/voprf.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/hpke.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/trust_token.h FILE: ../../../flutter/third_party/boringssl/src/tool/fd.cc @@ -39674,18 +39670,18 @@ SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/blake2/blake2.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/blake2/blake2.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/blake2/blake2b256_tests.txt -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_apple.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_apple.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdsa/internal.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/blake2.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/x509_vfy.h ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/encrypted_client_hello.cc ORIGIN: ../../../flutter/third_party/boringssl/src/tool/generate_ech.cc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/blake2/blake2.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/blake2/blake2.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/blake2/blake2b256_tests.txt -FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_apple.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_apple.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdsa/internal.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/blake2.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/x509_vfy.h @@ -39801,20 +39797,20 @@ SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/posix_time.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_arm_freebsd.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_hkdf.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/asn1/posix_time.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_arm_freebsd.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_hkdf.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/dh/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/policy.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/policy.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/ctrdrbg.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/kdf.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/posix_time.h TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/posix_time.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_arm_freebsd.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_hkdf.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/asn1/posix_time.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_arm_freebsd.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_hkdf.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/dh/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/policy.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/policy.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ctrdrbg.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/kdf.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/posix_time.h @@ -39836,9 +39832,9 @@ CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_openbsd.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_openbsd.cc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_openbsd.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_openbsd.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2022, Robert Nagy @@ -39897,39 +39893,43 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_sysreg.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/curve25519/curve25519_64_adx.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_sysreg.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/curve25519/curve25519_64_adx.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/builtin_curves.h ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/keccak/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/keccak/keccak.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/keccak/keccak.cc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/kyber/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/kyber/kyber.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/getentropy.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/ios.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/trusty.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/kyber/kyber.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/mlkem/internal.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/getentropy.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/ios.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/trusty.cc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/asm_base.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/experimental/kyber.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/pki/certificate.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/target.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/x509v3.h ORIGIN: ../../../flutter/third_party/boringssl/src/pki/certificate.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/pki/verify.cc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_sysreg.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/curve25519/curve25519_64_adx.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_sysreg.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/curve25519/curve25519_64_adx.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/builtin_curves.h FILE: ../../../flutter/third_party/boringssl/src/crypto/keccak/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/keccak/keccak.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/keccak/keccak.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/kyber/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/kyber/kyber.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/getentropy.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/ios.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/trusty.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/kyber/kyber.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/internal.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/getentropy.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/ios.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/trusty.cc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/asm_base.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/experimental/kyber.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/pki/certificate.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/target.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/x509v3.h FILE: ../../../flutter/third_party/boringssl/src/pki/certificate.cc +FILE: ../../../flutter/third_party/boringssl/src/pki/verify.cc ---------------------------------------------------------------------------------------------------- Copyright (c) 2023, Google Inc. @@ -39948,39 +39948,35 @@ CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/address.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/address.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/fors.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/fors.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/merkle.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/merkle.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/params.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/spx.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/spx_util.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/spx_util.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/thash.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/thash.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/wots.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/spx/wots.h -ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/experimental/spx.h +ORIGIN: ../../../flutter/third_party/boringssl/src/MODULE.bazel +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/bcm_support.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bcm_interface.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/sysrand_internal.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_extra.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/sha/sha1.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/sha/sha256.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/sha/sha512.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/mlkem.h +ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/pki/verify_error.h +ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/time.h +ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/ssl_credential.cc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/address.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/address.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/fors.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/fors.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/merkle.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/merkle.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/params.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/spx.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/spx_util.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/spx_util.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/thash.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/thash.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/wots.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/wots.h -FILE: ../../../flutter/third_party/boringssl/src/include/openssl/experimental/spx.h ----------------------------------------------------------------------------------------------------- -Copyright (c) 2023, Google LLC +FILE: ../../../flutter/third_party/boringssl/src/MODULE.bazel +FILE: ../../../flutter/third_party/boringssl/src/crypto/bcm_support.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bcm_interface.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/rand_extra/sysrand_internal.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_extra.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/sha/sha1.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/sha/sha256.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/sha/sha512.cc +FILE: ../../../flutter/third_party/boringssl/src/include/openssl/mlkem.h +FILE: ../../../flutter/third_party/boringssl/src/include/openssl/pki/verify_error.h +FILE: ../../../flutter/third_party/boringssl/src/include/openssl/time.h +FILE: ../../../flutter/third_party/boringssl/src/ssl/ssl_credential.cc +---------------------------------------------------------------------------------------------------- +Copyright (c) 2024, Google Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -39997,17 +39993,43 @@ CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/MODULE.bazel -ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/pki/verify_error.h -ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/time.h -ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/ssl_credential.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/mldsa/internal.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/mldsa/mldsa.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/address.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/fors.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/fors.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/internal.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/merkle.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/merkle.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/params.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/slhdsa.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/thash.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/thash.h +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/wots.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/wots.h +ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/bcm_public.h +ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/mldsa.h +ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/slhdsa.h TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/MODULE.bazel -FILE: ../../../flutter/third_party/boringssl/src/include/openssl/pki/verify_error.h -FILE: ../../../flutter/third_party/boringssl/src/include/openssl/time.h -FILE: ../../../flutter/third_party/boringssl/src/ssl/ssl_credential.cc ----------------------------------------------------------------------------------------------------- -Copyright (c) 2024, Google Inc. +FILE: ../../../flutter/third_party/boringssl/src/crypto/mldsa/internal.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/mldsa/mldsa.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/address.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/fors.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/fors.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/internal.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/merkle.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/merkle.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/params.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/slhdsa.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/thash.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/thash.h +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/wots.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/wots.h +FILE: ../../../flutter/third_party/boringssl/src/include/openssl/bcm_public.h +FILE: ../../../flutter/third_party/boringssl/src/include/openssl/mldsa.h +FILE: ../../../flutter/third_party/boringssl/src/include/openssl/slhdsa.h +---------------------------------------------------------------------------------------------------- +Copyright (c) 2024, Google LLC Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -40385,9 +40407,9 @@ THE SOFTWARE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ocsp.c (with ../../../flutter/third_party/boringssl/src/LICENSE) +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ocsp.cc (with ../../../flutter/third_party/boringssl/src/LICENSE) TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ocsp.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ocsp.cc ---------------------------------------------------------------------------------------------------- Copyright 2000-2016 The OpenSSL Project Authors. All Rights Reserved. @@ -40494,13 +40516,13 @@ license provided above. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_key.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_montgomery.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_key.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_montgomery.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/oct.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/wnaf.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/oct.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple.cc.inc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/wnaf.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/ec.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/ec_key.h ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/tls1.h @@ -40508,13 +40530,13 @@ ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/handshake_client.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/handshake_server.cc ORIGIN: ../../../flutter/third_party/boringssl/src/ssl/s3_lib.cc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_key.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_montgomery.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_key.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_montgomery.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/oct.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/wnaf.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/oct.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/simple.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/wnaf.cc.inc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ec.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ec_key.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/tls1.h @@ -40533,12 +40555,12 @@ license provided above. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.c + ../../../flutter/third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.c -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdh/ecdh.c + ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdh/ecdh.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.cc + ../../../flutter/third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.cc +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdh/ecdh.cc.inc + ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdh/ecdh.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/ecdh.h + ../../../flutter/third_party/boringssl/src/include/openssl/ecdh.h TYPE: LicenseType.bsd -FILE: ../../../flutter/third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdh/ecdh.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdh/ecdh.cc.inc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ecdh.h ---------------------------------------------------------------------------------------------------- Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. @@ -41137,9 +41159,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_print.c (with ../../../flutter/third_party/boringssl/src/LICENSE) +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_print.cc (with ../../../flutter/third_party/boringssl/src/LICENSE) TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_print.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_print.cc ---------------------------------------------------------------------------------------------------- Copyright 2006-2017 The OpenSSL Project Authors. All Rights Reserved. @@ -41151,9 +41173,9 @@ https://www.openssl.org/source/license.html ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh.c (with ../../../flutter/third_party/boringssl/src/LICENSE) +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh.cc (with ../../../flutter/third_party/boringssl/src/LICENSE) TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh.cc ---------------------------------------------------------------------------------------------------- Copyright 2006-2019 The OpenSSL Project Authors. All Rights Reserved. @@ -41165,9 +41187,9 @@ https://www.openssl.org/source/license.html ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh_asn1.c (with ../../../flutter/third_party/boringssl/src/LICENSE) +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh_asn1.cc (with ../../../flutter/third_party/boringssl/src/LICENSE) TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh_asn1.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh_asn1.cc ---------------------------------------------------------------------------------------------------- Copyright 2006-2021 The OpenSSL Project Authors. All Rights Reserved. @@ -42477,6 +42499,8 @@ ORIGIN: ../../../flutter/assets/asset_manager.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/assets/asset_resolver.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/assets/directory_asset_bundle.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/assets/directory_asset_bundle.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/assets/native_assets.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/assets/native_assets.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/benchmarking/benchmarking.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/benchmarking/benchmarking.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/benchmarking/library.cc + ../../../flutter/LICENSE @@ -42525,19 +42549,62 @@ ORIGIN: ../../../flutter/display_list/dl_op_records.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/dl_paint.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/dl_paint.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/dl_sampling_options.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/dl_storage.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/dl_storage.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/dl_tile_mode.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/dl_vertices.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/dl_vertices.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_filters/dl_blend_color_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_filters/dl_blend_color_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_filters/dl_matrix_color_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_filters/dl_matrix_color_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_conical_gradient_color_source.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_conical_gradient_color_source.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_gradient_color_source_base.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_image_color_source.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_image_color_source.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_linear_gradient_color_source.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_linear_gradient_color_source.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_matrix_color_source_base.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_radial_gradient_color_source.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_radial_gradient_color_source.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_runtime_effect_color_source.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_runtime_effect_color_source.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_sweep_gradient_color_source.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/color_sources/dl_sweep_gradient_color_source.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_color_filter.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_color_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/dl_color_filters.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_color_source.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_color_source.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/dl_color_sources.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_image_filter.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_image_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/dl_image_filters.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_mask_filter.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_mask_filter.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_runtime_effect.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_runtime_effect.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_blur_image_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_blur_image_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_color_filter_image_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_color_filter_image_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_compose_image_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_compose_image_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_dilate_image_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_dilate_image_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_erode_image_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_erode_image_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_local_matrix_image_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_local_matrix_image_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_matrix_image_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_matrix_image_filter.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/geometry/dl_geometry_types.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/geometry/dl_path.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/geometry/dl_path.h + ../../../flutter/LICENSE @@ -42727,18 +42794,10 @@ ORIGIN: ../../../flutter/fml/platform/darwin/message_loop_darwin.mm + ../../../f ORIGIN: ../../../flutter/fml/platform/darwin/paths_darwin.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/darwin/platform_version.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/darwin/platform_version.mm + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/fml/platform/darwin/scoped_block.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/fml/platform/darwin/scoped_block.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/darwin/scoped_nsautorelease_pool.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/darwin/scoped_nsautorelease_pool.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/fml/platform/darwin/scoped_nsobject.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/fml/platform/darwin/scoped_nsobject.mm + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/fml/platform/darwin/scoped_policy.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/fml/platform/darwin/scoped_typeref.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/darwin/string_range_sanitization.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/darwin/string_range_sanitization.mm + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/fml/platform/darwin/weak_nsobject.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/fml/platform/darwin/weak_nsobject.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/fuchsia/log_interest_listener.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/fuchsia/log_interest_listener.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/fuchsia/log_state.cc + ../../../flutter/LICENSE @@ -42898,6 +42957,7 @@ ORIGIN: ../../../flutter/impeller/core/formats.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/core/formats.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/core/host_buffer.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/core/host_buffer.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/core/idle_waiter.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/core/platform.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/core/platform.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/core/range.cc + ../../../flutter/LICENSE @@ -42988,6 +43048,8 @@ ORIGIN: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents ORIGIN: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/filters/yuv_to_rgb_filter_contents.cc + ../../../flutter/LICENSE @@ -43044,6 +43106,8 @@ ORIGIN: ../../../flutter/impeller/entity/geometry/rect_geometry.cc + ../../../fl ORIGIN: ../../../flutter/impeller/entity/geometry/rect_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/round_rect_geometry.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/round_rect_geometry.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/geometry/round_superellipse_geometry.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/geometry/round_superellipse_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/superellipse_geometry.cc + ../../../flutter/LICENSE @@ -43066,6 +43130,7 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/blending/porter_duff_blend.vert ORIGIN: ../../../flutter/impeller/entity/shaders/blending/vertices_uber.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/clip.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/clip.vert + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/shaders/downsample.glsl + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/filters/border_mask_blur.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/filters/color_matrix_color_filter.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/filters/filter_position.vert + ../../../flutter/LICENSE @@ -43079,21 +43144,26 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/glyph_atlas.frag + ../../../flu ORIGIN: ../../../flutter/impeller/entity/shaders/glyph_atlas.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/conical_gradient_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/conical_gradient_ssbo_fill.frag + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/conical_gradient_uniform_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/fast_gradient.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/fast_gradient.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/gradient_fill.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/linear_gradient_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/linear_gradient_ssbo_fill.frag + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/linear_gradient_uniform_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/radial_gradient_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/radial_gradient_ssbo_fill.frag + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/radial_gradient_uniform_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/sweep_gradient_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/sweep_gradient_ssbo_fill.frag + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/sweep_gradient_uniform_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/rrect_blur.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/rrect_blur.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/runtime_effect.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/solid_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/solid_fill.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/texture_downsample.frag + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/shaders/texture_downsample_gles.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/texture_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/texture_fill.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/texture_fill_strict_src.frag + ../../../flutter/LICENSE @@ -43483,7 +43553,9 @@ ORIGIN: ../../../flutter/impeller/toolkit/interop/image_filter.cc + ../../../flu ORIGIN: ../../../flutter/impeller/toolkit/interop/image_filter.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/toolkit/interop/impeller.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/toolkit/interop/impeller.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/interop/impeller.hpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/toolkit/interop/impeller_c.c + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/interop/impeller_cc.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/toolkit/interop/mask_filter.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/toolkit/interop/mask_filter.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/toolkit/interop/object.cc + ../../../flutter/LICENSE @@ -43544,8 +43616,6 @@ ORIGIN: ../../../flutter/lib/gpu/export.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/gpu/export.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/gpu/formats.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/gpu/formats.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/gpu/host_buffer.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/gpu/host_buffer.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/gpu/lib/gpu.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/gpu/lib/src/buffer.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/gpu/lib/src/command_buffer.dart + ../../../flutter/LICENSE @@ -43886,6 +43956,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics.dart + ../../../flu ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/checkable.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/header.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/heading.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/image.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/incrementable.dart + ../../../flutter/LICENSE @@ -44009,6 +44080,8 @@ ORIGIN: ../../../flutter/lib/web_ui/skwasm/filters.cpp + ../../../flutter/LICENS ORIGIN: ../../../flutter/lib/web_ui/skwasm/fonts.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/helpers.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/image.cpp + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/skwasm/library_skwasm_multi_threaded.js + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/skwasm/library_skwasm_single_threaded.js + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/library_skwasm_support.js + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/paint.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/path.cpp + ../../../flutter/LICENSE @@ -44018,6 +44091,8 @@ ORIGIN: ../../../flutter/lib/web_ui/skwasm/skwasm_support.h + ../../../flutter/L ORIGIN: ../../../flutter/lib/web_ui/skwasm/string.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/surface.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/surface.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/skwasm/surface_mt.cpp + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/skwasm/surface_st.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/line_metrics.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/paragraph.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/paragraph_builder.cpp + ../../../flutter/LICENSE @@ -44211,13 +44286,6 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/Build.java + ../../.. ORIGIN: ../../../flutter/shell/platform/android/io/flutter/BuildConfig.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/FlutterInjector.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/Log.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivity.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityEvents.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/app/FlutterApplication.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/app/FlutterFragmentActivity.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPlayStoreSplitApplication.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/ExclusiveAppComponent.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java + ../../../flutter/LICENSE @@ -44274,8 +44342,6 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plug ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/service/ServiceAware.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/service/ServiceControlSurface.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/service/ServicePluginBinding.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java + ../../../flutter/LICENSE @@ -44349,10 +44415,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/util/ViewUtils.java + ORIGIN: ../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/view/FlutterCallbackInformation.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/view/FlutterMain.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/view/FlutterRunArguments.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/view/FlutterView.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/view/TextureRegistry.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/view/VsyncWaiter.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/jni/jni_mock.h + ../../../flutter/LICENSE @@ -44531,9 +44594,11 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterOverl ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h + ../../../flutter/LICENSE @@ -44570,6 +44635,7 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewT ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/IOKit.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap.g.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap_Internal.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject+UIFocusSystem.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm + ../../../flutter/LICENSE @@ -44592,8 +44658,6 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/overlay_laye ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h + ../../../flutter/LICENSE @@ -44773,6 +44837,8 @@ ORIGIN: ../../../flutter/shell/platform/embedder/embedder_surface_software.cc + ORIGIN: ../../../flutter/shell/platform/embedder/embedder_surface_software.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/embedder/embedder_surface_vulkan.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/embedder/embedder_surface_vulkan.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/embedder/embedder_task_runner.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/embedder/embedder_task_runner.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/embedder/embedder_thread_host.cc + ../../../flutter/LICENSE @@ -44972,6 +45038,7 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_event_channel.cc + ../../../flu ORIGIN: ../../../flutter/shell/platform/linux/fl_event_channel_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_framebuffer.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_framebuffer.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_framebuffer_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_gnome_settings.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_gnome_settings.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_gnome_settings_test.cc + ../../../flutter/LICENSE @@ -44988,6 +45055,10 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_private. ORIGIN: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_key_event.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_key_event.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_key_event_channel.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_key_event_channel.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_channel.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_channel.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler_test.cc + ../../../flutter/LICENSE @@ -45013,11 +45084,16 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_method_codec_private.h + ../../ ORIGIN: ../../../flutter/shell/platform/linux/fl_method_codec_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_method_response.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_method_response_test.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_mouse_cursor_channel.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_mouse_cursor_channel.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_private.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_test.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_channel.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_channel.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_channel_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_handler.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_platform_handler_test.cc + ../../../flutter/LICENSE @@ -45025,6 +45101,9 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_plugin_registrar.cc + ../../../ ORIGIN: ../../../flutter/shell/platform/linux/fl_plugin_registrar_private.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_plugin_registrar_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_plugin_registry.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_pointer_manager.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_pointer_manager.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_pointer_manager_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_renderable.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_renderable.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_renderer.cc + ../../../flutter/LICENSE @@ -45037,10 +45116,10 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_renderer_test.cc + ../../../flu ORIGIN: ../../../flutter/shell/platform/linux/fl_scrolling_manager.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_scrolling_manager.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_scrolling_manager_test.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/linux/fl_scrolling_view_delegate.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/linux/fl_scrolling_view_delegate.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_channel.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_channel.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_handler.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_settings_handler_test.cc + ../../../flutter/LICENSE @@ -45057,6 +45136,8 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_string_codec.cc + ../../../flut ORIGIN: ../../../flutter/shell/platform/linux/fl_string_codec_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_task_runner.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_task_runner.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_channel.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_channel.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_handler.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_text_input_handler_test.cc + ../../../flutter/LICENSE @@ -45076,7 +45157,6 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_view.cc + ../../../flutter/LICE ORIGIN: ../../../flutter/shell/platform/linux/fl_view_accessible.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_view_accessible.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_view_accessible_test.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/linux/fl_view_private.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_view_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_window_state_monitor.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_window_state_monitor.h + ../../../flutter/LICENSE @@ -45222,8 +45302,6 @@ ORIGIN: ../../../flutter/third_party/accessibility/base/container_utils.h + ../. ORIGIN: ../../../flutter/third_party/accessibility/base/logging.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/third_party/accessibility/base/logging.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/third_party/accessibility/base/macros.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/third_party/accessibility/base/platform/darwin/scoped_nsobject.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/third_party/accessibility/base/platform/darwin/scoped_nsobject.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/third_party/accessibility/base/simple_token.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/third_party/accessibility/base/simple_token.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/third_party/accessibility/base/string_utils.cc + ../../../flutter/LICENSE @@ -45346,6 +45424,8 @@ FILE: ../../../flutter/assets/asset_manager.h FILE: ../../../flutter/assets/asset_resolver.h FILE: ../../../flutter/assets/directory_asset_bundle.cc FILE: ../../../flutter/assets/directory_asset_bundle.h +FILE: ../../../flutter/assets/native_assets.cc +FILE: ../../../flutter/assets/native_assets.h FILE: ../../../flutter/benchmarking/benchmarking.cc FILE: ../../../flutter/benchmarking/benchmarking.h FILE: ../../../flutter/benchmarking/library.cc @@ -45398,19 +45478,62 @@ FILE: ../../../flutter/display_list/dl_op_records.h FILE: ../../../flutter/display_list/dl_paint.cc FILE: ../../../flutter/display_list/dl_paint.h FILE: ../../../flutter/display_list/dl_sampling_options.h +FILE: ../../../flutter/display_list/dl_storage.cc +FILE: ../../../flutter/display_list/dl_storage.h FILE: ../../../flutter/display_list/dl_tile_mode.h FILE: ../../../flutter/display_list/dl_vertices.cc FILE: ../../../flutter/display_list/dl_vertices.h +FILE: ../../../flutter/display_list/effects/color_filters/dl_blend_color_filter.cc +FILE: ../../../flutter/display_list/effects/color_filters/dl_blend_color_filter.h +FILE: ../../../flutter/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.cc +FILE: ../../../flutter/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.h +FILE: ../../../flutter/display_list/effects/color_filters/dl_matrix_color_filter.cc +FILE: ../../../flutter/display_list/effects/color_filters/dl_matrix_color_filter.h +FILE: ../../../flutter/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.cc +FILE: ../../../flutter/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.h +FILE: ../../../flutter/display_list/effects/color_sources/dl_conical_gradient_color_source.cc +FILE: ../../../flutter/display_list/effects/color_sources/dl_conical_gradient_color_source.h +FILE: ../../../flutter/display_list/effects/color_sources/dl_gradient_color_source_base.h +FILE: ../../../flutter/display_list/effects/color_sources/dl_image_color_source.cc +FILE: ../../../flutter/display_list/effects/color_sources/dl_image_color_source.h +FILE: ../../../flutter/display_list/effects/color_sources/dl_linear_gradient_color_source.cc +FILE: ../../../flutter/display_list/effects/color_sources/dl_linear_gradient_color_source.h +FILE: ../../../flutter/display_list/effects/color_sources/dl_matrix_color_source_base.h +FILE: ../../../flutter/display_list/effects/color_sources/dl_radial_gradient_color_source.cc +FILE: ../../../flutter/display_list/effects/color_sources/dl_radial_gradient_color_source.h +FILE: ../../../flutter/display_list/effects/color_sources/dl_runtime_effect_color_source.cc +FILE: ../../../flutter/display_list/effects/color_sources/dl_runtime_effect_color_source.h +FILE: ../../../flutter/display_list/effects/color_sources/dl_sweep_gradient_color_source.cc +FILE: ../../../flutter/display_list/effects/color_sources/dl_sweep_gradient_color_source.h FILE: ../../../flutter/display_list/effects/dl_color_filter.cc FILE: ../../../flutter/display_list/effects/dl_color_filter.h +FILE: ../../../flutter/display_list/effects/dl_color_filters.h FILE: ../../../flutter/display_list/effects/dl_color_source.cc FILE: ../../../flutter/display_list/effects/dl_color_source.h +FILE: ../../../flutter/display_list/effects/dl_color_sources.h FILE: ../../../flutter/display_list/effects/dl_image_filter.cc FILE: ../../../flutter/display_list/effects/dl_image_filter.h +FILE: ../../../flutter/display_list/effects/dl_image_filters.h FILE: ../../../flutter/display_list/effects/dl_mask_filter.cc FILE: ../../../flutter/display_list/effects/dl_mask_filter.h FILE: ../../../flutter/display_list/effects/dl_runtime_effect.cc FILE: ../../../flutter/display_list/effects/dl_runtime_effect.h +FILE: ../../../flutter/display_list/effects/image_filters/dl_blur_image_filter.cc +FILE: ../../../flutter/display_list/effects/image_filters/dl_blur_image_filter.h +FILE: ../../../flutter/display_list/effects/image_filters/dl_color_filter_image_filter.cc +FILE: ../../../flutter/display_list/effects/image_filters/dl_color_filter_image_filter.h +FILE: ../../../flutter/display_list/effects/image_filters/dl_compose_image_filter.cc +FILE: ../../../flutter/display_list/effects/image_filters/dl_compose_image_filter.h +FILE: ../../../flutter/display_list/effects/image_filters/dl_dilate_image_filter.cc +FILE: ../../../flutter/display_list/effects/image_filters/dl_dilate_image_filter.h +FILE: ../../../flutter/display_list/effects/image_filters/dl_erode_image_filter.cc +FILE: ../../../flutter/display_list/effects/image_filters/dl_erode_image_filter.h +FILE: ../../../flutter/display_list/effects/image_filters/dl_local_matrix_image_filter.cc +FILE: ../../../flutter/display_list/effects/image_filters/dl_local_matrix_image_filter.h +FILE: ../../../flutter/display_list/effects/image_filters/dl_matrix_image_filter.cc +FILE: ../../../flutter/display_list/effects/image_filters/dl_matrix_image_filter.h +FILE: ../../../flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.cc +FILE: ../../../flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.h FILE: ../../../flutter/display_list/geometry/dl_geometry_types.h FILE: ../../../flutter/display_list/geometry/dl_path.cc FILE: ../../../flutter/display_list/geometry/dl_path.h @@ -45600,18 +45723,10 @@ FILE: ../../../flutter/fml/platform/darwin/message_loop_darwin.mm FILE: ../../../flutter/fml/platform/darwin/paths_darwin.mm FILE: ../../../flutter/fml/platform/darwin/platform_version.h FILE: ../../../flutter/fml/platform/darwin/platform_version.mm -FILE: ../../../flutter/fml/platform/darwin/scoped_block.h -FILE: ../../../flutter/fml/platform/darwin/scoped_block.mm FILE: ../../../flutter/fml/platform/darwin/scoped_nsautorelease_pool.cc FILE: ../../../flutter/fml/platform/darwin/scoped_nsautorelease_pool.h -FILE: ../../../flutter/fml/platform/darwin/scoped_nsobject.h -FILE: ../../../flutter/fml/platform/darwin/scoped_nsobject.mm -FILE: ../../../flutter/fml/platform/darwin/scoped_policy.h -FILE: ../../../flutter/fml/platform/darwin/scoped_typeref.h FILE: ../../../flutter/fml/platform/darwin/string_range_sanitization.h FILE: ../../../flutter/fml/platform/darwin/string_range_sanitization.mm -FILE: ../../../flutter/fml/platform/darwin/weak_nsobject.h -FILE: ../../../flutter/fml/platform/darwin/weak_nsobject.mm FILE: ../../../flutter/fml/platform/fuchsia/log_interest_listener.cc FILE: ../../../flutter/fml/platform/fuchsia/log_interest_listener.h FILE: ../../../flutter/fml/platform/fuchsia/log_state.cc @@ -45771,6 +45886,7 @@ FILE: ../../../flutter/impeller/core/formats.cc FILE: ../../../flutter/impeller/core/formats.h FILE: ../../../flutter/impeller/core/host_buffer.cc FILE: ../../../flutter/impeller/core/host_buffer.h +FILE: ../../../flutter/impeller/core/idle_waiter.h FILE: ../../../flutter/impeller/core/platform.cc FILE: ../../../flutter/impeller/core/platform.h FILE: ../../../flutter/impeller/core/range.cc @@ -45861,6 +45977,8 @@ FILE: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents.c FILE: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents.h FILE: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.cc FILE: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.h +FILE: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.cc +FILE: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.h FILE: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.cc FILE: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.h FILE: ../../../flutter/impeller/entity/contents/filters/yuv_to_rgb_filter_contents.cc @@ -45917,6 +46035,8 @@ FILE: ../../../flutter/impeller/entity/geometry/rect_geometry.cc FILE: ../../../flutter/impeller/entity/geometry/rect_geometry.h FILE: ../../../flutter/impeller/entity/geometry/round_rect_geometry.cc FILE: ../../../flutter/impeller/entity/geometry/round_rect_geometry.h +FILE: ../../../flutter/impeller/entity/geometry/round_superellipse_geometry.cc +FILE: ../../../flutter/impeller/entity/geometry/round_superellipse_geometry.h FILE: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.cc FILE: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.h FILE: ../../../flutter/impeller/entity/geometry/superellipse_geometry.cc @@ -45939,6 +46059,7 @@ FILE: ../../../flutter/impeller/entity/shaders/blending/porter_duff_blend.vert FILE: ../../../flutter/impeller/entity/shaders/blending/vertices_uber.frag FILE: ../../../flutter/impeller/entity/shaders/clip.frag FILE: ../../../flutter/impeller/entity/shaders/clip.vert +FILE: ../../../flutter/impeller/entity/shaders/downsample.glsl FILE: ../../../flutter/impeller/entity/shaders/filters/border_mask_blur.frag FILE: ../../../flutter/impeller/entity/shaders/filters/color_matrix_color_filter.frag FILE: ../../../flutter/impeller/entity/shaders/filters/filter_position.vert @@ -45952,21 +46073,26 @@ FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.frag FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.vert FILE: ../../../flutter/impeller/entity/shaders/gradients/conical_gradient_fill.frag FILE: ../../../flutter/impeller/entity/shaders/gradients/conical_gradient_ssbo_fill.frag +FILE: ../../../flutter/impeller/entity/shaders/gradients/conical_gradient_uniform_fill.frag FILE: ../../../flutter/impeller/entity/shaders/gradients/fast_gradient.frag FILE: ../../../flutter/impeller/entity/shaders/gradients/fast_gradient.vert FILE: ../../../flutter/impeller/entity/shaders/gradients/gradient_fill.vert FILE: ../../../flutter/impeller/entity/shaders/gradients/linear_gradient_fill.frag FILE: ../../../flutter/impeller/entity/shaders/gradients/linear_gradient_ssbo_fill.frag +FILE: ../../../flutter/impeller/entity/shaders/gradients/linear_gradient_uniform_fill.frag FILE: ../../../flutter/impeller/entity/shaders/gradients/radial_gradient_fill.frag FILE: ../../../flutter/impeller/entity/shaders/gradients/radial_gradient_ssbo_fill.frag +FILE: ../../../flutter/impeller/entity/shaders/gradients/radial_gradient_uniform_fill.frag FILE: ../../../flutter/impeller/entity/shaders/gradients/sweep_gradient_fill.frag FILE: ../../../flutter/impeller/entity/shaders/gradients/sweep_gradient_ssbo_fill.frag +FILE: ../../../flutter/impeller/entity/shaders/gradients/sweep_gradient_uniform_fill.frag FILE: ../../../flutter/impeller/entity/shaders/rrect_blur.frag FILE: ../../../flutter/impeller/entity/shaders/rrect_blur.vert FILE: ../../../flutter/impeller/entity/shaders/runtime_effect.vert FILE: ../../../flutter/impeller/entity/shaders/solid_fill.frag FILE: ../../../flutter/impeller/entity/shaders/solid_fill.vert FILE: ../../../flutter/impeller/entity/shaders/texture_downsample.frag +FILE: ../../../flutter/impeller/entity/shaders/texture_downsample_gles.frag FILE: ../../../flutter/impeller/entity/shaders/texture_fill.frag FILE: ../../../flutter/impeller/entity/shaders/texture_fill.vert FILE: ../../../flutter/impeller/entity/shaders/texture_fill_strict_src.frag @@ -46359,7 +46485,9 @@ FILE: ../../../flutter/impeller/toolkit/interop/image_filter.cc FILE: ../../../flutter/impeller/toolkit/interop/image_filter.h FILE: ../../../flutter/impeller/toolkit/interop/impeller.cc FILE: ../../../flutter/impeller/toolkit/interop/impeller.h +FILE: ../../../flutter/impeller/toolkit/interop/impeller.hpp FILE: ../../../flutter/impeller/toolkit/interop/impeller_c.c +FILE: ../../../flutter/impeller/toolkit/interop/impeller_cc.cc FILE: ../../../flutter/impeller/toolkit/interop/mask_filter.cc FILE: ../../../flutter/impeller/toolkit/interop/mask_filter.h FILE: ../../../flutter/impeller/toolkit/interop/object.cc @@ -46421,8 +46549,6 @@ FILE: ../../../flutter/lib/gpu/export.cc FILE: ../../../flutter/lib/gpu/export.h FILE: ../../../flutter/lib/gpu/formats.cc FILE: ../../../flutter/lib/gpu/formats.h -FILE: ../../../flutter/lib/gpu/host_buffer.cc -FILE: ../../../flutter/lib/gpu/host_buffer.h FILE: ../../../flutter/lib/gpu/lib/gpu.dart FILE: ../../../flutter/lib/gpu/lib/src/buffer.dart FILE: ../../../flutter/lib/gpu/lib/src/command_buffer.dart @@ -46764,6 +46890,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/checkable.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/header.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/heading.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/image.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/incrementable.dart @@ -46887,6 +47014,8 @@ FILE: ../../../flutter/lib/web_ui/skwasm/filters.cpp FILE: ../../../flutter/lib/web_ui/skwasm/fonts.cpp FILE: ../../../flutter/lib/web_ui/skwasm/helpers.h FILE: ../../../flutter/lib/web_ui/skwasm/image.cpp +FILE: ../../../flutter/lib/web_ui/skwasm/library_skwasm_multi_threaded.js +FILE: ../../../flutter/lib/web_ui/skwasm/library_skwasm_single_threaded.js FILE: ../../../flutter/lib/web_ui/skwasm/library_skwasm_support.js FILE: ../../../flutter/lib/web_ui/skwasm/paint.cpp FILE: ../../../flutter/lib/web_ui/skwasm/path.cpp @@ -46896,6 +47025,8 @@ FILE: ../../../flutter/lib/web_ui/skwasm/skwasm_support.h FILE: ../../../flutter/lib/web_ui/skwasm/string.cpp FILE: ../../../flutter/lib/web_ui/skwasm/surface.cpp FILE: ../../../flutter/lib/web_ui/skwasm/surface.h +FILE: ../../../flutter/lib/web_ui/skwasm/surface_mt.cpp +FILE: ../../../flutter/lib/web_ui/skwasm/surface_st.cpp FILE: ../../../flutter/lib/web_ui/skwasm/text/line_metrics.cpp FILE: ../../../flutter/lib/web_ui/skwasm/text/paragraph.cpp FILE: ../../../flutter/lib/web_ui/skwasm/text/paragraph_builder.cpp @@ -47089,13 +47220,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/Build.java FILE: ../../../flutter/shell/platform/android/io/flutter/BuildConfig.java FILE: ../../../flutter/shell/platform/android/io/flutter/FlutterInjector.java FILE: ../../../flutter/shell/platform/android/io/flutter/Log.java -FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivity.java -FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java -FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityEvents.java -FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterApplication.java -FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterFragmentActivity.java -FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPlayStoreSplitApplication.java -FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/ExclusiveAppComponent.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -47155,8 +47279,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugin FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/service/ServiceAware.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/service/ServiceControlSurface.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/service/ServicePluginBinding.java -FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java -FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java @@ -47238,10 +47360,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/util/ViewUtils.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterCallbackInformation.java -FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterMain.java -FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterRunArguments.java -FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterView.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/TextureRegistry.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/VsyncWaiter.java FILE: ../../../flutter/shell/platform/android/jni/jni_mock.h @@ -47423,9 +47542,11 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlay FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h @@ -47462,6 +47583,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewTes FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/IOKit.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap.g.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap_Internal.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject+UIFocusSystem.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm @@ -47484,8 +47606,6 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.h -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h @@ -47675,6 +47795,8 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_surface_software.cc FILE: ../../../flutter/shell/platform/embedder/embedder_surface_software.h FILE: ../../../flutter/shell/platform/embedder/embedder_surface_vulkan.cc FILE: ../../../flutter/shell/platform/embedder/embedder_surface_vulkan.h +FILE: ../../../flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc +FILE: ../../../flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.cc FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.h FILE: ../../../flutter/shell/platform/embedder/embedder_thread_host.cc @@ -47877,6 +47999,7 @@ FILE: ../../../flutter/shell/platform/linux/fl_event_channel.cc FILE: ../../../flutter/shell/platform/linux/fl_event_channel_test.cc FILE: ../../../flutter/shell/platform/linux/fl_framebuffer.cc FILE: ../../../flutter/shell/platform/linux/fl_framebuffer.h +FILE: ../../../flutter/shell/platform/linux/fl_framebuffer_test.cc FILE: ../../../flutter/shell/platform/linux/fl_gnome_settings.cc FILE: ../../../flutter/shell/platform/linux/fl_gnome_settings.h FILE: ../../../flutter/shell/platform/linux/fl_gnome_settings_test.cc @@ -47893,6 +48016,10 @@ FILE: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_private.h FILE: ../../../flutter/shell/platform/linux/fl_key_embedder_responder_test.cc FILE: ../../../flutter/shell/platform/linux/fl_key_event.cc FILE: ../../../flutter/shell/platform/linux/fl_key_event.h +FILE: ../../../flutter/shell/platform/linux/fl_key_event_channel.cc +FILE: ../../../flutter/shell/platform/linux/fl_key_event_channel.h +FILE: ../../../flutter/shell/platform/linux/fl_keyboard_channel.cc +FILE: ../../../flutter/shell/platform/linux/fl_keyboard_channel.h FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler.cc FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler.h FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler_test.cc @@ -47918,11 +48045,16 @@ FILE: ../../../flutter/shell/platform/linux/fl_method_codec_private.h FILE: ../../../flutter/shell/platform/linux/fl_method_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_method_response.cc FILE: ../../../flutter/shell/platform/linux/fl_method_response_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_channel.cc +FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_channel.h FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.cc FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_handler.h FILE: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture.cc FILE: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_private.h FILE: ../../../flutter/shell/platform/linux/fl_pixel_buffer_texture_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_platform_channel.cc +FILE: ../../../flutter/shell/platform/linux/fl_platform_channel.h +FILE: ../../../flutter/shell/platform/linux/fl_platform_channel_test.cc FILE: ../../../flutter/shell/platform/linux/fl_platform_handler.cc FILE: ../../../flutter/shell/platform/linux/fl_platform_handler.h FILE: ../../../flutter/shell/platform/linux/fl_platform_handler_test.cc @@ -47930,6 +48062,9 @@ FILE: ../../../flutter/shell/platform/linux/fl_plugin_registrar.cc FILE: ../../../flutter/shell/platform/linux/fl_plugin_registrar_private.h FILE: ../../../flutter/shell/platform/linux/fl_plugin_registrar_test.cc FILE: ../../../flutter/shell/platform/linux/fl_plugin_registry.cc +FILE: ../../../flutter/shell/platform/linux/fl_pointer_manager.cc +FILE: ../../../flutter/shell/platform/linux/fl_pointer_manager.h +FILE: ../../../flutter/shell/platform/linux/fl_pointer_manager_test.cc FILE: ../../../flutter/shell/platform/linux/fl_renderable.cc FILE: ../../../flutter/shell/platform/linux/fl_renderable.h FILE: ../../../flutter/shell/platform/linux/fl_renderer.cc @@ -47942,10 +48077,10 @@ FILE: ../../../flutter/shell/platform/linux/fl_renderer_test.cc FILE: ../../../flutter/shell/platform/linux/fl_scrolling_manager.cc FILE: ../../../flutter/shell/platform/linux/fl_scrolling_manager.h FILE: ../../../flutter/shell/platform/linux/fl_scrolling_manager_test.cc -FILE: ../../../flutter/shell/platform/linux/fl_scrolling_view_delegate.cc -FILE: ../../../flutter/shell/platform/linux/fl_scrolling_view_delegate.h FILE: ../../../flutter/shell/platform/linux/fl_settings.cc FILE: ../../../flutter/shell/platform/linux/fl_settings.h +FILE: ../../../flutter/shell/platform/linux/fl_settings_channel.cc +FILE: ../../../flutter/shell/platform/linux/fl_settings_channel.h FILE: ../../../flutter/shell/platform/linux/fl_settings_handler.cc FILE: ../../../flutter/shell/platform/linux/fl_settings_handler.h FILE: ../../../flutter/shell/platform/linux/fl_settings_handler_test.cc @@ -47962,6 +48097,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_string_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_string_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_task_runner.cc FILE: ../../../flutter/shell/platform/linux/fl_task_runner.h +FILE: ../../../flutter/shell/platform/linux/fl_text_input_channel.cc +FILE: ../../../flutter/shell/platform/linux/fl_text_input_channel.h FILE: ../../../flutter/shell/platform/linux/fl_text_input_handler.cc FILE: ../../../flutter/shell/platform/linux/fl_text_input_handler.h FILE: ../../../flutter/shell/platform/linux/fl_text_input_handler_test.cc @@ -47981,7 +48118,6 @@ FILE: ../../../flutter/shell/platform/linux/fl_view.cc FILE: ../../../flutter/shell/platform/linux/fl_view_accessible.cc FILE: ../../../flutter/shell/platform/linux/fl_view_accessible.h FILE: ../../../flutter/shell/platform/linux/fl_view_accessible_test.cc -FILE: ../../../flutter/shell/platform/linux/fl_view_private.h FILE: ../../../flutter/shell/platform/linux/fl_view_test.cc FILE: ../../../flutter/shell/platform/linux/fl_window_state_monitor.cc FILE: ../../../flutter/shell/platform/linux/fl_window_state_monitor.h @@ -48127,8 +48263,6 @@ FILE: ../../../flutter/third_party/accessibility/base/container_utils.h FILE: ../../../flutter/third_party/accessibility/base/logging.cc FILE: ../../../flutter/third_party/accessibility/base/logging.h FILE: ../../../flutter/third_party/accessibility/base/macros.h -FILE: ../../../flutter/third_party/accessibility/base/platform/darwin/scoped_nsobject.h -FILE: ../../../flutter/third_party/accessibility/base/platform/darwin/scoped_nsobject.mm FILE: ../../../flutter/third_party/accessibility/base/simple_token.cc FILE: ../../../flutter/third_party/accessibility/base/simple_token.h FILE: ../../../flutter/third_party/accessibility/base/string_utils.cc @@ -48273,10 +48407,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/rsaz_exp.c (with ../../../flutter/third_party/boringssl/src/LICENSE) +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/rsaz_exp.cc.inc (with ../../../flutter/third_party/boringssl/src/LICENSE) ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/rsaz_exp.h (with ../../../flutter/third_party/boringssl/src/LICENSE) TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/rsaz_exp.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/rsaz_exp.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/rsaz_exp.h ---------------------------------------------------------------------------------------------------- Copyright 2013-2016 The OpenSSL Project Authors. All Rights Reserved. @@ -48773,10 +48907,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256-nistz.c (with ../../../flutter/third_party/boringssl/src/LICENSE) +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256-nistz.cc.inc (with ../../../flutter/third_party/boringssl/src/LICENSE) ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256-nistz.h (with ../../../flutter/third_party/boringssl/src/LICENSE) TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256-nistz.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256-nistz.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256-nistz.h ---------------------------------------------------------------------------------------------------- Copyright 2014-2016 The OpenSSL Project Authors. All Rights Reserved. @@ -49378,9 +49512,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/scrypt.c (with ../../../flutter/third_party/boringssl/src/LICENSE) +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/evp/scrypt.cc (with ../../../flutter/third_party/boringssl/src/LICENSE) TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/scrypt.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/scrypt.cc ---------------------------------------------------------------------------------------------------- Copyright 2015-2016 The OpenSSL Project Authors. All Rights Reserved. @@ -49392,9 +49526,9 @@ https://www.openssl.org/source/license.html ==================================================================================================== LIBRARY: boringssl -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery_inv.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery_inv.cc.inc TYPE: LicenseType.unknown -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery_inv.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/montgomery_inv.cc.inc ---------------------------------------------------------------------------------------------------- Copyright 2016 Brian Smith. @@ -53940,11 +54074,11 @@ found in the LICENSE file ==================================================================================================== LIBRARY: boringssl ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/service_indicator/internal.h -ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/service_indicator/service_indicator.c +ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/service_indicator/service_indicator.cc.inc ORIGIN: ../../../flutter/third_party/boringssl/src/include/openssl/service_indicator.h TYPE: LicenseType.unknown FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/service_indicator/internal.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/service_indicator/service_indicator.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/service_indicator/service_indicator.cc.inc FILE: ../../../flutter/third_party/boringssl/src/include/openssl/service_indicator.h ---------------------------------------------------------------------------------------------------- Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. @@ -57727,6 +57861,9 @@ LIBRARY: boringssl ORIGIN: ../../../flutter/third_party/boringssl/src/LICENSE TYPE: LicenseType.openssl FILE: ../../../flutter/third_party/boringssl/src/.bazelignore +FILE: ../../../flutter/third_party/boringssl/src/.bcr/metadata.template.json +FILE: ../../../flutter/third_party/boringssl/src/.bcr/presubmit.yml +FILE: ../../../flutter/third_party/boringssl/src/.bcr/source.template.json FILE: ../../../flutter/third_party/boringssl/src/MODULE.bazel.lock FILE: ../../../flutter/third_party/boringssl/src/PrivacyInfo.xcprivacy FILE: ../../../flutter/third_party/boringssl/src/build.json @@ -57756,13 +57893,13 @@ FILE: ../../../flutter/third_party/boringssl/src/crypto/err/trust_token.errordat FILE: ../../../flutter/third_party/boringssl/src/crypto/err/x509.errordata FILE: ../../../flutter/third_party/boringssl/src/crypto/err/x509v3.errordata FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/evp_tests.txt -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh_asn1.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/scrypt.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/p_dh_asn1.cc +FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/scrypt.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/evp/scrypt_tests.txt FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/aes/aes_tests.txt -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/asm/x86_64-gcc.c -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/rsaz_exp.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/asm/x86_64-gcc.cc.inc +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/rsaz_exp.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/bn/rsaz_exp.h FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cmac/cavp_3des_cmac_tests.txt FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cmac/cavp_aes128_cmac_tests.txt @@ -57770,7 +57907,7 @@ FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cmac/cavp_aes FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/cmac/cavp_aes256_cmac_tests.txt FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/ec_scalar_base_mult_tests.txt FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256-nistz-table.h -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256-nistz.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256-nistz.cc.inc FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256-nistz.h FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ec/p256-nistz_tests.txt FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/ecdsa/ecdsa_sign_tests.txt @@ -57779,7 +57916,6 @@ FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/fips_shared.l FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/intcheck1.png FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/intcheck2.png FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/intcheck3.png -FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/modes/gcm_tests.txt FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/policydocs/BoringCrypto-Android-Security-Policy-20191020.docx!/[Content_Types].xml FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/policydocs/BoringCrypto-Android-Security-Policy-20191020.docx!/_rels/.rels FILE: ../../../flutter/third_party/boringssl/src/crypto/fipsmodule/policydocs/BoringCrypto-Android-Security-Policy-20191020.docx!/customXml/_rels/item1.xml.rels @@ -57898,15 +58034,30 @@ FILE: ../../../flutter/third_party/boringssl/src/crypto/hpke/hpke_test_vectors.t FILE: ../../../flutter/third_party/boringssl/src/crypto/hpke/test-vectors.json FILE: ../../../flutter/third_party/boringssl/src/crypto/keccak/keccak_tests.txt FILE: ../../../flutter/third_party/boringssl/src/crypto/kyber/kyber_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mldsa/mldsa_nist_keygen_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mldsa/mldsa_nist_siggen_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem1024_decap_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem1024_encap_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem1024_keygen_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem1024_nist_decap_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem1024_nist_keygen_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem768_decap_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem768_encap_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem768_keygen_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem768_nist_decap_tests.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/mlkem/mlkem768_nist_keygen_tests.txt FILE: ../../../flutter/third_party/boringssl/src/crypto/obj/obj_mac.num FILE: ../../../flutter/third_party/boringssl/src/crypto/obj/objects.txt FILE: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305_arm_asm.S FILE: ../../../flutter/third_party/boringssl/src/crypto/poly1305/poly1305_tests.txt -FILE: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_print.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/rsa_extra/rsa_print.cc FILE: ../../../flutter/third_party/boringssl/src/crypto/siphash/siphash_tests.txt -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/spx_tests.txt -FILE: ../../../flutter/third_party/boringssl/src/crypto/spx/spx_tests_deterministic.txt -FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ocsp.c +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/slhdsa_keygen.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/slhdsa_prehash.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/slhdsa_siggen.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/slhdsa/slhdsa_sigver.txt +FILE: ../../../flutter/third_party/boringssl/src/crypto/x509/v3_ocsp.cc +FILE: ../../../flutter/third_party/boringssl/src/docs/releasing.md FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/aesni-gcm-x86_64-apple.S FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/aesni-gcm-x86_64-linux.S FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/aesni-gcm-x86_64-win.asm @@ -57957,12 +58108,6 @@ FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/ghashv8-armv7-linux.S FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/ghashv8-armv8-apple.S FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/ghashv8-armv8-linux.S FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/ghashv8-armv8-win.S -FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/md5-586-apple.S -FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/md5-586-linux.S -FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/md5-586-win.asm -FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/md5-x86_64-apple.S -FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/md5-x86_64-linux.S -FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/md5-x86_64-win.asm FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/p256-armv8-asm-apple.S FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/p256-armv8-asm-linux.S FILE: ../../../flutter/third_party/boringssl/src/gen/bcm/p256-armv8-asm-win.S @@ -58049,14 +58194,29 @@ FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/chacha20_poly1305_ar FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/chacha20_poly1305_x86_64-apple.S FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/chacha20_poly1305_x86_64-linux.S FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/chacha20_poly1305_x86_64-win.asm +FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/md5-586-apple.S +FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/md5-586-linux.S +FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/md5-586-win.asm +FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/md5-x86_64-apple.S +FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/md5-x86_64-linux.S +FILE: ../../../flutter/third_party/boringssl/src/gen/crypto/md5-x86_64-win.asm FILE: ../../../flutter/third_party/boringssl/src/gen/sources.json FILE: ../../../flutter/third_party/boringssl/src/go.mod FILE: ../../../flutter/third_party/boringssl/src/go.sum FILE: ../../../flutter/third_party/boringssl/src/include/openssl/pki/signature_verify_cache.h +FILE: ../../../flutter/third_party/boringssl/src/include/openssl/pki/verify.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ssl.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/ssl3.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/tls1.h FILE: ../../../flutter/third_party/boringssl/src/include/openssl/x509.h +FILE: ../../../flutter/third_party/boringssl/src/infra/config/generated/commit-queue.cfg +FILE: ../../../flutter/third_party/boringssl/src/infra/config/generated/cr-buildbucket.cfg +FILE: ../../../flutter/third_party/boringssl/src/infra/config/generated/luci-logdog.cfg +FILE: ../../../flutter/third_party/boringssl/src/infra/config/generated/luci-milo.cfg +FILE: ../../../flutter/third_party/boringssl/src/infra/config/generated/luci-notify.cfg +FILE: ../../../flutter/third_party/boringssl/src/infra/config/generated/luci-scheduler.cfg +FILE: ../../../flutter/third_party/boringssl/src/infra/config/generated/project.cfg +FILE: ../../../flutter/third_party/boringssl/src/infra/config/generated/realms.cfg FILE: ../../../flutter/third_party/boringssl/src/pki/cert_error_id.cc FILE: ../../../flutter/third_party/boringssl/src/pki/cert_error_id.h FILE: ../../../flutter/third_party/boringssl/src/pki/cert_error_params.cc @@ -59107,7 +59267,6 @@ FILE: ../../../flutter/third_party/icu/android/icudtl.dat FILE: ../../../flutter/third_party/icu/cast/brkitr.patch FILE: ../../../flutter/third_party/icu/cast/icudtl.dat FILE: ../../../flutter/third_party/icu/chromeos/icudtl.dat -FILE: ../../../flutter/third_party/icu/chromeos/icudtl.dat.hash FILE: ../../../flutter/third_party/icu/common/icudtb.dat FILE: ../../../flutter/third_party/icu/common/icudtl.dat FILE: ../../../flutter/third_party/icu/flutter/brkitr.patch diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index 7b0c02d38aa14..09164b4b1c213 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: 2fb0c74aa38530e4d6ded911a1dd9464 +Signature: 900dc900001d18e4977541a3c0a37c0f ==================================================================================================== LIBRARY: fuchsia_sdk @@ -739,188 +739,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/dist/libdriver_runtime.so @@ -1103,188 +921,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/dist/libdriver_runtime.so @@ -1831,6 +1467,188 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/libzircon.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libasync-loop-default.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libmagma_client.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libsync.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libtrace-vthread.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/alloca.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/ftp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/inet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/nameser.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/nameser_compat.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/telnet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/tftp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/aarch64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/aarch64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/alltypes.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/io.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/null.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/posix.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/riscv64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/riscv64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/x86_64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/x86_64/io.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/x86_64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/byteswap.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/complex.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/cpio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ctype.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/dirent.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/elf.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/endian.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/err.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/features.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/fnmatch.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/getopt.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/glob.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/grp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/iconv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ifaddrs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/inttypes.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/iso646.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/langinfo.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/libgen.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/link.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/locale.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/malloc.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/math.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/memory.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/monetary.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/ethernet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/if.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/if_arp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/route.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netdb.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ether.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/icmp6.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/if_ether.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/igmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/in.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/in_systm.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ip.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ip6.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ip_icmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/tcp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/udp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netpacket/packet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/nl_types.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/paths.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/pthread.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/pwd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/regex.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/resolv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sched.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/semaphore.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/spawn.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/stdio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/stdlib.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/string.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/strings.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/acct.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/auxv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/dir.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/eventfd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/file.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/inotify.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/io.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ipc.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/mman.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/mount.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/msg.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/mtio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/param.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/random.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/select.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/sem.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/shm.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/socket.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/statvfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/time.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/timeb.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/timerfd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/times.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ttydefaults.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/types.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/uio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/un.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/utsname.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/vfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sysexits.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/tar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/time.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/uchar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/unistd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/utime.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/values.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wchar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wctype.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wordexp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/Scrt1.o +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libc.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libdl.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libm.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libpthread.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/librt.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/dist/libdriver_runtime.so @@ -2195,188 +2013,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/dist/libdriver_runtime.so @@ -2559,188 +2195,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/dist/libdriver_runtime.so @@ -3287,6 +2741,188 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/libzircon.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libasync-loop-default.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libmagma_client.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libsync.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libtrace-vthread.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/alloca.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/ftp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/inet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/nameser.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/nameser_compat.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/telnet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/tftp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/aarch64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/aarch64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/alltypes.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/io.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/null.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/posix.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/riscv64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/riscv64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/x86_64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/x86_64/io.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/x86_64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/byteswap.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/complex.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/cpio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ctype.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/dirent.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/elf.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/endian.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/err.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/features.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/fnmatch.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/getopt.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/glob.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/grp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/iconv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ifaddrs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/inttypes.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/iso646.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/langinfo.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/libgen.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/link.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/locale.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/malloc.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/math.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/memory.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/monetary.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/ethernet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/if.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/if_arp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/route.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netdb.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ether.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/icmp6.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/if_ether.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/igmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/in.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/in_systm.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ip.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ip6.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ip_icmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/tcp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/udp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netpacket/packet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/nl_types.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/paths.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/pthread.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/pwd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/regex.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/resolv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sched.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/semaphore.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/spawn.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/stdio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/stdlib.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/string.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/strings.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/acct.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/auxv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/dir.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/eventfd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/file.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/inotify.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/io.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ipc.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/mman.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/mount.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/msg.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/mtio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/param.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/random.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/select.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/sem.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/shm.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/socket.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/statvfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/time.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/timeb.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/timerfd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/times.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ttydefaults.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/types.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/uio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/un.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/utsname.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/vfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sysexits.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/tar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/time.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/uchar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/unistd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/utime.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/values.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wchar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wctype.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wordexp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/Scrt1.o +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libc.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libdl.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libm.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libpthread.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/librt.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/dist/libdriver_runtime.so @@ -3651,188 +3287,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/dist/libdriver_runtime.so @@ -4015,188 +3469,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/dist/libdriver_runtime.so @@ -4743,6 +4015,188 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/libzircon.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libasync-loop-default.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libmagma_client.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libsync.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libtrace-vthread.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/alloca.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/ftp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/inet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/nameser.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/nameser_compat.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/telnet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/tftp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/aarch64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/aarch64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/alltypes.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/io.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/null.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/posix.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/riscv64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/riscv64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/x86_64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/x86_64/io.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/x86_64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/byteswap.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/complex.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/cpio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ctype.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/dirent.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/elf.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/endian.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/err.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/features.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/fnmatch.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/getopt.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/glob.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/grp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/iconv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ifaddrs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/inttypes.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/iso646.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/langinfo.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/libgen.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/link.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/locale.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/malloc.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/math.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/memory.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/monetary.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/ethernet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/if.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/if_arp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/route.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netdb.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ether.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/icmp6.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/if_ether.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/igmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/in.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/in_systm.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ip.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ip6.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ip_icmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/tcp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/udp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netpacket/packet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/nl_types.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/paths.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/pthread.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/pwd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/regex.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/resolv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sched.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/semaphore.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/spawn.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/stdio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/stdlib.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/string.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/strings.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/acct.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/auxv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/dir.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/eventfd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/file.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/inotify.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/io.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ipc.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/mman.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/mount.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/msg.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/mtio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/param.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/random.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/select.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/sem.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/shm.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/socket.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/statvfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/time.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/timeb.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/timerfd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/times.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ttydefaults.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/types.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/uio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/un.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/utsname.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/vfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sysexits.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/tar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/time.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/uchar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/unistd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/utime.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/values.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wchar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wctype.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wordexp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/Scrt1.o +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libc.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libdl.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libm.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libpthread.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/librt.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/dist/libdriver_runtime.so @@ -4926,172 +4380,169 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-16/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-17/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-16/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-17/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-16/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-17/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/pkg/async-default/async-default.ifs FILE: ../../../fuchsia/sdk/linux/pkg/backend_fuchsia_globals/backend_fuchsia_globals.ifs @@ -5100,7 +4551,7 @@ FILE: ../../../fuchsia/sdk/linux/pkg/driver_runtime_shared_lib/driver_runtime.if FILE: ../../../fuchsia/sdk/linux/pkg/fdio/fdio.ifs FILE: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/heapdump_instrumentation.ifs FILE: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/heapdump_instrumentation_shard.json -FILE: ../../../fuchsia/sdk/linux/pkg/inspect/inspect.json +FILE: ../../../fuchsia/sdk/linux/pkg/inspect/inspect_sdk.json FILE: ../../../fuchsia/sdk/linux/pkg/svc/svc.ifs FILE: ../../../fuchsia/sdk/linux/pkg/sys/component/realm_builder_shard_sdk.json FILE: ../../../fuchsia/sdk/linux/pkg/syslog/syslog.ifs @@ -5410,6 +4861,7 @@ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.math/math.fidl + ../../../fuchsi ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.playback/problem.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.playback/seeking_reader.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.media/audio.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.tracing.controller/trace_controller.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.tracing.provider/provider.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.policy/presenter.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE @@ -5428,22 +4880,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/sysca ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/processargs.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/status.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/debug.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/log.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/object.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/port.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/profile.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE @@ -5460,22 +4896,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/sysca ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/processargs.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/status.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/debug.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/log.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/object.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/port.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/profile.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE @@ -5524,6 +4944,22 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/sysca ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/processargs.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/status.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/debug.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/exception.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/log.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/object.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/pci.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/port.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/profile.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE @@ -5556,22 +4992,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/sys ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/processargs.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/status.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/debug.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/log.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/object.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/port.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/profile.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE @@ -5588,22 +5008,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/sys ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/processargs.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/status.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/debug.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/log.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/object.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/port.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/profile.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE @@ -5652,6 +5056,22 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/sys ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/processargs.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/status.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/debug.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/exception.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/log.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/object.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/pci.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/port.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/profile.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE @@ -5684,22 +5104,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscall ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/processargs.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/status.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/debug.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/log.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/object.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/port.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/profile.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE @@ -5716,22 +5120,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscall ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/processargs.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/status.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/debug.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/log.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/object.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/port.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/profile.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE @@ -5780,6 +5168,22 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscall ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/processargs.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/status.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/debug.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/exception.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/log.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/object.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/pci.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/port.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/profile.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/resource.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/types.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/types.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/assert.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/compiler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/listnode.h + ../../../fuchsia/sdk/linux/LICENSE @@ -5890,6 +5294,7 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.math/math.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.playback/problem.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.playback/seeking_reader.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media/audio.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.tracing.controller/trace_controller.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.tracing.provider/provider.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.policy/presenter.fidl FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/assert.h @@ -5908,22 +5313,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscall FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/resource.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/types.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/compiler.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/listnode.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/processargs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/status.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/debug.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/log.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/object.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/port.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/profile.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/resource.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/types.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/assert.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/compiler.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/listnode.h @@ -5940,22 +5329,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscall FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/resource.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/types.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/compiler.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/listnode.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/processargs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/status.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/debug.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/log.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/object.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/port.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/profile.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/resource.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/types.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/assert.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/compiler.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/listnode.h @@ -6004,6 +5377,22 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscall FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/resource.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/types.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/types.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/compiler.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/listnode.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/processargs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/status.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/debug.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/exception.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/log.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/object.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/pci.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/port.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/profile.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/resource.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/types.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/types.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/assert.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/compiler.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/listnode.h @@ -6036,22 +5425,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/sysca FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/resource.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/types.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/compiler.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/listnode.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/processargs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/status.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/debug.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/log.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/object.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/port.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/profile.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/resource.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/types.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/assert.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/compiler.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/listnode.h @@ -6068,22 +5441,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/sysca FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/resource.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/types.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/compiler.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/listnode.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/processargs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/status.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/debug.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/log.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/object.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/port.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/profile.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/resource.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/types.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/assert.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/compiler.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/listnode.h @@ -6132,6 +5489,22 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/sysca FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/resource.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/types.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/types.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/compiler.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/listnode.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/processargs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/status.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/debug.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/exception.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/log.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/object.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/pci.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/port.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/profile.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/resource.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/types.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/types.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/assert.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/compiler.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/listnode.h @@ -6164,22 +5537,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/resource.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/types.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/compiler.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/listnode.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/processargs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/status.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/debug.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/log.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/object.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/port.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/profile.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/resource.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/types.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/assert.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/compiler.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/listnode.h @@ -6196,22 +5553,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/resource.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/types.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/compiler.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/listnode.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/processargs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/status.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/debug.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/log.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/object.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/port.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/profile.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/resource.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/types.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/assert.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/compiler.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/listnode.h @@ -6260,6 +5601,22 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/resource.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/types.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/types.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/compiler.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/listnode.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/processargs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/status.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/debug.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/exception.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/log.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/object.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/pci.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/port.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/profile.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/resource.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/types.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/types.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/assert.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/compiler.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/listnode.h @@ -6413,17 +5770,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/sysca ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/process.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/rights.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/sanitizer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/hypervisor.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/iommu.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE @@ -6435,17 +5781,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/sysca ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/process.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/rights.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/sanitizer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/hypervisor.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/iommu.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE @@ -6479,6 +5814,17 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/sysca ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/process.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/rights.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/sanitizer.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/hypervisor.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/iommu.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE @@ -6501,17 +5847,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/sys ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/process.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/rights.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/sanitizer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/hypervisor.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/iommu.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE @@ -6523,17 +5858,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/sys ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/process.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/rights.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/sanitizer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/hypervisor.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/iommu.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE @@ -6567,6 +5891,17 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/sys ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/process.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/rights.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/sanitizer.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/hypervisor.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/iommu.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE @@ -6589,17 +5924,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscall ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/process.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/rights.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/sanitizer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/hypervisor.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/iommu.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE @@ -6611,17 +5935,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscall ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/process.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/rights.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/sanitizer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/hypervisor.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/iommu.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE @@ -6655,6 +5968,17 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscall ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/process.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/rights.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/sanitizer.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/hypervisor.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/iommu.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/policy.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/system.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/tls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/fidl.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/hw/gpt.h + ../../../fuchsia/sdk/linux/LICENSE @@ -6803,17 +6127,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscall FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/policy.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/system.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/tls.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/device/audio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/fidl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/hw/gpt.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/process.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/rights.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/sanitizer.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/hypervisor.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/iommu.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/policy.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/system.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/tls.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/device/audio.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/fidl.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/hw/gpt.h @@ -6825,17 +6138,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscall FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/policy.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/system.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/tls.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/device/audio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/fidl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/hw/gpt.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/process.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/rights.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/sanitizer.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/hypervisor.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/iommu.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/policy.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/system.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/tls.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/device/audio.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/fidl.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/hw/gpt.h @@ -6869,6 +6171,17 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscall FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/policy.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/system.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/tls.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/device/audio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/fidl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/hw/gpt.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/process.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/rights.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/sanitizer.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/hypervisor.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/iommu.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/policy.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/system.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/tls.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/device/audio.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/fidl.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/hw/gpt.h @@ -6891,17 +6204,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/sysca FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/policy.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/system.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/tls.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/device/audio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/fidl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/hw/gpt.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/process.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/rights.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/sanitizer.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/hypervisor.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/iommu.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/policy.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/system.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/tls.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/device/audio.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/fidl.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/hw/gpt.h @@ -6913,17 +6215,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/sysca FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/policy.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/system.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/tls.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/device/audio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/fidl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/hw/gpt.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/process.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/rights.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/sanitizer.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/hypervisor.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/iommu.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/policy.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/system.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/tls.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/device/audio.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/fidl.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/hw/gpt.h @@ -6957,6 +6248,17 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/sysca FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/policy.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/system.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/tls.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/device/audio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/fidl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/hw/gpt.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/process.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/rights.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/sanitizer.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/hypervisor.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/iommu.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/policy.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/system.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/tls.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/device/audio.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/fidl.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/hw/gpt.h @@ -6979,17 +6281,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/policy.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/system.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/tls.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/device/audio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/fidl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/hw/gpt.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/process.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/rights.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/sanitizer.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/hypervisor.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/iommu.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/policy.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/system.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/tls.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/device/audio.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/fidl.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/hw/gpt.h @@ -7001,17 +6292,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/policy.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/system.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/tls.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/device/audio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/fidl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/hw/gpt.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/process.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/rights.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/sanitizer.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/hypervisor.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/iommu.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/policy.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/system.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/tls.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/device/audio.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/fidl.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/hw/gpt.h @@ -7045,6 +6325,17 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/policy.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/system.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/tls.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/device/audio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/fidl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/hw/gpt.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/process.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/rights.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/sanitizer.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/hypervisor.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/iommu.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/policy.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/system.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/tls.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/device/audio.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/fidl.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/hw/gpt.h @@ -7239,13 +6530,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/limit ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/limits.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE @@ -7253,13 +6537,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/limit ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/limits.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE @@ -7281,6 +6558,13 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/limit ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/limits.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE @@ -7295,13 +6579,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/lim ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/limits.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE @@ -7309,13 +6586,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/lim ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/limits.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE @@ -7337,6 +6607,13 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/lim ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/limits.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE @@ -7351,13 +6628,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/limits. ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/limits.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE @@ -7365,13 +6635,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/limits. ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/limits.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE @@ -7393,6 +6656,13 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/limits. ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/limits.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/smc.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/threads.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/time.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/dlfcn.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/features.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/hw/pci.h + ../../../fuchsia/sdk/linux/LICENSE @@ -7628,13 +6898,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/limits. FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/smc.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/threads.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/features.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/hw/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/smc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/time.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/dlfcn.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/features.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/hw/pci.h @@ -7642,13 +6905,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/limits. FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/smc.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/threads.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/features.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/hw/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/smc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/time.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/dlfcn.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/features.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/hw/pci.h @@ -7670,6 +6926,13 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/limits. FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/smc.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/threads.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/time.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/features.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/hw/pci.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/smc.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/time.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/dlfcn.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/features.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/hw/pci.h @@ -7684,13 +6947,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/limit FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/smc.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/threads.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/features.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/hw/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/smc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/time.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/dlfcn.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/features.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/hw/pci.h @@ -7698,13 +6954,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/limit FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/smc.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/threads.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/features.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/hw/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/smc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/time.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/dlfcn.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/features.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/hw/pci.h @@ -7726,6 +6975,13 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/limit FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/smc.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/threads.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/time.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/features.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/hw/pci.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/smc.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/time.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/dlfcn.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/features.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/hw/pci.h @@ -7740,13 +6996,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/limits.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/smc.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/threads.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/features.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/hw/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/smc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/time.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/dlfcn.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/features.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/hw/pci.h @@ -7754,13 +7003,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/limits.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/smc.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/threads.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/features.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/hw/pci.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/smc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/time.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/dlfcn.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/features.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/hw/pci.h @@ -7782,6 +7024,13 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/limits.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/smc.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/threads.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/time.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/features.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/hw/pci.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/smc.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/time.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/dlfcn.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/features.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/hw/pci.h @@ -8695,188 +7944,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/dist/libdriver_runtime.so @@ -9059,188 +8126,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/dist/libdriver_runtime.so @@ -9787,6 +8672,188 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/libzircon.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libasync-loop-default.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libmagma_client.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libsync.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libtrace-vthread.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/alloca.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/ftp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/inet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/nameser.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/nameser_compat.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/telnet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/tftp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/aarch64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/aarch64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/alltypes.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/io.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/null.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/posix.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/riscv64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/riscv64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/x86_64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/x86_64/io.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/x86_64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/byteswap.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/complex.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/cpio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ctype.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/dirent.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/elf.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/endian.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/err.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/features.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/fnmatch.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/getopt.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/glob.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/grp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/iconv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ifaddrs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/inttypes.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/iso646.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/langinfo.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/libgen.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/link.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/locale.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/malloc.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/math.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/memory.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/monetary.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/ethernet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/if.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/if_arp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/route.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netdb.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ether.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/icmp6.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/if_ether.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/igmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/in.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/in_systm.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ip.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ip6.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ip_icmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/tcp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/udp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netpacket/packet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/nl_types.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/paths.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/pthread.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/pwd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/regex.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/resolv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sched.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/semaphore.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/spawn.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/stdio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/stdlib.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/string.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/strings.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/acct.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/auxv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/dir.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/eventfd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/file.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/inotify.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/io.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ipc.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/mman.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/mount.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/msg.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/mtio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/param.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/random.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/select.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/sem.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/shm.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/socket.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/statvfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/time.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/timeb.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/timerfd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/times.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ttydefaults.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/types.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/uio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/un.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/utsname.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/vfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sysexits.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/tar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/time.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/uchar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/unistd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/utime.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/values.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wchar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wctype.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wordexp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/Scrt1.o +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libc.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libdl.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libm.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libpthread.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/librt.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/dist/libdriver_runtime.so @@ -10151,188 +9218,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/dist/libdriver_runtime.so @@ -10515,188 +9400,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/dist/libdriver_runtime.so @@ -11243,6 +9946,188 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/libzircon.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libasync-loop-default.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libmagma_client.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libsync.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libtrace-vthread.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/alloca.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/ftp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/inet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/nameser.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/nameser_compat.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/telnet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/tftp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/aarch64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/aarch64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/alltypes.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/io.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/null.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/posix.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/riscv64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/riscv64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/x86_64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/x86_64/io.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/x86_64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/byteswap.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/complex.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/cpio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ctype.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/dirent.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/elf.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/endian.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/err.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/features.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/fnmatch.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/getopt.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/glob.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/grp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/iconv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ifaddrs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/inttypes.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/iso646.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/langinfo.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/libgen.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/link.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/locale.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/malloc.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/math.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/memory.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/monetary.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/ethernet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/if.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/if_arp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/route.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netdb.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ether.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/icmp6.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/if_ether.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/igmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/in.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/in_systm.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ip.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ip6.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ip_icmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/tcp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/udp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netpacket/packet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/nl_types.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/paths.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/pthread.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/pwd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/regex.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/resolv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sched.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/semaphore.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/spawn.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/stdio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/stdlib.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/string.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/strings.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/acct.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/auxv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/dir.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/eventfd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/file.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/inotify.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/io.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ipc.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/mman.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/mount.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/msg.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/mtio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/param.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/random.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/select.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/sem.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/shm.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/socket.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/statvfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/time.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/timeb.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/timerfd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/times.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ttydefaults.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/types.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/uio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/un.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/utsname.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/vfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sysexits.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/tar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/time.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/uchar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/unistd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/utime.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/values.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wchar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wctype.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wordexp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/Scrt1.o +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libc.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libdl.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libm.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libpthread.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/librt.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/dist/libdriver_runtime.so @@ -11607,188 +10492,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/dist/libdriver_runtime.so @@ -11971,188 +10674,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/dist/libdriver_runtime.so @@ -12699,6 +11220,188 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/libzircon.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libasync-loop-default.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libmagma_client.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libsync.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libtrace-vthread.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/alloca.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/ftp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/inet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/nameser.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/nameser_compat.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/telnet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/tftp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/aarch64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/aarch64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/alltypes.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/io.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/null.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/posix.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/riscv64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/riscv64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/x86_64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/x86_64/io.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/x86_64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/byteswap.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/complex.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/cpio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ctype.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/dirent.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/elf.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/endian.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/err.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/features.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/fnmatch.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/getopt.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/glob.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/grp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/iconv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ifaddrs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/inttypes.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/iso646.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/langinfo.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/libgen.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/link.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/locale.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/malloc.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/math.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/memory.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/monetary.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/ethernet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/if.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/if_arp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/route.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netdb.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ether.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/icmp6.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/if_ether.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/igmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/in.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/in_systm.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ip.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ip6.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ip_icmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/tcp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/udp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netpacket/packet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/nl_types.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/paths.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/pthread.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/pwd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/regex.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/resolv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sched.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/semaphore.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/spawn.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/stdio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/stdlib.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/string.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/strings.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/acct.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/auxv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/dir.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/eventfd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/file.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/inotify.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/io.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ipc.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/mman.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/mount.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/msg.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/mtio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/param.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/random.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/select.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/sem.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/shm.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/socket.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/statvfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/time.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/timeb.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/timerfd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/times.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ttydefaults.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/types.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/uio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/un.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/utsname.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/vfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sysexits.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/tar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/time.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/uchar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/unistd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/utime.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/values.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wchar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wctype.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wordexp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/Scrt1.o +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libc.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libdl.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libm.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libpthread.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/librt.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/dist/libdriver_runtime.so @@ -12882,172 +11585,169 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-16/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-17/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-16/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-17/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-16/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-17/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/pkg/async-default/async-default.ifs FILE: ../../../fuchsia/sdk/linux/pkg/backend_fuchsia_globals/backend_fuchsia_globals.ifs @@ -13056,7 +11756,7 @@ FILE: ../../../fuchsia/sdk/linux/pkg/driver_runtime_shared_lib/driver_runtime.if FILE: ../../../fuchsia/sdk/linux/pkg/fdio/fdio.ifs FILE: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/heapdump_instrumentation.ifs FILE: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/heapdump_instrumentation_shard.json -FILE: ../../../fuchsia/sdk/linux/pkg/inspect/inspect.json +FILE: ../../../fuchsia/sdk/linux/pkg/inspect/inspect_sdk.json FILE: ../../../fuchsia/sdk/linux/pkg/svc/svc.ifs FILE: ../../../fuchsia/sdk/linux/pkg/sys/component/realm_builder_shard_sdk.json FILE: ../../../fuchsia/sdk/linux/pkg/syslog/syslog.ifs @@ -13303,13 +12003,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/looku ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/lookup.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE @@ -13317,13 +12010,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/looku ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/lookup.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE @@ -13345,6 +12031,13 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/looku ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/lookup.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE @@ -13359,13 +12052,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/loo ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/lookup.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE @@ -13373,13 +12059,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/loo ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/lookup.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE @@ -13401,6 +12080,13 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/loo ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/lookup.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE @@ -13415,13 +12101,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/lookup. ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/lookup.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE @@ -13429,13 +12108,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/lookup. ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/lookup.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE @@ -13457,6 +12129,13 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/lookup. ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/lookup.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/clock.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/scheduler.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/utc.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/exception.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/hw/debug/arm64.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/hw/debug/x86.h + ../../../fuchsia/sdk/linux/LICENSE @@ -13749,13 +12428,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/lookup. FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/scheduler.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/utc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/hw/debug/arm64.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/hw/debug/x86.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/lookup.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/clock.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/scheduler.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/utc.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/hw/debug/arm64.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/hw/debug/x86.h @@ -13763,13 +12435,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/lookup. FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/scheduler.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/utc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/hw/debug/arm64.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/hw/debug/x86.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/lookup.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/clock.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/scheduler.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/utc.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/hw/debug/arm64.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/hw/debug/x86.h @@ -13791,6 +12456,13 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/lookup. FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/scheduler.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/utc.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/exception.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/hw/debug/arm64.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/hw/debug/x86.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/lookup.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/clock.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/scheduler.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/utc.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/hw/debug/arm64.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/hw/debug/x86.h @@ -13805,13 +12477,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/looku FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/scheduler.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/utc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/hw/debug/arm64.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/hw/debug/x86.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/lookup.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/clock.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/scheduler.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/utc.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/hw/debug/arm64.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/hw/debug/x86.h @@ -13819,13 +12484,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/looku FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/scheduler.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/utc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/hw/debug/arm64.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/hw/debug/x86.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/lookup.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/clock.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/scheduler.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/utc.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/hw/debug/arm64.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/hw/debug/x86.h @@ -13847,6 +12505,13 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/looku FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/scheduler.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/utc.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/exception.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/hw/debug/arm64.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/hw/debug/x86.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/lookup.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/clock.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/scheduler.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/utc.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/hw/debug/arm64.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/hw/debug/x86.h @@ -13861,13 +12526,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/lookup.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/scheduler.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/utc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/hw/debug/arm64.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/hw/debug/x86.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/lookup.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/clock.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/scheduler.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/utc.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/hw/debug/arm64.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/hw/debug/x86.h @@ -13875,13 +12533,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/lookup.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/scheduler.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/utc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/exception.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/hw/debug/arm64.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/hw/debug/x86.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/lookup.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/clock.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/scheduler.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/utc.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/hw/debug/arm64.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/hw/debug/x86.h @@ -13903,6 +12554,13 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/lookup.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/scheduler.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/utc.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/exception.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/hw/debug/arm64.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/hw/debug/x86.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/lookup.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/clock.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/scheduler.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/utc.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/hw/debug/arm64.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/hw/debug/x86.h @@ -14149,6 +12807,7 @@ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.settings/night_mode.fidl + ../.. ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/constraints.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/results.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/usages.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.system.state/system-state-transition.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input3/events.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input3/keyboard.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input3/modifiers.fidl + ../../../fuchsia/sdk/linux/LICENSE @@ -14181,21 +12840,11 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/boot/ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE @@ -14211,6 +12860,11 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/boot/ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE @@ -14221,21 +12875,11 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/boo ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE @@ -14251,6 +12895,11 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/boo ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE @@ -14261,21 +12910,11 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/boot/cr ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE @@ -14291,6 +12930,11 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/boot/cr ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls-next.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/testonly-syscalls.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/analyzer.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/boot/crash-reason.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/string_view.h + ../../../fuchsia/sdk/linux/LICENSE @@ -14469,6 +13113,7 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.settings/night_mode.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/constraints.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/results.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/usages.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.system.state/system-state-transition.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input3/events.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input3/keyboard.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input3/modifiers.fidl @@ -14501,21 +13146,11 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/boot/cr FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/string_view.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls-next.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/testonly-syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/analyzer.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/boot/crash-reason.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/string_view.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls-next.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/testonly-syscalls.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/analyzer.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/boot/crash-reason.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/string_view.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls-next.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/testonly-syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/analyzer.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/boot/crash-reason.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/string_view.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls-next.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/testonly-syscalls.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/analyzer.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/boot/crash-reason.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/string_view.h @@ -14531,6 +13166,11 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/boot/cr FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/string_view.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls-next.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/testonly-syscalls.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/analyzer.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/boot/crash-reason.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/string_view.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls-next.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/testonly-syscalls.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/analyzer.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/boot/crash-reason.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/string_view.h @@ -14541,21 +13181,11 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/boot/ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/string_view.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls-next.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/testonly-syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/analyzer.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/boot/crash-reason.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/string_view.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls-next.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/testonly-syscalls.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/analyzer.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/boot/crash-reason.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/string_view.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls-next.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/testonly-syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/analyzer.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/boot/crash-reason.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/string_view.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls-next.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/testonly-syscalls.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/analyzer.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/boot/crash-reason.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/string_view.h @@ -14571,6 +13201,11 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/boot/ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/string_view.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls-next.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/testonly-syscalls.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/analyzer.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/boot/crash-reason.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/string_view.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls-next.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/testonly-syscalls.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/analyzer.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/boot/crash-reason.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/string_view.h @@ -14581,21 +13216,11 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/boot/cras FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/string_view.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls-next.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/testonly-syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/analyzer.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/boot/crash-reason.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/string_view.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls-next.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/testonly-syscalls.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/analyzer.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/boot/crash-reason.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/string_view.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls-next.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/testonly-syscalls.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/analyzer.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/boot/crash-reason.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/string_view.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls-next.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/testonly-syscalls.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/analyzer.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/boot/crash-reason.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/string_view.h @@ -14611,6 +13236,11 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/boot/cras FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/string_view.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls-next.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/testonly-syscalls.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/analyzer.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/boot/crash-reason.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/string_view.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls-next.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/testonly-syscalls.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/analyzer.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/boot/crash-reason.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/string_view.h @@ -14744,6 +13374,7 @@ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.audio/health.fidl + ../ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.network/port.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.radar/radar.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.sharedmemory/sharedmemory.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.descriptor/usb.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.input.virtualkeyboard/virtual_keyboard.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.input/keymap.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.intl/calendar.fidl + ../../../fuchsia/sdk/linux/LICENSE @@ -14773,28 +13404,25 @@ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.wlan.ieee80211/constants.fidl + ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.wlan.ieee80211/rsn.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.wlan.policy/types.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-23/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-23/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-23/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/availability.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_logging_cpp/include/lib/driver/logging/cpp/internal/logger_internal.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_logging_cpp/include/lib/driver/logging/cpp/structured_logger.h + ../../../fuchsia/sdk/linux/LICENSE @@ -14926,6 +13554,7 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.audio/health.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.network/port.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.radar/radar.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.sharedmemory/sharedmemory.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.descriptor/usb.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.input.virtualkeyboard/virtual_keyboard.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.input/keymap.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.intl/calendar.fidl @@ -14955,28 +13584,25 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.wlan.ieee80211/constants.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.wlan.ieee80211/rsn.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.wlan.policy/types.fidl FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/availability.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/availability.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-23/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/availability.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/availability.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/availability.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-23/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/availability.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/availability.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/availability.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-23/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/availability.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/availability.h FILE: ../../../fuchsia/sdk/linux/pkg/driver_logging_cpp/include/lib/driver/logging/cpp/internal/logger_internal.h FILE: ../../../fuchsia/sdk/linux/pkg/driver_logging_cpp/include/lib/driver/logging/cpp/structured_logger.h @@ -15131,6 +13757,7 @@ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.audio.signalprocessing/ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.audio.signalprocessing/vendor_specific.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.audio/overview.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.i2c.businfo/businfo.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.mailbox/mailbox.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.network/diagnostics.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.network/overview.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.platform.bus/platform-bus.fidl + ../../../fuchsia/sdk/linux/LICENSE @@ -15225,15 +13852,9 @@ ORIGIN: ../../../fuchsia/sdk/linux/fidl/zx/overview.fidl + ../../../fuchsia/sdk/ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE @@ -15243,21 +13864,18 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-23/sysroot/include/zircon/sysca ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE @@ -15267,21 +13885,18 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-23/sysroot/include/zircon/sys ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE @@ -15291,6 +13906,9 @@ ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-23/sysroot/include/zircon/syscall ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/errors.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/syscalls/internal/cdecls.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/syscalls/iob.h + ../../../fuchsia/sdk/linux/LICENSE @@ -15394,7 +14012,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/pkg/fidl_driver_natural/natural_messaging.cc ORIGIN: ../../../fuchsia/sdk/linux/pkg/fit/include/lib/fit/inline_any.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/fit/include/lib/fit/internal/inline_any.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/input_report_reader/reader.cc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/pkg/inspect/client.shard.cml + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/inspect_component_cpp/component.cc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/inspect_component_cpp/include/lib/inspect/component/cpp/component.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/inspect_component_cpp/include/lib/inspect/component/cpp/service.h + ../../../fuchsia/sdk/linux/LICENSE @@ -15484,6 +14101,7 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.audio.signalprocessing/ov FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.audio.signalprocessing/vendor_specific.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.audio/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.i2c.businfo/businfo.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.mailbox/mailbox.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.network/diagnostics.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.network/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.platform.bus/platform-bus.fidl @@ -15578,15 +14196,9 @@ FILE: ../../../fuchsia/sdk/linux/fidl/zx/overview.fidl FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/syscalls/iob.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/errors.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/internal/cdecls.inc -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/syscalls/iob.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/syscalls/iob.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/errors.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/internal/cdecls.inc -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/syscalls/iob.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/syscalls/iob.h @@ -15596,21 +14208,18 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-23/sysroot/include/zircon/syscall FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/syscalls/iob.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/errors.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/internal/cdecls.inc +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/syscalls/iob.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/syscalls/iob.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/syscalls/iob.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/errors.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/internal/cdecls.inc -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/syscalls/iob.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/syscalls/iob.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/errors.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/internal/cdecls.inc -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/syscalls/iob.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/syscalls/iob.h @@ -15620,21 +14229,18 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-23/sysroot/include/zircon/sysca FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/syscalls/iob.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/errors.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/internal/cdecls.inc +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/syscalls/iob.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/syscalls/iob.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/syscalls/iob.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/errors.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/internal/cdecls.inc -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/syscalls/iob.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/syscalls/iob.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/errors.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/internal/cdecls.inc -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/syscalls/iob.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/syscalls/iob.h @@ -15644,6 +14250,9 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-23/sysroot/include/zircon/syscalls/ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/syscalls/iob.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/errors.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/internal/cdecls.inc +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/syscalls/iob.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/errors.h FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/syscalls/iob.h @@ -15747,7 +14356,6 @@ FILE: ../../../fuchsia/sdk/linux/pkg/fidl_driver_natural/natural_messaging.cc FILE: ../../../fuchsia/sdk/linux/pkg/fit/include/lib/fit/inline_any.h FILE: ../../../fuchsia/sdk/linux/pkg/fit/include/lib/fit/internal/inline_any.h FILE: ../../../fuchsia/sdk/linux/pkg/input_report_reader/reader.cc -FILE: ../../../fuchsia/sdk/linux/pkg/inspect/client.shard.cml FILE: ../../../fuchsia/sdk/linux/pkg/inspect_component_cpp/component.cc FILE: ../../../fuchsia/sdk/linux/pkg/inspect_component_cpp/include/lib/inspect/component/cpp/component.h FILE: ../../../fuchsia/sdk/linux/pkg/inspect_component_cpp/include/lib/inspect/component/cpp/service.h @@ -15826,8 +14434,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.device.fs/names.fidl + ../../../ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.development/overview.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.framework/driver.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.framework/driver_info.fidl + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.legacy/legacy_types.fidl + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.legacy/overview.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.registrar/overview.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.element/graphical_presenter.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.adcimpl/adc-impl.fidl + ../../../fuchsia/sdk/linux/LICENSE @@ -15849,7 +14455,10 @@ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.sdmmc/metadata.fidl + . ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.sdmmc/sdmmc.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.spiimpl/spi-impl.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.trippoint/trippoint.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.dci/usb-dci.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.endpoint/endpoint.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.phy/usb-phy.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.request/request.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.images2/math.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.inspect/inspect_sink.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/symlink.fidl + ../../../fuchsia/sdk/linux/LICENSE @@ -15912,8 +14521,6 @@ ORIGIN: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/collector.shard. ORIGIN: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/include/heapdump/bind.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/include/heapdump/snapshot.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/include/heapdump/stats.h + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/pkg/inspect/offer.shard.cml + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/pkg/inspect/use.shard.cml + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/mmio/include/lib/mmio/mmio-ops.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/sync_cpp/include/lib/sync/cpp/mutex.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/sys_service_cpp/service_handler.cc + ../../../fuchsia/sdk/linux/LICENSE @@ -15934,8 +14541,6 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.device.fs/names.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.development/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.framework/driver.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.framework/driver_info.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.legacy/legacy_types.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.legacy/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.registrar/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.element/graphical_presenter.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.adcimpl/adc-impl.fidl @@ -15957,7 +14562,10 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.sdmmc/metadata.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.sdmmc/sdmmc.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.spiimpl/spi-impl.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.trippoint/trippoint.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.dci/usb-dci.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.endpoint/endpoint.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.phy/usb-phy.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.usb.request/request.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.images2/math.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.inspect/inspect_sink.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/symlink.fidl @@ -16020,8 +14628,6 @@ FILE: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/collector.shard.cm FILE: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/include/heapdump/bind.h FILE: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/include/heapdump/snapshot.h FILE: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/include/heapdump/stats.h -FILE: ../../../fuchsia/sdk/linux/pkg/inspect/offer.shard.cml -FILE: ../../../fuchsia/sdk/linux/pkg/inspect/use.shard.cml FILE: ../../../fuchsia/sdk/linux/pkg/mmio/include/lib/mmio/mmio-ops.h FILE: ../../../fuchsia/sdk/linux/pkg/sync_cpp/include/lib/sync/cpp/mutex.h FILE: ../../../fuchsia/sdk/linux/pkg/sys_service_cpp/service_handler.cc @@ -16062,6 +14668,7 @@ LIBRARY: fuchsia_sdk ORIGIN: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/arch/riscv64/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/bind/fuchsia.mailbox/fuchsia.mailbox.bind + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/bind/fuchsia.regulator/fuchsia.regulator.bind + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/bind/fuchsia.spmi/fuchsia.spmi.bind + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.bredr/overview.fidl + ../../../fuchsia/sdk/linux/LICENSE @@ -16070,20 +14677,25 @@ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.le/iso.fidl + ../../.. ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.le/l2cap.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth/channel.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth/coding_format.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.component.runner/task_provider.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.component.sandbox/availability.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.component/introspector.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.device.fs/controller.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.diagnostics/log_stream.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.metadata/fuchsia.driver.metadata.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.test/internal.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.bluetooth/emulator.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.bluetooth/overview.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.light/metadata.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.mailbox/metadata.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.pci/metadata.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.pin/pin.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.pinimpl/metadata.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.pinimpl/pin-impl.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.platform.device/overview.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.power/metadata.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.power/overview.fidl + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.sdhci/sdhci.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.spmi/metadata.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.spmi/spmi.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.images2/overview.fidl + ../../../fuchsia/sdk/linux/LICENSE @@ -16095,29 +14707,36 @@ ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/config.fidl + ../../../f ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/error.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/secure_mem.fidl + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-23/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-23/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE -ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-23/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_fake_bti_cpp/fake-bti.cc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_fake_bti_cpp/include/lib/driver/fake-bti/cpp/fake-bti.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_fake_object_cpp/fake-handle.cc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_fake_object_cpp/fake-object.cc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_fake_object_cpp/include/lib/driver/fake-object/cpp/fake-object.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_fake_object_cpp/internal.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_fake_platform_device_cpp/fake-pdev.cc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_fake_platform_device_cpp/include/lib/driver/fake-platform-device/cpp/fake-pdev.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_fake_resource_cpp/fake-resource.cc + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_fake_resource_cpp/include/lib/driver/fake-resource/cpp/fake-resource.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_incoming_cpp/include/lib/driver/incoming/cpp/service_validator.h + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_incoming_cpp/service_validator.cc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_metadata_cpp/include/lib/driver/metadata/cpp/metadata.h + ../../../fuchsia/sdk/linux/LICENSE @@ -16137,6 +14756,9 @@ ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_power_cpp/types.cc + ../../../fuch ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_power_cpp/wake-lease.cc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_testing_scoped_global_logger_cpp/scoped_global_logger.cc + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/cpp/time.h + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/inspect/client.shard.cml + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/inspect/offer.shard.cml + ../../../fuchsia/sdk/linux/LICENSE +ORIGIN: ../../../fuchsia/sdk/linux/pkg/inspect/use.shard.cml + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/syslog/client.shard.cml + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/syslog/offer.shard.cml + ../../../fuchsia/sdk/linux/LICENSE ORIGIN: ../../../fuchsia/sdk/linux/pkg/syslog/use.shard.cml + ../../../fuchsia/sdk/linux/LICENSE @@ -16148,6 +14770,7 @@ TYPE: LicenseType.bsd FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/arch/riscv64/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/availability_levels.inc +FILE: ../../../fuchsia/sdk/linux/bind/fuchsia.mailbox/fuchsia.mailbox.bind FILE: ../../../fuchsia/sdk/linux/bind/fuchsia.regulator/fuchsia.regulator.bind FILE: ../../../fuchsia/sdk/linux/bind/fuchsia.spmi/fuchsia.spmi.bind FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.bredr/overview.fidl @@ -16156,20 +14779,25 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.le/iso.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.le/l2cap.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth/channel.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth/coding_format.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.component.runner/task_provider.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.component.sandbox/availability.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.component/introspector.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.device.fs/controller.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.diagnostics/log_stream.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.metadata/fuchsia.driver.metadata.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.driver.test/internal.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.bluetooth/emulator.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.bluetooth/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.light/metadata.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.mailbox/metadata.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.pci/metadata.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.pin/pin.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.pinimpl/metadata.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.pinimpl/pin-impl.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.platform.device/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.power/metadata.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.power/overview.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.sdhci/sdhci.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.spmi/metadata.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.spmi/spmi.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.images2/overview.fidl @@ -16181,29 +14809,36 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/config.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/error.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sysmem2/secure_mem.fidl FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/include/zircon/availability_levels.inc -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/include/zircon/availability_levels.inc -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-23/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/include/zircon/availability_levels.inc +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/include/zircon/availability_levels.inc -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/include/zircon/availability_levels.inc -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-23/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/include/zircon/availability_levels.inc +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/include/zircon/availability_levels.inc -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/include/zircon/availability_levels.inc -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/x64-api-23/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/availability_levels.inc +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/zircon/availability_levels.inc FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/availability_levels.inc +FILE: ../../../fuchsia/sdk/linux/pkg/driver_fake_bti_cpp/fake-bti.cc +FILE: ../../../fuchsia/sdk/linux/pkg/driver_fake_bti_cpp/include/lib/driver/fake-bti/cpp/fake-bti.h +FILE: ../../../fuchsia/sdk/linux/pkg/driver_fake_object_cpp/fake-handle.cc +FILE: ../../../fuchsia/sdk/linux/pkg/driver_fake_object_cpp/fake-object.cc +FILE: ../../../fuchsia/sdk/linux/pkg/driver_fake_object_cpp/include/lib/driver/fake-object/cpp/fake-object.h +FILE: ../../../fuchsia/sdk/linux/pkg/driver_fake_object_cpp/internal.h +FILE: ../../../fuchsia/sdk/linux/pkg/driver_fake_platform_device_cpp/fake-pdev.cc +FILE: ../../../fuchsia/sdk/linux/pkg/driver_fake_platform_device_cpp/include/lib/driver/fake-platform-device/cpp/fake-pdev.h +FILE: ../../../fuchsia/sdk/linux/pkg/driver_fake_resource_cpp/fake-resource.cc +FILE: ../../../fuchsia/sdk/linux/pkg/driver_fake_resource_cpp/include/lib/driver/fake-resource/cpp/fake-resource.h FILE: ../../../fuchsia/sdk/linux/pkg/driver_incoming_cpp/include/lib/driver/incoming/cpp/service_validator.h FILE: ../../../fuchsia/sdk/linux/pkg/driver_incoming_cpp/service_validator.cc FILE: ../../../fuchsia/sdk/linux/pkg/driver_metadata_cpp/include/lib/driver/metadata/cpp/metadata.h @@ -16223,6 +14858,9 @@ FILE: ../../../fuchsia/sdk/linux/pkg/driver_power_cpp/types.cc FILE: ../../../fuchsia/sdk/linux/pkg/driver_power_cpp/wake-lease.cc FILE: ../../../fuchsia/sdk/linux/pkg/driver_testing_scoped_global_logger_cpp/scoped_global_logger.cc FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/cpp/time.h +FILE: ../../../fuchsia/sdk/linux/pkg/inspect/client.shard.cml +FILE: ../../../fuchsia/sdk/linux/pkg/inspect/offer.shard.cml +FILE: ../../../fuchsia/sdk/linux/pkg/inspect/use.shard.cml FILE: ../../../fuchsia/sdk/linux/pkg/syslog/client.shard.cml FILE: ../../../fuchsia/sdk/linux/pkg/syslog/offer.shard.cml FILE: ../../../fuchsia/sdk/linux/pkg/syslog/use.shard.cml @@ -16996,188 +15634,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-16/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-17/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/dist/libdriver_runtime.so @@ -17360,188 +15816,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-20/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-21/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-22/dist/libdriver_runtime.so @@ -18088,6 +16362,188 @@ FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-24/sysroot/lib/libzircon.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/dist/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libasync-loop-default.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libmagma_client.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libsync.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libtrace-vthread.a +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/lib/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/alloca.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/ftp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/inet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/nameser.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/nameser_compat.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/telnet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/arpa/tftp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/aarch64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/aarch64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/alltypes.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/io.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/null.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/posix.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/riscv64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/riscv64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/x86_64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/x86_64/io.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/bits/x86_64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/byteswap.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/complex.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/cpio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ctype.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/dirent.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/elf.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/endian.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/err.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/features.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/fnmatch.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/getopt.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/glob.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/grp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/iconv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ifaddrs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/inttypes.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/iso646.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/langinfo.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/libgen.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/link.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/locale.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/malloc.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/math.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/memory.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/monetary.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/ethernet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/if.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/if_arp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/net/route.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netdb.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ether.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/icmp6.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/if_ether.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/igmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/in.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/in_systm.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ip.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ip6.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/ip_icmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/tcp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netinet/udp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/netpacket/packet.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/nl_types.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/paths.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/pthread.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/pwd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/regex.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/resolv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sched.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/semaphore.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/spawn.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/stdio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/stdlib.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/string.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/strings.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/acct.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/auxv.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/dir.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/eventfd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/file.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/inotify.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/io.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ipc.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/mman.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/mount.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/msg.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/mtio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/param.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/random.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/select.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/sem.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/shm.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/socket.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/statvfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/time.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/timeb.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/timerfd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/times.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ttydefaults.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/types.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/uio.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/un.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/utsname.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/vfs.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sys/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/sysexits.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/tar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/time.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/uchar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/unistd.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/utime.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/values.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wchar.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wctype.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/include/wordexp.h +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/Scrt1.o +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libc.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libdl.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libm.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libpthread.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/librt.so +FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-25/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/arm64-api-NEXT/dist/libdriver_runtime.so @@ -18452,188 +16908,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-16/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-17/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/dist/libdriver_runtime.so @@ -18816,188 +17090,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-20/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-21/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-22/dist/libdriver_runtime.so @@ -19544,6 +17636,188 @@ FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-24/sysroot/lib/libzircon.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/dist/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libasync-loop-default.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libmagma_client.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libsync.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libtrace-vthread.a +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/lib/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/alloca.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/ftp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/inet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/nameser.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/nameser_compat.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/telnet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/arpa/tftp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/aarch64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/aarch64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/alltypes.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/io.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/null.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/posix.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/riscv64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/riscv64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/x86_64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/x86_64/io.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/bits/x86_64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/byteswap.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/complex.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/cpio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ctype.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/dirent.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/elf.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/endian.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/err.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/features.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/fnmatch.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/getopt.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/glob.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/grp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/iconv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ifaddrs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/inttypes.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/iso646.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/langinfo.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/libgen.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/link.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/locale.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/malloc.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/math.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/memory.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/monetary.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/ethernet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/if.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/if_arp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/net/route.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netdb.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ether.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/icmp6.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/if_ether.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/igmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/in.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/in_systm.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ip.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ip6.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/ip_icmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/tcp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netinet/udp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/netpacket/packet.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/nl_types.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/paths.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/pthread.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/pwd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/regex.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/resolv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sched.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/semaphore.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/spawn.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/stdio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/stdlib.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/string.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/strings.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/acct.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/auxv.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/dir.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/eventfd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/file.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/inotify.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/io.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ipc.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/mman.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/mount.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/msg.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/mtio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/param.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/random.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/select.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/sem.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/shm.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/socket.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/statvfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/time.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/timeb.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/timerfd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/times.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ttydefaults.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/types.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/uio.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/un.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/utsname.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/vfs.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sys/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/sysexits.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/tar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/time.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/uchar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/unistd.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/utime.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/values.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wchar.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wctype.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/include/wordexp.h +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/Scrt1.o +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libc.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libdl.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libm.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libpthread.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/librt.so +FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-25/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/riscv64-api-NEXT/dist/libdriver_runtime.so @@ -19908,188 +18182,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-16/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-17/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/dist/libdriver_runtime.so @@ -20272,188 +18364,6 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-20/sysroot/lib/libzircon.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/dist/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libasync-default.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libasync-loop-default.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libbackend_fuchsia_globals.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libdriver_runtime.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libheapdump_instrumentation.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libmagma_client.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libsvc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libsync.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libsyslog.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libtrace-engine.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libtrace-provider-so.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libtrace-vthread.a -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libvfs_internal.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/lib/libvulkan.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/alloca.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/ftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/inet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/nameser.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/nameser_compat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/telnet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/arpa/tftp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/assert.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/aarch64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/aarch64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/alltypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/null.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/posix.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/riscv64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/riscv64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/x86_64/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/x86_64/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/bits/x86_64/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/byteswap.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/complex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/cpio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/dirent.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/dlfcn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/elf.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/endian.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/err.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/features.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/fenv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/fnmatch.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/getopt.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/glob.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/grp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/iconv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ifaddrs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/inttypes.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/iso646.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/langinfo.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/libgen.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/limits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/link.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/locale.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/malloc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/math.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/memory.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/monetary.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/ethernet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/if.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/if_arp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/net/route.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netdb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/icmp6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/if_ether.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/igmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/in.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/in_systm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ip.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ip6.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/ip_icmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/tcp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netinet/udp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/netpacket/packet.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/nl_types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/paths.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/pthread.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/pwd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/regex.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/resolv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sched.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/semaphore.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/setjmp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/spawn.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/stdio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/stdlib.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/string.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/strings.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/acct.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/auxv.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/dir.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/errno.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/eventfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/fcntl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/file.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/inotify.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/io.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ioctl.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ipc.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/mman.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/mount.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/msg.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/mtio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/param.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/poll.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/random.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/select.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/sem.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/shm.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/signal.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/socket.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/stat.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/statfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/statvfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/stropts.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/timeb.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/timerfd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/times.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ttydefaults.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/types.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/uio.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/un.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/utsname.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/vfs.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sys/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/sysexits.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/syslog.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/tar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/termios.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/threads.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/time.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/uchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/ucontext.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/unistd.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/utime.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/values.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wait.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wchar.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wctype.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/include/wordexp.h -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/Scrt1.o -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libc.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libdl.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libm.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libpthread.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/librt.so -FILE: ../../../fuchsia/sdk/linux/obj/x64-api-21/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-22/dist/libdriver_runtime.so @@ -21000,6 +18910,188 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/libm.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/lib/libzircon.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/dist/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libasync-default.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libasync-loop-default.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libbackend_fuchsia_globals.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libdriver_runtime.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libfdio.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libheapdump_instrumentation.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libmagma_client.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libsvc.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libsync.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libsyslog.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libtrace-engine.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libtrace-provider-so.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libtrace-vthread.a +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libvfs_internal.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/lib/libvulkan.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/alloca.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/ftp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/inet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/nameser.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/nameser_compat.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/telnet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/arpa/tftp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/assert.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/aarch64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/aarch64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/alltypes.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/io.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/null.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/posix.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/riscv64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/riscv64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/x86_64/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/x86_64/io.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/bits/x86_64/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/byteswap.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/complex.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/cpio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ctype.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/dirent.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/dlfcn.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/elf.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/endian.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/err.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/features.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/fenv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/fnmatch.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/getopt.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/glob.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/grp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/iconv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ifaddrs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/inttypes.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/iso646.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/langinfo.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/libgen.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/limits.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/link.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/locale.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/malloc.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/math.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/memory.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/monetary.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/ethernet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/if.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/if_arp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/net/route.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netdb.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ether.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/icmp6.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/if_ether.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/igmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/in.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/in_systm.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ip.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ip6.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/ip_icmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/tcp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netinet/udp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/netpacket/packet.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/nl_types.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/paths.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/pthread.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/pwd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/regex.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/resolv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sched.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/semaphore.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/setjmp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/spawn.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/stdio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/stdlib.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/string.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/strings.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/acct.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/auxv.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/dir.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/errno.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/eventfd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/fcntl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/file.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/inotify.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/io.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ioctl.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ipc.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/mman.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/mount.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/msg.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/mtio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/param.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/poll.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/random.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/select.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/sem.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/shm.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/signal.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/socket.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/stat.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/statfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/statvfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/stropts.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/time.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/timeb.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/timerfd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/times.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ttydefaults.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/types.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/uio.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/un.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/utsname.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/vfs.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sys/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/sysexits.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/syslog.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/tar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/termios.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/threads.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/time.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/uchar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/ucontext.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/unistd.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/utime.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/values.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wait.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wchar.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wctype.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/include/wordexp.h +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/Scrt1.o +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libc.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libdl.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libm.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libpthread.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/librt.so +FILE: ../../../fuchsia/sdk/linux/obj/x64-api-25/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/dist/libbackend_fuchsia_globals.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/dist/libdriver_runtime.so @@ -21183,172 +19275,169 @@ FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/lib/libpthread.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/lib/librt.so FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/lib/libzircon.so FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/cmd-buf-benchmark-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/heapdump-collector/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-16/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-17/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-16/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-17/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-16/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-17/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/magma_conformance_tests/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/realm_builder_server/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkcopy-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkext-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkloop-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkproto-driver-test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/arm64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/riscv64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-20/release/package_manifest.json -FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-21/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-22/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-23/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-24/release/package_manifest.json +FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-25/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/packages/vkreadback_test/x64-api-NEXT/release/package_manifest.json FILE: ../../../fuchsia/sdk/linux/pkg/async-default/async-default.ifs FILE: ../../../fuchsia/sdk/linux/pkg/backend_fuchsia_globals/backend_fuchsia_globals.ifs @@ -21357,7 +19446,7 @@ FILE: ../../../fuchsia/sdk/linux/pkg/driver_runtime_shared_lib/driver_runtime.if FILE: ../../../fuchsia/sdk/linux/pkg/fdio/fdio.ifs FILE: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/heapdump_instrumentation.ifs FILE: ../../../fuchsia/sdk/linux/pkg/heapdump_instrumentation/heapdump_instrumentation_shard.json -FILE: ../../../fuchsia/sdk/linux/pkg/inspect/inspect.json +FILE: ../../../fuchsia/sdk/linux/pkg/inspect/inspect_sdk.json FILE: ../../../fuchsia/sdk/linux/pkg/svc/svc.ifs FILE: ../../../fuchsia/sdk/linux/pkg/sys/component/realm_builder_shard_sdk.json FILE: ../../../fuchsia/sdk/linux/pkg/syslog/syslog.ifs diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index a96a0bb423fff..c557c60594124 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 83c4ecafc6e635226445e328864ab843 +Signature: 7f26d1550cef3678c79bfeed907ae5be ==================================================================================================== LIBRARY: etc1 @@ -399,6 +399,11 @@ FILE: ../../../flutter/third_party/skia/modules/pathkit/perf/pathops.bench.js FILE: ../../../flutter/third_party/skia/modules/pathkit/perf/perfReporter.js FILE: ../../../flutter/third_party/skia/modules/skparagraph/test.html FILE: ../../../flutter/third_party/skia/package-lock.json +FILE: ../../../flutter/third_party/skia/relnotes/GpuStats.md +FILE: ../../../flutter/third_party/skia/relnotes/GraphiteLogPriority.md +FILE: ../../../flutter/third_party/skia/relnotes/SkColorSpaceMakeCICP.md +FILE: ../../../flutter/third_party/skia/relnotes/debugtrace.md +FILE: ../../../flutter/third_party/skia/relnotes/precompilecontext.md FILE: ../../../flutter/third_party/skia/src/gpu/gpu_workaround_list.txt FILE: ../../../flutter/third_party/skia/src/ports/fontations/Cargo.toml FILE: ../../../flutter/third_party/skia/src/sksl/generated/sksl_compute.minified.sksl @@ -8045,6 +8050,7 @@ ORIGIN: ../../../flutter/third_party/skia/src/gpu/ganesh/tessellate/PathTessella ORIGIN: ../../../flutter/third_party/skia/src/gpu/ganesh/tessellate/StrokeTessellator.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/ganesh/tessellate/StrokeTessellator.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/ganesh/tessellate/VertexChunkPatchAllocator.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/AndroidSpecificPrecompile.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/BuiltInCodeSnippetID.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/ClientMappedBufferManager.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/ClientMappedBufferManager.h + ../../../flutter/third_party/skia/LICENSE @@ -8076,7 +8082,6 @@ ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/PaintParamsKey.h + .. ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/PipelineData.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/PrecompileInternal.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/PublicPrecompile.cpp + ../../../flutter/third_party/skia/LICENSE -ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/PublicPrecompile.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/QueueManager.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/QueueManager.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/RecorderPriv.h + ../../../flutter/third_party/skia/LICENSE @@ -8258,6 +8263,7 @@ FILE: ../../../flutter/third_party/skia/src/gpu/ganesh/tessellate/PathTessellato FILE: ../../../flutter/third_party/skia/src/gpu/ganesh/tessellate/StrokeTessellator.cpp FILE: ../../../flutter/third_party/skia/src/gpu/ganesh/tessellate/StrokeTessellator.h FILE: ../../../flutter/third_party/skia/src/gpu/ganesh/tessellate/VertexChunkPatchAllocator.h +FILE: ../../../flutter/third_party/skia/src/gpu/graphite/AndroidSpecificPrecompile.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/BuiltInCodeSnippetID.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/ClientMappedBufferManager.cpp FILE: ../../../flutter/third_party/skia/src/gpu/graphite/ClientMappedBufferManager.h @@ -8289,7 +8295,6 @@ FILE: ../../../flutter/third_party/skia/src/gpu/graphite/PaintParamsKey.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/PipelineData.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/PrecompileInternal.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/PublicPrecompile.cpp -FILE: ../../../flutter/third_party/skia/src/gpu/graphite/PublicPrecompile.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/QueueManager.cpp FILE: ../../../flutter/third_party/skia/src/gpu/graphite/QueueManager.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/RecorderPriv.h @@ -9415,7 +9420,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: skia +ORIGIN: ../../../flutter/third_party/skia/include/core/SkFontScanner.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/include/ports/SkFontMgr_Fontations.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/include/ports/SkFontScanner_Fontations.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/include/ports/SkFontScanner_FreeType.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/modules/skottie/utils/PreshapeTool.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/modules/skottie/utils/TextPreshape.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/modules/skottie/utils/TextPreshape.h + ../../../flutter/third_party/skia/LICENSE @@ -9428,13 +9436,18 @@ ORIGIN: ../../../flutter/third_party/skia/src/codec/SkCrabbyAvifCodec.cpp + ../. ORIGIN: ../../../flutter/third_party/skia/src/codec/SkCrabbyAvifCodec.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/codec/SkJpegMetadataDecoderImpl.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/codec/SkJpegMetadataDecoderImpl.h + ../../../flutter/third_party/skia/LICENSE -ORIGIN: ../../../flutter/third_party/skia/src/core/SkFontScanner.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/codec/SkPngCompositeChunkReader.cpp + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/codec/SkPngCompositeChunkReader.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/ganesh/gl/win/GrGLMakeWinInterface.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/ports/SkFontMgr_android_ndk.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/ports/SkFontMgr_fontations_empty.cpp + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/ports/SkFontScanner_FreeType_priv.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/sfnt/SkOTTable_sbix.h + ../../../flutter/third_party/skia/LICENSE TYPE: LicenseType.bsd +FILE: ../../../flutter/third_party/skia/include/core/SkFontScanner.h FILE: ../../../flutter/third_party/skia/include/ports/SkFontMgr_Fontations.h +FILE: ../../../flutter/third_party/skia/include/ports/SkFontScanner_Fontations.h +FILE: ../../../flutter/third_party/skia/include/ports/SkFontScanner_FreeType.h FILE: ../../../flutter/third_party/skia/modules/skottie/utils/PreshapeTool.cpp FILE: ../../../flutter/third_party/skia/modules/skottie/utils/TextPreshape.cpp FILE: ../../../flutter/third_party/skia/modules/skottie/utils/TextPreshape.h @@ -9447,10 +9460,12 @@ FILE: ../../../flutter/third_party/skia/src/codec/SkCrabbyAvifCodec.cpp FILE: ../../../flutter/third_party/skia/src/codec/SkCrabbyAvifCodec.h FILE: ../../../flutter/third_party/skia/src/codec/SkJpegMetadataDecoderImpl.cpp FILE: ../../../flutter/third_party/skia/src/codec/SkJpegMetadataDecoderImpl.h -FILE: ../../../flutter/third_party/skia/src/core/SkFontScanner.h +FILE: ../../../flutter/third_party/skia/src/codec/SkPngCompositeChunkReader.cpp +FILE: ../../../flutter/third_party/skia/src/codec/SkPngCompositeChunkReader.h FILE: ../../../flutter/third_party/skia/src/gpu/ganesh/gl/win/GrGLMakeWinInterface.cpp FILE: ../../../flutter/third_party/skia/src/ports/SkFontMgr_android_ndk.cpp FILE: ../../../flutter/third_party/skia/src/ports/SkFontMgr_fontations_empty.cpp +FILE: ../../../flutter/third_party/skia/src/ports/SkFontScanner_FreeType_priv.h FILE: ../../../flutter/third_party/skia/src/sfnt/SkOTTable_sbix.h ---------------------------------------------------------------------------------------------------- Copyright 2024 Google Inc. @@ -9502,6 +9517,8 @@ ORIGIN: ../../../flutter/third_party/skia/include/gpu/ganesh/gl/win/GrGLMakeWinI ORIGIN: ../../../flutter/third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendSemaphore.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendSurface.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/include/gpu/ganesh/mtl/GrMtlDirectContext.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/include/gpu/graphite/LogPriority.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/include/gpu/graphite/PrecompileContext.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/PaintOptions.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/Precompile.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/PrecompileBase.h + ../../../flutter/third_party/skia/LICENSE @@ -9511,6 +9528,7 @@ ORIGIN: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/Precom ORIGIN: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/PrecompileMaskFilter.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/PrecompileRuntimeEffect.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/PrecompileShader.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/modules/jsonreader/SkJSONReader.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/modules/skshaper/include/SkShaper_coretext.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/modules/skshaper/include/SkShaper_factory.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/modules/skshaper/include/SkShaper_harfbuzz.h + ../../../flutter/third_party/skia/LICENSE @@ -9544,12 +9562,16 @@ ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/BackendTexturePriv.h ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/ComputePathAtlas.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/ComputePathAtlas.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/InternalDrawTypeFlags.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/PrecompileContext.cpp + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/PrecompileContextPriv.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/RasterPathUtils.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/RasterPathUtils.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/RenderPassDesc.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/RenderPassDesc.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/ScratchResourceManager.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/ScratchResourceManager.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/ShaderInfo.cpp + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/ShaderInfo.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/TextureInfoPriv.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/dawn/DawnBackendTexture.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/dawn/DawnTextureInfo.cpp + ../../../flutter/third_party/skia/LICENSE @@ -9577,6 +9599,8 @@ ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/precompile/Precompile ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/precompile/PrecompileShadersPriv.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/render/AnalyticBlurRenderStep.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/render/AnalyticBlurRenderStep.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/render/CircularArcRenderStep.cpp + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/render/CircularArcRenderStep.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/render/SDFTextLCDRenderStep.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/render/SDFTextLCDRenderStep.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/task/DrawTask.cpp + ../../../flutter/third_party/skia/LICENSE @@ -9584,6 +9608,8 @@ ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/task/DrawTask.h + ../ ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/vk/VulkanBackendSemaphore.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/vk/VulkanBackendTexture.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/graphite/vk/VulkanTextureInfo.cpp + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/ports/SkTypeface_proxy.cpp + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/ports/SkTypeface_proxy.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/sksl/SkSLGraphiteModules.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/sksl/SkSLGraphiteModules.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/sksl/SkSLModule.cpp + ../../../flutter/third_party/skia/LICENSE @@ -9619,6 +9645,8 @@ FILE: ../../../flutter/third_party/skia/include/gpu/ganesh/gl/win/GrGLMakeWinInt FILE: ../../../flutter/third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendSemaphore.h FILE: ../../../flutter/third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendSurface.h FILE: ../../../flutter/third_party/skia/include/gpu/ganesh/mtl/GrMtlDirectContext.h +FILE: ../../../flutter/third_party/skia/include/gpu/graphite/LogPriority.h +FILE: ../../../flutter/third_party/skia/include/gpu/graphite/PrecompileContext.h FILE: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/PaintOptions.h FILE: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/Precompile.h FILE: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/PrecompileBase.h @@ -9628,6 +9656,7 @@ FILE: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/Precompi FILE: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/PrecompileMaskFilter.h FILE: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/PrecompileRuntimeEffect.h FILE: ../../../flutter/third_party/skia/include/gpu/graphite/precompile/PrecompileShader.h +FILE: ../../../flutter/third_party/skia/modules/jsonreader/SkJSONReader.h FILE: ../../../flutter/third_party/skia/modules/skshaper/include/SkShaper_coretext.h FILE: ../../../flutter/third_party/skia/modules/skshaper/include/SkShaper_factory.h FILE: ../../../flutter/third_party/skia/modules/skshaper/include/SkShaper_harfbuzz.h @@ -9661,12 +9690,16 @@ FILE: ../../../flutter/third_party/skia/src/gpu/graphite/BackendTexturePriv.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/ComputePathAtlas.cpp FILE: ../../../flutter/third_party/skia/src/gpu/graphite/ComputePathAtlas.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/InternalDrawTypeFlags.h +FILE: ../../../flutter/third_party/skia/src/gpu/graphite/PrecompileContext.cpp +FILE: ../../../flutter/third_party/skia/src/gpu/graphite/PrecompileContextPriv.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/RasterPathUtils.cpp FILE: ../../../flutter/third_party/skia/src/gpu/graphite/RasterPathUtils.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/RenderPassDesc.cpp FILE: ../../../flutter/third_party/skia/src/gpu/graphite/RenderPassDesc.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/ScratchResourceManager.cpp FILE: ../../../flutter/third_party/skia/src/gpu/graphite/ScratchResourceManager.h +FILE: ../../../flutter/third_party/skia/src/gpu/graphite/ShaderInfo.cpp +FILE: ../../../flutter/third_party/skia/src/gpu/graphite/ShaderInfo.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/TextureInfoPriv.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/dawn/DawnBackendTexture.cpp FILE: ../../../flutter/third_party/skia/src/gpu/graphite/dawn/DawnTextureInfo.cpp @@ -9694,6 +9727,8 @@ FILE: ../../../flutter/third_party/skia/src/gpu/graphite/precompile/PrecompileSh FILE: ../../../flutter/third_party/skia/src/gpu/graphite/precompile/PrecompileShadersPriv.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/render/AnalyticBlurRenderStep.cpp FILE: ../../../flutter/third_party/skia/src/gpu/graphite/render/AnalyticBlurRenderStep.h +FILE: ../../../flutter/third_party/skia/src/gpu/graphite/render/CircularArcRenderStep.cpp +FILE: ../../../flutter/third_party/skia/src/gpu/graphite/render/CircularArcRenderStep.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/render/SDFTextLCDRenderStep.cpp FILE: ../../../flutter/third_party/skia/src/gpu/graphite/render/SDFTextLCDRenderStep.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/task/DrawTask.cpp @@ -9701,6 +9736,8 @@ FILE: ../../../flutter/third_party/skia/src/gpu/graphite/task/DrawTask.h FILE: ../../../flutter/third_party/skia/src/gpu/graphite/vk/VulkanBackendSemaphore.cpp FILE: ../../../flutter/third_party/skia/src/gpu/graphite/vk/VulkanBackendTexture.cpp FILE: ../../../flutter/third_party/skia/src/gpu/graphite/vk/VulkanTextureInfo.cpp +FILE: ../../../flutter/third_party/skia/src/ports/SkTypeface_proxy.cpp +FILE: ../../../flutter/third_party/skia/src/ports/SkTypeface_proxy.h FILE: ../../../flutter/third_party/skia/src/sksl/SkSLGraphiteModules.cpp FILE: ../../../flutter/third_party/skia/src/sksl/SkSLGraphiteModules.h FILE: ../../../flutter/third_party/skia/src/sksl/SkSLModule.cpp @@ -9757,6 +9794,8 @@ ORIGIN: ../../../flutter/third_party/skia/gm/smallcircles.cpp + ../../../flutter ORIGIN: ../../../flutter/third_party/skia/include/ports/SkFontMgr_android_ndk.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/codec/SkPngCodecBase.cpp + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/codec/SkPngCodecBase.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/encode/SkPngEncoderBase.cpp + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/encode/SkPngEncoderBase.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/gpu/vk/vulkanmemoryallocator/VulkanMemoryAllocatorPriv.h + ../../../flutter/third_party/skia/LICENSE ORIGIN: ../../../flutter/third_party/skia/src/sksl/transform/SkSLTransform.cpp + ../../../flutter/third_party/skia/LICENSE TYPE: LicenseType.bsd @@ -9764,6 +9803,8 @@ FILE: ../../../flutter/third_party/skia/gm/smallcircles.cpp FILE: ../../../flutter/third_party/skia/include/ports/SkFontMgr_android_ndk.h FILE: ../../../flutter/third_party/skia/src/codec/SkPngCodecBase.cpp FILE: ../../../flutter/third_party/skia/src/codec/SkPngCodecBase.h +FILE: ../../../flutter/third_party/skia/src/encode/SkPngEncoderBase.cpp +FILE: ../../../flutter/third_party/skia/src/encode/SkPngEncoderBase.h FILE: ../../../flutter/third_party/skia/src/gpu/vk/vulkanmemoryallocator/VulkanMemoryAllocatorPriv.h FILE: ../../../flutter/third_party/skia/src/sksl/transform/SkSLTransform.cpp ---------------------------------------------------------------------------------------------------- @@ -9801,10 +9842,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: skia ORIGIN: ../../../flutter/third_party/skia/src/ports/SkFontScanner_fontations.cpp + ../../../flutter/third_party/skia/LICENSE -ORIGIN: ../../../flutter/third_party/skia/src/ports/SkFontScanner_fontations.h + ../../../flutter/third_party/skia/LICENSE +ORIGIN: ../../../flutter/third_party/skia/src/ports/SkFontScanner_fontations_priv.h + ../../../flutter/third_party/skia/LICENSE TYPE: LicenseType.bsd FILE: ../../../flutter/third_party/skia/src/ports/SkFontScanner_fontations.cpp -FILE: ../../../flutter/third_party/skia/src/ports/SkFontScanner_fontations.h +FILE: ../../../flutter/third_party/skia/src/ports/SkFontScanner_fontations_priv.h ---------------------------------------------------------------------------------------------------- Copyright 2024 The Android Open Source Project diff --git a/ci/licenses_golden/tool_signature b/ci/licenses_golden/tool_signature index 800d7abe3100f..1e8b05bf95448 100644 --- a/ci/licenses_golden/tool_signature +++ b/ci/licenses_golden/tool_signature @@ -1,2 +1,2 @@ -Signature: e2ff885216f18058fdca914a1cc4149c +Signature: 08cd2e281007e92182d3d540ff50e0ad diff --git a/ci/pubspec.yaml b/ci/pubspec.yaml index b86c37bb84744..540d06b324852 100644 --- a/ci/pubspec.yaml +++ b/ci/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/common/config.gni b/common/config.gni index 0a5a95306c2fd..b2ae1118e2881 100644 --- a/common/config.gni +++ b/common/config.gni @@ -77,14 +77,13 @@ if (is_ios || is_mac) { flutter_cflags_objc = [ "-Werror=overriding-method-mismatch", "-Werror=undeclared-selector", + "-fobjc-arc", ] if (is_mac) { flutter_cflags_objc += [ "-fapplication-extension" ] } flutter_cflags_objcc = flutter_cflags_objc - flutter_cflags_objc_arc = flutter_cflags_objc + [ "-fobjc-arc" ] - flutter_cflags_objcc_arc = flutter_cflags_objc_arc } # A combo of host os name and cpu is used in several locations to diff --git a/common/settings.h b/common/settings.h index 689a0781d5e44..617ac202adb81 100644 --- a/common/settings.h +++ b/common/settings.h @@ -226,10 +226,10 @@ struct Settings { #if FML_OS_ANDROID || FML_OS_IOS || FML_OS_IOS_SIMULATOR // On iOS devices, Impeller is the default with no opt-out and this field is // const. -#if FML_OS_IOS && !FML_OS_IOS_SIMULATOR +#if FML_OS_IOS || FML_OS_IOS_SIMULATOR static constexpr const -#endif // FML_OS_IOS && !FML_OS_IOS_SIMULATOR - bool enable_impeller = true; +#endif // FML_OS_IOS && !FML_OS_IOS_SIMULATOR + bool enable_impeller = true; // NOLINT(readability-identifier-naming) #else bool enable_impeller = false; #endif diff --git a/display_list/BUILD.gn b/display_list/BUILD.gn index 1948cca0887fb..3ed4e721bfc21 100644 --- a/display_list/BUILD.gn +++ b/display_list/BUILD.gn @@ -42,19 +42,60 @@ source_set("display_list") { "dl_paint.cc", "dl_paint.h", "dl_sampling_options.h", + "dl_storage.cc", + "dl_storage.h", "dl_tile_mode.h", "dl_vertices.cc", "dl_vertices.h", + "effects/color_filters/dl_blend_color_filter.cc", + "effects/color_filters/dl_blend_color_filter.h", + "effects/color_filters/dl_linear_to_srgb_gamma_color_filter.cc", + "effects/color_filters/dl_linear_to_srgb_gamma_color_filter.h", + "effects/color_filters/dl_matrix_color_filter.cc", + "effects/color_filters/dl_matrix_color_filter.h", + "effects/color_filters/dl_srgb_to_linear_gamma_color_filter.cc", + "effects/color_filters/dl_srgb_to_linear_gamma_color_filter.h", + "effects/color_sources/dl_conical_gradient_color_source.cc", + "effects/color_sources/dl_conical_gradient_color_source.h", + "effects/color_sources/dl_image_color_source.cc", + "effects/color_sources/dl_image_color_source.h", + "effects/color_sources/dl_linear_gradient_color_source.cc", + "effects/color_sources/dl_linear_gradient_color_source.h", + "effects/color_sources/dl_radial_gradient_color_source.cc", + "effects/color_sources/dl_radial_gradient_color_source.h", + "effects/color_sources/dl_runtime_effect_color_source.cc", + "effects/color_sources/dl_runtime_effect_color_source.h", + "effects/color_sources/dl_sweep_gradient_color_source.cc", + "effects/color_sources/dl_sweep_gradient_color_source.h", "effects/dl_color_filter.cc", "effects/dl_color_filter.h", + "effects/dl_color_filters.h", "effects/dl_color_source.cc", "effects/dl_color_source.h", + "effects/dl_color_sources.h", "effects/dl_image_filter.cc", "effects/dl_image_filter.h", + "effects/dl_image_filters.h", "effects/dl_mask_filter.cc", "effects/dl_mask_filter.h", "effects/dl_runtime_effect.cc", "effects/dl_runtime_effect.h", + "effects/image_filters/dl_blur_image_filter.cc", + "effects/image_filters/dl_blur_image_filter.h", + "effects/image_filters/dl_color_filter_image_filter.cc", + "effects/image_filters/dl_color_filter_image_filter.h", + "effects/image_filters/dl_compose_image_filter.cc", + "effects/image_filters/dl_compose_image_filter.h", + "effects/image_filters/dl_dilate_image_filter.cc", + "effects/image_filters/dl_dilate_image_filter.h", + "effects/image_filters/dl_erode_image_filter.cc", + "effects/image_filters/dl_erode_image_filter.h", + "effects/image_filters/dl_local_matrix_image_filter.cc", + "effects/image_filters/dl_local_matrix_image_filter.h", + "effects/image_filters/dl_matrix_image_filter.cc", + "effects/image_filters/dl_matrix_image_filter.h", + "effects/image_filters/dl_runtime_effect_image_filter.cc", + "effects/image_filters/dl_runtime_effect_image_filter.h", "geometry/dl_geometry_types.h", "geometry/dl_path.cc", "geometry/dl_path.h", @@ -111,6 +152,7 @@ if (enable_unittests) { "display_list_unittests.cc", "dl_color_unittests.cc", "dl_paint_unittests.cc", + "dl_storage_unittests.cc", "dl_vertices_unittests.cc", "effects/dl_color_filter_unittests.cc", "effects/dl_color_source_unittests.cc", diff --git a/display_list/benchmarking/dl_transform_benchmarks.cc b/display_list/benchmarking/dl_transform_benchmarks.cc index e6581461be4c3..201efaf4846b3 100644 --- a/display_list/benchmarking/dl_transform_benchmarks.cc +++ b/display_list/benchmarking/dl_transform_benchmarks.cc @@ -457,7 +457,7 @@ class ImpellerMatrixAdapter : public TransformAdapter { bool InvertAndCheck(const TestTransform& transform, TestTransform& result) const override { result.impeller_matrix = transform.impeller_matrix.Invert(); - return transform.impeller_matrix.GetDeterminant() != 0.0f; + return transform.impeller_matrix.IsInvertible(); } }; diff --git a/display_list/display_list.cc b/display_list/display_list.cc index 1d30ebd2813a3..fc823dee83209 100644 --- a/display_list/display_list.cc +++ b/display_list/display_list.cc @@ -15,8 +15,7 @@ const SaveLayerOptions SaveLayerOptions::kWithAttributes = kNoAttributes.with_renders_with_attributes(); DisplayList::DisplayList() - : byte_count_(0), - op_count_(0), + : op_count_(0), nested_byte_count_(0), nested_op_count_(0), total_depth_(0), @@ -27,25 +26,13 @@ DisplayList::DisplayList() modifies_transparent_black_(false), root_has_backdrop_filter_(false), root_is_unbounded_(false), - max_root_blend_mode_(DlBlendMode::kClear) {} - -// Eventually we should rework DisplayListBuilder to compute these and -// deliver the vector alongside the storage. -static std::vector MakeOffsets(const DisplayListStorage& storage, - size_t byte_count) { - std::vector offsets; - const uint8_t* start = storage.get(); - const uint8_t* end = start + byte_count; - const uint8_t* ptr = start; - while (ptr < end) { - offsets.push_back(ptr - start); - ptr += reinterpret_cast(ptr)->size; - } - return offsets; + max_root_blend_mode_(DlBlendMode::kClear) { + FML_DCHECK(offsets_.size() == 0u); + FML_DCHECK(storage_.size() == 0u); } DisplayList::DisplayList(DisplayListStorage&& storage, - size_t byte_count, + std::vector&& offsets, uint32_t op_count, size_t nested_byte_count, uint32_t nested_op_count, @@ -59,8 +46,7 @@ DisplayList::DisplayList(DisplayListStorage&& storage, bool root_is_unbounded, sk_sp rtree) : storage_(std::move(storage)), - offsets_(MakeOffsets(storage_, byte_count)), - byte_count_(byte_count), + offsets_(std::move(offsets)), op_count_(op_count), nested_byte_count_(nested_byte_count), nested_op_count_(nested_op_count), @@ -73,11 +59,12 @@ DisplayList::DisplayList(DisplayListStorage&& storage, root_has_backdrop_filter_(root_has_backdrop_filter), root_is_unbounded_(root_is_unbounded), max_root_blend_mode_(max_root_blend_mode), - rtree_(std::move(rtree)) {} + rtree_(std::move(rtree)) { + FML_DCHECK(storage_.capacity() == storage_.size()); +} DisplayList::~DisplayList() { - const uint8_t* ptr = storage_.get(); - DisposeOps(ptr, ptr + byte_count_); + DisposeOps(storage_, offsets_); } uint32_t DisplayList::next_unique_id() { @@ -132,7 +119,7 @@ void DisplayList::RTreeResultsToIndexVector( return; } } - const uint8_t* ptr = storage_.get() + offsets_[index]; + const uint8_t* ptr = storage_.base() + offsets_[index]; const DLOp* op = reinterpret_cast(ptr); switch (GetOpCategory(op->type)) { case DisplayListOpCategory::kAttribute: @@ -193,7 +180,7 @@ void DisplayList::RTreeResultsToIndexVector( } void DisplayList::Dispatch(DlOpReceiver& receiver) const { - const uint8_t* base = storage_.get(); + const uint8_t* base = storage_.base(); for (size_t offset : offsets_) { DispatchOneOp(receiver, base + offset); } @@ -213,7 +200,7 @@ void DisplayList::Dispatch(DlOpReceiver& receiver, Dispatch(receiver); } else { auto op_indices = GetCulledIndices(cull_rect); - const uint8_t* base = storage_.get(); + const uint8_t* base = storage_.base(); for (DlIndex index : op_indices) { DispatchOneOp(receiver, base + offsets_[index]); } @@ -240,11 +227,14 @@ void DisplayList::DispatchOneOp(DlOpReceiver& receiver, } } -void DisplayList::DisposeOps(const uint8_t* ptr, const uint8_t* end) { - while (ptr < end) { - auto op = reinterpret_cast(ptr); - ptr += op->size; - FML_DCHECK(ptr <= end); +void DisplayList::DisposeOps(const DisplayListStorage& storage, + const std::vector& offsets) { + const uint8_t* base = storage.base(); + if (!base) { + return; + } + for (size_t offset : offsets) { + auto op = reinterpret_cast(base + offset); switch (op->type) { #define DL_OP_DISPOSE(name) \ case DisplayListOpType::k##name: \ @@ -362,10 +352,9 @@ DisplayListOpType DisplayList::GetOpType(DlIndex index) const { } size_t offset = offsets_[index]; - FML_DCHECK(offset < byte_count_); - auto ptr = storage_.get() + offset; + FML_DCHECK(offset < storage_.size()); + auto ptr = storage_.base() + offset; auto op = reinterpret_cast(ptr); - FML_DCHECK(ptr + op->size <= storage_.get() + byte_count_); return op->type; } @@ -399,34 +388,32 @@ bool DisplayList::Dispatch(DlOpReceiver& receiver, DlIndex index) const { } size_t offset = offsets_[index]; - FML_DCHECK(offset < byte_count_); - auto ptr = storage_.get() + offset; - FML_DCHECK(offset + reinterpret_cast(ptr)->size <= byte_count_); + FML_DCHECK(offset < storage_.size()); + auto ptr = storage_.base() + offset; DispatchOneOp(receiver, ptr); return true; } -static bool CompareOps(const uint8_t* ptrA, - const uint8_t* endA, - const uint8_t* ptrB, - const uint8_t* endB) { +static bool CompareOps(const DisplayListStorage& storageA, + const std::vector& offsetsA, + const DisplayListStorage& storageB, + const std::vector& offsetsB) { + const uint8_t* base_a = storageA.base(); + const uint8_t* base_b = storageB.base(); // These conditions are checked by the caller... - FML_DCHECK((endA - ptrA) == (endB - ptrB)); - FML_DCHECK(ptrA != ptrB); - const uint8_t* bulk_start_a = ptrA; - const uint8_t* bulk_start_b = ptrB; - while (ptrA < endA && ptrB < endB) { - auto opA = reinterpret_cast(ptrA); - auto opB = reinterpret_cast(ptrB); - if (opA->type != opB->type || opA->size != opB->size) { + FML_DCHECK(offsetsA.size() == offsetsB.size()); + FML_DCHECK(base_a != base_b); + size_t bulk_start = 0u; + for (size_t i = 0; i < offsetsA.size(); i++) { + size_t offset = offsetsA[i]; + FML_DCHECK(offsetsB[i] == offset); + auto opA = reinterpret_cast(base_a + offset); + auto opB = reinterpret_cast(base_b + offset); + if (opA->type != opB->type) { return false; } - ptrA += opA->size; - ptrB += opB->size; - FML_DCHECK(ptrA <= endA); - FML_DCHECK(ptrB <= endB); DisplayListCompare result; switch (opA->type) { #define DL_OP_EQUALS(name) \ @@ -451,23 +438,23 @@ static bool CompareOps(const uint8_t* ptrA, case DisplayListCompare::kEqual: // Check if we have a backlog of bytes to bulk compare and then // reset the bulk compare pointers to the address following this op - auto bulk_bytes = reinterpret_cast(opA) - bulk_start_a; - if (bulk_bytes > 0) { - if (memcmp(bulk_start_a, bulk_start_b, bulk_bytes) != 0) { + if (bulk_start < offset) { + const uint8_t* bulk_start_a = base_a + bulk_start; + const uint8_t* bulk_start_b = base_b + bulk_start; + if (memcmp(bulk_start_a, bulk_start_b, offset - bulk_start) != 0) { return false; } } - bulk_start_a = ptrA; - bulk_start_b = ptrB; + bulk_start = + i + 1 < offsetsA.size() ? offsetsA[i + 1] : storageA.size(); break; } } - if (ptrA != endA || ptrB != endB) { - return false; - } - if (bulk_start_a < ptrA) { + if (bulk_start < storageA.size()) { // Perform a final bulk compare if we have remaining bytes waiting - if (memcmp(bulk_start_a, bulk_start_b, ptrA - bulk_start_a) != 0) { + const uint8_t* bulk_start_a = base_a + bulk_start; + const uint8_t* bulk_start_b = base_b + bulk_start; + if (memcmp(bulk_start_a, bulk_start_b, storageA.size() - bulk_start) != 0) { return false; } } @@ -478,15 +465,15 @@ bool DisplayList::Equals(const DisplayList* other) const { if (this == other) { return true; } - if (byte_count_ != other->byte_count_ || op_count_ != other->op_count_) { + if (offsets_.size() != other->offsets_.size() || + storage_.size() != other->storage_.size() || + op_count_ != other->op_count_) { return false; } - const uint8_t* ptr = storage_.get(); - const uint8_t* o_ptr = other->storage_.get(); - if (ptr == o_ptr) { + if (storage_.base() == other->storage_.base()) { return true; } - return CompareOps(ptr, ptr + byte_count_, o_ptr, o_ptr + other->byte_count_); + return CompareOps(storage_, offsets_, other->storage_, other->offsets_); } } // namespace flutter diff --git a/display_list/display_list.h b/display_list/display_list.h index 339c291c2f684..0c13a437df60e 100644 --- a/display_list/display_list.h +++ b/display_list/display_list.h @@ -5,14 +5,10 @@ #ifndef FLUTTER_DISPLAY_LIST_DISPLAY_LIST_H_ #define FLUTTER_DISPLAY_LIST_DISPLAY_LIST_H_ -#include -#include - #include "flutter/display_list/dl_blend_mode.h" -#include "flutter/display_list/dl_sampling_options.h" +#include "flutter/display_list/dl_storage.h" #include "flutter/display_list/geometry/dl_geometry_types.h" #include "flutter/display_list/geometry/dl_rtree.h" -#include "flutter/fml/logging.h" // The Flutter DisplayList mechanism encapsulates a persistent sequence of // rendering operations. @@ -263,28 +259,6 @@ class SaveLayerOptions { }; }; -// Manages a buffer allocated with malloc. -class DisplayListStorage { - public: - DisplayListStorage() = default; - DisplayListStorage(DisplayListStorage&&) = default; - - uint8_t* get() { return ptr_.get(); } - - const uint8_t* get() const { return ptr_.get(); } - - void realloc(size_t count) { - ptr_.reset(static_cast(std::realloc(ptr_.release(), count))); - FML_CHECK(ptr_); - } - - private: - struct FreeDeleter { - void operator()(uint8_t* p) { std::free(p); } - }; - std::unique_ptr ptr_; -}; - using DlIndex = uint32_t; // The base class that contains a sequence of rendering operations @@ -304,7 +278,7 @@ class DisplayList : public SkRefCnt { // but nested ops are only included if requested. The defaults used // here for these accessors follow that pattern. size_t bytes(bool nested = true) const { - return sizeof(DisplayList) + byte_count_ + + return sizeof(DisplayList) + storage_.size() + (nested ? nested_byte_count_ : 0); } @@ -530,7 +504,7 @@ class DisplayList : public SkRefCnt { private: DisplayList(DisplayListStorage&& ptr, - size_t byte_count, + std::vector&& offsets, uint32_t op_count, size_t nested_byte_count, uint32_t nested_op_count, @@ -546,13 +520,13 @@ class DisplayList : public SkRefCnt { static uint32_t next_unique_id(); - static void DisposeOps(const uint8_t* ptr, const uint8_t* end); + static void DisposeOps(const DisplayListStorage& storage, + const std::vector& offsets); const DisplayListStorage storage_; const std::vector offsets_; - const size_t byte_count_; - const uint32_t op_count_; + const uint32_t op_count_; const size_t nested_byte_count_; const uint32_t nested_op_count_; diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index d43a2334dc928..cfd1f6b66bbc5 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -12,6 +12,7 @@ #include "flutter/display_list/dl_blend_mode.h" #include "flutter/display_list/dl_builder.h" #include "flutter/display_list/dl_paint.h" +#include "flutter/display_list/effects/dl_image_filters.h" #include "flutter/display_list/geometry/dl_rtree.h" #include "flutter/display_list/skia/dl_sk_dispatcher.h" #include "flutter/display_list/testing/dl_test_snippets.h" @@ -108,7 +109,7 @@ class DisplayListTestBase : public BaseT { static void check_defaults( DisplayListBuilder& builder, - const SkRect& cull_rect = DisplayListBuilder::kMaxCullRect) { + const DlRect& cull_rect = DisplayListBuilder::kMaxCullRect) { DlPaint builder_paint = DisplayListBuilderTestingAttributes(builder); DlPaint defaults; @@ -131,8 +132,8 @@ class DisplayListTestBase : public BaseT { EXPECT_EQ(builder.GetTransform(), SkMatrix()); EXPECT_EQ(builder.GetTransformFullPerspective(), SkM44()); - EXPECT_EQ(builder.GetLocalClipBounds(), cull_rect); - EXPECT_EQ(builder.GetDestinationClipBounds(), cull_rect); + EXPECT_EQ(builder.GetLocalClipCoverage(), cull_rect); + EXPECT_EQ(builder.GetDestinationClipCoverage(), cull_rect); EXPECT_EQ(builder.GetSaveCount(), 1); } @@ -253,6 +254,25 @@ TEST_F(DisplayListTest, EmptyRebuild) { ASSERT_TRUE(dl2->Equals(dl3)); } +TEST_F(DisplayListTest, NopReusedBuildIsReallyEmpty) { + DisplayListBuilder builder; + builder.DrawRect(DlRect::MakeLTRB(0.0f, 0.0f, 10.0f, 10.0f), DlPaint()); + + { + auto dl1 = builder.Build(); + EXPECT_EQ(dl1->op_count(), 1u); + EXPECT_GT(dl1->bytes(), sizeof(DisplayList)); + EXPECT_EQ(dl1->GetBounds(), DlRect::MakeLTRB(0.0f, 0.0f, 10.0f, 10.0f)); + } + + { + auto dl2 = builder.Build(); + EXPECT_EQ(dl2->op_count(), 0u); + EXPECT_EQ(dl2->bytes(), sizeof(DisplayList)); + EXPECT_EQ(dl2->GetBounds(), DlRect()); + } +} + TEST_F(DisplayListTest, GeneralReceiverInitialValues) { DisplayListGeneralReceiver receiver; @@ -327,7 +347,7 @@ TEST_F(DisplayListTest, BuilderCanBeReused) { } TEST_F(DisplayListTest, SaveRestoreRestoresTransform) { - SkRect cull_rect = SkRect::MakeLTRB(-10.0f, -10.0f, 500.0f, 500.0f); + DlRect cull_rect = DlRect::MakeLTRB(-10.0f, -10.0f, 500.0f, 500.0f); DisplayListBuilder builder(cull_rect); builder.Save(); @@ -376,7 +396,7 @@ TEST_F(DisplayListTest, SaveRestoreRestoresTransform) { } TEST_F(DisplayListTest, BuildRestoresTransform) { - SkRect cull_rect = SkRect::MakeLTRB(-10.0f, -10.0f, 500.0f, 500.0f); + DlRect cull_rect = DlRect::MakeLTRB(-10.0f, -10.0f, 500.0f, 500.0f); DisplayListBuilder builder(cull_rect); builder.Translate(10.0f, 10.0f); @@ -417,7 +437,7 @@ TEST_F(DisplayListTest, BuildRestoresTransform) { } TEST_F(DisplayListTest, SaveRestoreRestoresClip) { - SkRect cull_rect = SkRect::MakeLTRB(-10.0f, -10.0f, 500.0f, 500.0f); + DlRect cull_rect = DlRect::MakeLTRB(-10.0f, -10.0f, 500.0f, 500.0f); DisplayListBuilder builder(cull_rect); builder.Save(); @@ -437,7 +457,7 @@ TEST_F(DisplayListTest, SaveRestoreRestoresClip) { } TEST_F(DisplayListTest, BuildRestoresClip) { - SkRect cull_rect = SkRect::MakeLTRB(-10.0f, -10.0f, 500.0f, 500.0f); + DlRect cull_rect = DlRect::MakeLTRB(-10.0f, -10.0f, 500.0f, 500.0f); DisplayListBuilder builder(cull_rect); builder.ClipRect(SkRect{0.0f, 0.0f, 10.0f, 10.0f}); @@ -454,7 +474,7 @@ TEST_F(DisplayListTest, BuildRestoresClip) { } TEST_F(DisplayListTest, BuildRestoresAttributes) { - SkRect cull_rect = SkRect::MakeLTRB(-10.0f, -10.0f, 500.0f, 500.0f); + DlRect cull_rect = DlRect::MakeLTRB(-10.0f, -10.0f, 500.0f, 500.0f); DisplayListBuilder builder(cull_rect); DlOpReceiver& receiver = ToReceiver(builder); @@ -494,11 +514,11 @@ TEST_F(DisplayListTest, BuildRestoresAttributes) { builder.Build(); check_defaults(builder, cull_rect); - receiver.setColorSource(&kTestSource1); + receiver.setColorSource(kTestSource1.get()); builder.Build(); check_defaults(builder, cull_rect); - receiver.setColorFilter(&kTestMatrixColorFilter1); + receiver.setColorFilter(kTestMatrixColorFilter1.get()); builder.Build(); check_defaults(builder, cull_rect); @@ -576,7 +596,7 @@ TEST_F(DisplayListTest, UnclippedSaveLayerContentAccountsForFilter) { SkRect cull_rect = SkRect::MakeLTRB(0.0f, 0.0f, 300.0f, 300.0f); SkRect clip_rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); SkRect draw_rect = SkRect::MakeLTRB(50.0f, 140.0f, 101.0f, 160.0f); - auto filter = DlBlurImageFilter::Make(10.0f, 10.0f, DlTileMode::kDecal); + auto filter = DlImageFilter::MakeBlur(10.0f, 10.0f, DlTileMode::kDecal); DlPaint layer_paint = DlPaint().setImageFilter(filter); ASSERT_TRUE(clip_rect.intersects(draw_rect)); @@ -609,7 +629,7 @@ TEST_F(DisplayListTest, ClippedSaveLayerContentAccountsForFilter) { SkRect cull_rect = SkRect::MakeLTRB(0.0f, 0.0f, 300.0f, 300.0f); SkRect clip_rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); SkRect draw_rect = SkRect::MakeLTRB(50.0f, 140.0f, 99.0f, 160.0f); - auto filter = DlBlurImageFilter::Make(10.0f, 10.0f, DlTileMode::kDecal); + auto filter = DlImageFilter::MakeBlur(10.0f, 10.0f, DlTileMode::kDecal); DlPaint layer_paint = DlPaint().setImageFilter(filter); ASSERT_FALSE(clip_rect.intersects(draw_rect)); @@ -639,22 +659,22 @@ TEST_F(DisplayListTest, ClippedSaveLayerContentAccountsForFilter) { } TEST_F(DisplayListTest, OOBSaveLayerContentCulledWithBlurFilter) { - SkRect cull_rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); - SkRect draw_rect = SkRect::MakeLTRB(25.0f, 25.0f, 99.0f, 75.0f); - auto filter = DlBlurImageFilter::Make(10.0f, 10.0f, DlTileMode::kDecal); + DlRect cull_rect = DlRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + DlRect draw_rect = DlRect::MakeLTRB(25.0f, 25.0f, 99.0f, 75.0f); + auto filter = DlImageFilter::MakeBlur(10.0f, 10.0f, DlTileMode::kDecal); DlPaint layer_paint = DlPaint().setImageFilter(filter); // We want a draw rect that is outside the layer bounds even though its // filtered output might be inside. The drawn rect should be culled by // the expectations of the layer bounds even though it is close enough // to be visible due to filtering. - ASSERT_FALSE(cull_rect.intersects(draw_rect)); - SkRect mapped_rect; + ASSERT_FALSE(cull_rect.IntersectsWithRect(draw_rect)); + DlRect mapped_rect; ASSERT_TRUE(filter->map_local_bounds(draw_rect, mapped_rect)); - ASSERT_TRUE(mapped_rect.intersects(cull_rect)); + ASSERT_TRUE(mapped_rect.IntersectsWithRect(cull_rect)); DisplayListBuilder builder; - builder.SaveLayer(&cull_rect, &layer_paint); + builder.SaveLayer(cull_rect, &layer_paint); { // builder.DrawRect(draw_rect, DlPaint()); } @@ -668,23 +688,23 @@ TEST_F(DisplayListTest, OOBSaveLayerContentCulledWithBlurFilter) { } TEST_F(DisplayListTest, OOBSaveLayerContentCulledWithMatrixFilter) { - SkRect cull_rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); - SkRect draw_rect = SkRect::MakeLTRB(25.0f, 125.0f, 75.0f, 175.0f); - auto filter = DlMatrixImageFilter::Make(SkMatrix::Translate(100.0f, 0.0f), - DlImageSampling::kLinear); + DlRect cull_rect = DlRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + DlRect draw_rect = DlRect::MakeLTRB(25.0f, 125.0f, 75.0f, 175.0f); + auto filter = DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({100.0f, 0.0f}), DlImageSampling::kLinear); DlPaint layer_paint = DlPaint().setImageFilter(filter); // We want a draw rect that is outside the layer bounds even though its // filtered output might be inside. The drawn rect should be culled by // the expectations of the layer bounds even though it is close enough // to be visible due to filtering. - ASSERT_FALSE(cull_rect.intersects(draw_rect)); - SkRect mapped_rect; + ASSERT_FALSE(cull_rect.IntersectsWithRect(draw_rect)); + DlRect mapped_rect; ASSERT_TRUE(filter->map_local_bounds(draw_rect, mapped_rect)); - ASSERT_TRUE(mapped_rect.intersects(cull_rect)); + ASSERT_TRUE(mapped_rect.IntersectsWithRect(cull_rect)); DisplayListBuilder builder; - builder.SaveLayer(&cull_rect, &layer_paint); + builder.SaveLayer(cull_rect, &layer_paint); { // builder.DrawRect(draw_rect, DlPaint()); } @@ -920,7 +940,7 @@ TEST_F(DisplayListTest, DisplayListSaveLayerBoundsWithAlphaFilter) { 0, 0, 0, 1, 0, }; // clang-format on - DlMatrixColorFilter base_color_filter(color_matrix); + auto base_color_filter = DlColorFilter::MakeMatrix(color_matrix); // clang-format off const float alpha_matrix[] = { 0, 0, 0, 0, 0, @@ -929,7 +949,7 @@ TEST_F(DisplayListTest, DisplayListSaveLayerBoundsWithAlphaFilter) { 0, 0, 0, 0, 1, }; // clang-format on - DlMatrixColorFilter alpha_color_filter(alpha_matrix); + auto alpha_color_filter = DlColorFilter::MakeMatrix(alpha_matrix); sk_sp sk_alpha_color_filter = SkColorFilters::Matrix(alpha_matrix); @@ -947,7 +967,7 @@ TEST_F(DisplayListTest, DisplayListSaveLayerBoundsWithAlphaFilter) { // Now checking that a normal color filter still produces rect bounds DisplayListBuilder builder(build_bounds); DlPaint save_paint; - save_paint.setColorFilter(&base_color_filter); + save_paint.setColorFilter(base_color_filter); builder.SaveLayer(&save_bounds, &save_paint); builder.DrawRect(rect, DlPaint()); builder.Restore(); @@ -979,7 +999,7 @@ TEST_F(DisplayListTest, DisplayListSaveLayerBoundsWithAlphaFilter) { // save layer that modifies an unbounded region DisplayListBuilder builder(build_bounds); DlPaint save_paint; - save_paint.setColorFilter(&alpha_color_filter); + save_paint.setColorFilter(alpha_color_filter); builder.SaveLayer(&save_bounds, &save_paint); builder.DrawRect(rect, DlPaint()); builder.Restore(); @@ -992,7 +1012,7 @@ TEST_F(DisplayListTest, DisplayListSaveLayerBoundsWithAlphaFilter) { // to the behavior in the previous example DisplayListBuilder builder(build_bounds); DlPaint save_paint; - save_paint.setColorFilter(&alpha_color_filter); + save_paint.setColorFilter(alpha_color_filter); builder.SaveLayer(nullptr, &save_paint); builder.DrawRect(rect, DlPaint()); builder.Restore(); @@ -1347,7 +1367,7 @@ TEST_F(DisplayListTest, SaveLayerFalseWithSrcBlendSupportsGroupOpacity) { DisplayListBuilder builder; // This empty draw rect will not actually be inserted into the stream, // but the Src blend mode will be synchronized as an attribute. The - // saveLayer following it should not use that attribute to base its + // SaveLayer following it should not use that attribute to base its // decisions about group opacity and the draw rect after that comes // with its own compatible blend mode. builder.DrawRect(SkRect{0, 0, 0, 0}, @@ -1396,7 +1416,7 @@ TEST_F(DisplayListTest, SaveLayerBoundsSnapshotsImageFilter) { DlPaint save_paint; builder.SaveLayer(nullptr, &save_paint); builder.DrawRect(SkRect{50, 50, 100, 100}, DlPaint()); - // This image filter should be ignored since it was not set before saveLayer + // This image filter should be ignored since it was not set before SaveLayer // And the rect drawn with it will not contribute any more area to the bounds DlPaint draw_paint; draw_paint.setImageFilter(&kTestBlurImageFilter1); @@ -1648,7 +1668,7 @@ TEST_F(DisplayListTest, SaveLayerColorFilterDoesNotInheritOpacity) { DisplayListBuilder builder; DlPaint save_paint; save_paint.setColor(DlColor(SkColorSetARGB(127, 255, 255, 255))); - save_paint.setColorFilter(&kTestMatrixColorFilter1); + save_paint.setColorFilter(kTestMatrixColorFilter1); builder.SaveLayer(nullptr, &save_paint); builder.DrawRect(SkRect{10, 10, 20, 20}, DlPaint()); builder.Restore(); @@ -1700,7 +1720,7 @@ TEST_F(DisplayListTest, SaveLayerColorFilterOnChildDoesNotInheritOpacity) { save_paint.setColor(DlColor(SkColorSetARGB(127, 255, 255, 255))); builder.SaveLayer(nullptr, &save_paint); DlPaint draw_paint = save_paint; - draw_paint.setColorFilter(&kTestMatrixColorFilter1); + draw_paint.setColorFilter(kTestMatrixColorFilter1); builder.DrawRect(SkRect{10, 10, 20, 20}, draw_paint); builder.Restore(); @@ -2490,7 +2510,7 @@ TEST_F(DisplayListTest, RTreeOfSaveLayerFilterScene) { builder.DrawRect(SkRect{10, 10, 20, 20}, default_paint); builder.SaveLayer(nullptr, &filter_paint); // the following rectangle will be expanded to 50,50,60,60 - // by the saveLayer filter during the restore operation + // by the SaveLayer filter during the restore operation builder.DrawRect(SkRect{53, 53, 57, 57}, default_paint); builder.Restore(); auto display_list = builder.Build(); @@ -2651,7 +2671,7 @@ TEST_F(DisplayListTest, RemoveUnnecessarySaveRestorePairsInSetPaint) { 0, 0, 0, 0, 1, }; // clang-format on - DlMatrixColorFilter alpha_color_filter(alpha_matrix); + auto alpha_color_filter = DlColorFilter::MakeMatrix(alpha_matrix); // Making sure hiding a problematic ColorFilter as an ImageFilter // will generate the same behavior as setting it as a ColorFilter @@ -3252,7 +3272,7 @@ TEST_F(DisplayListTest, RTreeOfClippedSaveLayerFilterScene) { builder.ClipRect(SkRect{50, 50, 60, 60}, ClipOp::kIntersect, false); builder.SaveLayer(nullptr, &filter_paint); // the following rectangle will be expanded to 23,23,87,87 - // by the saveLayer filter during the restore operation + // by the SaveLayer filter during the restore operation // but it will then be clipped to 50,50,60,60 builder.DrawRect(SkRect{53, 53, 57, 57}, default_paint); builder.Restore(); @@ -3742,7 +3762,7 @@ TEST_F(DisplayListTest, SaveLayerBoundsComputationOfMaskBlurredRect) { TEST_F(DisplayListTest, SaveLayerBoundsComputationOfImageBlurredRect) { SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); DlPaint draw_paint; - auto image_filter = DlBlurImageFilter::Make(2.0f, 3.0f, DlTileMode::kDecal); + auto image_filter = DlImageFilter::MakeBlur(2.0f, 3.0f, DlTileMode::kDecal); draw_paint.setImageFilter(image_filter); DisplayListBuilder builder; @@ -3842,7 +3862,7 @@ TEST_F(DisplayListTest, TransformResetSaveLayerBoundsComputationOfSimpleRect) { builder.SaveLayer(nullptr, nullptr); builder.TransformReset(); builder.Scale(20.0f, 20.0f); - // Net local transform for saveLayer is Scale(2, 2) + // Net local transform for SaveLayer is Scale(2, 2) { // builder.DrawRect(rect, DlPaint()); } @@ -3937,7 +3957,7 @@ TEST_F(DisplayListTest, FloodingSaveLayerBoundsComputationOfSimpleRect) { SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); DlPaint save_paint; auto color_filter = - DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kSrc); + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kSrc); ASSERT_TRUE(color_filter->modifies_transparent_black()); save_paint.setColorFilter(color_filter); SkRect clip_rect = rect.makeOutset(100.0f, 100.0f); @@ -3963,7 +3983,7 @@ TEST_F(DisplayListTest, NestedFloodingSaveLayerBoundsComputationOfSimpleRect) { SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); DlPaint save_paint; auto color_filter = - DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kSrc); + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kSrc); ASSERT_TRUE(color_filter->modifies_transparent_black()); save_paint.setColorFilter(color_filter); SkRect clip_rect = rect.makeOutset(100.0f, 100.0f); @@ -3996,9 +4016,9 @@ TEST_F(DisplayListTest, SaveLayerBoundsComputationOfFloodingImageFilter) { SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); DlPaint draw_paint; auto color_filter = - DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kSrc); + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kSrc); ASSERT_TRUE(color_filter->modifies_transparent_black()); - auto image_filter = DlColorFilterImageFilter::Make(color_filter); + auto image_filter = DlImageFilter::MakeColorFilter(color_filter); draw_paint.setImageFilter(image_filter); SkRect clip_rect = rect.makeOutset(100.0f, 100.0f); ASSERT_NE(clip_rect, rect); @@ -4023,7 +4043,7 @@ TEST_F(DisplayListTest, SaveLayerBoundsComputationOfFloodingColorFilter) { SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); DlPaint draw_paint; auto color_filter = - DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kSrc); + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kSrc); ASSERT_TRUE(color_filter->modifies_transparent_black()); draw_paint.setColorFilter(color_filter); SkRect clip_rect = rect.makeOutset(100.0f, 100.0f); @@ -4213,8 +4233,8 @@ TEST_F(DisplayListTest, FloodingFilteredLayerPushesRestoreOpIndex) { 0.5f, 0.0f, 0.0f, 0.0f, 0.5f }; // clang-format on - auto color_filter = DlMatrixColorFilter::Make(matrix); - save_paint.setImageFilter(DlColorFilterImageFilter::Make(color_filter)); + auto color_filter = DlColorFilter::MakeMatrix(matrix); + save_paint.setImageFilter(DlImageFilter::MakeColorFilter(color_filter)); builder.SaveLayer(nullptr, &save_paint); int save_layer_id = DisplayListBuilderTestingLastOpIndex(builder); @@ -4238,8 +4258,9 @@ TEST_F(DisplayListTest, TransformingFilterSaveLayerSimpleContentBounds) { builder.ClipRect(SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f)); DlPaint save_paint; - auto image_filter = DlMatrixImageFilter::Make( - SkMatrix::Translate(100.0f, 100.0f), DlImageSampling::kNearestNeighbor); + auto image_filter = + DlImageFilter::MakeMatrix(DlMatrix::MakeTranslation({100.0f, 100.0f}), + DlImageSampling::kNearestNeighbor); save_paint.setImageFilter(image_filter); builder.SaveLayer(nullptr, &save_paint); @@ -4256,8 +4277,9 @@ TEST_F(DisplayListTest, TransformingFilterSaveLayerFloodedContentBounds) { builder.ClipRect(SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f)); DlPaint save_paint; - auto image_filter = DlMatrixImageFilter::Make( - SkMatrix::Translate(100.0f, 100.0f), DlImageSampling::kNearestNeighbor); + auto image_filter = + DlImageFilter::MakeMatrix(DlMatrix::MakeTranslation({100.0f, 100.0f}), + DlImageSampling::kNearestNeighbor); save_paint.setImageFilter(image_filter); builder.SaveLayer(nullptr, &save_paint); @@ -4429,7 +4451,7 @@ TEST_F(DisplayListTest, MaxBlendModeInsideComplexSaveLayers) { builder.Restore(); // Double check that kModulate is the max blend mode for the first - // saveLayer operations + // SaveLayer operations auto expect = std::max(DlBlendMode::kModulate, DlBlendMode::kSrc); ASSERT_EQ(expect, DlBlendMode::kModulate); @@ -4465,8 +4487,8 @@ TEST_F(DisplayListTest, BackdropDetectionSimpleSaveLayer) { auto dl = builder.Build(); EXPECT_TRUE(dl->root_has_backdrop_filter()); - // The saveLayer itself, though, does not have the contains backdrop - // flag set because its content does not contain a saveLayer with backdrop + // The SaveLayer itself, though, does not have the contains backdrop + // flag set because its content does not contain a SaveLayer with backdrop SAVE_LAYER_EXPECTOR(expector); expector.addExpectation( SaveLayerOptions::kNoAttributes.with_can_distribute_opacity()); @@ -5058,7 +5080,7 @@ TEST_F(DisplayListTest, RecordLargeVertices) { auto vertices = DlVertices::Make(DlVertexMode::kTriangleStrip, vertex_count, points.data(), points.data(), colors.data()); ASSERT_GT(vertices->size(), 1u << 24); - auto backdrop = DlBlurImageFilter::Make(5.0f, 5.0f, DlTileMode::kDecal); + auto backdrop = DlImageFilter::MakeBlur(5.0f, 5.0f, DlTileMode::kDecal); for (int i = 0; i < 1000; i++) { DisplayListBuilder builder; @@ -5678,9 +5700,9 @@ TEST_F(DisplayListTest, BoundedRenderOpsDoNotReportUnbounded) { } TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) { - static const SkRect root_cull = SkRect::MakeLTRB(100, 100, 200, 200); - static const SkRect clip_rect = SkRect::MakeLTRB(120, 120, 180, 180); - static const SkRect draw_rect = SkRect::MakeLTRB(110, 110, 190, 190); + static const DlRect root_cull = DlRect::MakeLTRB(100, 100, 200, 200); + static const DlRect clip_rect = DlRect::MakeLTRB(120, 120, 180, 180); + static const DlRect draw_rect = DlRect::MakeLTRB(110, 110, 190, 190); using Renderer = const std::function; auto test_unbounded = [](const std::string& label, // @@ -5691,7 +5713,7 @@ TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) { renderer(builder); auto display_list = builder.Build(); - EXPECT_EQ(display_list->bounds(), root_cull) << label; + EXPECT_EQ(display_list->GetBounds(), root_cull) << label; EXPECT_TRUE(display_list->root_is_unbounded()) << label; } @@ -5701,7 +5723,7 @@ TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) { renderer(builder); auto display_list = builder.Build(); - EXPECT_EQ(display_list->bounds(), clip_rect) << label; + EXPECT_EQ(display_list->GetBounds(), clip_rect) << label; EXPECT_FALSE(display_list->root_is_unbounded()) << label; } @@ -5712,7 +5734,7 @@ TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) { builder.Restore(); auto display_list = builder.Build(); - EXPECT_EQ(display_list->bounds(), root_cull) << label; + EXPECT_EQ(display_list->GetBounds(), root_cull) << label; EXPECT_FALSE(display_list->root_is_unbounded()) << label; SAVE_LAYER_EXPECTOR(expector); @@ -5735,7 +5757,7 @@ TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) { builder.Restore(); auto display_list = builder.Build(); - EXPECT_EQ(display_list->bounds(), clip_rect) << label; + EXPECT_EQ(display_list->GetBounds(), clip_rect) << label; EXPECT_FALSE(display_list->root_is_unbounded()) << label; SAVE_LAYER_EXPECTOR(expector); @@ -5764,13 +5786,13 @@ TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) { }); test_unbounded("DrawPathEvenOddInverted", [](DlCanvas& builder) { - SkPath path = SkPath::Rect(draw_rect); + SkPath path = SkPath::Rect(ToSkRect(draw_rect)); path.setFillType(SkPathFillType::kInverseEvenOdd); builder.DrawPath(path, DlPaint()); }); test_unbounded("DrawPathWindingInverted", [](DlCanvas& builder) { - SkPath path = SkPath::Rect(draw_rect); + SkPath path = SkPath::Rect(ToSkRect(draw_rect)); path.setFillType(SkPathFillType::kInverseWinding); builder.DrawPath(path, DlPaint()); }); @@ -5780,7 +5802,7 @@ TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) { nested_builder.DrawPaint(DlPaint()); auto nested_display_list = nested_builder.Build(); - EXPECT_EQ(nested_display_list->bounds(), root_cull); + EXPECT_EQ(nested_display_list->GetBounds(), root_cull); ASSERT_TRUE(nested_display_list->root_is_unbounded()); builder.DrawDisplayList(nested_display_list); @@ -5795,11 +5817,11 @@ TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; // clang-format on - auto unbounded_cf = DlMatrixColorFilter::Make(matrix); + auto unbounded_cf = DlColorFilter::MakeMatrix(matrix); // ColorFilter must modify transparent black to be "unbounded" ASSERT_TRUE(unbounded_cf->modifies_transparent_black()); - auto unbounded_if = DlColorFilterImageFilter::Make(unbounded_cf); - SkRect output_bounds; + auto unbounded_if = DlImageFilter::MakeColorFilter(unbounded_cf); + DlRect output_bounds; // ImageFilter returns null from bounds queries if it is "unbounded" ASSERT_EQ(unbounded_if->map_local_bounds(draw_rect, output_bounds), nullptr); @@ -5810,7 +5832,7 @@ TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) { test_unbounded( "SaveLayerWithBackdropFilter", [](DlCanvas& builder) { - auto filter = DlBlurImageFilter::Make(3.0f, 3.0f, DlTileMode::kMirror); + auto filter = DlImageFilter::MakeBlur(3.0f, 3.0f, DlTileMode::kMirror); builder.SaveLayer(nullptr, nullptr, filter.get()); builder.Restore(); }, @@ -5825,7 +5847,7 @@ TEST_F(DisplayListTest, BackdropFilterCulledAlongsideClipAndTransform) { SkRect draw_rect1 = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); SkRect draw_rect2 = SkRect::MakeLTRB(45.0f, 20.0f, 55.0f, 55.0f); SkRect cull_rect = SkRect::MakeLTRB(1.0f, 1.0f, 99.0f, 30.0f); - auto bdf_filter = DlBlurImageFilter::Make(5.0f, 5.0f, DlTileMode::kClamp); + auto bdf_filter = DlImageFilter::MakeBlur(5.0f, 5.0f, DlTileMode::kClamp); ASSERT_TRUE(frame_bounds.contains(clip_rect)); ASSERT_TRUE(frame_bounds.contains(draw_rect1)); @@ -5894,5 +5916,164 @@ TEST_F(DisplayListTest, BackdropFilterCulledAlongsideClipAndTransform) { } } +TEST_F(DisplayListTest, RecordManyLargeDisplayListOperations) { + DisplayListBuilder builder; + + // 2050 points is sizeof(DlPoint) * 2050 = 16400 bytes, this is more + // than the page size of 16384 bytes. + std::vector points(2050); + builder.DrawPoints(PointMode::kPoints, points.size(), points.data(), + DlPaint{}); + builder.DrawPoints(PointMode::kPoints, points.size(), points.data(), + DlPaint{}); + builder.DrawPoints(PointMode::kPoints, points.size(), points.data(), + DlPaint{}); + builder.DrawPoints(PointMode::kPoints, points.size(), points.data(), + DlPaint{}); + builder.DrawPoints(PointMode::kPoints, points.size(), points.data(), + DlPaint{}); + builder.DrawPoints(PointMode::kPoints, points.size(), points.data(), + DlPaint{}); + + EXPECT_TRUE(!!builder.Build()); +} + +TEST_F(DisplayListTest, RecordSingleLargeDisplayListOperation) { + DisplayListBuilder builder; + + std::vector points(40000); + builder.DrawPoints(PointMode::kPoints, points.size(), points.data(), + DlPaint{}); + + EXPECT_TRUE(!!builder.Build()); +} + +TEST_F(DisplayListTest, DisplayListDetectsRuntimeEffect) { + const auto runtime_effect = DlRuntimeEffect::MakeSkia( + SkRuntimeEffect::MakeForShader( + SkString("vec4 main(vec2 p) { return vec4(0); }")) + .effect); + auto color_source = DlColorSource::MakeRuntimeEffect( + runtime_effect, {}, std::make_shared>()); + auto image_filter = DlImageFilter::MakeRuntimeEffect( + runtime_effect, {}, std::make_shared>()); + + { + // Default - no runtime effects, supports group opacity + DisplayListBuilder builder; + DlPaint paint; + + builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint); + EXPECT_TRUE(builder.Build()->can_apply_group_opacity()); + } + + { + // Draw with RTE color source does not support group opacity + DisplayListBuilder builder; + DlPaint paint; + + paint.setColorSource(color_source); + builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint); + + EXPECT_FALSE(builder.Build()->can_apply_group_opacity()); + } + + { + // Draw with RTE image filter does not support group opacity + DisplayListBuilder builder; + DlPaint paint; + + paint.setImageFilter(image_filter); + builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint); + + EXPECT_FALSE(builder.Build()->can_apply_group_opacity()); + } + + { + // Draw with RTE color source inside SaveLayer does not support group + // opacity on the SaveLayer, but does support it on the DisplayList + DisplayListBuilder builder; + DlPaint paint; + + builder.SaveLayer(nullptr, nullptr); + paint.setColorSource(color_source); + builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint); + builder.Restore(); + + auto display_list = builder.Build(); + EXPECT_TRUE(display_list->can_apply_group_opacity()); + + SAVE_LAYER_EXPECTOR(expector); + expector.addExpectation([](const SaveLayerOptions& options) { + return !options.can_distribute_opacity(); + }); + display_list->Dispatch(expector); + } + + { + // Draw with RTE image filter inside SaveLayer does not support group + // opacity on the SaveLayer, but does support it on the DisplayList + DisplayListBuilder builder; + DlPaint paint; + + builder.SaveLayer(nullptr, nullptr); + paint.setImageFilter(image_filter); + builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint); + builder.Restore(); + + auto display_list = builder.Build(); + EXPECT_TRUE(display_list->can_apply_group_opacity()); + + SAVE_LAYER_EXPECTOR(expector); + expector.addExpectation([](const SaveLayerOptions& options) { + return !options.can_distribute_opacity(); + }); + display_list->Dispatch(expector); + } + + { + // Draw with RTE color source inside nested saveLayers does not support + // group opacity on the inner SaveLayer, but does support it on the + // outer SaveLayer and the DisplayList + DisplayListBuilder builder; + DlPaint paint; + + builder.SaveLayer(nullptr, nullptr); + + builder.SaveLayer(nullptr, nullptr); + paint.setColorSource(color_source); + builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint); + paint.setColorSource(nullptr); + builder.Restore(); + + builder.SaveLayer(nullptr, nullptr); + paint.setImageFilter(image_filter); + // Make sure these DrawRects are non-overlapping otherwise the outer + // SaveLayer and DisplayList will be incompatible due to overlaps + builder.DrawRect(DlRect::MakeLTRB(60, 60, 100, 100), paint); + paint.setImageFilter(nullptr); + builder.Restore(); + + builder.Restore(); + auto display_list = builder.Build(); + EXPECT_TRUE(display_list->can_apply_group_opacity()); + + SAVE_LAYER_EXPECTOR(expector); + expector.addExpectation([](const SaveLayerOptions& options) { + // outer SaveLayer supports group opacity + return options.can_distribute_opacity(); + }); + expector.addExpectation([](const SaveLayerOptions& options) { + // first inner SaveLayer does not support group opacity + return !options.can_distribute_opacity(); + }); + expector.addExpectation([](const SaveLayerOptions& options) { + // second inner SaveLayer does not support group opacity + return !options.can_distribute_opacity(); + }); + display_list->Dispatch(expector); + } +} + } // namespace testing } // namespace flutter diff --git a/display_list/dl_builder.cc b/display_list/dl_builder.cc index 2d10ad33e44b5..682e8823c5607 100644 --- a/display_list/dl_builder.cc +++ b/display_list/dl_builder.cc @@ -8,15 +8,15 @@ #include "flutter/display_list/dl_blend_mode.h" #include "flutter/display_list/dl_op_flags.h" #include "flutter/display_list/dl_op_records.h" +#include "flutter/display_list/effects/dl_color_filters.h" #include "flutter/display_list/effects/dl_color_source.h" +#include "flutter/display_list/effects/dl_image_filters.h" #include "flutter/display_list/utils/dl_accumulation_rect.h" #include "fml/logging.h" #include "third_party/skia/include/core/SkScalar.h" namespace flutter { -#define DL_BUILDER_PAGE 4096 - // CopyV(dst, src,n, src,n, ...) copies any number of typed srcs into dst. static void CopyV(void* dst) {} @@ -35,32 +35,29 @@ static void CopyV(void* dst, const S* src, int n, Rest&&... rest) { CopyV(dst, std::forward(rest)...); } -static constexpr inline bool is_power_of_two(int value) { - return (value & (value - 1)) == 0; -} - template void* DisplayListBuilder::Push(size_t pod, Args&&... args) { + // Plan out where and how large a space we need size_t size = SkAlignPtr(sizeof(T) + pod); - FML_CHECK(size < (1 << 24)); - if (used_ + size > allocated_) { - static_assert(is_power_of_two(DL_BUILDER_PAGE), - "This math needs updating for non-pow2."); - // Next greater multiple of DL_BUILDER_PAGE. - allocated_ = (used_ + size + DL_BUILDER_PAGE) & ~(DL_BUILDER_PAGE - 1); - storage_.realloc(allocated_); - FML_CHECK(storage_.get()); - memset(storage_.get() + used_, 0, allocated_ - used_); - } - FML_CHECK(used_ + size <= allocated_); - auto op = reinterpret_cast(storage_.get() + used_); - used_ += size; + size_t offset = storage_.size(); + + // Allocate the space + auto ptr = storage_.allocate(size); + FML_CHECK(ptr); + + // Initialize the space via the constructor + auto op = reinterpret_cast(ptr); new (op) T{std::forward(args)...}; - op->type = T::kType; - op->size = size; + FML_DCHECK(op->type == T::kType); + + // Adjust the counters and offsets (the memory is mostly initialized + // at this point except that the caller might do some pod-based copying + // past the end of the DlOp structure itself when we return) + offsets_.push_back(offset); render_op_count_ += T::kRenderOpInc; depth_ += T::kDepthInc * render_op_depth_cost_; op_index_++; + return op + 1; } @@ -69,7 +66,6 @@ sk_sp DisplayListBuilder::Build() { restore(); } - size_t bytes = used_; int count = render_op_count_; size_t nested_bytes = nested_bytes_; int nested_count = nested_op_count_; @@ -97,7 +93,7 @@ sk_sp DisplayListBuilder::Build() { bounds = current_layer().global_space_accumulator.bounds(); } - used_ = allocated_ = render_op_count_ = op_index_ = 0; + render_op_count_ = op_index_ = 0; nested_bytes_ = nested_op_count_ = 0; depth_ = 0; is_ui_thread_safe_ = true; @@ -108,9 +104,14 @@ sk_sp DisplayListBuilder::Build() { save_stack_.pop_back(); Init(rtree != nullptr); - storage_.realloc(bytes); + storage_.trim(); + DisplayListStorage storage; + std::vector offsets; + std::swap(offsets, offsets_); + std::swap(storage, storage_); + return sk_sp(new DisplayList( - std::move(storage_), bytes, count, nested_bytes, nested_count, + std::move(storage), std::move(offsets), count, nested_bytes, nested_count, total_depth, bounds, opacity_compatible, is_safe, affects_transparency, max_root_blend_mode, root_has_backdrop_filter, root_is_unbounded, std::move(rtree))); @@ -118,12 +119,12 @@ sk_sp DisplayListBuilder::Build() { static constexpr DlRect kEmpty = DlRect(); -static const DlRect& ProtectEmpty(const SkRect& rect) { +static const DlRect& ProtectEmpty(const DlRect& rect) { // isEmpty protects us against NaN while we normalize any empty cull rects - return rect.isEmpty() ? kEmpty : ToDlRect(rect); + return rect.IsEmpty() ? kEmpty : rect; } -DisplayListBuilder::DisplayListBuilder(const SkRect& cull_rect, +DisplayListBuilder::DisplayListBuilder(const DlRect& cull_rect, bool prepare_rtree) : original_cull_rect_(ProtectEmpty(cull_rect)) { Init(prepare_rtree); @@ -141,10 +142,7 @@ void DisplayListBuilder::Init(bool prepare_rtree) { } DisplayListBuilder::~DisplayListBuilder() { - uint8_t* ptr = storage_.get(); - if (ptr) { - DisplayList::DisposeOps(ptr, ptr + used_); - } + DisplayList::DisposeOps(storage_, offsets_); } DlISize DisplayListBuilder::GetBaseLayerDimensions() const { @@ -203,12 +201,6 @@ void DisplayListBuilder::onSetColorSource(const DlColorSource* source) { current_.setColorSource(source->shared()); is_ui_thread_safe_ = is_ui_thread_safe_ && source->isUIThreadSafe(); switch (source->type()) { - case DlColorSourceType::kColor: { - const DlColorColorSource* color_source = source->asColor(); - current_.setColorSource(nullptr); - setColor(color_source->color()); - break; - } case DlColorSourceType::kImage: { const DlImageColorSource* image_source = source->asImage(); FML_DCHECK(image_source); @@ -252,6 +244,7 @@ void DisplayListBuilder::onSetColorSource(const DlColorSource* source) { } } } + UpdateCurrentOpacityCompatibility(); } void DisplayListBuilder::onSetImageFilter(const DlImageFilter* filter) { if (filter == nullptr) { @@ -290,12 +283,14 @@ void DisplayListBuilder::onSetImageFilter(const DlImageFilter* filter) { } case DlImageFilterType::kCompose: case DlImageFilterType::kLocalMatrix: - case DlImageFilterType::kColorFilter: { + case DlImageFilterType::kColorFilter: + case DlImageFilterType::kRuntimeEffect: { Push(0, filter); break; } } } + UpdateCurrentOpacityCompatibility(); } void DisplayListBuilder::onSetColorFilter(const DlColorFilter* filter) { if (filter == nullptr) { @@ -390,7 +385,7 @@ void DisplayListBuilder::SetAttributesFromPaint( void DisplayListBuilder::checkForDeferredSave() { if (current_info().has_deferred_save_op) { - size_t save_offset = used_; + size_t save_offset = storage_.size(); Push(0); current_info().save_offset = save_offset; current_info().save_depth = depth_; @@ -435,12 +430,12 @@ void DisplayListBuilder::saveLayer(const DlRect& bounds, // Snapshot these values before we do any work as we need the values // from before the method was called, but some of the operations below // might update them. - size_t save_offset = used_; + size_t save_offset = storage_.size(); uint32_t save_depth = depth_; // A backdrop will affect up to the entire surface, bounded by the clip bool will_be_unbounded = (backdrop != nullptr); - std::shared_ptr filter; + std::shared_ptr filter; if (options.renders_with_attributes()) { if (!paint_nops_on_transparency()) { @@ -488,15 +483,16 @@ void DisplayListBuilder::saveLayer(const DlRect& bounds, // to adjust them so that we cull for the correct input space for the // output of the filter. if (filter) { - SkRect outer_cull_rect = current_info().global_state.device_cull_rect(); - SkMatrix matrix = current_info().global_state.matrix_3x3(); + DlRect outer_cull_rect = + current_info().global_state.GetDeviceCullCoverage(); + DlMatrix matrix = current_info().global_state.matrix(); - SkIRect output_bounds = outer_cull_rect.roundOut(); - SkIRect input_bounds; + DlIRect output_bounds = DlIRect::RoundOut(outer_cull_rect); + DlIRect input_bounds; if (filter->get_input_device_bounds(output_bounds, matrix, input_bounds)) { current_info().global_state.resetDeviceCullRect( - SkRect::Make(input_bounds)); + DlRect::Make(input_bounds)); } else { // Filter could not make any promises about the bounds it needs to // fill the output space, so we use a maximal rect to accumulate @@ -542,7 +538,7 @@ void DisplayListBuilder::saveLayer(const DlRect& bounds, } } } -void DisplayListBuilder::SaveLayer(std::optional& bounds, +void DisplayListBuilder::SaveLayer(const std::optional& bounds, const DlPaint* paint, const DlImageFilter* backdrop, std::optional backdrop_id) { @@ -568,7 +564,7 @@ void DisplayListBuilder::Restore() { } if (!current_info().has_deferred_save_op) { - SaveOpBase* op = reinterpret_cast(storage_.get() + + SaveOpBase* op = reinterpret_cast(storage_.base() + current_info().save_offset); FML_CHECK(op->type == DisplayListOpType::kSave || op->type == DisplayListOpType::kSaveLayer || @@ -605,7 +601,7 @@ void DisplayListBuilder::RestoreLayer() { SkRect content_bounds = current_layer().layer_local_accumulator.bounds(); SaveLayerOpBase* layer_op = reinterpret_cast( - storage_.get() + current_info().save_offset); + storage_.base() + current_info().save_offset); FML_CHECK(layer_op->type == DisplayListOpType::kSaveLayer || layer_op->type == DisplayListOpType::kSaveLayerBackdrop); @@ -696,8 +692,8 @@ void DisplayListBuilder::TransferLayerBounds(const SkRect& content_bounds) { // Matrix and Clip for the filter adjustment are the global values from // just before our saveLayer and should still be the current values // present in the parent layer. - const SkRect clip = parent_info().global_state.device_cull_rect(); - const SkMatrix matrix = parent_info().global_state.matrix_3x3(); + const DlRect clip = parent_info().global_state.GetDeviceCullCoverage(); + const DlMatrix matrix = parent_info().global_state.matrix(); if (rtree_data_.has_value()) { // Neither current or parent layer should have any global bounds in @@ -720,15 +716,17 @@ void DisplayListBuilder::TransferLayerBounds(const SkRect& content_bounds) { parent_is_flooded = true; } } else { - SkRect global_bounds = current_layer().global_space_accumulator.bounds(); - if (!global_bounds.isEmpty()) { - SkIRect global_ibounds = global_bounds.roundOut(); + DlRect global_bounds = current_layer().global_space_accumulator.GetBounds(); + if (!global_bounds.IsEmpty()) { + DlIRect global_ibounds = DlIRect::RoundOut(global_bounds); if (!filter->map_device_bounds(global_ibounds, matrix, global_ibounds)) { parent_is_flooded = true; } else { - global_bounds.set(global_ibounds); - if (global_bounds.intersect(clip)) { - parent_layer().global_space_accumulator.accumulate(global_bounds); + global_bounds = DlRect::Make(global_ibounds); + std::optional clipped_bounds = global_bounds.Intersection(clip); + if (clipped_bounds.has_value()) { + parent_layer().global_space_accumulator.accumulate( + clipped_bounds.value()); } } } @@ -745,7 +743,10 @@ void DisplayListBuilder::TransferLayerBounds(const SkRect& content_bounds) { // run the filter on the content bounds only to discover the same // condition. if (!parent_is_flooded && !bounds_for_parent.isEmpty()) { - if (!filter->map_local_bounds(bounds_for_parent, bounds_for_parent)) { + DlRect mappable_bounds = ToDlRect(bounds_for_parent); + if (filter->map_local_bounds(mappable_bounds, mappable_bounds)) { + bounds_for_parent = ToSkRect(mappable_bounds); + } else { parent_is_flooded = true; } } @@ -768,8 +769,8 @@ void DisplayListBuilder::TransferLayerBounds(const SkRect& content_bounds) { bool DisplayListBuilder::AdjustRTreeRects(RTreeData& data, const DlImageFilter& filter, - const SkMatrix& matrix, - const SkRect& clip, + const DlMatrix& matrix, + const DlRect& clip, size_t rect_start_index) { auto& rects = data.rects; auto& indices = data.indices; @@ -777,17 +778,18 @@ bool DisplayListBuilder::AdjustRTreeRects(RTreeData& data, int ret = false; auto rect_keep = rect_start_index; for (size_t i = rect_start_index; i < rects.size(); i++) { - SkRect bounds = rects[i]; - SkIRect ibounds; - if (filter.map_device_bounds(bounds.roundOut(), matrix, ibounds)) { - bounds.set(ibounds); + DlRect bounds = ToDlRect(rects[i]); + DlIRect ibounds = DlIRect::RoundOut(bounds); + if (filter.map_device_bounds(ibounds, matrix, ibounds)) { + bounds = DlRect::Make(ibounds); } else { bounds = clip; ret = true; } - if (bounds.intersect(clip)) { + auto clipped_bounds = bounds.Intersection(clip); + if (clipped_bounds.has_value()) { indices[rect_keep] = indices[i]; - rects[rect_keep] = bounds; + rects[rect_keep] = ToSkRect(clipped_bounds.value()); rect_keep++; } } @@ -1789,8 +1791,12 @@ bool DisplayListBuilder::AdjustBoundsForPaint(SkRect& bounds, if (flags.applies_image_filter()) { auto filter = current_.getImageFilterPtr(); - if (filter && !filter->map_local_bounds(bounds, bounds)) { - return false; + if (filter) { + DlRect dl_bounds; + if (!filter->map_local_bounds(ToDlRect(bounds), dl_bounds)) { + return false; + } + bounds = ToSkRect(dl_bounds); } } @@ -1943,11 +1949,9 @@ DlColor DisplayListBuilder::GetEffectiveColor(const DlPaint& paint, if (flags.applies_color()) { const DlColorSource* source = paint.getColorSourcePtr(); if (source) { - if (source->asColor()) { - color = source->asColor()->color(); - } else { - color = source->is_opaque() ? DlColor::kBlack() : kAnyColor; - } + // Suspecting that we need to modulate the ColorSource color by the + // color property, see https://github.com/flutter/flutter/issues/159507 + color = source->is_opaque() ? DlColor::kBlack() : kAnyColor; } else { color = paint.getColor(); } diff --git a/display_list/dl_builder.h b/display_list/dl_builder.h index 0dfb365c11993..92dc9b83246c0 100644 --- a/display_list/dl_builder.h +++ b/display_list/dl_builder.h @@ -29,17 +29,21 @@ class DisplayListBuilder final : public virtual DlCanvas, virtual DlOpReceiver, DisplayListOpFlags { public: - static constexpr SkRect kMaxCullRect = - SkRect::MakeLTRB(-1E9F, -1E9F, 1E9F, 1E9F); + static constexpr DlRect kMaxCullRect = + DlRect::MakeLTRB(-1E9F, -1E9F, 1E9F, 1E9F); explicit DisplayListBuilder(bool prepare_rtree) : DisplayListBuilder(kMaxCullRect, prepare_rtree) {} - explicit DisplayListBuilder(const SkRect& cull_rect = kMaxCullRect, + explicit DisplayListBuilder(const DlRect& cull_rect = kMaxCullRect, bool prepare_rtree = false); DisplayListBuilder(DlScalar width, DlScalar height) - : DisplayListBuilder(SkRect::MakeWH(width, height)) {} + : DisplayListBuilder(DlRect::MakeWH(width, height)) {} + + explicit DisplayListBuilder(const SkRect& cull_rect, + bool prepare_rtree = false) + : DisplayListBuilder(ToDlRect(cull_rect), prepare_rtree) {} ~DisplayListBuilder(); @@ -52,7 +56,7 @@ class DisplayListBuilder final : public virtual DlCanvas, void Save() override; // |DlCanvas| - void SaveLayer(std::optional& bounds, + void SaveLayer(const std::optional& bounds, const DlPaint* paint = nullptr, const DlImageFilter* backdrop = nullptr, std::optional backdrop_id = std::nullopt) override; @@ -497,8 +501,7 @@ class DisplayListBuilder final : public virtual DlCanvas, void checkForDeferredSave(); DisplayListStorage storage_; - size_t used_ = 0u; - size_t allocated_ = 0u; + std::vector offsets_; uint32_t render_op_count_ = 0u; uint32_t depth_ = 0u; // Most rendering ops will use 1 depth value, but some attributes may @@ -521,14 +524,14 @@ class DisplayListBuilder final : public virtual DlCanvas, }; struct LayerInfo { - LayerInfo(const std::shared_ptr& filter, + LayerInfo(const std::shared_ptr& filter, size_t rtree_rects_start_index) : filter(filter), rtree_rects_start_index(rtree_rects_start_index) {} // The filter that will be applied to the contents of the saveLayer // when it is restored into the parent layer. - const std::shared_ptr filter; + const std::shared_ptr filter; // The index of the rtree rects when the saveLayer was called, used // only in the case that the saveLayer has a filter so that the @@ -591,7 +594,7 @@ class DisplayListBuilder final : public virtual DlCanvas, // For saveLayer calls: explicit SaveInfo(const SaveInfo* parent_info, - const std::shared_ptr& filter, + const std::shared_ptr& filter, int rtree_rect_index) : is_save_layer(true), has_valid_clip(false), @@ -698,8 +701,8 @@ class DisplayListBuilder final : public virtual DlCanvas, void TransferLayerBounds(const SkRect& content_bounds); bool AdjustRTreeRects(RTreeData& data, const DlImageFilter& filter, - const SkMatrix& matrix, - const SkRect& clip, + const DlMatrix& matrix, + const DlRect& clip, size_t rect_index); // This flag indicates whether or not the current rendering attributes @@ -720,6 +723,7 @@ class DisplayListBuilder final : public virtual DlCanvas, current_opacity_compatibility_ = // current_.getColorFilter() == nullptr && // !current_.isInvertColors() && // + !current_.usesRuntimeEffect() && // IsOpacityCompatible(current_.getBlendMode()); } diff --git a/display_list/dl_canvas.h b/display_list/dl_canvas.h index e27e4e7c7a192..c2ae784ff5f59 100644 --- a/display_list/dl_canvas.h +++ b/display_list/dl_canvas.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_DISPLAY_LIST_DL_CANVAS_H_ #define FLUTTER_DISPLAY_LIST_DL_CANVAS_H_ +#include "flutter/display_list/display_list.h" #include "flutter/display_list/dl_blend_mode.h" #include "flutter/display_list/dl_paint.h" #include "flutter/display_list/dl_vertices.h" @@ -60,7 +61,7 @@ class DlCanvas { virtual SkImageInfo GetImageInfo() const = 0; virtual void Save() = 0; - virtual void SaveLayer(std::optional& bounds, + virtual void SaveLayer(const std::optional& bounds, const DlPaint* paint = nullptr, const DlImageFilter* backdrop = nullptr, std::optional backdrop_id = std::nullopt) = 0; @@ -237,8 +238,7 @@ class DlCanvas { const DlPaint* paint = nullptr, const DlImageFilter* backdrop = nullptr, std::optional backdrop_id = std::nullopt) { - auto optional_bounds = ToOptDlRect(bounds); - SaveLayer(optional_bounds, paint, backdrop, backdrop_id); + SaveLayer(ToOptDlRect(bounds), paint, backdrop, backdrop_id); } void Transform(const SkMatrix* matrix) { diff --git a/display_list/dl_op_records.h b/display_list/dl_op_records.h index d08602994e876..1df10f933f3c0 100644 --- a/display_list/dl_op_records.h +++ b/display_list/dl_op_records.h @@ -9,7 +9,8 @@ #include "flutter/display_list/dl_blend_mode.h" #include "flutter/display_list/dl_op_receiver.h" #include "flutter/display_list/dl_sampling_options.h" -#include "flutter/display_list/effects/dl_color_source.h" +#include "flutter/display_list/effects/dl_color_sources.h" +#include "flutter/display_list/utils/dl_comparable.h" #include "flutter/fml/macros.h" #include "flutter/impeller/geometry/path.h" @@ -58,8 +59,9 @@ struct DLOp { static constexpr uint32_t kDepthInc = 0; static constexpr uint32_t kRenderOpInc = 0; - DisplayListOpType type : 8; - uint32_t size : 24; + explicit DLOp(DisplayListOpType type) : type(type) {} + + const DisplayListOpType type; DisplayListCompare equals(const DLOp* other) const { return DisplayListCompare::kUseBulkCompare; @@ -67,34 +69,35 @@ struct DLOp { }; // 4 byte header + 4 byte payload packs into minimum 8 bytes -#define DEFINE_SET_BOOL_OP(name) \ - struct Set##name##Op final : DLOp { \ - static constexpr auto kType = DisplayListOpType::kSet##name; \ - \ - explicit Set##name##Op(bool value) : value(value) {} \ - \ - const bool value; \ - \ - void dispatch(DlOpReceiver& receiver) const { \ - receiver.set##name(value); \ - } \ +#define DEFINE_SET_BOOL_OP(name) \ + struct Set##name##Op final : DLOp { \ + static constexpr auto kType = DisplayListOpType::kSet##name; \ + \ + explicit Set##name##Op(bool value) : DLOp(kType), value(value) {} \ + \ + const bool value; \ + \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.set##name(value); \ + } \ }; DEFINE_SET_BOOL_OP(AntiAlias) DEFINE_SET_BOOL_OP(InvertColors) #undef DEFINE_SET_BOOL_OP // 4 byte header + 4 byte payload packs into minimum 8 bytes -#define DEFINE_SET_ENUM_OP(name) \ - struct SetStroke##name##Op final : DLOp { \ - static constexpr auto kType = DisplayListOpType::kSetStroke##name; \ - \ - explicit SetStroke##name##Op(DlStroke##name value) : value(value) {} \ - \ - const DlStroke##name value; \ - \ - void dispatch(DlOpReceiver& receiver) const { \ - receiver.setStroke##name(value); \ - } \ +#define DEFINE_SET_ENUM_OP(name) \ + struct SetStroke##name##Op final : DLOp { \ + static constexpr auto kType = DisplayListOpType::kSetStroke##name; \ + \ + explicit SetStroke##name##Op(DlStroke##name value) \ + : DLOp(kType), value(value) {} \ + \ + const DlStroke##name value; \ + \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.setStroke##name(value); \ + } \ }; DEFINE_SET_ENUM_OP(Cap) DEFINE_SET_ENUM_OP(Join) @@ -104,7 +107,7 @@ DEFINE_SET_ENUM_OP(Join) struct SetStyleOp final : DLOp { static constexpr auto kType = DisplayListOpType::kSetStyle; - explicit SetStyleOp(DlDrawStyle style) : style(style) {} + explicit SetStyleOp(DlDrawStyle style) : DLOp(kType), style(style) {} const DlDrawStyle style; @@ -116,7 +119,7 @@ struct SetStyleOp final : DLOp { struct SetStrokeWidthOp final : DLOp { static constexpr auto kType = DisplayListOpType::kSetStrokeWidth; - explicit SetStrokeWidthOp(float width) : width(width) {} + explicit SetStrokeWidthOp(float width) : DLOp(kType), width(width) {} const float width; @@ -128,7 +131,7 @@ struct SetStrokeWidthOp final : DLOp { struct SetStrokeMiterOp final : DLOp { static constexpr auto kType = DisplayListOpType::kSetStrokeMiter; - explicit SetStrokeMiterOp(float limit) : limit(limit) {} + explicit SetStrokeMiterOp(float limit) : DLOp(kType), limit(limit) {} const float limit; @@ -141,7 +144,7 @@ struct SetStrokeMiterOp final : DLOp { struct SetColorOp final : DLOp { static constexpr auto kType = DisplayListOpType::kSetColor; - explicit SetColorOp(DlColor color) : color(color) {} + explicit SetColorOp(DlColor color) : DLOp(kType), color(color) {} const DlColor color; @@ -151,7 +154,7 @@ struct SetColorOp final : DLOp { struct SetBlendModeOp final : DLOp { static constexpr auto kType = DisplayListOpType::kSetBlendMode; - explicit SetBlendModeOp(DlBlendMode mode) : mode(mode) {} + explicit SetBlendModeOp(DlBlendMode mode) : DLOp(kType), mode(mode) {} const DlBlendMode mode; @@ -166,11 +169,11 @@ struct SetBlendModeOp final : DLOp { // instance copied to the memory following the record // yields a size and efficiency that has somewhere between // 4 and 8 bytes unused -#define DEFINE_SET_CLEAR_DLATTR_OP(name, sk_name, field) \ +#define DEFINE_SET_CLEAR_DLATTR_OP(name, field) \ struct Clear##name##Op final : DLOp { \ static constexpr auto kType = DisplayListOpType::kClear##name; \ \ - Clear##name##Op() {} \ + Clear##name##Op() : DLOp(kType) {} \ \ void dispatch(DlOpReceiver& receiver) const { \ receiver.set##name(nullptr); \ @@ -179,26 +182,27 @@ struct SetBlendModeOp final : DLOp { struct SetPod##name##Op final : DLOp { \ static constexpr auto kType = DisplayListOpType::kSetPod##name; \ \ - SetPod##name##Op() {} \ + SetPod##name##Op() : DLOp(kType) {} \ \ void dispatch(DlOpReceiver& receiver) const { \ const Dl##name* filter = reinterpret_cast(this + 1); \ receiver.set##name(filter); \ } \ }; -DEFINE_SET_CLEAR_DLATTR_OP(ColorFilter, ColorFilter, filter) -DEFINE_SET_CLEAR_DLATTR_OP(ImageFilter, ImageFilter, filter) -DEFINE_SET_CLEAR_DLATTR_OP(MaskFilter, MaskFilter, filter) -DEFINE_SET_CLEAR_DLATTR_OP(ColorSource, Shader, source) +DEFINE_SET_CLEAR_DLATTR_OP(ColorFilter, filter) +DEFINE_SET_CLEAR_DLATTR_OP(ImageFilter, filter) +DEFINE_SET_CLEAR_DLATTR_OP(MaskFilter, filter) +DEFINE_SET_CLEAR_DLATTR_OP(ColorSource, source) #undef DEFINE_SET_CLEAR_DLATTR_OP -// 4 byte header + 80 bytes for the embedded DlImageColorSource -// uses 84 total bytes (4 bytes unused) +// 4 byte header + 96 bytes for the embedded DlImageColorSource +// uses 104 total bytes (4 bytes unused) struct SetImageColorSourceOp : DLOp { static constexpr auto kType = DisplayListOpType::kSetImageColorSource; explicit SetImageColorSourceOp(const DlImageColorSource* source) - : source(source->image(), + : DLOp(kType), + source(source->image(), source->horizontal_tile_mode(), source->vertical_tile_mode(), source->sampling(), @@ -218,7 +222,8 @@ struct SetRuntimeEffectColorSourceOp : DLOp { explicit SetRuntimeEffectColorSourceOp( const DlRuntimeEffectColorSource* source) - : source(source->runtime_effect(), + : DLOp(kType), + source(source->runtime_effect(), source->samplers(), source->uniform_data()) {} @@ -239,7 +244,7 @@ struct SetSharedImageFilterOp : DLOp { static constexpr auto kType = DisplayListOpType::kSetSharedImageFilter; explicit SetSharedImageFilterOp(const DlImageFilter* filter) - : filter(filter->shared()) {} + : DLOp(kType), filter(filter->shared()) {} const std::shared_ptr filter; @@ -259,10 +264,14 @@ struct SaveOpBase : DLOp { static constexpr uint32_t kDepthInc = 0; static constexpr uint32_t kRenderOpInc = 1; - SaveOpBase() : options(), restore_index(0) {} + explicit SaveOpBase(DisplayListOpType type) + : DLOp(type), options(), restore_index(0), total_content_depth(0) {} - explicit SaveOpBase(const SaveLayerOptions& options) - : options(options), restore_index(0), total_content_depth(0) {} + SaveOpBase(DisplayListOpType type, const SaveLayerOptions& options) + : DLOp(type), + options(options), + restore_index(0), + total_content_depth(0) {} // options parameter is only used by saveLayer operations, but since // it packs neatly into the empty space created by laying out the rest @@ -276,7 +285,7 @@ struct SaveOpBase : DLOp { struct SaveOp final : SaveOpBase { static constexpr auto kType = DisplayListOpType::kSave; - SaveOp() : SaveOpBase() {} + SaveOp() : SaveOpBase(kType) {} void dispatch(DlOpReceiver& receiver) const { receiver.save(total_content_depth); @@ -285,8 +294,10 @@ struct SaveOp final : SaveOpBase { // The base struct for all saveLayer() ops // 16 byte SaveOpBase + 20 byte payload packs into 36 bytes struct SaveLayerOpBase : SaveOpBase { - SaveLayerOpBase(const SaveLayerOptions& options, const DlRect& rect) - : SaveOpBase(options), rect(rect) {} + SaveLayerOpBase(DisplayListOpType type, + const SaveLayerOptions& options, + const DlRect& rect) + : SaveOpBase(type, options), rect(rect) {} DlRect rect; DlBlendMode max_blend_mode = DlBlendMode::kClear; @@ -297,7 +308,7 @@ struct SaveLayerOp final : SaveLayerOpBase { static constexpr auto kType = DisplayListOpType::kSaveLayer; SaveLayerOp(const SaveLayerOptions& options, const DlRect& rect) - : SaveLayerOpBase(options, rect) {} + : SaveLayerOpBase(kType, options, rect) {} void dispatch(DlOpReceiver& receiver) const { receiver.saveLayer(rect, options, total_content_depth, max_blend_mode); @@ -312,7 +323,7 @@ struct SaveLayerBackdropOp final : SaveLayerOpBase { const DlRect& rect, const DlImageFilter* backdrop, std::optional backdrop_id) - : SaveLayerOpBase(options, rect), + : SaveLayerOpBase(kType, options, rect), backdrop(backdrop->shared()), backdrop_id_(backdrop_id) {} @@ -338,7 +349,7 @@ struct RestoreOp final : DLOp { static constexpr uint32_t kDepthInc = 0; static constexpr uint32_t kRenderOpInc = 1; - RestoreOp() {} + RestoreOp() : DLOp(kType) {} void dispatch(DlOpReceiver& receiver) const { // receiver.restore(); @@ -348,13 +359,16 @@ struct RestoreOp final : DLOp { struct TransformClipOpBase : DLOp { static constexpr uint32_t kDepthInc = 0; static constexpr uint32_t kRenderOpInc = 1; + + explicit TransformClipOpBase(DisplayListOpType type) : DLOp(type) {} }; // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes // (4 bytes unused) struct TranslateOp final : TransformClipOpBase { static constexpr auto kType = DisplayListOpType::kTranslate; - TranslateOp(DlScalar tx, DlScalar ty) : tx(tx), ty(ty) {} + TranslateOp(DlScalar tx, DlScalar ty) + : TransformClipOpBase(kType), tx(tx), ty(ty) {} const DlScalar tx; const DlScalar ty; @@ -368,7 +382,8 @@ struct TranslateOp final : TransformClipOpBase { struct ScaleOp final : TransformClipOpBase { static constexpr auto kType = DisplayListOpType::kScale; - ScaleOp(DlScalar sx, DlScalar sy) : sx(sx), sy(sy) {} + ScaleOp(DlScalar sx, DlScalar sy) + : TransformClipOpBase(kType), sx(sx), sy(sy) {} const DlScalar sx; const DlScalar sy; @@ -381,7 +396,8 @@ struct ScaleOp final : TransformClipOpBase { struct RotateOp final : TransformClipOpBase { static constexpr auto kType = DisplayListOpType::kRotate; - explicit RotateOp(DlScalar degrees) : degrees(degrees) {} + explicit RotateOp(DlScalar degrees) + : TransformClipOpBase(kType), degrees(degrees) {} const DlScalar degrees; @@ -394,7 +410,8 @@ struct RotateOp final : TransformClipOpBase { struct SkewOp final : TransformClipOpBase { static constexpr auto kType = DisplayListOpType::kSkew; - SkewOp(DlScalar sx, DlScalar sy) : sx(sx), sy(sy) {} + SkewOp(DlScalar sx, DlScalar sy) + : TransformClipOpBase(kType), sx(sx), sy(sy) {} const DlScalar sx; const DlScalar sy; @@ -411,7 +428,9 @@ struct Transform2DAffineOp final : TransformClipOpBase { // clang-format off Transform2DAffineOp(DlScalar mxx, DlScalar mxy, DlScalar mxt, DlScalar myx, DlScalar myy, DlScalar myt) - : mxx(mxx), mxy(mxy), mxt(mxt), myx(myx), myy(myy), myt(myt) {} + : TransformClipOpBase(kType), + mxx(mxx), mxy(mxy), mxt(mxt), + myx(myx), myy(myy), myt(myt) {} // clang-format on const DlScalar mxx, mxy, mxt; @@ -433,7 +452,8 @@ struct TransformFullPerspectiveOp final : TransformClipOpBase { DlScalar myx, DlScalar myy, DlScalar myz, DlScalar myt, DlScalar mzx, DlScalar mzy, DlScalar mzz, DlScalar mzt, DlScalar mwx, DlScalar mwy, DlScalar mwz, DlScalar mwt) - : mxx(mxx), mxy(mxy), mxz(mxz), mxt(mxt), + : TransformClipOpBase(kType), + mxx(mxx), mxy(mxy), mxz(mxz), mxt(mxt), myx(myx), myy(myy), myz(myz), myt(myt), mzx(mzx), mzy(mzy), mzz(mzz), mzt(mzt), mwx(mwx), mwy(mwy), mwz(mwz), mwt(mwt) {} @@ -456,7 +476,7 @@ struct TransformFullPerspectiveOp final : TransformClipOpBase { struct TransformResetOp final : TransformClipOpBase { static constexpr auto kType = DisplayListOpType::kTransformReset; - TransformResetOp() = default; + TransformResetOp() : TransformClipOpBase(kType) {} void dispatch(DlOpReceiver& receiver) const { // receiver.transformReset(); @@ -478,7 +498,7 @@ struct TransformResetOp final : TransformClipOpBase { static constexpr auto kType = DisplayListOpType::kClip##clipop##shapename; \ \ Clip##clipop##shapename##Op(shapetype shape, bool is_aa) \ - : is_aa(is_aa), shape(shape) {} \ + : TransformClipOpBase(kType), is_aa(is_aa), shape(shape) {} \ \ const bool is_aa; \ const shapetype shape; \ @@ -501,7 +521,7 @@ DEFINE_CLIP_SHAPE_OP(RoundRect, DlRoundRect, Difference) static constexpr auto kType = DisplayListOpType::kClip##clipop##Path; \ \ Clip##clipop##PathOp(const DlPath& path, bool is_aa) \ - : is_aa(is_aa), path(path) {} \ + : TransformClipOpBase(kType), is_aa(is_aa), path(path) {} \ \ const bool is_aa; \ const DlPath path; \ @@ -523,13 +543,15 @@ DEFINE_CLIP_PATH_OP(Difference) struct DrawOpBase : DLOp { static constexpr uint32_t kDepthInc = 1; static constexpr uint32_t kRenderOpInc = 1; + + explicit DrawOpBase(DisplayListOpType type) : DLOp(type) {} }; // 4 byte header + no payload uses minimum 8 bytes (4 bytes unused) struct DrawPaintOp final : DrawOpBase { static constexpr auto kType = DisplayListOpType::kDrawPaint; - DrawPaintOp() {} + DrawPaintOp() : DrawOpBase(kType) {} void dispatch(DlOpReceiver& receiver) const { // receiver.drawPaint(); @@ -540,7 +562,8 @@ struct DrawPaintOp final : DrawOpBase { struct DrawColorOp final : DrawOpBase { static constexpr auto kType = DisplayListOpType::kDrawColor; - DrawColorOp(DlColor color, DlBlendMode mode) : color(color), mode(mode) {} + DrawColorOp(DlColor color, DlBlendMode mode) + : DrawOpBase(kType), color(color), mode(mode) {} const DlColor color; const DlBlendMode mode; @@ -556,17 +579,18 @@ struct DrawColorOp final : DrawOpBase { // SkOval is same as DlRect // DlRoundRect is 48 more bytes, using 52 bytes which rounds up to 56 bytes // total (4 bytes unused) -#define DEFINE_DRAW_1ARG_OP(op_name, arg_type, arg_name) \ - struct Draw##op_name##Op final : DrawOpBase { \ - static constexpr auto kType = DisplayListOpType::kDraw##op_name; \ - \ - explicit Draw##op_name##Op(arg_type arg_name) : arg_name(arg_name) {} \ - \ - const arg_type arg_name; \ - \ - void dispatch(DlOpReceiver& receiver) const { \ - receiver.draw##op_name(arg_name); \ - } \ +#define DEFINE_DRAW_1ARG_OP(op_name, arg_type, arg_name) \ + struct Draw##op_name##Op final : DrawOpBase { \ + static constexpr auto kType = DisplayListOpType::kDraw##op_name; \ + \ + explicit Draw##op_name##Op(arg_type arg_name) \ + : DrawOpBase(kType), arg_name(arg_name) {} \ + \ + const arg_type arg_name; \ + \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.draw##op_name(arg_name); \ + } \ }; DEFINE_DRAW_1ARG_OP(Rect, DlRect, rect) DEFINE_DRAW_1ARG_OP(Oval, DlRect, oval) @@ -578,7 +602,7 @@ DEFINE_DRAW_1ARG_OP(RoundRect, DlRoundRect, rrect) struct DrawPathOp final : DrawOpBase { static constexpr auto kType = DisplayListOpType::kDrawPath; - explicit DrawPathOp(const DlPath& path) : path(path) {} + explicit DrawPathOp(const DlPath& path) : DrawOpBase(kType), path(path) {} const DlPath path; @@ -603,7 +627,7 @@ struct DrawPathOp final : DrawOpBase { static constexpr auto kType = DisplayListOpType::kDraw##op_name; \ \ Draw##op_name##Op(type1 name1, type2 name2) \ - : name1(name1), name2(name2) {} \ + : DrawOpBase(kType), name1(name1), name2(name2) {} \ \ const type1 name1; \ const type2 name2; \ @@ -625,7 +649,11 @@ struct DrawDashedLineOp final : DrawOpBase { const DlPoint& p1, DlScalar on_length, DlScalar off_length) - : p0(p0), p1(p1), on_length(on_length), off_length(off_length) {} + : DrawOpBase(kType), + p0(p0), + p1(p1), + on_length(on_length), + off_length(off_length) {} const DlPoint p0; const DlPoint p1; @@ -642,7 +670,11 @@ struct DrawArcOp final : DrawOpBase { static constexpr auto kType = DisplayListOpType::kDrawArc; DrawArcOp(DlRect bounds, DlScalar start, DlScalar sweep, bool center) - : bounds(bounds), start(start), sweep(sweep), center(center) {} + : DrawOpBase(kType), + bounds(bounds), + start(start), + sweep(sweep), + center(center) {} const DlRect bounds; const DlScalar start; @@ -664,7 +696,8 @@ struct DrawArcOp final : DrawOpBase { struct Draw##name##Op final : DrawOpBase { \ static constexpr auto kType = DisplayListOpType::kDraw##name; \ \ - explicit Draw##name##Op(uint32_t count) : count(count) {} \ + explicit Draw##name##Op(uint32_t count) \ + : DrawOpBase(kType), count(count) {} \ \ const uint32_t count; \ \ @@ -684,7 +717,7 @@ struct DrawVerticesOp final : DrawOpBase { explicit DrawVerticesOp(const std::shared_ptr& vertices, DlBlendMode mode) - : mode(mode), vertices(vertices) {} + : DrawOpBase(kType), mode(mode), vertices(vertices) {} const DlBlendMode mode; const std::shared_ptr vertices; @@ -696,29 +729,32 @@ struct DrawVerticesOp final : DrawOpBase { // 4 byte header + 40 byte payload uses 44 bytes but is rounded up to 48 bytes // (4 bytes unused) -#define DEFINE_DRAW_IMAGE_OP(name, with_attributes) \ - struct name##Op final : DrawOpBase { \ - static constexpr auto kType = DisplayListOpType::k##name; \ - \ - name##Op(const sk_sp& image, \ - const DlPoint& point, \ - DlImageSampling sampling) \ - : point(point), sampling(sampling), image(std::move(image)) {} \ - \ - const DlPoint point; \ - const DlImageSampling sampling; \ - const sk_sp image; \ - \ - void dispatch(DlOpReceiver& receiver) const { \ - receiver.drawImage(image, point, sampling, with_attributes); \ - } \ - \ - DisplayListCompare equals(const name##Op* other) const { \ - return (point == other->point && sampling == other->sampling && \ - image->Equals(other->image)) \ - ? DisplayListCompare::kEqual \ - : DisplayListCompare::kNotEqual; \ - } \ +#define DEFINE_DRAW_IMAGE_OP(name, with_attributes) \ + struct name##Op final : DrawOpBase { \ + static constexpr auto kType = DisplayListOpType::k##name; \ + \ + name##Op(const sk_sp& image, \ + const DlPoint& point, \ + DlImageSampling sampling) \ + : DrawOpBase(kType), \ + point(point), \ + sampling(sampling), \ + image(std::move(image)) {} \ + \ + const DlPoint point; \ + const DlImageSampling sampling; \ + const sk_sp image; \ + \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.drawImage(image, point, sampling, with_attributes); \ + } \ + \ + DisplayListCompare equals(const name##Op* other) const { \ + return (point == other->point && sampling == other->sampling && \ + image->Equals(other->image)) \ + ? DisplayListCompare::kEqual \ + : DisplayListCompare::kNotEqual; \ + } \ }; DEFINE_DRAW_IMAGE_OP(DrawImage, false) DEFINE_DRAW_IMAGE_OP(DrawImageWithAttr, true) @@ -735,7 +771,8 @@ struct DrawImageRectOp final : DrawOpBase { DlImageSampling sampling, bool render_with_attributes, DlCanvas::SrcRectConstraint constraint) - : src(src), + : DrawOpBase(kType), + src(src), dst(dst), sampling(sampling), render_with_attributes(render_with_attributes), @@ -765,33 +802,37 @@ struct DrawImageRectOp final : DrawOpBase { }; // 4 byte header + 44 byte payload packs efficiently into 48 bytes -#define DEFINE_DRAW_IMAGE_NINE_OP(name, render_with_attributes) \ - struct name##Op final : DrawOpBase { \ - static constexpr auto kType = DisplayListOpType::k##name; \ - static constexpr uint32_t kDepthInc = 9; \ - \ - name##Op(const sk_sp& image, \ - const DlIRect& center, \ - const DlRect& dst, \ - DlFilterMode mode) \ - : center(center), dst(dst), mode(mode), image(std::move(image)) {} \ - \ - const DlIRect center; \ - const DlRect dst; \ - const DlFilterMode mode; \ - const sk_sp image; \ - \ - void dispatch(DlOpReceiver& receiver) const { \ - receiver.drawImageNine(image, center, dst, mode, \ - render_with_attributes); \ - } \ - \ - DisplayListCompare equals(const name##Op* other) const { \ - return (center == other->center && dst == other->dst && \ - mode == other->mode && image->Equals(other->image)) \ - ? DisplayListCompare::kEqual \ - : DisplayListCompare::kNotEqual; \ - } \ +#define DEFINE_DRAW_IMAGE_NINE_OP(name, render_with_attributes) \ + struct name##Op final : DrawOpBase { \ + static constexpr auto kType = DisplayListOpType::k##name; \ + static constexpr uint32_t kDepthInc = 9; \ + \ + name##Op(const sk_sp& image, \ + const DlIRect& center, \ + const DlRect& dst, \ + DlFilterMode mode) \ + : DrawOpBase(kType), \ + center(center), \ + dst(dst), \ + mode(mode), \ + image(std::move(image)) {} \ + \ + const DlIRect center; \ + const DlRect dst; \ + const DlFilterMode mode; \ + const sk_sp image; \ + \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.drawImageNine(image, center, dst, mode, \ + render_with_attributes); \ + } \ + \ + DisplayListCompare equals(const name##Op* other) const { \ + return (center == other->center && dst == other->dst && \ + mode == other->mode && image->Equals(other->image)) \ + ? DisplayListCompare::kEqual \ + : DisplayListCompare::kNotEqual; \ + } \ }; DEFINE_DRAW_IMAGE_NINE_OP(DrawImageNine, false) DEFINE_DRAW_IMAGE_NINE_OP(DrawImageNineWithAttr, true) @@ -805,13 +846,15 @@ DEFINE_DRAW_IMAGE_NINE_OP(DrawImageNineWithAttr, true) // DlColor list only packs well if the count is even, otherwise there // can be 4 unusued bytes at the end. struct DrawAtlasBaseOp : DrawOpBase { - DrawAtlasBaseOp(const sk_sp& atlas, + DrawAtlasBaseOp(DisplayListOpType type, + const sk_sp& atlas, int count, DlBlendMode mode, DlImageSampling sampling, bool has_colors, bool render_with_attributes) - : count(count), + : DrawOpBase(type), + count(count), mode_index(static_cast(mode)), has_colors(has_colors), render_with_attributes(render_with_attributes), @@ -854,7 +897,8 @@ struct DrawAtlasOp final : DrawAtlasBaseOp { DlImageSampling sampling, bool has_colors, bool render_with_attributes) - : DrawAtlasBaseOp(atlas, + : DrawAtlasBaseOp(kType, + atlas, count, mode, sampling, @@ -894,7 +938,8 @@ struct DrawAtlasCulledOp final : DrawAtlasBaseOp { bool has_colors, const DlRect& cull_rect, bool render_with_attributes) - : DrawAtlasBaseOp(atlas, + : DrawAtlasBaseOp(kType, + atlas, count, mode, sampling, @@ -931,7 +976,7 @@ struct DrawDisplayListOp final : DrawOpBase { explicit DrawDisplayListOp(const sk_sp& display_list, DlScalar opacity) - : opacity(opacity), display_list(display_list) {} + : DrawOpBase(kType), opacity(opacity), display_list(display_list) {} DlScalar opacity; const sk_sp display_list; @@ -954,7 +999,7 @@ struct DrawTextBlobOp final : DrawOpBase { static constexpr auto kType = DisplayListOpType::kDrawTextBlob; DrawTextBlobOp(const sk_sp& blob, DlScalar x, DlScalar y) - : x(x), y(y), blob(blob) {} + : DrawOpBase(kType), x(x), y(y), blob(blob) {} const DlScalar x; const DlScalar y; @@ -971,7 +1016,7 @@ struct DrawTextFrameOp final : DrawOpBase { DrawTextFrameOp(const std::shared_ptr& text_frame, DlScalar x, DlScalar y) - : x(x), y(y), text_frame(text_frame) {} + : DrawOpBase(kType), x(x), y(y), text_frame(text_frame) {} const DlScalar x; const DlScalar y; @@ -991,7 +1036,11 @@ struct DrawTextFrameOp final : DrawOpBase { DlColor color, \ DlScalar elevation, \ DlScalar dpr) \ - : color(color), elevation(elevation), dpr(dpr), path(path) {} \ + : DrawOpBase(kType), \ + color(color), \ + elevation(elevation), \ + dpr(dpr), \ + path(path) {} \ \ const DlColor color; \ const DlScalar elevation; \ diff --git a/display_list/dl_paint.cc b/display_list/dl_paint.cc index 498f2ec2e8fda..76a682eab9967 100644 --- a/display_list/dl_paint.cc +++ b/display_list/dl_paint.cc @@ -4,6 +4,8 @@ #include "flutter/display_list/dl_paint.h" +#include "flutter/display_list/utils/dl_comparable.h" + namespace flutter { DlPaint::DlPaint(DlColor color) diff --git a/display_list/dl_paint.h b/display_list/dl_paint.h index 62bf15a04c52f..409bd80602558 100644 --- a/display_list/dl_paint.h +++ b/display_list/dl_paint.h @@ -150,11 +150,11 @@ class DlPaint { return *this; } - std::shared_ptr getImageFilter() const { + std::shared_ptr getImageFilter() const { return image_filter_; } const DlImageFilter* getImageFilterPtr() const { return image_filter_.get(); } - DlPaint& setImageFilter(const std::shared_ptr& filter) { + DlPaint& setImageFilter(const std::shared_ptr& filter) { image_filter_ = filter; return *this; } @@ -178,6 +178,11 @@ class DlPaint { bool isDefault() const { return *this == kDefault; } + bool usesRuntimeEffect() const { + return ((color_source_ && color_source_->asRuntimeEffect()) || + (image_filter_ && image_filter_->asRuntimeEffectFilter())); + } + bool operator==(DlPaint const& other) const; bool operator!=(DlPaint const& other) const { return !(*this == other); } @@ -212,7 +217,7 @@ class DlPaint { std::shared_ptr color_source_; std::shared_ptr color_filter_; - std::shared_ptr image_filter_; + std::shared_ptr image_filter_; std::shared_ptr mask_filter_; }; diff --git a/display_list/dl_paint_unittests.cc b/display_list/dl_paint_unittests.cc index 4974a209a7052..1f76459af758a 100644 --- a/display_list/dl_paint_unittests.cc +++ b/display_list/dl_paint_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/display_list/dl_paint.h" +#include "flutter/display_list/testing/dl_test_equality.h" #include "flutter/display_list/utils/dl_comparable.h" #include "gtest/gtest.h" @@ -55,14 +56,24 @@ TEST(DisplayListPaint, ConstructorDefaults) { EXPECT_NE(paint, DlPaint().setStrokeWidth(6)); EXPECT_NE(paint, DlPaint().setStrokeMiter(7)); - DlColorColorSource color_source(DlColor::kMagenta()); - EXPECT_NE(paint, DlPaint().setColorSource(color_source.shared())); - - DlBlendColorFilter color_filter(DlColor::kYellow(), DlBlendMode::kDstIn); - EXPECT_NE(paint, DlPaint().setColorFilter(color_filter.shared())); - - DlBlurImageFilter image_filter(1.3, 4.7, DlTileMode::kClamp); - EXPECT_NE(paint, DlPaint().setImageFilter(image_filter.shared())); + DlColor colors[2] = { + DlColor::kGreen(), + DlColor::kBlue(), + }; + float stops[2] = { + 0.0f, + 1.0f, + }; + auto color_source = DlColorSource::MakeLinear({0, 0}, {10, 10}, 2, colors, + stops, DlTileMode::kClamp); + EXPECT_NE(paint, DlPaint().setColorSource(color_source)); + + auto color_filter = + DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kDstATop); + EXPECT_NE(paint, DlPaint().setColorFilter(color_filter)); + + auto image_filter = DlImageFilter::MakeBlur(1.3, 4.7, DlTileMode::kClamp); + EXPECT_NE(paint, DlPaint().setImageFilter(image_filter)); DlBlurMaskFilter mask_filter(DlBlurStyle::kInner, 3.14); EXPECT_NE(paint, DlPaint().setMaskFilter(mask_filter.shared())); @@ -93,24 +104,31 @@ TEST(DisplayListPaint, NullSharedPointerSetGet) { } TEST(DisplayListPaint, ChainingConstructor) { + DlColor colors[2] = { + DlColor::kGreen(), + DlColor::kBlue(), + }; + float stops[2] = { + 0.0f, + 1.0f, + }; DlPaint paint = - DlPaint() // - .setAntiAlias(true) // - .setInvertColors(true) // - .setColor(DlColor::kGreen()) // - .setAlpha(0x7F) // - .setBlendMode(DlBlendMode::kLuminosity) // - .setDrawStyle(DlDrawStyle::kStrokeAndFill) // - .setStrokeCap(DlStrokeCap::kSquare) // - .setStrokeJoin(DlStrokeJoin::kBevel) // - .setStrokeWidth(42) // - .setStrokeMiter(1.5) // - .setColorSource(DlColorColorSource(DlColor::kMagenta()).shared()) // + DlPaint() // + .setAntiAlias(true) // + .setInvertColors(true) // + .setColor(DlColor::kGreen()) // + .setAlpha(0x7F) // + .setBlendMode(DlBlendMode::kLuminosity) // + .setDrawStyle(DlDrawStyle::kStrokeAndFill) // + .setStrokeCap(DlStrokeCap::kSquare) // + .setStrokeJoin(DlStrokeJoin::kBevel) // + .setStrokeWidth(42) // + .setStrokeMiter(1.5) // + .setColorSource(DlColorSource::MakeLinear( // + {0, 0}, {10, 10}, 2, colors, stops, DlTileMode::kClamp)) // .setColorFilter( - DlBlendColorFilter(DlColor::kYellow(), DlBlendMode::kDstIn) - .shared()) - .setImageFilter( - DlBlurImageFilter(1.3, 4.7, DlTileMode::kClamp).shared()) + DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kDstIn)) + .setImageFilter(DlImageFilter::MakeBlur(1.3, 4.7, DlTileMode::kClamp)) .setMaskFilter(DlBlurMaskFilter(DlBlurStyle::kInner, 3.14).shared()); EXPECT_TRUE(paint.isAntiAlias()); EXPECT_TRUE(paint.isInvertColors()); @@ -122,16 +140,63 @@ TEST(DisplayListPaint, ChainingConstructor) { EXPECT_EQ(paint.getStrokeJoin(), DlStrokeJoin::kBevel); EXPECT_EQ(paint.getStrokeWidth(), 42); EXPECT_EQ(paint.getStrokeMiter(), 1.5); - EXPECT_EQ(*paint.getColorSource(), DlColorColorSource(DlColor::kMagenta())); - EXPECT_EQ(*paint.getColorFilter(), - DlBlendColorFilter(DlColor::kYellow(), DlBlendMode::kDstIn)); - EXPECT_EQ(*paint.getImageFilter(), - DlBlurImageFilter(1.3, 4.7, DlTileMode::kClamp)); + EXPECT_TRUE(Equals(paint.getColorSource(), + DlColorSource::MakeLinear({0, 0}, {10, 10}, 2, colors, + stops, DlTileMode::kClamp))); + EXPECT_TRUE(Equals( + paint.getColorFilter(), + DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kDstIn))); + EXPECT_TRUE(Equals(paint.getImageFilter(), + DlImageFilter::MakeBlur(1.3, 4.7, DlTileMode::kClamp))); EXPECT_EQ(*paint.getMaskFilter(), DlBlurMaskFilter(DlBlurStyle::kInner, 3.14)); EXPECT_NE(paint, DlPaint()); } +TEST(DisplayListPaint, PaintDetectsRuntimeEffects) { + const auto runtime_effect = DlRuntimeEffect::MakeSkia( + SkRuntimeEffect::MakeForShader( + SkString("vec4 main(vec2 p) { return vec4(0); }")) + .effect); + auto color_source = DlColorSource::MakeRuntimeEffect( + runtime_effect, {}, std::make_shared>()); + auto image_filter = DlImageFilter::MakeRuntimeEffect( + runtime_effect, {}, std::make_shared>()); + DlPaint paint; + + EXPECT_FALSE(paint.usesRuntimeEffect()); + paint.setColorSource(color_source); + EXPECT_TRUE(paint.usesRuntimeEffect()); + paint.setColorSource(nullptr); + EXPECT_FALSE(paint.usesRuntimeEffect()); + + EXPECT_FALSE(paint.usesRuntimeEffect()); + paint.setImageFilter(image_filter); + EXPECT_TRUE(paint.usesRuntimeEffect()); + paint.setImageFilter(nullptr); + EXPECT_FALSE(paint.usesRuntimeEffect()); + + EXPECT_FALSE(paint.usesRuntimeEffect()); + paint.setColorSource(color_source); + EXPECT_TRUE(paint.usesRuntimeEffect()); + paint.setImageFilter(image_filter); + EXPECT_TRUE(paint.usesRuntimeEffect()); + paint.setImageFilter(nullptr); + EXPECT_TRUE(paint.usesRuntimeEffect()); + paint.setColorSource(nullptr); + EXPECT_FALSE(paint.usesRuntimeEffect()); + + EXPECT_FALSE(paint.usesRuntimeEffect()); + paint.setColorSource(color_source); + EXPECT_TRUE(paint.usesRuntimeEffect()); + paint.setImageFilter(image_filter); + EXPECT_TRUE(paint.usesRuntimeEffect()); + paint.setColorSource(nullptr); + EXPECT_TRUE(paint.usesRuntimeEffect()); + paint.setImageFilter(nullptr); + EXPECT_FALSE(paint.usesRuntimeEffect()); +} + } // namespace testing } // namespace flutter diff --git a/display_list/dl_storage.cc b/display_list/dl_storage.cc new file mode 100644 index 0000000000000..9a36d461980f0 --- /dev/null +++ b/display_list/dl_storage.cc @@ -0,0 +1,62 @@ +// 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. + +#include "flutter/display_list/dl_storage.h" + +namespace flutter { + +static constexpr inline bool is_power_of_two(int value) { + return (value & (value - 1)) == 0; +} + +void DisplayListStorage::realloc(size_t count) { + ptr_.reset(static_cast(std::realloc(ptr_.release(), count))); + FML_CHECK(ptr_); + allocated_ = count; +} + +uint8_t* DisplayListStorage::allocate(size_t needed) { + if (used_ + needed > allocated_) { + static_assert(is_power_of_two(kDLPageSize), + "This math needs updating for non-pow2."); + // Next greater multiple of DL_BUILDER_PAGE. + size_t new_size = (used_ + needed + kDLPageSize) & ~(kDLPageSize - 1); + size_t old_size = allocated_; + realloc(new_size); + FML_CHECK(ptr_.get()); + FML_CHECK(allocated_ == new_size); + FML_CHECK(allocated_ >= old_size); + FML_CHECK(used_ + needed <= allocated_); + memset(ptr_.get() + used_, 0, allocated_ - old_size); + } + uint8_t* ret = ptr_.get() + used_; + used_ += needed; + FML_CHECK(used_ <= allocated_); + return ret; +} + +DisplayListStorage::DisplayListStorage(DisplayListStorage&& source) { + ptr_ = std::move(source.ptr_); + used_ = source.used_; + allocated_ = source.allocated_; + source.used_ = 0u; + source.allocated_ = 0u; +} + +void DisplayListStorage::reset() { + ptr_.reset(); + used_ = 0u; + allocated_ = 0u; +} + +DisplayListStorage& DisplayListStorage::operator=(DisplayListStorage&& source) { + ptr_ = std::move(source.ptr_); + used_ = source.used_; + allocated_ = source.allocated_; + source.used_ = 0u; + source.allocated_ = 0u; + return *this; +} + +} // namespace flutter diff --git a/display_list/dl_storage.h b/display_list/dl_storage.h new file mode 100644 index 0000000000000..89108d75b5fa2 --- /dev/null +++ b/display_list/dl_storage.h @@ -0,0 +1,60 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_DL_STORAGE_H_ +#define FLUTTER_DISPLAY_LIST_DL_STORAGE_H_ + +#include + +#include "flutter/fml/logging.h" + +namespace flutter { + +// Manages a buffer allocated with malloc. +class DisplayListStorage { + public: + static const constexpr size_t kDLPageSize = 4096u; + + DisplayListStorage() = default; + DisplayListStorage(DisplayListStorage&&); + + /// Returns a pointer to the base of the storage. + uint8_t* base() { return ptr_.get(); } + const uint8_t* base() const { return ptr_.get(); } + + /// Returns the currently allocated size + size_t size() const { return used_; } + + /// Returns the maximum currently allocated space + size_t capacity() const { return allocated_; } + + /// Ensures the indicated number of bytes are available and returns + /// a pointer to that memory within the storage while also invalidating + /// any other outstanding pointers into the storage. + uint8_t* allocate(size_t needed); + + /// Trims the storage to the currently allocated size and invalidates + /// any outstanding pointers into the storage. + void trim() { realloc(used_); } + + /// Resets the storage and allocation of the object to an empty state + void reset(); + + DisplayListStorage& operator=(DisplayListStorage&& other); + + private: + void realloc(size_t count); + + struct FreeDeleter { + void operator()(uint8_t* p) { std::free(p); } + }; + std::unique_ptr ptr_; + + size_t used_ = 0u; + size_t allocated_ = 0u; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_DL_STORAGE_H_ diff --git a/display_list/dl_storage_unittests.cc b/display_list/dl_storage_unittests.cc new file mode 100644 index 0000000000000..78d6dcef0cbc4 --- /dev/null +++ b/display_list/dl_storage_unittests.cc @@ -0,0 +1,47 @@ +// 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. + +#include "flutter/display_list/dl_storage.h" + +#include "flutter/testing/testing.h" + +namespace flutter { +namespace testing { + +TEST(DisplayListStorage, DefaultConstructed) { + DisplayListStorage storage; + EXPECT_EQ(storage.base(), nullptr); + EXPECT_EQ(storage.size(), 0u); + EXPECT_EQ(storage.capacity(), 0u); +} + +TEST(DisplayListStorage, Allocation) { + DisplayListStorage storage; + EXPECT_NE(storage.allocate(10u), nullptr); + EXPECT_NE(storage.base(), nullptr); + EXPECT_EQ(storage.size(), 10u); + EXPECT_EQ(storage.capacity(), DisplayListStorage::kDLPageSize); +} + +TEST(DisplayListStorage, PostMove) { + DisplayListStorage original; + EXPECT_NE(original.allocate(10u), nullptr); + + DisplayListStorage moved = std::move(original); + + // NOLINTBEGIN(bugprone-use-after-move) + // NOLINTBEGIN(clang-analyzer-cplusplus.Move) + EXPECT_EQ(original.base(), nullptr); + EXPECT_EQ(original.size(), 0u); + EXPECT_EQ(original.capacity(), 0u); + // NOLINTEND(clang-analyzer-cplusplus.Move) + // NOLINTEND(bugprone-use-after-move) + + EXPECT_NE(moved.base(), nullptr); + EXPECT_EQ(moved.size(), 10u); + EXPECT_EQ(moved.capacity(), DisplayListStorage::kDLPageSize); +} + +} // namespace testing +} // namespace flutter diff --git a/display_list/effects/color_filters/dl_blend_color_filter.cc b/display_list/effects/color_filters/dl_blend_color_filter.cc new file mode 100644 index 0000000000000..98f1af11c0a05 --- /dev/null +++ b/display_list/effects/color_filters/dl_blend_color_filter.cc @@ -0,0 +1,126 @@ +// 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. + +#include "flutter/display_list/effects/color_filters/dl_blend_color_filter.h" + +namespace flutter { + +std::shared_ptr DlBlendColorFilter::Make( + DlColor color, + DlBlendMode mode) { + switch (mode) { + case DlBlendMode::kDst: { + return nullptr; + } + case DlBlendMode::kSrcOver: { + if (color.isTransparent()) { + return nullptr; + } + if (color.isOpaque()) { + mode = DlBlendMode::kSrc; + } + break; + } + case DlBlendMode::kDstOver: + case DlBlendMode::kDstOut: + case DlBlendMode::kSrcATop: + case DlBlendMode::kXor: + case DlBlendMode::kDarken: { + if (color.isTransparent()) { + return nullptr; + } + break; + } + case DlBlendMode::kDstIn: { + if (color.isOpaque()) { + return nullptr; + } + break; + } + default: + break; + } + return std::make_shared(color, mode); +} + +bool DlBlendColorFilter::modifies_transparent_black() const { + switch (mode_) { + // These modes all act like kSrc when the dest is all 0s. + // So they modify transparent black when the src color is + // not transparent. + case DlBlendMode::kSrc: + case DlBlendMode::kSrcOver: + case DlBlendMode::kDstOver: + case DlBlendMode::kSrcOut: + case DlBlendMode::kDstATop: + case DlBlendMode::kXor: + case DlBlendMode::kPlus: + case DlBlendMode::kScreen: + case DlBlendMode::kOverlay: + case DlBlendMode::kDarken: + case DlBlendMode::kLighten: + case DlBlendMode::kColorDodge: + case DlBlendMode::kColorBurn: + case DlBlendMode::kHardLight: + case DlBlendMode::kSoftLight: + case DlBlendMode::kDifference: + case DlBlendMode::kExclusion: + case DlBlendMode::kMultiply: + case DlBlendMode::kHue: + case DlBlendMode::kSaturation: + case DlBlendMode::kColor: + case DlBlendMode::kLuminosity: + return !color_.isTransparent(); + + // These modes are all like kDst when the dest is all 0s. + // So they never modify transparent black. + case DlBlendMode::kClear: + case DlBlendMode::kDst: + case DlBlendMode::kSrcIn: + case DlBlendMode::kDstIn: + case DlBlendMode::kDstOut: + case DlBlendMode::kSrcATop: + case DlBlendMode::kModulate: + return false; + } +} + +bool DlBlendColorFilter::can_commute_with_opacity() const { + switch (mode_) { + case DlBlendMode::kClear: + case DlBlendMode::kDst: + case DlBlendMode::kSrcIn: + case DlBlendMode::kDstIn: + case DlBlendMode::kDstOut: + case DlBlendMode::kSrcATop: + case DlBlendMode::kModulate: + return true; + + case DlBlendMode::kSrc: + case DlBlendMode::kSrcOver: + case DlBlendMode::kDstOver: + case DlBlendMode::kSrcOut: + case DlBlendMode::kDstATop: + case DlBlendMode::kXor: + case DlBlendMode::kPlus: + case DlBlendMode::kScreen: + case DlBlendMode::kOverlay: + case DlBlendMode::kDarken: + case DlBlendMode::kLighten: + case DlBlendMode::kColorDodge: + case DlBlendMode::kColorBurn: + case DlBlendMode::kHardLight: + case DlBlendMode::kSoftLight: + case DlBlendMode::kDifference: + case DlBlendMode::kExclusion: + case DlBlendMode::kMultiply: + case DlBlendMode::kHue: + case DlBlendMode::kSaturation: + case DlBlendMode::kColor: + case DlBlendMode::kLuminosity: + return color_.isTransparent(); + } +} + +} // namespace flutter diff --git a/display_list/effects/color_filters/dl_blend_color_filter.h b/display_list/effects/color_filters/dl_blend_color_filter.h new file mode 100644 index 0000000000000..f79f82ed1ac6c --- /dev/null +++ b/display_list/effects/color_filters/dl_blend_color_filter.h @@ -0,0 +1,60 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_BLEND_COLOR_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_BLEND_COLOR_FILTER_H_ + +#include "flutter/display_list/effects/dl_color_filter.h" + +namespace flutter { + +// The Blend type of ColorFilter which specifies modifying the +// colors as if the color specified in the Blend filter is the +// source color and the color drawn by the rendering operation +// is the destination color. The mode parameter of the Blend +// filter is then used to combine those colors. +class DlBlendColorFilter final : public DlColorFilter { + public: + DlBlendColorFilter(DlColor color, DlBlendMode mode) + : color_(color), mode_(mode) {} + DlBlendColorFilter(const DlBlendColorFilter& filter) + : DlBlendColorFilter(filter.color_, filter.mode_) {} + explicit DlBlendColorFilter(const DlBlendColorFilter* filter) + : DlBlendColorFilter(filter->color_, filter->mode_) {} + + DlColorFilterType type() const override { return DlColorFilterType::kBlend; } + size_t size() const override { return sizeof(*this); } + + bool modifies_transparent_black() const override; + bool can_commute_with_opacity() const override; + + std::shared_ptr shared() const override { + return std::make_shared(this); + } + + const DlBlendColorFilter* asBlend() const override { return this; } + + DlColor color() const { return color_; } + DlBlendMode mode() const { return mode_; } + + protected: + bool equals_(DlColorFilter const& other) const override { + FML_DCHECK(other.type() == DlColorFilterType::kBlend); + auto that = static_cast(&other); + return color_ == that->color_ && mode_ == that->mode_; + } + + private: + static std::shared_ptr Make(DlColor color, + DlBlendMode mode); + + DlColor color_; + DlBlendMode mode_; + + friend class DlColorFilter; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_BLEND_COLOR_FILTER_H_ diff --git a/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.cc b/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.cc new file mode 100644 index 0000000000000..b2dc9e51dd820 --- /dev/null +++ b/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.cc @@ -0,0 +1,13 @@ +// 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. + +#include "flutter/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.h" + +namespace flutter { + +const std::shared_ptr + DlLinearToSrgbGammaColorFilter::kInstance = + std::make_shared(); + +} // namespace flutter diff --git a/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.h b/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.h new file mode 100644 index 0000000000000..b3c6ef1094e86 --- /dev/null +++ b/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_LINEAR_TO_SRGB_GAMMA_COLOR_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_LINEAR_TO_SRGB_GAMMA_COLOR_FILTER_H_ + +#include "flutter/display_list/effects/dl_color_filter.h" + +namespace flutter { + +// The LinearToSrgb type of ColorFilter that applies the sRGB gamma curve +// to the rendered pixels. +class DlLinearToSrgbGammaColorFilter final : public DlColorFilter { + public: + DlLinearToSrgbGammaColorFilter() {} + DlLinearToSrgbGammaColorFilter(const DlLinearToSrgbGammaColorFilter& filter) + : DlLinearToSrgbGammaColorFilter() {} + explicit DlLinearToSrgbGammaColorFilter( + const DlLinearToSrgbGammaColorFilter* filter) + : DlLinearToSrgbGammaColorFilter() {} + + DlColorFilterType type() const override { + return DlColorFilterType::kLinearToSrgbGamma; + } + size_t size() const override { return sizeof(*this); } + bool modifies_transparent_black() const override { return false; } + bool can_commute_with_opacity() const override { return true; } + + std::shared_ptr shared() const override { return kInstance; } + + protected: + bool equals_(const DlColorFilter& other) const override { + FML_DCHECK(other.type() == DlColorFilterType::kLinearToSrgbGamma); + return true; + } + + private: + static const std::shared_ptr kInstance; + + friend class DlColorFilter; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_LINEAR_TO_SRGB_GAMMA_COLOR_FILTER_H_ diff --git a/display_list/effects/color_filters/dl_matrix_color_filter.cc b/display_list/effects/color_filters/dl_matrix_color_filter.cc new file mode 100644 index 0000000000000..8837c71bac941 --- /dev/null +++ b/display_list/effects/color_filters/dl_matrix_color_filter.cc @@ -0,0 +1,74 @@ +// 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. + +#include "flutter/display_list/effects/color_filters/dl_matrix_color_filter.h" + +namespace flutter { + +std::shared_ptr DlMatrixColorFilter::Make( + const float matrix[20]) { + float product = 0; + for (int i = 0; i < 20; i++) { + product *= matrix[i]; + } + // If any of the elements of the matrix are infinity or NaN, then + // |product| will be NaN, otherwise 0. + if (product == 0) { + return std::make_shared(matrix); + } + return nullptr; +} + +bool DlMatrixColorFilter::modifies_transparent_black() const { + // Values are considered in non-premultiplied form when the matrix is + // applied, but we only care about this answer for whether it leaves + // an incoming color with a transparent alpha as transparent on output. + // Thus, we only need to consider the alpha part of the matrix equation, + // which is the last row. Since the incoming alpha value is 0, the last + // equation ends up becoming A' = matrix_[19]. Negative results will be + // clamped to the range [0,1] so we only care about positive values. + // Non-finite values are clamped to a zero alpha. + return (std::isfinite(matrix_[19]) && matrix_[19] > 0); +} + +bool DlMatrixColorFilter::can_commute_with_opacity() const { + // We need to check if: + // filter(color) * opacity == filter(color * opacity). + // + // filter(RGBA) = R' = [ R*m[ 0] + G*m[ 1] + B*m[ 2] + A*m[ 3] + m[ 4] ] + // G' = [ R*m[ 5] + G*m[ 6] + B*m[ 7] + A*m[ 8] + m[ 9] ] + // B' = [ R*m[10] + G*m[11] + B*m[12] + A*m[13] + m[14] ] + // A' = [ R*m[15] + G*m[16] + B*m[17] + A*m[18] + m[19] ] + // + // Applying the opacity only affects the alpha value since the operations + // are performed on non-premultiplied colors. (If the data is stored in + // premultiplied form, though, there may be rounding errors due to + // premul->unpremul->premul conversions.) + + // We test for the successful cases and return false if they fail so that + // we fail and return false if any matrix values are NaN. + + // If any of the alpha column are non-zero then the prior alpha affects + // the result color, so applying opacity before the filter will change + // the incoming alpha and therefore the colors that are produced. + if (!(matrix_[3] == 0 && // A does not affect R' + matrix_[8] == 0 && // A does not affect G' + matrix_[13] == 0)) { // A does not affect B' + return false; + } + + // Similarly, if any of the alpha row are non-zero then the prior colors + // affect the result alpha in a way that prevents opacity from commuting + // through the filter operation. + if (!(matrix_[15] == 0 && // R does not affect A' + matrix_[16] == 0 && // G does not affect A' + matrix_[17] == 0 && // B does not affect A' + matrix_[19] == 0)) { // A' is not offset by an absolute value + return false; + } + + return true; +} + +} // namespace flutter diff --git a/display_list/effects/color_filters/dl_matrix_color_filter.h b/display_list/effects/color_filters/dl_matrix_color_filter.h new file mode 100644 index 0000000000000..271fa8cac919c --- /dev/null +++ b/display_list/effects/color_filters/dl_matrix_color_filter.h @@ -0,0 +1,72 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_MATRIX_COLOR_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_MATRIX_COLOR_FILTER_H_ + +#include "flutter/display_list/effects/dl_color_filter.h" + +namespace flutter { + +// The Matrix type of ColorFilter which runs every pixel drawn by +// the rendering operation [iR,iG,iB,iA] through a vector/matrix +// multiplication, as in: +// +// [ oR ] [ m[ 0] m[ 1] m[ 2] m[ 3] m[ 4] ] [ iR ] +// [ oG ] [ m[ 5] m[ 6] m[ 7] m[ 8] m[ 9] ] [ iG ] +// [ oB ] = [ m[10] m[11] m[12] m[13] m[14] ] x [ iB ] +// [ oA ] [ m[15] m[16] m[17] m[18] m[19] ] [ iA ] +// [ 1 ] +// +// The resulting color [oR,oG,oB,oA] is then clamped to the range of +// valid pixel components before storing in the output. +// +// The incoming and outgoing [iR,iG,iB,iA] and [oR,oG,oB,oA] are +// considered to be non-premultiplied. When working on premultiplied +// pixel data, the necessary pre<->non-pre conversions must be performed. +class DlMatrixColorFilter final : public DlColorFilter { + public: + explicit DlMatrixColorFilter(const float matrix[20]) { + memcpy(matrix_, matrix, sizeof(matrix_)); + } + DlMatrixColorFilter(const DlMatrixColorFilter& filter) + : DlMatrixColorFilter(filter.matrix_) {} + explicit DlMatrixColorFilter(const DlMatrixColorFilter* filter) + : DlMatrixColorFilter(filter->matrix_) {} + + DlColorFilterType type() const override { return DlColorFilterType::kMatrix; } + size_t size() const override { return sizeof(*this); } + + bool modifies_transparent_black() const override; + bool can_commute_with_opacity() const override; + + std::shared_ptr shared() const override { + return std::make_shared(this); + } + + const DlMatrixColorFilter* asMatrix() const override { return this; } + + const float& operator[](int index) const { return matrix_[index]; } + void get_matrix(float matrix[20]) const { + memcpy(matrix, matrix_, sizeof(matrix_)); + } + + protected: + bool equals_(const DlColorFilter& other) const override { + FML_DCHECK(other.type() == DlColorFilterType::kMatrix); + auto that = static_cast(&other); + return memcmp(matrix_, that->matrix_, sizeof(matrix_)) == 0; + } + + private: + static std::shared_ptr Make(const float matrix[20]); + + float matrix_[20]; + + friend class DlColorFilter; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_MATRIX_COLOR_FILTER_H_ diff --git a/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.cc b/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.cc new file mode 100644 index 0000000000000..30b3db2017360 --- /dev/null +++ b/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.cc @@ -0,0 +1,13 @@ +// 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. + +#include "flutter/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.h" + +namespace flutter { + +const std::shared_ptr + DlSrgbToLinearGammaColorFilter::kInstance = + std::make_shared(); + +} // namespace flutter diff --git a/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.h b/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.h new file mode 100644 index 0000000000000..393d8feee3f7f --- /dev/null +++ b/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_SRGB_TO_LINEAR_GAMMA_COLOR_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_SRGB_TO_LINEAR_GAMMA_COLOR_FILTER_H_ + +#include "flutter/display_list/effects/dl_color_filter.h" + +namespace flutter { + +// The SrgbToLinear type of ColorFilter that applies the inverse of the sRGB +// gamma curve to the rendered pixels. +class DlSrgbToLinearGammaColorFilter final : public DlColorFilter { + public: + DlSrgbToLinearGammaColorFilter() {} + DlSrgbToLinearGammaColorFilter(const DlSrgbToLinearGammaColorFilter& filter) + : DlSrgbToLinearGammaColorFilter() {} + explicit DlSrgbToLinearGammaColorFilter( + const DlSrgbToLinearGammaColorFilter* filter) + : DlSrgbToLinearGammaColorFilter() {} + + DlColorFilterType type() const override { + return DlColorFilterType::kSrgbToLinearGamma; + } + size_t size() const override { return sizeof(*this); } + bool modifies_transparent_black() const override { return false; } + bool can_commute_with_opacity() const override { return true; } + + std::shared_ptr shared() const override { return kInstance; } + + protected: + bool equals_(const DlColorFilter& other) const override { + FML_DCHECK(other.type() == DlColorFilterType::kSrgbToLinearGamma); + return true; + } + + private: + static const std::shared_ptr kInstance; + + friend class DlColorFilter; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_FILTERS_DL_SRGB_TO_LINEAR_GAMMA_COLOR_FILTER_H_ diff --git a/display_list/effects/color_sources/dl_conical_gradient_color_source.cc b/display_list/effects/color_sources/dl_conical_gradient_color_source.cc new file mode 100644 index 0000000000000..0e94d0c6078d3 --- /dev/null +++ b/display_list/effects/color_sources/dl_conical_gradient_color_source.cc @@ -0,0 +1,54 @@ +// 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. + +#include "flutter/display_list/effects/color_sources/dl_conical_gradient_color_source.h" + +namespace flutter { + +DlConicalGradientColorSource::DlConicalGradientColorSource( + DlPoint start_center, + DlScalar start_radius, + DlPoint end_center, + DlScalar end_radius, + uint32_t stop_count, + const DlColor* colors, + const float* stops, + DlTileMode tile_mode, + const DlMatrix* matrix) + : DlGradientColorSourceBase(stop_count, tile_mode, matrix), + start_center_(start_center), + start_radius_(start_radius), + end_center_(end_center), + end_radius_(end_radius) { + store_color_stops(this + 1, colors, stops); +} + +DlConicalGradientColorSource::DlConicalGradientColorSource( + const DlConicalGradientColorSource* source) + : DlGradientColorSourceBase(source->stop_count(), + source->tile_mode(), + source->matrix_ptr()), + start_center_(source->start_center()), + start_radius_(source->start_radius()), + end_center_(source->end_center()), + end_radius_(source->end_radius()) { + store_color_stops(this + 1, source->colors(), source->stops()); +} + +std::shared_ptr DlConicalGradientColorSource::shared() const { + return MakeConical(start_center_, start_radius_, end_center_, end_radius_, + stop_count(), colors(), stops(), tile_mode(), + matrix_ptr()); +} + +bool DlConicalGradientColorSource::equals_(DlColorSource const& other) const { + FML_DCHECK(other.type() == DlColorSourceType::kConicalGradient); + auto that = static_cast(&other); + return (start_center_ == that->start_center_ && + start_radius_ == that->start_radius_ && + end_center_ == that->end_center_ && + end_radius_ == that->end_radius_ && base_equals_(that)); +} + +} // namespace flutter diff --git a/display_list/effects/color_sources/dl_conical_gradient_color_source.h b/display_list/effects/color_sources/dl_conical_gradient_color_source.h new file mode 100644 index 0000000000000..db523bc94c1c5 --- /dev/null +++ b/display_list/effects/color_sources/dl_conical_gradient_color_source.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_CONICAL_GRADIENT_COLOR_SOURCE_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_CONICAL_GRADIENT_COLOR_SOURCE_H_ + +#include "flutter/display_list/effects/color_sources/dl_gradient_color_source_base.h" + +namespace flutter { + +class DlConicalGradientColorSource final : public DlGradientColorSourceBase { + public: + const DlConicalGradientColorSource* asConicalGradient() const override { + return this; + } + + bool isUIThreadSafe() const override { return true; } + + std::shared_ptr shared() const override; + + DlColorSourceType type() const override { + return DlColorSourceType::kConicalGradient; + } + size_t size() const override { return sizeof(*this) + vector_sizes(); } + + DlPoint start_center() const { return start_center_; } + DlScalar start_radius() const { return start_radius_; } + DlPoint end_center() const { return end_center_; } + DlScalar end_radius() const { return end_radius_; } + + protected: + virtual const void* pod() const override { return this + 1; } + + bool equals_(DlColorSource const& other) const override; + + private: + DlConicalGradientColorSource(DlPoint start_center, + DlScalar start_radius, + DlPoint end_center, + DlScalar end_radius, + uint32_t stop_count, + const DlColor* colors, + const float* stops, + DlTileMode tile_mode, + const DlMatrix* matrix = nullptr); + + explicit DlConicalGradientColorSource( + const DlConicalGradientColorSource* source); + + DlPoint start_center_; + DlScalar start_radius_; + DlPoint end_center_; + DlScalar end_radius_; + + friend class DlColorSource; + friend class DisplayListBuilder; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlConicalGradientColorSource); +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_CONICAL_GRADIENT_COLOR_SOURCE_H_ diff --git a/display_list/effects/color_sources/dl_gradient_color_source_base.h b/display_list/effects/color_sources/dl_gradient_color_source_base.h new file mode 100644 index 0000000000000..f7a8620373e5b --- /dev/null +++ b/display_list/effects/color_sources/dl_gradient_color_source_base.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_GRADIENT_COLOR_SOURCE_BASE_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_GRADIENT_COLOR_SOURCE_BASE_H_ + +#include "flutter/display_list/effects/color_sources/dl_matrix_color_source_base.h" + +namespace flutter { + +// Utility base class common to all gradient DlColorSource implementations +class DlGradientColorSourceBase : public DlMatrixColorSourceBase { + public: + bool is_opaque() const override; + + bool isGradient() const override { return true; } + + DlTileMode tile_mode() const { return mode_; } + int stop_count() const { return stop_count_; } + const DlColor* colors() const { + return reinterpret_cast(pod()); + } + const float* stops() const { + return reinterpret_cast(colors() + stop_count()); + } + + protected: + DlGradientColorSourceBase(uint32_t stop_count, + DlTileMode tile_mode, + const DlMatrix* matrix = nullptr); + + size_t vector_sizes() const { + return stop_count_ * (sizeof(DlColor) + sizeof(float)); + } + + virtual const void* pod() const = 0; + + bool base_equals_(DlGradientColorSourceBase const* other_base) const; + + void store_color_stops(void* pod, + const DlColor* color_data, + const float* stop_data); + + private: + DlTileMode mode_; + uint32_t stop_count_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlGradientColorSourceBase); +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_GRADIENT_COLOR_SOURCE_BASE_H_ diff --git a/display_list/effects/color_sources/dl_image_color_source.cc b/display_list/effects/color_sources/dl_image_color_source.cc new file mode 100644 index 0000000000000..bd778efe93ca7 --- /dev/null +++ b/display_list/effects/color_sources/dl_image_color_source.cc @@ -0,0 +1,36 @@ +// 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. + +#include "flutter/display_list/effects/color_sources/dl_image_color_source.h" + +namespace flutter { + +DlImageColorSource::DlImageColorSource(sk_sp image, + DlTileMode horizontal_tile_mode, + DlTileMode vertical_tile_mode, + DlImageSampling sampling, + const DlMatrix* matrix) + : DlMatrixColorSourceBase(matrix), + image_(std::move(image)), + horizontal_tile_mode_(horizontal_tile_mode), + vertical_tile_mode_(vertical_tile_mode), + sampling_(sampling) {} + +std::shared_ptr DlImageColorSource::WithSampling( + DlImageSampling sampling) const { + return std::make_shared(image_, horizontal_tile_mode_, + vertical_tile_mode_, sampling, + matrix_ptr()); +} + +bool DlImageColorSource::equals_(DlColorSource const& other) const { + FML_DCHECK(other.type() == DlColorSourceType::kImage); + auto that = static_cast(&other); + return (image_->Equals(that->image_) && matrix() == that->matrix() && + horizontal_tile_mode_ == that->horizontal_tile_mode_ && + vertical_tile_mode_ == that->vertical_tile_mode_ && + sampling_ == that->sampling_); +} + +} // namespace flutter diff --git a/display_list/effects/color_sources/dl_image_color_source.h b/display_list/effects/color_sources/dl_image_color_source.h new file mode 100644 index 0000000000000..046b9352fcbb3 --- /dev/null +++ b/display_list/effects/color_sources/dl_image_color_source.h @@ -0,0 +1,56 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_IMAGE_COLOR_SOURCE_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_IMAGE_COLOR_SOURCE_H_ + +#include "flutter/display_list/effects/color_sources/dl_matrix_color_source_base.h" + +namespace flutter { + +class DlImageColorSource final : public DlMatrixColorSourceBase { + public: + DlImageColorSource(sk_sp image, + DlTileMode horizontal_tile_mode, + DlTileMode vertical_tile_mode, + DlImageSampling sampling = DlImageSampling::kLinear, + const DlMatrix* matrix = nullptr); + + bool isUIThreadSafe() const override { + return image_ ? image_->isUIThreadSafe() : true; + } + + const DlImageColorSource* asImage() const override { return this; } + + std::shared_ptr shared() const override { + return WithSampling(sampling_); + } + + std::shared_ptr WithSampling(DlImageSampling sampling) const; + + DlColorSourceType type() const override { return DlColorSourceType::kImage; } + size_t size() const override { return sizeof(*this); } + + bool is_opaque() const override { return image_->isOpaque(); } + + sk_sp image() const { return image_; } + DlTileMode horizontal_tile_mode() const { return horizontal_tile_mode_; } + DlTileMode vertical_tile_mode() const { return vertical_tile_mode_; } + DlImageSampling sampling() const { return sampling_; } + + protected: + bool equals_(DlColorSource const& other) const override; + + private: + sk_sp image_; + DlTileMode horizontal_tile_mode_; + DlTileMode vertical_tile_mode_; + DlImageSampling sampling_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlImageColorSource); +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_IMAGE_COLOR_SOURCE_H_ diff --git a/display_list/effects/color_sources/dl_linear_gradient_color_source.cc b/display_list/effects/color_sources/dl_linear_gradient_color_source.cc new file mode 100644 index 0000000000000..380dce96f95bd --- /dev/null +++ b/display_list/effects/color_sources/dl_linear_gradient_color_source.cc @@ -0,0 +1,45 @@ +// 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. + +#include "flutter/display_list/effects/color_sources/dl_linear_gradient_color_source.h" + +namespace flutter { + +DlLinearGradientColorSource::DlLinearGradientColorSource( + const DlPoint start_point, + const DlPoint end_point, + uint32_t stop_count, + const DlColor* colors, + const float* stops, + DlTileMode tile_mode, + const DlMatrix* matrix) + : DlGradientColorSourceBase(stop_count, tile_mode, matrix), + start_point_(start_point), + end_point_(end_point) { + store_color_stops(this + 1, colors, stops); +} + +DlLinearGradientColorSource::DlLinearGradientColorSource( + const DlLinearGradientColorSource* source) + : DlGradientColorSourceBase(source->stop_count(), + source->tile_mode(), + source->matrix_ptr()), + start_point_(source->start_point()), + end_point_(source->end_point()) { + store_color_stops(this + 1, source->colors(), source->stops()); +} + +std::shared_ptr DlLinearGradientColorSource::shared() const { + return MakeLinear(start_point_, end_point_, stop_count(), colors(), stops(), + tile_mode(), matrix_ptr()); +} + +bool DlLinearGradientColorSource::equals_(DlColorSource const& other) const { + FML_DCHECK(other.type() == DlColorSourceType::kLinearGradient); + auto that = static_cast(&other); + return (start_point_ == that->start_point_ && + end_point_ == that->end_point_ && base_equals_(that)); +} + +} // namespace flutter diff --git a/display_list/effects/color_sources/dl_linear_gradient_color_source.h b/display_list/effects/color_sources/dl_linear_gradient_color_source.h new file mode 100644 index 0000000000000..886d4ceb8811a --- /dev/null +++ b/display_list/effects/color_sources/dl_linear_gradient_color_source.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_LINEAR_GRADIENT_COLOR_SOURCE_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_LINEAR_GRADIENT_COLOR_SOURCE_H_ + +#include "flutter/display_list/effects/color_sources/dl_gradient_color_source_base.h" + +namespace flutter { + +class DlLinearGradientColorSource final : public DlGradientColorSourceBase { + public: + const DlLinearGradientColorSource* asLinearGradient() const override { + return this; + } + + bool isUIThreadSafe() const override { return true; } + + DlColorSourceType type() const override { + return DlColorSourceType::kLinearGradient; + } + size_t size() const override { return sizeof(*this) + vector_sizes(); } + + std::shared_ptr shared() const override; + + const DlPoint& start_point() const { return start_point_; } + const DlPoint& end_point() const { return end_point_; } + + protected: + virtual const void* pod() const override { return this + 1; } + + bool equals_(DlColorSource const& other) const override; + + private: + DlLinearGradientColorSource(const DlPoint start_point, + const DlPoint end_point, + uint32_t stop_count, + const DlColor* colors, + const float* stops, + DlTileMode tile_mode, + const DlMatrix* matrix = nullptr); + + explicit DlLinearGradientColorSource( + const DlLinearGradientColorSource* source); + + DlPoint start_point_; + DlPoint end_point_; + + friend class DlColorSource; + friend class DisplayListBuilder; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlLinearGradientColorSource); +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_LINEAR_GRADIENT_COLOR_SOURCE_H_ diff --git a/display_list/effects/color_sources/dl_matrix_color_source_base.h b/display_list/effects/color_sources/dl_matrix_color_source_base.h new file mode 100644 index 0000000000000..e9c178e5526ef --- /dev/null +++ b/display_list/effects/color_sources/dl_matrix_color_source_base.h @@ -0,0 +1,31 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_MATRIX_COLOR_SOURCE_BASE_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_MATRIX_COLOR_SOURCE_BASE_H_ + +#include "flutter/display_list/effects/dl_color_source.h" + +namespace flutter { + +// Utility base class common to all DlColorSource implementations that +// hold an optional DlMatrix +class DlMatrixColorSourceBase : public DlColorSource { + public: + const DlMatrix& matrix() const { return matrix_; } + const DlMatrix* matrix_ptr() const { + return matrix_.IsIdentity() ? nullptr : &matrix_; + } + + protected: + explicit DlMatrixColorSourceBase(const DlMatrix* matrix) + : matrix_(matrix ? *matrix : DlMatrix()) {} + + private: + const DlMatrix matrix_; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_MATRIX_COLOR_SOURCE_BASE_H_ diff --git a/display_list/effects/color_sources/dl_radial_gradient_color_source.cc b/display_list/effects/color_sources/dl_radial_gradient_color_source.cc new file mode 100644 index 0000000000000..7c217f649fbd7 --- /dev/null +++ b/display_list/effects/color_sources/dl_radial_gradient_color_source.cc @@ -0,0 +1,44 @@ +// 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. + +#include "flutter/display_list/effects/color_sources/dl_radial_gradient_color_source.h" + +namespace flutter { + +DlRadialGradientColorSource::DlRadialGradientColorSource(DlPoint center, + DlScalar radius, + uint32_t stop_count, + const DlColor* colors, + const float* stops, + DlTileMode tile_mode, + const DlMatrix* matrix) + : DlGradientColorSourceBase(stop_count, tile_mode, matrix), + center_(center), + radius_(radius) { + store_color_stops(this + 1, colors, stops); +} + +DlRadialGradientColorSource::DlRadialGradientColorSource( + const DlRadialGradientColorSource* source) + : DlGradientColorSourceBase(source->stop_count(), + source->tile_mode(), + source->matrix_ptr()), + center_(source->center()), + radius_(source->radius()) { + store_color_stops(this + 1, source->colors(), source->stops()); +} + +std::shared_ptr DlRadialGradientColorSource::shared() const { + return MakeRadial(center_, radius_, stop_count(), colors(), stops(), + tile_mode(), matrix_ptr()); +} + +bool DlRadialGradientColorSource::equals_(DlColorSource const& other) const { + FML_DCHECK(other.type() == DlColorSourceType::kRadialGradient); + auto that = static_cast(&other); + return (center_ == that->center_ && radius_ == that->radius_ && + base_equals_(that)); +} + +} // namespace flutter diff --git a/display_list/effects/color_sources/dl_radial_gradient_color_source.h b/display_list/effects/color_sources/dl_radial_gradient_color_source.h new file mode 100644 index 0000000000000..70b48d504578f --- /dev/null +++ b/display_list/effects/color_sources/dl_radial_gradient_color_source.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_RADIAL_GRADIENT_COLOR_SOURCE_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_RADIAL_GRADIENT_COLOR_SOURCE_H_ + +#include "flutter/display_list/effects/color_sources/dl_gradient_color_source_base.h" + +namespace flutter { + +class DlRadialGradientColorSource final : public DlGradientColorSourceBase { + public: + const DlRadialGradientColorSource* asRadialGradient() const override { + return this; + } + + bool isUIThreadSafe() const override { return true; } + + std::shared_ptr shared() const override; + + DlColorSourceType type() const override { + return DlColorSourceType::kRadialGradient; + } + size_t size() const override { return sizeof(*this) + vector_sizes(); } + + DlPoint center() const { return center_; } + DlScalar radius() const { return radius_; } + + protected: + virtual const void* pod() const override { return this + 1; } + + bool equals_(DlColorSource const& other) const override; + + private: + DlRadialGradientColorSource(DlPoint center, + DlScalar radius, + uint32_t stop_count, + const DlColor* colors, + const float* stops, + DlTileMode tile_mode, + const DlMatrix* matrix = nullptr); + + explicit DlRadialGradientColorSource( + const DlRadialGradientColorSource* source); + + DlPoint center_; + DlScalar radius_; + + friend class DlColorSource; + friend class DisplayListBuilder; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlRadialGradientColorSource); +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_RADIAL_GRADIENT_COLOR_SOURCE_H_ diff --git a/display_list/effects/color_sources/dl_runtime_effect_color_source.cc b/display_list/effects/color_sources/dl_runtime_effect_color_source.cc new file mode 100644 index 0000000000000..8f38b994cacae --- /dev/null +++ b/display_list/effects/color_sources/dl_runtime_effect_color_source.cc @@ -0,0 +1,52 @@ +// 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. + +#include "flutter/display_list/effects/color_sources/dl_runtime_effect_color_source.h" + +namespace flutter { + +DlRuntimeEffectColorSource::DlRuntimeEffectColorSource( + sk_sp runtime_effect, + std::vector> samplers, + std::shared_ptr> uniform_data) + : runtime_effect_(std::move(runtime_effect)), + samplers_(std::move(samplers)), + uniform_data_(std::move(uniform_data)) {} + +std::shared_ptr DlRuntimeEffectColorSource::shared() const { + return std::make_shared(runtime_effect_, // + samplers_, // + uniform_data_); +} + +bool DlRuntimeEffectColorSource::isUIThreadSafe() const { + for (const auto& sampler : samplers_) { + if (!sampler->isUIThreadSafe()) { + return false; + } + } + return true; +} + +bool DlRuntimeEffectColorSource::equals_(DlColorSource const& other) const { + FML_DCHECK(other.type() == DlColorSourceType::kRuntimeEffect); + auto that = static_cast(&other); + if (runtime_effect_ != that->runtime_effect_) { + return false; + } + if (uniform_data_ != that->uniform_data_) { + return false; + } + if (samplers_.size() != that->samplers_.size()) { + return false; + } + for (size_t i = 0; i < samplers_.size(); i++) { + if (samplers_[i] != that->samplers_[i]) { + return false; + } + } + return true; +} + +} // namespace flutter diff --git a/display_list/effects/color_sources/dl_runtime_effect_color_source.h b/display_list/effects/color_sources/dl_runtime_effect_color_source.h new file mode 100644 index 0000000000000..126a3b6a0a8f0 --- /dev/null +++ b/display_list/effects/color_sources/dl_runtime_effect_color_source.h @@ -0,0 +1,57 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_RUNTIME_EFFECT_COLOR_SOURCE_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_RUNTIME_EFFECT_COLOR_SOURCE_H_ + +#include "flutter/display_list/effects/dl_color_source.h" + +namespace flutter { + +class DlRuntimeEffectColorSource final : public DlColorSource { + public: + DlRuntimeEffectColorSource( + sk_sp runtime_effect, + std::vector> samplers, + std::shared_ptr> uniform_data); + + bool isUIThreadSafe() const override; + + const DlRuntimeEffectColorSource* asRuntimeEffect() const override { + return this; + } + + std::shared_ptr shared() const override; + + DlColorSourceType type() const override { + return DlColorSourceType::kRuntimeEffect; + } + size_t size() const override { return sizeof(*this); } + + bool is_opaque() const override { return false; } + + const sk_sp runtime_effect() const { + return runtime_effect_; + } + const std::vector> samplers() const { + return samplers_; + } + const std::shared_ptr> uniform_data() const { + return uniform_data_; + } + + protected: + bool equals_(DlColorSource const& other) const override; + + private: + sk_sp runtime_effect_; + std::vector> samplers_; + std::shared_ptr> uniform_data_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlRuntimeEffectColorSource); +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_RUNTIME_EFFECT_COLOR_SOURCE_H_ diff --git a/display_list/effects/color_sources/dl_sweep_gradient_color_source.cc b/display_list/effects/color_sources/dl_sweep_gradient_color_source.cc new file mode 100644 index 0000000000000..c575e72947a31 --- /dev/null +++ b/display_list/effects/color_sources/dl_sweep_gradient_color_source.cc @@ -0,0 +1,47 @@ +// 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. + +#include "flutter/display_list/effects/color_sources/dl_sweep_gradient_color_source.h" + +namespace flutter { + +DlSweepGradientColorSource::DlSweepGradientColorSource(DlPoint center, + DlScalar start, + DlScalar end, + uint32_t stop_count, + const DlColor* colors, + const float* stops, + DlTileMode tile_mode, + const DlMatrix* matrix) + : DlGradientColorSourceBase(stop_count, tile_mode, matrix), + center_(center), + start_(start), + end_(end) { + store_color_stops(this + 1, colors, stops); +} + +DlSweepGradientColorSource::DlSweepGradientColorSource( + const DlSweepGradientColorSource* source) + : DlGradientColorSourceBase(source->stop_count(), + source->tile_mode(), + source->matrix_ptr()), + center_(source->center()), + start_(source->start()), + end_(source->end()) { + store_color_stops(this + 1, source->colors(), source->stops()); +} + +std::shared_ptr DlSweepGradientColorSource::shared() const { + return MakeSweep(center_, start_, end_, stop_count(), colors(), stops(), + tile_mode(), matrix_ptr()); +} + +bool DlSweepGradientColorSource::equals_(DlColorSource const& other) const { + FML_DCHECK(other.type() == DlColorSourceType::kSweepGradient); + auto that = static_cast(&other); + return (center_ == that->center_ && start_ == that->start_ && + end_ == that->end_ && base_equals_(that)); +} + +} // namespace flutter diff --git a/display_list/effects/color_sources/dl_sweep_gradient_color_source.h b/display_list/effects/color_sources/dl_sweep_gradient_color_source.h new file mode 100644 index 0000000000000..b33753898ce31 --- /dev/null +++ b/display_list/effects/color_sources/dl_sweep_gradient_color_source.h @@ -0,0 +1,60 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_SWEEP_GRADIENT_COLOR_SOURCE_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_SWEEP_GRADIENT_COLOR_SOURCE_H_ + +#include "flutter/display_list/effects/color_sources/dl_gradient_color_source_base.h" + +namespace flutter { + +class DlSweepGradientColorSource final : public DlGradientColorSourceBase { + public: + const DlSweepGradientColorSource* asSweepGradient() const override { + return this; + } + + bool isUIThreadSafe() const override { return true; } + + std::shared_ptr shared() const override; + + DlColorSourceType type() const override { + return DlColorSourceType::kSweepGradient; + } + size_t size() const override { return sizeof(*this) + vector_sizes(); } + + DlPoint center() const { return center_; } + DlScalar start() const { return start_; } + DlScalar end() const { return end_; } + + protected: + virtual const void* pod() const override { return this + 1; } + + bool equals_(DlColorSource const& other) const override; + + private: + DlSweepGradientColorSource(DlPoint center, + DlScalar start, + DlScalar end, + uint32_t stop_count, + const DlColor* colors, + const float* stops, + DlTileMode tile_mode, + const DlMatrix* matrix = nullptr); + + explicit DlSweepGradientColorSource(const DlSweepGradientColorSource* source); + + DlPoint center_; + DlScalar start_; + DlScalar end_; + + friend class DlColorSource; + friend class DisplayListBuilder; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlSweepGradientColorSource); +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_COLOR_SOURCES_DL_SWEEP_GRADIENT_COLOR_SOURCE_H_ diff --git a/display_list/effects/dl_color_filter.cc b/display_list/effects/dl_color_filter.cc index 2ad15e0ab92bf..c78cfa3ca2398 100644 --- a/display_list/effects/dl_color_filter.cc +++ b/display_list/effects/dl_color_filter.cc @@ -5,196 +5,34 @@ #include "flutter/display_list/effects/dl_color_filter.h" #include "flutter/display_list/dl_color.h" +#include "flutter/display_list/effects/color_filters/dl_blend_color_filter.h" +#include "flutter/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.h" +#include "flutter/display_list/effects/color_filters/dl_matrix_color_filter.h" +#include "flutter/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.h" namespace flutter { -std::shared_ptr DlBlendColorFilter::Make(DlColor color, - DlBlendMode mode) { - switch (mode) { - case DlBlendMode::kDst: { - return nullptr; - } - case DlBlendMode::kSrcOver: { - if (color.isTransparent()) { - return nullptr; - } - if (color.isOpaque()) { - mode = DlBlendMode::kSrc; - } - break; - } - case DlBlendMode::kDstOver: - case DlBlendMode::kDstOut: - case DlBlendMode::kSrcATop: - case DlBlendMode::kXor: - case DlBlendMode::kDarken: { - if (color.isTransparent()) { - return nullptr; - } - break; - } - case DlBlendMode::kDstIn: { - if (color.isOpaque()) { - return nullptr; - } - break; - } - default: - break; - } - return std::make_shared(color, mode); +std::shared_ptr DlColorFilter::MakeBlend( + DlColor color, + DlBlendMode mode) { + // Delegate to a method private to DlBlendColorFilter due to private + // constructor preventing |make_shared| from here. + return DlBlendColorFilter::Make(color, mode); } -bool DlBlendColorFilter::modifies_transparent_black() const { - switch (mode_) { - // These modes all act like kSrc when the dest is all 0s. - // So they modify transparent black when the src color is - // not transparent. - case DlBlendMode::kSrc: - case DlBlendMode::kSrcOver: - case DlBlendMode::kDstOver: - case DlBlendMode::kSrcOut: - case DlBlendMode::kDstATop: - case DlBlendMode::kXor: - case DlBlendMode::kPlus: - case DlBlendMode::kScreen: - case DlBlendMode::kOverlay: - case DlBlendMode::kDarken: - case DlBlendMode::kLighten: - case DlBlendMode::kColorDodge: - case DlBlendMode::kColorBurn: - case DlBlendMode::kHardLight: - case DlBlendMode::kSoftLight: - case DlBlendMode::kDifference: - case DlBlendMode::kExclusion: - case DlBlendMode::kMultiply: - case DlBlendMode::kHue: - case DlBlendMode::kSaturation: - case DlBlendMode::kColor: - case DlBlendMode::kLuminosity: - return !color_.isTransparent(); - - // These modes are all like kDst when the dest is all 0s. - // So they never modify transparent black. - case DlBlendMode::kClear: - case DlBlendMode::kDst: - case DlBlendMode::kSrcIn: - case DlBlendMode::kDstIn: - case DlBlendMode::kDstOut: - case DlBlendMode::kSrcATop: - case DlBlendMode::kModulate: - return false; - } -} - -bool DlBlendColorFilter::can_commute_with_opacity() const { - switch (mode_) { - case DlBlendMode::kClear: - case DlBlendMode::kDst: - case DlBlendMode::kSrcIn: - case DlBlendMode::kDstIn: - case DlBlendMode::kDstOut: - case DlBlendMode::kSrcATop: - case DlBlendMode::kModulate: - return true; - - case DlBlendMode::kSrc: - case DlBlendMode::kSrcOver: - case DlBlendMode::kDstOver: - case DlBlendMode::kSrcOut: - case DlBlendMode::kDstATop: - case DlBlendMode::kXor: - case DlBlendMode::kPlus: - case DlBlendMode::kScreen: - case DlBlendMode::kOverlay: - case DlBlendMode::kDarken: - case DlBlendMode::kLighten: - case DlBlendMode::kColorDodge: - case DlBlendMode::kColorBurn: - case DlBlendMode::kHardLight: - case DlBlendMode::kSoftLight: - case DlBlendMode::kDifference: - case DlBlendMode::kExclusion: - case DlBlendMode::kMultiply: - case DlBlendMode::kHue: - case DlBlendMode::kSaturation: - case DlBlendMode::kColor: - case DlBlendMode::kLuminosity: - return color_.isTransparent(); - } -} - -std::shared_ptr DlMatrixColorFilter::Make( +std::shared_ptr DlColorFilter::MakeMatrix( const float matrix[20]) { - float product = 0; - for (int i = 0; i < 20; i++) { - product *= matrix[i]; - } - // If any of the elements of the matrix are infinity or NaN, then - // |product| will be NaN, otherwise 0. - if (product == 0) { - return std::make_shared(matrix); - } - return nullptr; + // Delegate to a method private to DlBlendColorFilter due to private + // constructor preventing |make_shared| from here. + return DlMatrixColorFilter::Make(matrix); } -bool DlMatrixColorFilter::modifies_transparent_black() const { - // Values are considered in non-premultiplied form when the matrix is - // applied, but we only care about this answer for whether it leaves - // an incoming color with a transparent alpha as transparent on output. - // Thus, we only need to consider the alpha part of the matrix equation, - // which is the last row. Since the incoming alpha value is 0, the last - // equation ends up becoming A' = matrix_[19]. Negative results will be - // clamped to the range [0,1] so we only care about positive values. - // Non-finite values are clamped to a zero alpha. - return (std::isfinite(matrix_[19]) && matrix_[19] > 0); +std::shared_ptr DlColorFilter::MakeSrgbToLinearGamma() { + return DlSrgbToLinearGammaColorFilter::kInstance; } -bool DlMatrixColorFilter::can_commute_with_opacity() const { - // We need to check if: - // filter(color) * opacity == filter(color * opacity). - // - // filter(RGBA) = R' = [ R*m[ 0] + G*m[ 1] + B*m[ 2] + A*m[ 3] + m[ 4] ] - // G' = [ R*m[ 5] + G*m[ 6] + B*m[ 7] + A*m[ 8] + m[ 9] ] - // B' = [ R*m[10] + G*m[11] + B*m[12] + A*m[13] + m[14] ] - // A' = [ R*m[15] + G*m[16] + B*m[17] + A*m[18] + m[19] ] - // - // Applying the opacity only affects the alpha value since the operations - // are performed on non-premultiplied colors. (If the data is stored in - // premultiplied form, though, there may be rounding errors due to - // premul->unpremul->premul conversions.) - - // We test for the successful cases and return false if they fail so that - // we fail and return false if any matrix values are NaN. - - // If any of the alpha column are non-zero then the prior alpha affects - // the result color, so applying opacity before the filter will change - // the incoming alpha and therefore the colors that are produced. - if (!(matrix_[3] == 0 && // A does not affect R' - matrix_[8] == 0 && // A does not affect G' - matrix_[13] == 0)) { // A does not affect B' - return false; - } - - // Similarly, if any of the alpha row are non-zero then the prior colors - // affect the result alpha in a way that prevents opacity from commuting - // through the filter operation. - if (!(matrix_[15] == 0 && // R does not affect A' - matrix_[16] == 0 && // G does not affect A' - matrix_[17] == 0 && // B does not affect A' - matrix_[19] == 0)) { // A' is not offset by an absolute value - return false; - } - - return true; +std::shared_ptr DlColorFilter::MakeLinearToSrgbGamma() { + return DlLinearToSrgbGammaColorFilter::kInstance; } -const std::shared_ptr - DlSrgbToLinearGammaColorFilter::kInstance = - std::make_shared(); - -const std::shared_ptr - DlLinearToSrgbGammaColorFilter::kInstance = - std::make_shared(); - } // namespace flutter diff --git a/display_list/effects/dl_color_filter.h b/display_list/effects/dl_color_filter.h index 42a4845e0a90c..c3c0d64241edb 100644 --- a/display_list/effects/dl_color_filter.h +++ b/display_list/effects/dl_color_filter.h @@ -15,10 +15,6 @@ namespace flutter { class DlBlendColorFilter; class DlMatrixColorFilter; -// The DisplayList ColorFilter class. This class implements all of the -// facilities and adheres to the design goals of the |DlAttribute| base -// class. - // An enumerated type for the supported ColorFilter operations. enum class DlColorFilterType { kBlend, @@ -27,8 +23,51 @@ enum class DlColorFilterType { kLinearToSrgbGamma, }; +/// The DisplayList ColorFilter base class. This class implements all of the +/// facilities and adheres to the design goals of the |DlAttribute| base +/// class. class DlColorFilter : public DlAttribute { public: + /// Return a shared pointer to a DlColorFilter that acts as if blending + /// the specified color over the rendered colors using the specified + /// blend mode, or a nullptr if the operation would be a NOP. + /// + /// The blend mode takes the color from the filter as the source color and + /// the rendered color as the destination color. + static std::shared_ptr MakeBlend(DlColor color, + DlBlendMode mode); + + /// Return a shared pointer to a DlColorFilter which transforms each + /// rendered color using a per-component equation specified by the + /// contents of the specified 5 column by 4 row matrix specified in + /// row major order, or a null pointer if the operation would be a NOP. + /// + /// The filter runs every pixel drawn by the rendering operation + /// [iR,iG,iB,iA] through a vector/matrix multiplication, as in: + /// + /// [ oR ] [ m[ 0] m[ 1] m[ 2] m[ 3] m[ 4] ] [ iR ] + /// [ oG ] [ m[ 5] m[ 6] m[ 7] m[ 8] m[ 9] ] [ iG ] + /// [ oB ] = [ m[10] m[11] m[12] m[13] m[14] ] x [ iB ] + /// [ oA ] [ m[15] m[16] m[17] m[18] m[19] ] [ iA ] + /// [ 1 ] + /// + /// The resulting color [oR,oG,oB,oA] is then clamped to the range of + /// valid pixel components before storing in the output. + /// + /// The incoming and outgoing [iR,iG,iB,iA] and [oR,oG,oB,oA] are + /// considered to be non-premultiplied. When working on premultiplied + /// pixel data, the necessary pre<->non-pre conversions must be performed. + static std::shared_ptr MakeMatrix( + const float matrix[20]); + + /// Return a shared pointer to a singleton DlColorFilter that transforms + /// each rendered pixel from Srgb to Linear gamma space. + static std::shared_ptr MakeSrgbToLinearGamma(); + + /// Return a shared pointer to a singleton DlColorFilter that transforms + /// each rendered pixel from Linear to Srgb gamma space. + static std::shared_ptr MakeLinearToSrgbGamma(); + // Return a boolean indicating whether the color filtering operation will // modify transparent black. This is typically used to determine if applying // the ColorFilter to a temporary saveLayer buffer will turn the surrounding @@ -48,173 +87,10 @@ class DlColorFilter : public DlAttribute { // type of ColorFilter, otherwise return nullptr. virtual const DlMatrixColorFilter* asMatrix() const { return nullptr; } - // asSrgb<->Linear is not needed because it has no properties to query. + // asSrgb<->Linear are not needed because it has no properties to query. // Its type fully specifies its operation. }; -// The Blend type of ColorFilter which specifies modifying the -// colors as if the color specified in the Blend filter is the -// source color and the color drawn by the rendering operation -// is the destination color. The mode parameter of the Blend -// filter is then used to combine those colors. -class DlBlendColorFilter final : public DlColorFilter { - public: - DlBlendColorFilter(DlColor color, DlBlendMode mode) - : color_(color), mode_(mode) {} - DlBlendColorFilter(const DlBlendColorFilter& filter) - : DlBlendColorFilter(filter.color_, filter.mode_) {} - explicit DlBlendColorFilter(const DlBlendColorFilter* filter) - : DlBlendColorFilter(filter->color_, filter->mode_) {} - - static std::shared_ptr Make(DlColor color, DlBlendMode mode); - - DlColorFilterType type() const override { return DlColorFilterType::kBlend; } - size_t size() const override { return sizeof(*this); } - - bool modifies_transparent_black() const override; - bool can_commute_with_opacity() const override; - - std::shared_ptr shared() const override { - return std::make_shared(this); - } - - const DlBlendColorFilter* asBlend() const override { return this; } - - DlColor color() const { return color_; } - DlBlendMode mode() const { return mode_; } - - protected: - bool equals_(DlColorFilter const& other) const override { - FML_DCHECK(other.type() == DlColorFilterType::kBlend); - auto that = static_cast(&other); - return color_ == that->color_ && mode_ == that->mode_; - } - - private: - DlColor color_; - DlBlendMode mode_; -}; - -// The Matrix type of ColorFilter which runs every pixel drawn by -// the rendering operation [iR,iG,iB,iA] through a vector/matrix -// multiplication, as in: -// -// [ oR ] [ m[ 0] m[ 1] m[ 2] m[ 3] m[ 4] ] [ iR ] -// [ oG ] [ m[ 5] m[ 6] m[ 7] m[ 8] m[ 9] ] [ iG ] -// [ oB ] = [ m[10] m[11] m[12] m[13] m[14] ] x [ iB ] -// [ oA ] [ m[15] m[16] m[17] m[18] m[19] ] [ iA ] -// [ 1 ] -// -// The resulting color [oR,oG,oB,oA] is then clamped to the range of -// valid pixel components before storing in the output. -// -// The incoming and outgoing [iR,iG,iB,iA] and [oR,oG,oB,oA] are -// considered to be non-premultiplied. When working on premultiplied -// pixel data, the necessary pre<->non-pre conversions must be performed. -class DlMatrixColorFilter final : public DlColorFilter { - public: - explicit DlMatrixColorFilter(const float matrix[20]) { - memcpy(matrix_, matrix, sizeof(matrix_)); - } - DlMatrixColorFilter(const DlMatrixColorFilter& filter) - : DlMatrixColorFilter(filter.matrix_) {} - explicit DlMatrixColorFilter(const DlMatrixColorFilter* filter) - : DlMatrixColorFilter(filter->matrix_) {} - - static std::shared_ptr Make(const float matrix[20]); - - DlColorFilterType type() const override { return DlColorFilterType::kMatrix; } - size_t size() const override { return sizeof(*this); } - - bool modifies_transparent_black() const override; - bool can_commute_with_opacity() const override; - - std::shared_ptr shared() const override { - return std::make_shared(this); - } - - const DlMatrixColorFilter* asMatrix() const override { return this; } - - const float& operator[](int index) const { return matrix_[index]; } - void get_matrix(float matrix[20]) const { - memcpy(matrix, matrix_, sizeof(matrix_)); - } - - protected: - bool equals_(const DlColorFilter& other) const override { - FML_DCHECK(other.type() == DlColorFilterType::kMatrix); - auto that = static_cast(&other); - return memcmp(matrix_, that->matrix_, sizeof(matrix_)) == 0; - } - - private: - float matrix_[20]; -}; - -// The SrgbToLinear type of ColorFilter that applies the inverse of the sRGB -// gamma curve to the rendered pixels. -class DlSrgbToLinearGammaColorFilter final : public DlColorFilter { - public: - static const std::shared_ptr kInstance; - - DlSrgbToLinearGammaColorFilter() {} - DlSrgbToLinearGammaColorFilter(const DlSrgbToLinearGammaColorFilter& filter) - : DlSrgbToLinearGammaColorFilter() {} - explicit DlSrgbToLinearGammaColorFilter( - const DlSrgbToLinearGammaColorFilter* filter) - : DlSrgbToLinearGammaColorFilter() {} - - DlColorFilterType type() const override { - return DlColorFilterType::kSrgbToLinearGamma; - } - size_t size() const override { return sizeof(*this); } - bool modifies_transparent_black() const override { return false; } - bool can_commute_with_opacity() const override { return true; } - - std::shared_ptr shared() const override { return kInstance; } - - protected: - bool equals_(const DlColorFilter& other) const override { - FML_DCHECK(other.type() == DlColorFilterType::kSrgbToLinearGamma); - return true; - } - - private: - friend class DlColorFilter; -}; - -// The LinearToSrgb type of ColorFilter that applies the sRGB gamma curve -// to the rendered pixels. -class DlLinearToSrgbGammaColorFilter final : public DlColorFilter { - public: - static const std::shared_ptr kInstance; - - DlLinearToSrgbGammaColorFilter() {} - DlLinearToSrgbGammaColorFilter(const DlLinearToSrgbGammaColorFilter& filter) - : DlLinearToSrgbGammaColorFilter() {} - explicit DlLinearToSrgbGammaColorFilter( - const DlLinearToSrgbGammaColorFilter* filter) - : DlLinearToSrgbGammaColorFilter() {} - - DlColorFilterType type() const override { - return DlColorFilterType::kLinearToSrgbGamma; - } - size_t size() const override { return sizeof(*this); } - bool modifies_transparent_black() const override { return false; } - bool can_commute_with_opacity() const override { return true; } - - std::shared_ptr shared() const override { return kInstance; } - - protected: - bool equals_(const DlColorFilter& other) const override { - FML_DCHECK(other.type() == DlColorFilterType::kLinearToSrgbGamma); - return true; - } - - private: - friend class DlColorFilter; -}; - } // namespace flutter #endif // FLUTTER_DISPLAY_LIST_EFFECTS_DL_COLOR_FILTER_H_ diff --git a/display_list/effects/dl_color_filter_unittests.cc b/display_list/effects/dl_color_filter_unittests.cc index 157348f3cf3ae..524b8fb481cc4 100644 --- a/display_list/effects/dl_color_filter_unittests.cc +++ b/display_list/effects/dl_color_filter_unittests.cc @@ -3,6 +3,8 @@ // found in the LICENSE file. #include "flutter/display_list/effects/dl_color_filter.h" + +#include "flutter/display_list/effects/dl_color_filters.h" #include "flutter/display_list/testing/dl_test_equality.h" namespace flutter { @@ -135,7 +137,7 @@ TEST(DisplayListColorFilter, SrgbToLinearEquals) { DlSrgbToLinearGammaColorFilter filter1; DlSrgbToLinearGammaColorFilter filter2; TestEquals(filter1, filter2); - TestEquals(filter1, *DlSrgbToLinearGammaColorFilter::kInstance); + TestEquals(filter1, *DlColorFilter::MakeSrgbToLinearGamma()); } TEST(DisplayListColorFilter, LinearToSrgbConstructor) { @@ -152,7 +154,7 @@ TEST(DisplayListColorFilter, LinearToSrgbEquals) { DlLinearToSrgbGammaColorFilter filter1; DlLinearToSrgbGammaColorFilter filter2; TestEquals(filter1, filter2); - TestEquals(filter1, *DlLinearToSrgbGammaColorFilter::kInstance); + TestEquals(filter1, *DlColorFilter::MakeLinearToSrgbGamma()); } } // namespace testing diff --git a/display_list/effects/dl_color_filters.h b/display_list/effects/dl_color_filters.h new file mode 100644 index 0000000000000..48bb78ae67bd2 --- /dev/null +++ b/display_list/effects/dl_color_filters.h @@ -0,0 +1,13 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_DL_COLOR_FILTERS_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_DL_COLOR_FILTERS_H_ + +#include "flutter/display_list/effects/color_filters/dl_blend_color_filter.h" +#include "flutter/display_list/effects/color_filters/dl_linear_to_srgb_gamma_color_filter.h" +#include "flutter/display_list/effects/color_filters/dl_matrix_color_filter.h" +#include "flutter/display_list/effects/color_filters/dl_srgb_to_linear_gamma_color_filter.h" + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_DL_COLOR_FILTERS_H_ diff --git a/display_list/effects/dl_color_source.cc b/display_list/effects/dl_color_source.cc index 47a30d544a803..5a5fa8b1fbf19 100644 --- a/display_list/effects/dl_color_source.cc +++ b/display_list/effects/dl_color_source.cc @@ -5,6 +5,7 @@ #include "flutter/display_list/effects/dl_color_source.h" #include "flutter/display_list/dl_sampling_options.h" +#include "flutter/display_list/effects/dl_color_sources.h" #include "flutter/display_list/effects/dl_runtime_effect.h" #include "flutter/fml/logging.h" @@ -19,14 +20,24 @@ static void DlGradientDeleter(void* p) { ::operator delete(p); } -std::shared_ptr DlColorSource::MakeLinear( - const SkPoint start_point, - const SkPoint end_point, +std::shared_ptr DlColorSource::MakeImage( + const sk_sp& image, + DlTileMode horizontal_tile_mode, + DlTileMode vertical_tile_mode, + DlImageSampling sampling, + const DlMatrix* matrix) { + return std::make_shared( + image, horizontal_tile_mode, vertical_tile_mode, sampling, matrix); +} + +std::shared_ptr DlColorSource::MakeLinear( + const DlPoint start_point, + const DlPoint end_point, uint32_t stop_count, const DlColor* colors, const float* stops, DlTileMode tile_mode, - const SkMatrix* matrix) { + const DlMatrix* matrix) { size_t needed = sizeof(DlLinearGradientColorSource) + (stop_count * (sizeof(DlColor) + sizeof(float))); @@ -40,14 +51,14 @@ std::shared_ptr DlColorSource::MakeLinear( return ret; } -std::shared_ptr DlColorSource::MakeRadial( - SkPoint center, - SkScalar radius, +std::shared_ptr DlColorSource::MakeRadial( + DlPoint center, + DlScalar radius, uint32_t stop_count, const DlColor* colors, const float* stops, DlTileMode tile_mode, - const SkMatrix* matrix) { + const DlMatrix* matrix) { size_t needed = sizeof(DlRadialGradientColorSource) + (stop_count * (sizeof(DlColor) + sizeof(float))); @@ -60,16 +71,16 @@ std::shared_ptr DlColorSource::MakeRadial( return ret; } -std::shared_ptr DlColorSource::MakeConical( - SkPoint start_center, - SkScalar start_radius, - SkPoint end_center, - SkScalar end_radius, +std::shared_ptr DlColorSource::MakeConical( + DlPoint start_center, + DlScalar start_radius, + DlPoint end_center, + DlScalar end_radius, uint32_t stop_count, const DlColor* colors, const float* stops, DlTileMode tile_mode, - const SkMatrix* matrix) { + const DlMatrix* matrix) { size_t needed = sizeof(DlConicalGradientColorSource) + (stop_count * (sizeof(DlColor) + sizeof(float))); @@ -83,15 +94,15 @@ std::shared_ptr DlColorSource::MakeConical( return ret; } -std::shared_ptr DlColorSource::MakeSweep( - SkPoint center, - SkScalar start, - SkScalar end, +std::shared_ptr DlColorSource::MakeSweep( + DlPoint center, + DlScalar start, + DlScalar end, uint32_t stop_count, const DlColor* colors, const float* stops, DlTileMode tile_mode, - const SkMatrix* matrix) { + const DlMatrix* matrix) { size_t needed = sizeof(DlSweepGradientColorSource) + (stop_count * (sizeof(DlColor) + sizeof(float))); @@ -105,7 +116,7 @@ std::shared_ptr DlColorSource::MakeSweep( return ret; } -std::shared_ptr DlColorSource::MakeRuntimeEffect( +std::shared_ptr DlColorSource::MakeRuntimeEffect( sk_sp runtime_effect, std::vector> samplers, std::shared_ptr> uniform_data) { @@ -114,4 +125,55 @@ std::shared_ptr DlColorSource::MakeRuntimeEffect( std::move(runtime_effect), std::move(samplers), std::move(uniform_data)); } +DlGradientColorSourceBase::DlGradientColorSourceBase(uint32_t stop_count, + DlTileMode tile_mode, + const DlMatrix* matrix) + : DlMatrixColorSourceBase(matrix), + mode_(tile_mode), + stop_count_(stop_count) {} + +bool DlGradientColorSourceBase::is_opaque() const { + if (mode_ == DlTileMode::kDecal) { + return false; + } + const DlColor* my_colors = colors(); + for (uint32_t i = 0; i < stop_count_; i++) { + if (my_colors[i].getAlpha() < 255) { + return false; + } + } + return true; +} + +bool DlGradientColorSourceBase::base_equals_( + DlGradientColorSourceBase const* other_base) const { + if (mode_ != other_base->mode_ || matrix() != other_base->matrix() || + stop_count_ != other_base->stop_count_) { + return false; + } + return (memcmp(colors(), other_base->colors(), + stop_count_ * sizeof(colors()[0])) == 0 && + memcmp(stops(), other_base->stops(), + stop_count_ * sizeof(stops()[0])) == 0); +} + +void DlGradientColorSourceBase::store_color_stops(void* pod, + const DlColor* color_data, + const float* stop_data) { + DlColor* color_storage = reinterpret_cast(pod); + memcpy(color_storage, color_data, stop_count_ * sizeof(*color_data)); + float* stop_storage = reinterpret_cast(color_storage + stop_count_); + if (stop_data) { + memcpy(stop_storage, stop_data, stop_count_ * sizeof(*stop_data)); + } else { + float div = stop_count_ - 1; + if (div <= 0) { + div = 1; + } + for (uint32_t i = 0; i < stop_count_; i++) { + stop_storage[i] = i / div; + } + } +} + } // namespace flutter diff --git a/display_list/effects/dl_color_source.h b/display_list/effects/dl_color_source.h index 90f034ddee263..05fe3ca5853d2 100644 --- a/display_list/effects/dl_color_source.h +++ b/display_list/effects/dl_color_source.h @@ -5,11 +5,6 @@ #ifndef FLUTTER_DISPLAY_LIST_EFFECTS_DL_COLOR_SOURCE_H_ #define FLUTTER_DISPLAY_LIST_EFFECTS_DL_COLOR_SOURCE_H_ -#include -#include -#include - -#include "flutter/display_list/display_list.h" #include "flutter/display_list/dl_attributes.h" #include "flutter/display_list/dl_color.h" #include "flutter/display_list/dl_sampling_options.h" @@ -18,11 +13,8 @@ #include "flutter/display_list/image/dl_image.h" #include "flutter/fml/logging.h" -#include "third_party/skia/include/core/SkShader.h" - namespace flutter { -class DlColorColorSource; class DlImageColorSource; class DlLinearGradientColorSource; class DlRadialGradientColorSource; @@ -41,7 +33,6 @@ class DlRuntimeEffectColorSource; // attributes, and the final blend with the pixels in the destination. enum class DlColorSourceType { - kColor, kImage, kLinearGradient, kRadialGradient, @@ -52,46 +43,53 @@ enum class DlColorSourceType { class DlColorSource : public DlAttribute { public: - static std::shared_ptr MakeLinear( - const SkPoint start_point, - const SkPoint end_point, + static std::shared_ptr MakeImage( + const sk_sp& image, + DlTileMode horizontal_tile_mode, + DlTileMode vertical_tile_mode, + DlImageSampling sampling = DlImageSampling::kLinear, + const DlMatrix* matrix = nullptr); + + static std::shared_ptr MakeLinear( + const DlPoint start_point, + const DlPoint end_point, uint32_t stop_count, const DlColor* colors, const float* stops, DlTileMode tile_mode, - const SkMatrix* matrix = nullptr); + const DlMatrix* matrix = nullptr); - static std::shared_ptr MakeRadial( - SkPoint center, - SkScalar radius, + static std::shared_ptr MakeRadial( + DlPoint center, + DlScalar radius, uint32_t stop_count, const DlColor* colors, const float* stops, DlTileMode tile_mode, - const SkMatrix* matrix = nullptr); + const DlMatrix* matrix = nullptr); - static std::shared_ptr MakeConical( - SkPoint start_center, - SkScalar start_radius, - SkPoint end_center, - SkScalar end_radius, + static std::shared_ptr MakeConical( + DlPoint start_center, + DlScalar start_radius, + DlPoint end_center, + DlScalar end_radius, uint32_t stop_count, const DlColor* colors, const float* stops, DlTileMode tile_mode, - const SkMatrix* matrix = nullptr); + const DlMatrix* matrix = nullptr); - static std::shared_ptr MakeSweep( - SkPoint center, - SkScalar start, - SkScalar end, + static std::shared_ptr MakeSweep( + DlPoint center, + DlScalar start, + DlScalar end, uint32_t stop_count, const DlColor* colors, const float* stops, DlTileMode tile_mode, - const SkMatrix* matrix = nullptr); + const DlMatrix* matrix = nullptr); - static std::shared_ptr MakeRuntimeEffect( + static std::shared_ptr MakeRuntimeEffect( sk_sp runtime_effect, std::vector> samplers, std::shared_ptr> uniform_data); @@ -118,10 +116,6 @@ class DlColorSource : public DlAttribute { /// virtual bool isGradient() const { return false; } - // Return a DlColorColorSource pointer to this object iff it is an Color - // type of ColorSource, otherwise return nullptr. - virtual const DlColorColorSource* asColor() const { return nullptr; } - // Return a DlImageColorSource pointer to this object iff it is an Image // type of ColorSource, otherwise return nullptr. virtual const DlImageColorSource* asImage() const { return nullptr; } @@ -161,535 +155,6 @@ class DlColorSource : public DlAttribute { FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlColorSource); }; -class DlColorColorSource final : public DlColorSource { - public: - explicit DlColorColorSource(DlColor color) : color_(color) {} - - bool isUIThreadSafe() const override { return true; } - - std::shared_ptr shared() const override { - return std::make_shared(color_); - } - - const DlColorColorSource* asColor() const override { return this; } - - DlColorSourceType type() const override { return DlColorSourceType::kColor; } - size_t size() const override { return sizeof(*this); } - - bool is_opaque() const override { return color_.getAlpha() == 255; } - - DlColor color() const { return color_; } - - protected: - bool equals_(DlColorSource const& other) const override { - FML_DCHECK(other.type() == DlColorSourceType::kColor); - auto that = static_cast(&other); - return color_ == that->color_; - } - - private: - DlColor color_; - - FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlColorColorSource); -}; - -class DlMatrixColorSourceBase : public DlColorSource { - public: - const SkMatrix& matrix() const { return matrix_; } - const SkMatrix* matrix_ptr() const { - return matrix_.isIdentity() ? nullptr : &matrix_; - } - - protected: - explicit DlMatrixColorSourceBase(const SkMatrix* matrix) - : matrix_(matrix ? *matrix : SkMatrix::I()) {} - - private: - const SkMatrix matrix_; -}; - -class DlImageColorSource final : public SkRefCnt, - public DlMatrixColorSourceBase { - public: - DlImageColorSource(sk_sp image, - DlTileMode horizontal_tile_mode, - DlTileMode vertical_tile_mode, - DlImageSampling sampling = DlImageSampling::kLinear, - const SkMatrix* matrix = nullptr) - : DlMatrixColorSourceBase(matrix), - image_(std::move(image)), - horizontal_tile_mode_(horizontal_tile_mode), - vertical_tile_mode_(vertical_tile_mode), - sampling_(sampling) {} - - bool isUIThreadSafe() const override { - return image_ ? image_->isUIThreadSafe() : true; - } - - const DlImageColorSource* asImage() const override { return this; } - - std::shared_ptr shared() const override { - return with_sampling(sampling_); - } - - std::shared_ptr with_sampling(DlImageSampling sampling) const { - return std::make_shared(image_, horizontal_tile_mode_, - vertical_tile_mode_, sampling, - matrix_ptr()); - } - - DlColorSourceType type() const override { return DlColorSourceType::kImage; } - size_t size() const override { return sizeof(*this); } - - bool is_opaque() const override { return image_->isOpaque(); } - - sk_sp image() const { return image_; } - DlTileMode horizontal_tile_mode() const { return horizontal_tile_mode_; } - DlTileMode vertical_tile_mode() const { return vertical_tile_mode_; } - DlImageSampling sampling() const { return sampling_; } - - protected: - bool equals_(DlColorSource const& other) const override { - FML_DCHECK(other.type() == DlColorSourceType::kImage); - auto that = static_cast(&other); - return (image_->Equals(that->image_) && matrix() == that->matrix() && - horizontal_tile_mode_ == that->horizontal_tile_mode_ && - vertical_tile_mode_ == that->vertical_tile_mode_ && - sampling_ == that->sampling_); - } - - private: - sk_sp image_; - DlTileMode horizontal_tile_mode_; - DlTileMode vertical_tile_mode_; - DlImageSampling sampling_; - - FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlImageColorSource); -}; - -class DlGradientColorSourceBase : public DlMatrixColorSourceBase { - public: - bool is_opaque() const override { - if (mode_ == DlTileMode::kDecal) { - return false; - } - const DlColor* my_colors = colors(); - for (uint32_t i = 0; i < stop_count_; i++) { - if (my_colors[i].getAlpha() < 255) { - return false; - } - } - return true; - } - - bool isGradient() const override { return true; } - - DlTileMode tile_mode() const { return mode_; } - int stop_count() const { return stop_count_; } - const DlColor* colors() const { - return reinterpret_cast(pod()); - } - const float* stops() const { - return reinterpret_cast(colors() + stop_count()); - } - - protected: - DlGradientColorSourceBase(uint32_t stop_count, - DlTileMode tile_mode, - const SkMatrix* matrix = nullptr) - : DlMatrixColorSourceBase(matrix), - mode_(tile_mode), - stop_count_(stop_count) {} - - size_t vector_sizes() const { - return stop_count_ * (sizeof(DlColor) + sizeof(float)); - } - - virtual const void* pod() const = 0; - - bool base_equals_(DlGradientColorSourceBase const* other_base) const { - if (mode_ != other_base->mode_ || matrix() != other_base->matrix() || - stop_count_ != other_base->stop_count_) { - return false; - } - return (memcmp(colors(), other_base->colors(), - stop_count_ * sizeof(colors()[0])) == 0 && - memcmp(stops(), other_base->stops(), - stop_count_ * sizeof(stops()[0])) == 0); - } - - void store_color_stops(void* pod, - const DlColor* color_data, - const float* stop_data) { - DlColor* color_storage = reinterpret_cast(pod); - memcpy(color_storage, color_data, stop_count_ * sizeof(*color_data)); - float* stop_storage = reinterpret_cast(color_storage + stop_count_); - if (stop_data) { - memcpy(stop_storage, stop_data, stop_count_ * sizeof(*stop_data)); - } else { - float div = stop_count_ - 1; - if (div <= 0) { - div = 1; - } - for (uint32_t i = 0; i < stop_count_; i++) { - stop_storage[i] = i / div; - } - } - } - - private: - DlTileMode mode_; - uint32_t stop_count_; - - FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlGradientColorSourceBase); -}; - -class DlLinearGradientColorSource final : public DlGradientColorSourceBase { - public: - const DlLinearGradientColorSource* asLinearGradient() const override { - return this; - } - - bool isUIThreadSafe() const override { return true; } - - DlColorSourceType type() const override { - return DlColorSourceType::kLinearGradient; - } - size_t size() const override { return sizeof(*this) + vector_sizes(); } - - std::shared_ptr shared() const override { - return MakeLinear(start_point_, end_point_, stop_count(), colors(), stops(), - tile_mode(), matrix_ptr()); - } - - const SkPoint& start_point() const { return start_point_; } - const SkPoint& end_point() const { return end_point_; } - - protected: - virtual const void* pod() const override { return this + 1; } - - bool equals_(DlColorSource const& other) const override { - FML_DCHECK(other.type() == DlColorSourceType::kLinearGradient); - auto that = static_cast(&other); - return (start_point_ == that->start_point_ && - end_point_ == that->end_point_ && base_equals_(that)); - } - - private: - DlLinearGradientColorSource(const SkPoint start_point, - const SkPoint end_point, - uint32_t stop_count, - const DlColor* colors, - const float* stops, - DlTileMode tile_mode, - const SkMatrix* matrix = nullptr) - : DlGradientColorSourceBase(stop_count, tile_mode, matrix), - start_point_(start_point), - end_point_(end_point) { - store_color_stops(this + 1, colors, stops); - } - - explicit DlLinearGradientColorSource( - const DlLinearGradientColorSource* source) - : DlGradientColorSourceBase(source->stop_count(), - source->tile_mode(), - source->matrix_ptr()), - start_point_(source->start_point()), - end_point_(source->end_point()) { - store_color_stops(this + 1, source->colors(), source->stops()); - } - - SkPoint start_point_; - SkPoint end_point_; - - friend class DlColorSource; - friend class DisplayListBuilder; - - FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlLinearGradientColorSource); -}; - -class DlRadialGradientColorSource final : public DlGradientColorSourceBase { - public: - const DlRadialGradientColorSource* asRadialGradient() const override { - return this; - } - - bool isUIThreadSafe() const override { return true; } - - std::shared_ptr shared() const override { - return MakeRadial(center_, radius_, stop_count(), colors(), stops(), - tile_mode(), matrix_ptr()); - } - - DlColorSourceType type() const override { - return DlColorSourceType::kRadialGradient; - } - size_t size() const override { return sizeof(*this) + vector_sizes(); } - - SkPoint center() const { return center_; } - SkScalar radius() const { return radius_; } - - protected: - virtual const void* pod() const override { return this + 1; } - - bool equals_(DlColorSource const& other) const override { - FML_DCHECK(other.type() == DlColorSourceType::kRadialGradient); - auto that = static_cast(&other); - return (center_ == that->center_ && radius_ == that->radius_ && - base_equals_(that)); - } - - private: - DlRadialGradientColorSource(SkPoint center, - SkScalar radius, - uint32_t stop_count, - const DlColor* colors, - const float* stops, - DlTileMode tile_mode, - const SkMatrix* matrix = nullptr) - : DlGradientColorSourceBase(stop_count, tile_mode, matrix), - center_(center), - radius_(radius) { - store_color_stops(this + 1, colors, stops); - } - - explicit DlRadialGradientColorSource( - const DlRadialGradientColorSource* source) - : DlGradientColorSourceBase(source->stop_count(), - source->tile_mode(), - source->matrix_ptr()), - center_(source->center()), - radius_(source->radius()) { - store_color_stops(this + 1, source->colors(), source->stops()); - } - - SkPoint center_; - SkScalar radius_; - - friend class DlColorSource; - friend class DisplayListBuilder; - - FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlRadialGradientColorSource); -}; - -class DlConicalGradientColorSource final : public DlGradientColorSourceBase { - public: - const DlConicalGradientColorSource* asConicalGradient() const override { - return this; - } - - bool isUIThreadSafe() const override { return true; } - - std::shared_ptr shared() const override { - return MakeConical(start_center_, start_radius_, end_center_, end_radius_, - stop_count(), colors(), stops(), tile_mode(), - matrix_ptr()); - } - - DlColorSourceType type() const override { - return DlColorSourceType::kConicalGradient; - } - size_t size() const override { return sizeof(*this) + vector_sizes(); } - - SkPoint start_center() const { return start_center_; } - SkScalar start_radius() const { return start_radius_; } - SkPoint end_center() const { return end_center_; } - SkScalar end_radius() const { return end_radius_; } - - protected: - virtual const void* pod() const override { return this + 1; } - - bool equals_(DlColorSource const& other) const override { - FML_DCHECK(other.type() == DlColorSourceType::kConicalGradient); - auto that = static_cast(&other); - return (start_center_ == that->start_center_ && - start_radius_ == that->start_radius_ && - end_center_ == that->end_center_ && - end_radius_ == that->end_radius_ && base_equals_(that)); - } - - private: - DlConicalGradientColorSource(SkPoint start_center, - SkScalar start_radius, - SkPoint end_center, - SkScalar end_radius, - uint32_t stop_count, - const DlColor* colors, - const float* stops, - DlTileMode tile_mode, - const SkMatrix* matrix = nullptr) - : DlGradientColorSourceBase(stop_count, tile_mode, matrix), - start_center_(start_center), - start_radius_(start_radius), - end_center_(end_center), - end_radius_(end_radius) { - store_color_stops(this + 1, colors, stops); - } - - explicit DlConicalGradientColorSource( - const DlConicalGradientColorSource* source) - : DlGradientColorSourceBase(source->stop_count(), - source->tile_mode(), - source->matrix_ptr()), - start_center_(source->start_center()), - start_radius_(source->start_radius()), - end_center_(source->end_center()), - end_radius_(source->end_radius()) { - store_color_stops(this + 1, source->colors(), source->stops()); - } - - SkPoint start_center_; - SkScalar start_radius_; - SkPoint end_center_; - SkScalar end_radius_; - - friend class DlColorSource; - friend class DisplayListBuilder; - - FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlConicalGradientColorSource); -}; - -class DlSweepGradientColorSource final : public DlGradientColorSourceBase { - public: - const DlSweepGradientColorSource* asSweepGradient() const override { - return this; - } - - bool isUIThreadSafe() const override { return true; } - - std::shared_ptr shared() const override { - return MakeSweep(center_, start_, end_, stop_count(), colors(), stops(), - tile_mode(), matrix_ptr()); - } - - DlColorSourceType type() const override { - return DlColorSourceType::kSweepGradient; - } - size_t size() const override { return sizeof(*this) + vector_sizes(); } - - SkPoint center() const { return center_; } - SkScalar start() const { return start_; } - SkScalar end() const { return end_; } - - protected: - virtual const void* pod() const override { return this + 1; } - - bool equals_(DlColorSource const& other) const override { - FML_DCHECK(other.type() == DlColorSourceType::kSweepGradient); - auto that = static_cast(&other); - return (center_ == that->center_ && start_ == that->start_ && - end_ == that->end_ && base_equals_(that)); - } - - private: - DlSweepGradientColorSource(SkPoint center, - SkScalar start, - SkScalar end, - uint32_t stop_count, - const DlColor* colors, - const float* stops, - DlTileMode tile_mode, - const SkMatrix* matrix = nullptr) - : DlGradientColorSourceBase(stop_count, tile_mode, matrix), - center_(center), - start_(start), - end_(end) { - store_color_stops(this + 1, colors, stops); - } - - explicit DlSweepGradientColorSource(const DlSweepGradientColorSource* source) - : DlGradientColorSourceBase(source->stop_count(), - source->tile_mode(), - source->matrix_ptr()), - center_(source->center()), - start_(source->start()), - end_(source->end()) { - store_color_stops(this + 1, source->colors(), source->stops()); - } - - SkPoint center_; - SkScalar start_; - SkScalar end_; - - friend class DlColorSource; - friend class DisplayListBuilder; - - FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlSweepGradientColorSource); -}; - -class DlRuntimeEffectColorSource final : public DlColorSource { - public: - DlRuntimeEffectColorSource( - sk_sp runtime_effect, - std::vector> samplers, - std::shared_ptr> uniform_data) - : runtime_effect_(std::move(runtime_effect)), - samplers_(std::move(samplers)), - uniform_data_(std::move(uniform_data)) {} - - bool isUIThreadSafe() const override { - for (const auto& sampler : samplers_) { - if (!sampler->isUIThreadSafe()) { - return false; - } - } - return true; - } - - const DlRuntimeEffectColorSource* asRuntimeEffect() const override { - return this; - } - - std::shared_ptr shared() const override { - return std::make_shared( - runtime_effect_, samplers_, uniform_data_); - } - - DlColorSourceType type() const override { - return DlColorSourceType::kRuntimeEffect; - } - size_t size() const override { return sizeof(*this); } - - bool is_opaque() const override { return false; } - - const sk_sp runtime_effect() const { - return runtime_effect_; - } - const std::vector> samplers() const { - return samplers_; - } - const std::shared_ptr> uniform_data() const { - return uniform_data_; - } - - protected: - bool equals_(DlColorSource const& other) const override { - FML_DCHECK(other.type() == DlColorSourceType::kRuntimeEffect); - auto that = static_cast(&other); - if (runtime_effect_ != that->runtime_effect_) { - return false; - } - if (uniform_data_ != that->uniform_data_) { - return false; - } - if (samplers_.size() != that->samplers_.size()) { - return false; - } - for (size_t i = 0; i < samplers_.size(); i++) { - if (samplers_[i] != that->samplers_[i]) { - return false; - } - } - return true; - } - - private: - sk_sp runtime_effect_; - std::vector> samplers_; - std::shared_ptr> uniform_data_; - - FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlRuntimeEffectColorSource); -}; } // namespace flutter #endif // FLUTTER_DISPLAY_LIST_EFFECTS_DL_COLOR_SOURCE_H_ diff --git a/display_list/effects/dl_color_source_unittests.cc b/display_list/effects/dl_color_source_unittests.cc index 6fc0aac4cddb4..944de9c994836 100644 --- a/display_list/effects/dl_color_source_unittests.cc +++ b/display_list/effects/dl_color_source_unittests.cc @@ -5,9 +5,9 @@ #include #include -#include "display_list/dl_color.h" +#include "flutter/display_list/dl_color.h" #include "flutter/display_list/dl_sampling_options.h" -#include "flutter/display_list/effects/dl_color_source.h" +#include "flutter/display_list/effects/dl_color_sources.h" #include "flutter/display_list/effects/dl_runtime_effect.h" #include "flutter/display_list/image/dl_image.h" #include "flutter/display_list/testing/dl_test_equality.h" @@ -47,14 +47,16 @@ static const sk_sp kTestImage1 = MakeTestImage(10, 10, SK_ColorGREEN); static const sk_sp kTestAlphaImage1 = MakeTestImage(10, 10, SK_ColorTRANSPARENT); // clang-format off -static const SkMatrix kTestMatrix1 = - SkMatrix::MakeAll(2, 0, 10, - 0, 3, 12, - 0, 0, 1); -static const SkMatrix kTestMatrix2 = - SkMatrix::MakeAll(4, 0, 15, - 0, 7, 17, - 0, 0, 1); +static const DlMatrix kTestMatrix1 = + DlMatrix::MakeRow(2, 0, 0, 10, + 0, 3, 0, 12, + 0, 0, 1, 0, + 0, 0, 0, 1); +static const DlMatrix kTestMatrix2 = + DlMatrix::MakeRow(4, 0, 0, 15, + 0, 7, 0, 17, + 0, 0, 1, 0, + 0, 0, 0, 1); // clang-format on static constexpr int kTestStopCount = 3; static constexpr DlColor kTestColors[kTestStopCount] = { @@ -77,62 +79,15 @@ static constexpr float kTestStops2[kTestStopCount] = { 0.3f, 1.0f, }; -static constexpr SkPoint kTestPoints[2] = { - SkPoint::Make(5, 15), - SkPoint::Make(7, 18), +static constexpr DlPoint kTestPoints[2] = { + DlPoint(5, 15), + DlPoint(7, 18), }; -static constexpr SkPoint kTestPoints2[2] = { - SkPoint::Make(100, 115), - SkPoint::Make(107, 118), +static constexpr DlPoint kTestPoints2[2] = { + DlPoint(100, 115), + DlPoint(107, 118), }; -TEST(DisplayListColorSource, ColorConstructor) { - DlColorColorSource source(DlColor::kRed()); -} - -TEST(DisplayListColorSource, ColorShared) { - DlColorColorSource source(DlColor::kRed()); - ASSERT_NE(source.shared().get(), &source); - ASSERT_EQ(*source.shared(), source); -} - -TEST(DisplayListColorSource, ColorAsColor) { - DlColorColorSource source(DlColor::kRed()); - ASSERT_NE(source.asColor(), nullptr); - ASSERT_EQ(source.asColor(), &source); - - ASSERT_EQ(source.asImage(), nullptr); - ASSERT_EQ(source.asLinearGradient(), nullptr); - ASSERT_EQ(source.asRadialGradient(), nullptr); - ASSERT_EQ(source.asConicalGradient(), nullptr); - ASSERT_EQ(source.asSweepGradient(), nullptr); - ASSERT_EQ(source.asRuntimeEffect(), nullptr); -} - -TEST(DisplayListColorSource, ColorContents) { - DlColorColorSource source(DlColor::kRed()); - ASSERT_EQ(source.color(), DlColor::kRed()); - ASSERT_EQ(source.is_opaque(), true); - for (int i = 0; i < 255; i++) { - SkColor alpha_color = SkColorSetA(SK_ColorRED, i); - auto const alpha_source = DlColorColorSource(DlColor(alpha_color)); - ASSERT_EQ(alpha_source.color(), alpha_color); - ASSERT_EQ(alpha_source.is_opaque(), false); - } -} - -TEST(DisplayListColorSource, ColorEquals) { - DlColorColorSource source1(DlColor::kRed()); - DlColorColorSource source2(DlColor::kRed()); - TestEquals(source1, source2); -} - -TEST(DisplayListColorSource, ColorNotEquals) { - DlColorColorSource source1(DlColor::kRed()); - DlColorColorSource source2(DlColor::kBlue()); - TestNotEquals(source1, source2, "Color differs"); -} - TEST(DisplayListColorSource, ImageConstructor) { DlImageColorSource source(kTestImage1, DlTileMode::kClamp, DlTileMode::kClamp, DlImageSampling::kLinear, &kTestMatrix1); @@ -151,11 +106,11 @@ TEST(DisplayListColorSource, ImageAsImage) { ASSERT_NE(source.asImage(), nullptr); ASSERT_EQ(source.asImage(), &source); - ASSERT_EQ(source.asColor(), nullptr); ASSERT_EQ(source.asLinearGradient(), nullptr); ASSERT_EQ(source.asRadialGradient(), nullptr); ASSERT_EQ(source.asConicalGradient(), nullptr); ASSERT_EQ(source.asSweepGradient(), nullptr); + ASSERT_EQ(source.asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, ImageContents) { @@ -249,7 +204,6 @@ TEST(DisplayListColorSource, LinearGradientAsLinear) { ASSERT_NE(source->asLinearGradient(), nullptr); ASSERT_EQ(source->asLinearGradient(), source.get()); - ASSERT_EQ(source->asColor(), nullptr); ASSERT_EQ(source->asImage(), nullptr); ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); @@ -368,7 +322,6 @@ TEST(DisplayListColorSource, RadialGradientAsRadial) { ASSERT_NE(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asRadialGradient(), source.get()); - ASSERT_EQ(source->asColor(), nullptr); ASSERT_EQ(source->asImage(), nullptr); ASSERT_EQ(source->asLinearGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); @@ -487,7 +440,6 @@ TEST(DisplayListColorSource, ConicalGradientAsConical) { ASSERT_NE(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), source.get()); - ASSERT_EQ(source->asColor(), nullptr); ASSERT_EQ(source->asImage(), nullptr); ASSERT_EQ(source->asLinearGradient(), nullptr); ASSERT_EQ(source->asRadialGradient(), nullptr); @@ -622,7 +574,6 @@ TEST(DisplayListColorSource, SweepGradientAsSweep) { ASSERT_NE(source->asSweepGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), source.get()); - ASSERT_EQ(source->asColor(), nullptr); ASSERT_EQ(source->asImage(), nullptr); ASSERT_EQ(source->asLinearGradient(), nullptr); ASSERT_EQ(source->asRadialGradient(), nullptr); @@ -729,22 +680,18 @@ TEST(DisplayListColorSource, SweepGradientNotEquals) { } TEST(DisplayListColorSource, RuntimeEffect) { - std::shared_ptr source1 = - DlColorSource::MakeRuntimeEffect( - kTestRuntimeEffect1, {}, std::make_shared>()); - std::shared_ptr source2 = - DlColorSource::MakeRuntimeEffect( - kTestRuntimeEffect2, {}, std::make_shared>()); - std::shared_ptr source3 = - DlColorSource::MakeRuntimeEffect( - nullptr, {}, std::make_shared>()); + std::shared_ptr source1 = DlColorSource::MakeRuntimeEffect( + kTestRuntimeEffect1, {}, std::make_shared>()); + std::shared_ptr source2 = DlColorSource::MakeRuntimeEffect( + kTestRuntimeEffect2, {}, std::make_shared>()); + std::shared_ptr source3 = DlColorSource::MakeRuntimeEffect( + nullptr, {}, std::make_shared>()); ASSERT_EQ(source1->type(), DlColorSourceType::kRuntimeEffect); ASSERT_EQ(source1->asRuntimeEffect(), source1.get()); ASSERT_NE(source2->asRuntimeEffect(), source1.get()); ASSERT_EQ(source1->asImage(), nullptr); - ASSERT_EQ(source1->asColor(), nullptr); ASSERT_EQ(source1->asLinearGradient(), nullptr); ASSERT_EQ(source1->asRadialGradient(), nullptr); ASSERT_EQ(source1->asConicalGradient(), nullptr); diff --git a/display_list/effects/dl_color_sources.h b/display_list/effects/dl_color_sources.h new file mode 100644 index 0000000000000..23e810adc4eb7 --- /dev/null +++ b/display_list/effects/dl_color_sources.h @@ -0,0 +1,15 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_DL_COLOR_SOURCES_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_DL_COLOR_SOURCES_H_ + +#include "flutter/display_list/effects/color_sources/dl_conical_gradient_color_source.h" +#include "flutter/display_list/effects/color_sources/dl_image_color_source.h" +#include "flutter/display_list/effects/color_sources/dl_linear_gradient_color_source.h" +#include "flutter/display_list/effects/color_sources/dl_radial_gradient_color_source.h" +#include "flutter/display_list/effects/color_sources/dl_runtime_effect_color_source.h" +#include "flutter/display_list/effects/color_sources/dl_sweep_gradient_color_source.h" + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_DL_COLOR_SOURCES_H_ diff --git a/display_list/effects/dl_image_filter.cc b/display_list/effects/dl_image_filter.cc index 7f0481f0562e0..468b420991656 100644 --- a/display_list/effects/dl_image_filter.cc +++ b/display_list/effects/dl_image_filter.cc @@ -4,24 +4,164 @@ #include "flutter/display_list/effects/dl_image_filter.h" +#include "flutter/display_list/effects/dl_image_filters.h" + namespace flutter { +std::shared_ptr DlImageFilter::MakeBlur(DlScalar sigma_x, + DlScalar sigma_y, + DlTileMode tile_mode) { + return DlBlurImageFilter::Make(sigma_x, sigma_y, tile_mode); +} + +std::shared_ptr DlImageFilter::MakeDilate(DlScalar radius_x, + DlScalar radius_y) { + return DlDilateImageFilter::Make(radius_x, radius_y); +} + +std::shared_ptr DlImageFilter::MakeErode(DlScalar radius_x, + DlScalar radius_y) { + return DlErodeImageFilter::Make(radius_x, radius_y); +} + +std::shared_ptr DlImageFilter::MakeMatrix( + const DlMatrix& matrix, + DlImageSampling sampling) { + return DlMatrixImageFilter::Make(matrix, sampling); +} + +std::shared_ptr DlImageFilter::MakeRuntimeEffect( + sk_sp runtime_effect, + std::vector> samplers, + std::shared_ptr> uniform_data) { + return DlRuntimeEffectImageFilter::Make( + std::move(runtime_effect), std::move(samplers), std::move(uniform_data)); +} + +std::shared_ptr DlImageFilter::MakeColorFilter( + const std::shared_ptr& filter) { + return DlColorFilterImageFilter::Make(filter); +} + +std::shared_ptr DlImageFilter::MakeCompose( + const std::shared_ptr& outer, + const std::shared_ptr& inner) { + return DlComposeImageFilter::Make(outer, inner); +} + +DlVector2 DlImageFilter::map_vectors_affine(const DlMatrix& ctm, + DlScalar x, + DlScalar y) { + FML_DCHECK(std::isfinite(x) && x >= 0); + FML_DCHECK(std::isfinite(y) && y >= 0); + FML_DCHECK(ctm.IsFinite() && !ctm.HasPerspective2D()); + + // The x and y scalars would have been used to expand a local space + // rectangle which is then transformed by ctm. In order to do the + // expansion correctly, we should look at the relevant math. The + // 4 corners will be moved outward by the following vectors: + // (UL,UR,LR,LL) = ((-x, -y), (+x, -y), (+x, +y), (-x, +y)) + // After applying the transform, each of these vectors could be + // pointing in any direction so we need to examine each transformed + // delta vector and how it affected the bounds. + // Looking at just the affine 2x3 entries of the CTM we can delta + // transform these corner offsets and get the following: + // UL = dCTM(-x, -y) = (- x*m00 - y*m01, - x*m10 - y*m11) + // UR = dCTM(+x, -y) = ( x*m00 - y*m01, x*m10 - y*m11) + // LR = dCTM(+x, +y) = ( x*m00 + y*m01, x*m10 + y*m11) + // LL = dCTM(-x, +y) = (- x*m00 + y*m01, - x*m10 + y*m11) + // The X vectors are all some variation of adding or subtracting + // the sum of x*m00 and y*m01 or their difference. Similarly the Y + // vectors are +/- the associated sum/difference of x*m10 and y*m11. + // The largest displacements, both left/right or up/down, will + // happen when the signs of the m00/m01/m10/m11 matrix entries + // coincide with the signs of the scalars, i.e. are all positive. + return {x * abs(ctm.m[0]) + y * abs(ctm.m[4]), + x * abs(ctm.m[1]) + y * abs(ctm.m[5])}; +} + +DlIRect* DlImageFilter::inset_device_bounds(const DlIRect& input_bounds, + DlScalar radius_x, + DlScalar radius_y, + const DlMatrix& ctm, + DlIRect& output_bounds) { + if (ctm.IsFinite() && ctm.IsInvertible()) { + if (ctm.HasPerspective2D()) { + // Ideally this code would use Impeller classes to do the math, see: + // https://github.com/flutter/flutter/issues/159175 + SkMatrix sk_ctm = ToSkMatrix(ctm); + FML_DCHECK(sk_ctm.hasPerspective()); + SkIRect sk_input_bounds = + SkIRect::MakeLTRB(input_bounds.GetLeft(), input_bounds.GetTop(), + input_bounds.GetRight(), input_bounds.GetBottom()); + + SkMatrix inverse; + if (sk_ctm.invert(&inverse)) { + SkRect local_bounds = inverse.mapRect(SkRect::Make(sk_input_bounds)); + local_bounds.inset(radius_x, radius_y); + output_bounds = ToDlIRect(sk_ctm.mapRect(local_bounds).roundOut()); + return &output_bounds; + } + } else { + DlVector2 device_radius = map_vectors_affine(ctm, radius_x, radius_y); + output_bounds = + input_bounds.Expand(-floor(device_radius.x), -floor(device_radius.y)); + return &output_bounds; + } + } + output_bounds = input_bounds; + return nullptr; +} + +DlIRect* DlImageFilter::outset_device_bounds(const DlIRect& input_bounds, + DlScalar radius_x, + DlScalar radius_y, + const DlMatrix& ctm, + DlIRect& output_bounds) { + if (ctm.IsFinite() && ctm.IsInvertible()) { + if (ctm.HasPerspective2D()) { + // Ideally this code would use Impeller classes to do the math, see: + // https://github.com/flutter/flutter/issues/159175 + SkMatrix sk_ctm = ToSkMatrix(ctm); + FML_DCHECK(sk_ctm.hasPerspective()); + SkIRect sk_input_bounds = + SkIRect::MakeLTRB(input_bounds.GetLeft(), input_bounds.GetTop(), + input_bounds.GetRight(), input_bounds.GetBottom()); + + SkMatrix inverse; + if (sk_ctm.invert(&inverse)) { + SkRect local_bounds = inverse.mapRect(SkRect::Make(sk_input_bounds)); + local_bounds.outset(radius_x, radius_y); + output_bounds = ToDlIRect(sk_ctm.mapRect(local_bounds).roundOut()); + return &output_bounds; + } + } else { + DlVector2 device_radius = map_vectors_affine(ctm, radius_x, radius_y); + output_bounds = + input_bounds.Expand(ceil(device_radius.x), ceil(device_radius.y)); + return &output_bounds; + } + } + output_bounds = input_bounds; + return nullptr; +} + std::shared_ptr DlImageFilter::makeWithLocalMatrix( - const SkMatrix& matrix) const { - if (matrix.isIdentity()) { + const DlMatrix& matrix) const { + if (matrix.IsIdentity()) { return shared(); } // Matrix switch (this->matrix_capability()) { case MatrixCapability::kTranslate: { - if (!matrix.isTranslate()) { + if (!matrix.IsTranslationOnly()) { // Nothing we can do at this point return nullptr; } break; } case MatrixCapability::kScaleTranslate: { - if (!matrix.isScaleTranslate()) { + if (!matrix.IsTranslationScaleOnly()) { // Nothing we can do at this point return nullptr; } @@ -30,70 +170,7 @@ std::shared_ptr DlImageFilter::makeWithLocalMatrix( default: break; } - return std::make_shared(matrix, shared()); -} - -SkRect* DlComposeImageFilter::map_local_bounds(const SkRect& input_bounds, - SkRect& output_bounds) const { - SkRect cur_bounds = input_bounds; - SkRect* ret = &output_bounds; - // We set this result in case neither filter is present. - output_bounds = input_bounds; - if (inner_) { - if (!inner_->map_local_bounds(cur_bounds, output_bounds)) { - ret = nullptr; - } - cur_bounds = output_bounds; - } - if (outer_) { - if (!outer_->map_local_bounds(cur_bounds, output_bounds)) { - ret = nullptr; - } - } - return ret; -} - -SkIRect* DlComposeImageFilter::map_device_bounds(const SkIRect& input_bounds, - const SkMatrix& ctm, - SkIRect& output_bounds) const { - SkIRect cur_bounds = input_bounds; - SkIRect* ret = &output_bounds; - // We set this result in case neither filter is present. - output_bounds = input_bounds; - if (inner_) { - if (!inner_->map_device_bounds(cur_bounds, ctm, output_bounds)) { - ret = nullptr; - } - cur_bounds = output_bounds; - } - if (outer_) { - if (!outer_->map_device_bounds(cur_bounds, ctm, output_bounds)) { - ret = nullptr; - } - } - return ret; -} - -SkIRect* DlComposeImageFilter::get_input_device_bounds( - const SkIRect& output_bounds, - const SkMatrix& ctm, - SkIRect& input_bounds) const { - SkIRect cur_bounds = output_bounds; - SkIRect* ret = &input_bounds; - // We set this result in case neither filter is present. - input_bounds = output_bounds; - if (outer_) { - if (!outer_->get_input_device_bounds(cur_bounds, ctm, input_bounds)) { - ret = nullptr; - } - cur_bounds = input_bounds; - } - if (inner_) { - if (!inner_->get_input_device_bounds(cur_bounds, ctm, input_bounds)) { - ret = nullptr; - } - } - return ret; + return DlLocalMatrixImageFilter::Make(matrix, shared()); } } // namespace flutter diff --git a/display_list/effects/dl_image_filter.h b/display_list/effects/dl_image_filter.h index 1e6b01529429c..911fe62778247 100644 --- a/display_list/effects/dl_image_filter.h +++ b/display_list/effects/dl_image_filter.h @@ -5,16 +5,13 @@ #ifndef FLUTTER_DISPLAY_LIST_EFFECTS_DL_IMAGE_FILTER_H_ #define FLUTTER_DISPLAY_LIST_EFFECTS_DL_IMAGE_FILTER_H_ -#include - #include "flutter/display_list/dl_attributes.h" #include "flutter/display_list/dl_sampling_options.h" #include "flutter/display_list/dl_tile_mode.h" #include "flutter/display_list/effects/dl_color_filter.h" -#include "flutter/display_list/utils/dl_comparable.h" -#include "flutter/fml/logging.h" - -#include "third_party/skia/include/core/SkMatrix.h" +#include "flutter/display_list/effects/dl_color_source.h" +#include "flutter/display_list/effects/dl_runtime_effect.h" +#include "flutter/display_list/geometry/dl_geometry_types.h" namespace flutter { @@ -31,8 +28,9 @@ enum class DlImageFilterType { kDilate, kErode, kMatrix, - kCompose, + kRuntimeEffect, kColorFilter, + kCompose, kLocalMatrix, }; @@ -40,9 +38,10 @@ class DlBlurImageFilter; class DlDilateImageFilter; class DlErodeImageFilter; class DlMatrixImageFilter; -class DlLocalMatrixImageFilter; -class DlComposeImageFilter; +class DlRuntimeEffectImageFilter; class DlColorFilterImageFilter; +class DlComposeImageFilter; +class DlLocalMatrixImageFilter; class DlImageFilter : public DlAttribute { public: @@ -52,6 +51,31 @@ class DlImageFilter : public DlAttribute { kComplex, }; + static std::shared_ptr MakeBlur(DlScalar sigma_x, + DlScalar sigma_y, + DlTileMode tile_mode); + + static std::shared_ptr MakeDilate(DlScalar radius_x, + DlScalar radius_y); + + static std::shared_ptr MakeErode(DlScalar radius_x, + DlScalar radius_y); + + static std::shared_ptr MakeMatrix(const DlMatrix& matrix, + DlImageSampling sampling); + + static std::shared_ptr MakeRuntimeEffect( + sk_sp runtime_effect, + std::vector> samplers, + std::shared_ptr> uniform_data); + + static std::shared_ptr MakeColorFilter( + const std::shared_ptr& filter); + + static std::shared_ptr MakeCompose( + const std::shared_ptr& outer, + const std::shared_ptr& inner); + // Return a DlBlurImageFilter pointer to this object iff it is a Blur // type of ImageFilter, otherwise return nullptr. virtual const DlBlurImageFilter* asBlur() const { return nullptr; } @@ -73,7 +97,7 @@ class DlImageFilter : public DlAttribute { } virtual std::shared_ptr makeWithLocalMatrix( - const SkMatrix& matrix) const; + const DlMatrix& matrix) const; // Return a DlComposeImageFilter pointer to this object iff it is a Compose // type of ImageFilter, otherwise return nullptr. @@ -85,6 +109,12 @@ class DlImageFilter : public DlAttribute { return nullptr; } + // Return a DlRuntimeEffectImageFilter pointer to this object iff it is a + // DlRuntimeEffectImageFilter type of ImageFilter, otherwise return nullptr. + virtual const DlRuntimeEffectImageFilter* asRuntimeEffectFilter() const { + return nullptr; + } + // Return a boolean indicating whether the image filtering operation will // modify transparent black. This is typically used to determine if applying // the ImageFilter to a temporary saveLayer buffer will turn the surrounding @@ -99,8 +129,8 @@ class DlImageFilter : public DlAttribute { // can successfully compute the output bounds of the filter, otherwise the // method will return a nullptr and the output_bounds will be filled with // a best guess for the answer, even if just a copy of the input_bounds. - virtual SkRect* map_local_bounds(const SkRect& input_bounds, - SkRect& output_bounds) const = 0; + virtual DlRect* map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const = 0; // Return the device bounds of the output for this image filtering operation // based on the supplied input device bounds where both are measured in the @@ -113,9 +143,9 @@ class DlImageFilter : public DlAttribute { // can successfully compute the output bounds of the filter, otherwise the // method will return a nullptr and the output_bounds will be filled with // a best guess for the answer, even if just a copy of the input_bounds. - virtual SkIRect* map_device_bounds(const SkIRect& input_bounds, - const SkMatrix& ctm, - SkIRect& output_bounds) const = 0; + virtual DlIRect* map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const = 0; // Return the input bounds that will be needed in order for the filter to // properly fill the indicated output_bounds under the specified @@ -126,620 +156,30 @@ class DlImageFilter : public DlAttribute { // can successfully compute the required input bounds, otherwise the // method will return a nullptr and the input_bounds will be filled with // a best guess for the answer, even if just a copy of the output_bounds. - virtual SkIRect* get_input_device_bounds(const SkIRect& output_bounds, - const SkMatrix& ctm, - SkIRect& input_bounds) const = 0; + virtual DlIRect* get_input_device_bounds(const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const = 0; virtual MatrixCapability matrix_capability() const { return MatrixCapability::kScaleTranslate; } protected: - static SkVector map_vectors_affine(const SkMatrix& ctm, - SkScalar x, - SkScalar y) { - FML_DCHECK(std::isfinite(x) && x >= 0); - FML_DCHECK(std::isfinite(y) && y >= 0); - FML_DCHECK(ctm.isFinite() && !ctm.hasPerspective()); - - // The x and y scalars would have been used to expand a local space - // rectangle which is then transformed by ctm. In order to do the - // expansion correctly, we should look at the relevant math. The - // 4 corners will be moved outward by the following vectors: - // (UL,UR,LR,LL) = ((-x, -y), (+x, -y), (+x, +y), (-x, +y)) - // After applying the transform, each of these vectors could be - // pointing in any direction so we need to examine each transformed - // delta vector and how it affected the bounds. - // Looking at just the affine 2x3 entries of the CTM we can delta - // transform these corner offsets and get the following: - // UL = dCTM(-x, -y) = (- x*m00 - y*m01, - x*m10 - y*m11) - // UR = dCTM(+x, -y) = ( x*m00 - y*m01, x*m10 - y*m11) - // LR = dCTM(+x, +y) = ( x*m00 + y*m01, x*m10 + y*m11) - // LL = dCTM(-x, +y) = (- x*m00 + y*m01, - x*m10 + y*m11) - // The X vectors are all some variation of adding or subtracting - // the sum of x*m00 and y*m01 or their difference. Similarly the Y - // vectors are +/- the associated sum/difference of x*m10 and y*m11. - // The largest displacements, both left/right or up/down, will - // happen when the signs of the m00/m01/m10/m11 matrix entries - // coincide with the signs of the scalars, i.e. are all positive. - return {x * abs(ctm[0]) + y * abs(ctm[1]), - x * abs(ctm[3]) + y * abs(ctm[4])}; - } - - static SkIRect* inset_device_bounds(const SkIRect& input_bounds, - SkScalar radius_x, - SkScalar radius_y, - const SkMatrix& ctm, - SkIRect& output_bounds) { - if (ctm.isFinite()) { - if (ctm.hasPerspective()) { - SkMatrix inverse; - if (ctm.invert(&inverse)) { - SkRect local_bounds = inverse.mapRect(SkRect::Make(input_bounds)); - local_bounds.inset(radius_x, radius_y); - output_bounds = ctm.mapRect(local_bounds).roundOut(); - return &output_bounds; - } - } else { - SkVector device_radius = map_vectors_affine(ctm, radius_x, radius_y); - output_bounds = input_bounds.makeInset(floor(device_radius.fX), // - floor(device_radius.fY)); - return &output_bounds; - } - } - output_bounds = input_bounds; - return nullptr; - } - - static SkIRect* outset_device_bounds(const SkIRect& input_bounds, - SkScalar radius_x, - SkScalar radius_y, - const SkMatrix& ctm, - SkIRect& output_bounds) { - if (ctm.isFinite()) { - if (ctm.hasPerspective()) { - SkMatrix inverse; - if (ctm.invert(&inverse)) { - SkRect local_bounds = inverse.mapRect(SkRect::Make(input_bounds)); - local_bounds.outset(radius_x, radius_y); - output_bounds = ctm.mapRect(local_bounds).roundOut(); - return &output_bounds; - } - } else { - SkVector device_radius = map_vectors_affine(ctm, radius_x, radius_y); - output_bounds = input_bounds.makeOutset(ceil(device_radius.fX), // - ceil(device_radius.fY)); - return &output_bounds; - } - } - output_bounds = input_bounds; - return nullptr; - } -}; - -class DlBlurImageFilter final : public DlImageFilter { - public: - DlBlurImageFilter(SkScalar sigma_x, SkScalar sigma_y, DlTileMode tile_mode) - : sigma_x_(sigma_x), sigma_y_(sigma_y), tile_mode_(tile_mode) {} - explicit DlBlurImageFilter(const DlBlurImageFilter* filter) - : DlBlurImageFilter(filter->sigma_x_, - filter->sigma_y_, - filter->tile_mode_) {} - DlBlurImageFilter(const DlBlurImageFilter& filter) - : DlBlurImageFilter(&filter) {} - - static std::shared_ptr Make(SkScalar sigma_x, - SkScalar sigma_y, - DlTileMode tile_mode) { - if (!std::isfinite(sigma_x) || !std::isfinite(sigma_y)) { - return nullptr; - } - if (sigma_x < SK_ScalarNearlyZero && sigma_y < SK_ScalarNearlyZero) { - return nullptr; - } - sigma_x = (sigma_x < SK_ScalarNearlyZero) ? 0 : sigma_x; - sigma_y = (sigma_y < SK_ScalarNearlyZero) ? 0 : sigma_y; - return std::make_shared(sigma_x, sigma_y, tile_mode); - } - - std::shared_ptr shared() const override { - return std::make_shared(this); - } - - DlImageFilterType type() const override { return DlImageFilterType::kBlur; } - size_t size() const override { return sizeof(*this); } - - const DlBlurImageFilter* asBlur() const override { return this; } - - bool modifies_transparent_black() const override { return false; } - - SkRect* map_local_bounds(const SkRect& input_bounds, - SkRect& output_bounds) const override { - output_bounds = input_bounds.makeOutset(sigma_x_ * 3.0f, sigma_y_ * 3.0f); - return &output_bounds; - } - - SkIRect* map_device_bounds(const SkIRect& input_bounds, - const SkMatrix& ctm, - SkIRect& output_bounds) const override { - return outset_device_bounds(input_bounds, sigma_x_ * 3.0f, sigma_y_ * 3.0f, - ctm, output_bounds); - } - - SkIRect* get_input_device_bounds(const SkIRect& output_bounds, - const SkMatrix& ctm, - SkIRect& input_bounds) const override { - // Blurs are symmetric in terms of output-for-input and input-for-output - return map_device_bounds(output_bounds, ctm, input_bounds); - } - - SkScalar sigma_x() const { return sigma_x_; } - SkScalar sigma_y() const { return sigma_y_; } - DlTileMode tile_mode() const { return tile_mode_; } - - protected: - bool equals_(const DlImageFilter& other) const override { - FML_DCHECK(other.type() == DlImageFilterType::kBlur); - auto that = static_cast(&other); - return (SkScalarNearlyEqual(sigma_x_, that->sigma_x_) && - SkScalarNearlyEqual(sigma_y_, that->sigma_y_) && - tile_mode_ == that->tile_mode_); - } - - private: - SkScalar sigma_x_; - SkScalar sigma_y_; - DlTileMode tile_mode_; -}; - -class DlDilateImageFilter final : public DlImageFilter { - public: - DlDilateImageFilter(SkScalar radius_x, SkScalar radius_y) - : radius_x_(radius_x), radius_y_(radius_y) {} - explicit DlDilateImageFilter(const DlDilateImageFilter* filter) - : DlDilateImageFilter(filter->radius_x_, filter->radius_y_) {} - DlDilateImageFilter(const DlDilateImageFilter& filter) - : DlDilateImageFilter(&filter) {} - - static std::shared_ptr Make(SkScalar radius_x, - SkScalar radius_y) { - if (std::isfinite(radius_x) && radius_x > SK_ScalarNearlyZero && - std::isfinite(radius_y) && radius_y > SK_ScalarNearlyZero) { - return std::make_shared(radius_x, radius_y); - } - return nullptr; - } - - std::shared_ptr shared() const override { - return std::make_shared(this); - } - - DlImageFilterType type() const override { return DlImageFilterType::kDilate; } - size_t size() const override { return sizeof(*this); } - - const DlDilateImageFilter* asDilate() const override { return this; } - - bool modifies_transparent_black() const override { return false; } - - SkRect* map_local_bounds(const SkRect& input_bounds, - SkRect& output_bounds) const override { - output_bounds = input_bounds.makeOutset(radius_x_, radius_y_); - return &output_bounds; - } - - SkIRect* map_device_bounds(const SkIRect& input_bounds, - const SkMatrix& ctm, - SkIRect& output_bounds) const override { - return outset_device_bounds(input_bounds, radius_x_, radius_y_, ctm, - output_bounds); - } - - SkIRect* get_input_device_bounds(const SkIRect& output_bounds, - const SkMatrix& ctm, - SkIRect& input_bounds) const override { - return inset_device_bounds(output_bounds, radius_x_, radius_y_, ctm, - input_bounds); - } - - SkScalar radius_x() const { return radius_x_; } - SkScalar radius_y() const { return radius_y_; } - - protected: - bool equals_(const DlImageFilter& other) const override { - FML_DCHECK(other.type() == DlImageFilterType::kDilate); - auto that = static_cast(&other); - return (radius_x_ == that->radius_x_ && radius_y_ == that->radius_y_); - } - - private: - SkScalar radius_x_; - SkScalar radius_y_; -}; - -class DlErodeImageFilter final : public DlImageFilter { - public: - DlErodeImageFilter(SkScalar radius_x, SkScalar radius_y) - : radius_x_(radius_x), radius_y_(radius_y) {} - explicit DlErodeImageFilter(const DlErodeImageFilter* filter) - : DlErodeImageFilter(filter->radius_x_, filter->radius_y_) {} - DlErodeImageFilter(const DlErodeImageFilter& filter) - : DlErodeImageFilter(&filter) {} - - static std::shared_ptr Make(SkScalar radius_x, - SkScalar radius_y) { - if (std::isfinite(radius_x) && radius_x > SK_ScalarNearlyZero && - std::isfinite(radius_y) && radius_y > SK_ScalarNearlyZero) { - return std::make_shared(radius_x, radius_y); - } - return nullptr; - } - - std::shared_ptr shared() const override { - return std::make_shared(this); - } - - DlImageFilterType type() const override { return DlImageFilterType::kErode; } - size_t size() const override { return sizeof(*this); } - - const DlErodeImageFilter* asErode() const override { return this; } - - bool modifies_transparent_black() const override { return false; } - - SkRect* map_local_bounds(const SkRect& input_bounds, - SkRect& output_bounds) const override { - output_bounds = input_bounds.makeInset(radius_x_, radius_y_); - return &output_bounds; - } - - SkIRect* map_device_bounds(const SkIRect& input_bounds, - const SkMatrix& ctm, - SkIRect& output_bounds) const override { - return inset_device_bounds(input_bounds, radius_x_, radius_y_, ctm, - output_bounds); - } - - SkIRect* get_input_device_bounds(const SkIRect& output_bounds, - const SkMatrix& ctm, - SkIRect& input_bounds) const override { - return outset_device_bounds(output_bounds, radius_x_, radius_y_, ctm, - input_bounds); - } - - SkScalar radius_x() const { return radius_x_; } - SkScalar radius_y() const { return radius_y_; } - - protected: - bool equals_(const DlImageFilter& other) const override { - FML_DCHECK(other.type() == DlImageFilterType::kErode); - auto that = static_cast(&other); - return (radius_x_ == that->radius_x_ && radius_y_ == that->radius_y_); - } - - private: - SkScalar radius_x_; - SkScalar radius_y_; -}; - -class DlMatrixImageFilter final : public DlImageFilter { - public: - DlMatrixImageFilter(const SkMatrix& matrix, DlImageSampling sampling) - : matrix_(matrix), sampling_(sampling) {} - explicit DlMatrixImageFilter(const DlMatrixImageFilter* filter) - : DlMatrixImageFilter(filter->matrix_, filter->sampling_) {} - DlMatrixImageFilter(const DlMatrixImageFilter& filter) - : DlMatrixImageFilter(&filter) {} - - static std::shared_ptr Make(const SkMatrix& matrix, - DlImageSampling sampling) { - if (matrix.isFinite() && !matrix.isIdentity()) { - return std::make_shared(matrix, sampling); - } - return nullptr; - } - - std::shared_ptr shared() const override { - return std::make_shared(this); - } - - DlImageFilterType type() const override { return DlImageFilterType::kMatrix; } - size_t size() const override { return sizeof(*this); } - - const SkMatrix& matrix() const { return matrix_; } - DlImageSampling sampling() const { return sampling_; } - - const DlMatrixImageFilter* asMatrix() const override { return this; } - - bool modifies_transparent_black() const override { return false; } - - SkRect* map_local_bounds(const SkRect& input_bounds, - SkRect& output_bounds) const override { - output_bounds = matrix_.mapRect(input_bounds); - return &output_bounds; - } - - SkIRect* map_device_bounds(const SkIRect& input_bounds, - const SkMatrix& ctm, - SkIRect& output_bounds) const override { - SkMatrix matrix; - if (!ctm.invert(&matrix)) { - output_bounds = input_bounds; - return nullptr; - } - matrix.postConcat(matrix_); - matrix.postConcat(ctm); - SkRect device_rect; - matrix.mapRect(&device_rect, SkRect::Make(input_bounds)); - output_bounds = device_rect.roundOut(); - return &output_bounds; - } - - SkIRect* get_input_device_bounds(const SkIRect& output_bounds, - const SkMatrix& ctm, - SkIRect& input_bounds) const override { - SkMatrix matrix = SkMatrix::Concat(ctm, matrix_); - SkMatrix inverse; - if (!matrix.invert(&inverse)) { - input_bounds = output_bounds; - return nullptr; - } - inverse.postConcat(ctm); - SkRect bounds; - bounds.set(output_bounds); - inverse.mapRect(&bounds); - input_bounds = bounds.roundOut(); - return &input_bounds; - } - - protected: - bool equals_(const DlImageFilter& other) const override { - FML_DCHECK(other.type() == DlImageFilterType::kMatrix); - auto that = static_cast(&other); - return (matrix_ == that->matrix_ && sampling_ == that->sampling_); - } - - private: - SkMatrix matrix_; - DlImageSampling sampling_; -}; - -class DlComposeImageFilter final : public DlImageFilter { - public: - DlComposeImageFilter(std::shared_ptr outer, - std::shared_ptr inner) - : outer_(std::move(outer)), inner_(std::move(inner)) {} - DlComposeImageFilter(const DlImageFilter* outer, const DlImageFilter* inner) - : outer_(outer->shared()), inner_(inner->shared()) {} - DlComposeImageFilter(const DlImageFilter& outer, const DlImageFilter& inner) - : DlComposeImageFilter(&outer, &inner) {} - explicit DlComposeImageFilter(const DlComposeImageFilter* filter) - : DlComposeImageFilter(filter->outer_, filter->inner_) {} - DlComposeImageFilter(const DlComposeImageFilter& filter) - : DlComposeImageFilter(&filter) {} - - static std::shared_ptr Make( - std::shared_ptr outer, - std::shared_ptr inner) { - if (!outer) { - return inner; - } - if (!inner) { - return outer; - } - return std::make_shared(outer, inner); - } - - std::shared_ptr shared() const override { - return std::make_shared(this); - } - - DlImageFilterType type() const override { - return DlImageFilterType::kCompose; - } - size_t size() const override { return sizeof(*this); } - - std::shared_ptr outer() const { return outer_; } - std::shared_ptr inner() const { return inner_; } - - const DlComposeImageFilter* asCompose() const override { return this; } - - bool modifies_transparent_black() const override { - if (inner_ && inner_->modifies_transparent_black()) { - return true; - } - if (outer_ && outer_->modifies_transparent_black()) { - return true; - } - return false; - } - - SkRect* map_local_bounds(const SkRect& input_bounds, - SkRect& output_bounds) const override; - - SkIRect* map_device_bounds(const SkIRect& input_bounds, - const SkMatrix& ctm, - SkIRect& output_bounds) const override; - - SkIRect* get_input_device_bounds(const SkIRect& output_bounds, - const SkMatrix& ctm, - SkIRect& input_bounds) const override; - - MatrixCapability matrix_capability() const override { - return std::min(outer_->matrix_capability(), inner_->matrix_capability()); - } - - protected: - bool equals_(const DlImageFilter& other) const override { - FML_DCHECK(other.type() == DlImageFilterType::kCompose); - auto that = static_cast(&other); - return (Equals(outer_, that->outer_) && Equals(inner_, that->inner_)); - } - - private: - std::shared_ptr outer_; - std::shared_ptr inner_; -}; - -class DlColorFilterImageFilter final : public DlImageFilter { - public: - explicit DlColorFilterImageFilter(std::shared_ptr filter) - : color_filter_(std::move(filter)) {} - explicit DlColorFilterImageFilter(const DlColorFilter* filter) - : color_filter_(filter->shared()) {} - explicit DlColorFilterImageFilter(const DlColorFilter& filter) - : color_filter_(filter.shared()) {} - explicit DlColorFilterImageFilter(const DlColorFilterImageFilter* filter) - : DlColorFilterImageFilter(filter->color_filter_) {} - DlColorFilterImageFilter(const DlColorFilterImageFilter& filter) - : DlColorFilterImageFilter(&filter) {} - - static std::shared_ptr Make( - const std::shared_ptr& filter) { - if (filter) { - return std::make_shared(filter); - } - return nullptr; - } - - std::shared_ptr shared() const override { - return std::make_shared(color_filter_); - } - - DlImageFilterType type() const override { - return DlImageFilterType::kColorFilter; - } - size_t size() const override { return sizeof(*this); } - - const std::shared_ptr color_filter() const { - return color_filter_; - } - - const DlColorFilterImageFilter* asColorFilter() const override { - return this; - } - - bool modifies_transparent_black() const override { - if (color_filter_) { - return color_filter_->modifies_transparent_black(); - } - return false; - } - - SkRect* map_local_bounds(const SkRect& input_bounds, - SkRect& output_bounds) const override { - output_bounds = input_bounds; - return modifies_transparent_black() ? nullptr : &output_bounds; - } - - SkIRect* map_device_bounds(const SkIRect& input_bounds, - const SkMatrix& ctm, - SkIRect& output_bounds) const override { - output_bounds = input_bounds; - return modifies_transparent_black() ? nullptr : &output_bounds; - } - - SkIRect* get_input_device_bounds(const SkIRect& output_bounds, - const SkMatrix& ctm, - SkIRect& input_bounds) const override { - return map_device_bounds(output_bounds, ctm, input_bounds); - } - - MatrixCapability matrix_capability() const override { - return MatrixCapability::kComplex; - } - - std::shared_ptr makeWithLocalMatrix( - const SkMatrix& matrix) const override { - return shared(); - } - - protected: - bool equals_(const DlImageFilter& other) const override { - FML_DCHECK(other.type() == DlImageFilterType::kColorFilter); - auto that = static_cast(&other); - return Equals(color_filter_, that->color_filter_); - } - - private: - std::shared_ptr color_filter_; -}; - -class DlLocalMatrixImageFilter final : public DlImageFilter { - public: - explicit DlLocalMatrixImageFilter(const SkMatrix& matrix, - std::shared_ptr filter) - : matrix_(matrix), image_filter_(std::move(filter)) {} - explicit DlLocalMatrixImageFilter(const DlLocalMatrixImageFilter* filter) - : DlLocalMatrixImageFilter(filter->matrix_, filter->image_filter_) {} - DlLocalMatrixImageFilter(const DlLocalMatrixImageFilter& filter) - : DlLocalMatrixImageFilter(&filter) {} - std::shared_ptr shared() const override { - return std::make_shared(this); - } - - DlImageFilterType type() const override { - return DlImageFilterType::kLocalMatrix; - } - size_t size() const override { return sizeof(*this); } - - const SkMatrix& matrix() const { return matrix_; } - - const std::shared_ptr image_filter() const { - return image_filter_; - } - - const DlLocalMatrixImageFilter* asLocalMatrix() const override { - return this; - } - - bool modifies_transparent_black() const override { - if (!image_filter_) { - return false; - } - return image_filter_->modifies_transparent_black(); - } - - SkRect* map_local_bounds(const SkRect& input_bounds, - SkRect& output_bounds) const override { - if (!image_filter_) { - output_bounds = input_bounds; - return &output_bounds; - } - return image_filter_->map_local_bounds(input_bounds, output_bounds); - } - - SkIRect* map_device_bounds(const SkIRect& input_bounds, - const SkMatrix& ctm, - SkIRect& output_bounds) const override { - if (!image_filter_) { - output_bounds = input_bounds; - return &output_bounds; - } - return image_filter_->map_device_bounds( - input_bounds, SkMatrix::Concat(ctm, matrix_), output_bounds); - } - - SkIRect* get_input_device_bounds(const SkIRect& output_bounds, - const SkMatrix& ctm, - SkIRect& input_bounds) const override { - if (!image_filter_) { - input_bounds = output_bounds; - return &input_bounds; - } - return image_filter_->get_input_device_bounds( - output_bounds, SkMatrix::Concat(ctm, matrix_), input_bounds); - } - - protected: - bool equals_(const DlImageFilter& other) const override { - FML_DCHECK(other.type() == DlImageFilterType::kLocalMatrix); - auto that = static_cast(&other); - return (matrix_ == that->matrix_ && - Equals(image_filter_, that->image_filter_)); - } - - private: - SkMatrix matrix_; - std::shared_ptr image_filter_; + static DlVector2 map_vectors_affine(const DlMatrix& ctm, + DlScalar x, + DlScalar y); + + static DlIRect* inset_device_bounds(const DlIRect& input_bounds, + DlScalar radius_x, + DlScalar radius_y, + const DlMatrix& ctm, + DlIRect& output_bounds); + + static DlIRect* outset_device_bounds(const DlIRect& input_bounds, + DlScalar radius_x, + DlScalar radius_y, + const DlMatrix& ctm, + DlIRect& output_bounds); }; } // namespace flutter diff --git a/display_list/effects/dl_image_filter_unittests.cc b/display_list/effects/dl_image_filter_unittests.cc index 70925455aa999..150e85c078e8e 100644 --- a/display_list/effects/dl_image_filter_unittests.cc +++ b/display_list/effects/dl_image_filter_unittests.cc @@ -3,15 +3,19 @@ // found in the LICENSE file. #include "flutter/display_list/dl_blend_mode.h" +#include "flutter/display_list/dl_builder.h" #include "flutter/display_list/dl_color.h" #include "flutter/display_list/dl_sampling_options.h" #include "flutter/display_list/dl_tile_mode.h" -#include "flutter/display_list/effects/dl_color_filter.h" -#include "flutter/display_list/effects/dl_image_filter.h" +#include "flutter/display_list/effects/dl_color_filters.h" +#include "flutter/display_list/effects/dl_image_filters.h" #include "flutter/display_list/testing/dl_test_equality.h" #include "flutter/display_list/utils/dl_comparable.h" +#include "flutter/testing/display_list_testing.h" #include "gtest/gtest.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkRect.h" #include "third_party/skia/include/core/SkBlendMode.h" #include "third_party/skia/include/core/SkColorFilter.h" #include "third_party/skia/include/core/SkSamplingOptions.h" @@ -27,34 +31,34 @@ namespace testing { // and bottom edges are contained even if they are on the right or // bottom edge. This method does the "all sides inclusive" version // of SkRect::contains. -static bool containsInclusive(const SkRect rect, const SkPoint p) { +static bool containsInclusive(const DlRect rect, const DlPoint p) { // Test with a slight offset of 1E-9 to "forgive" IEEE bit-rounding // Ending up with bounds that are off by 1E-9 (these numbers are all // being tested in device space with this method) will be off by a // negligible amount of a pixel that wouldn't contribute to changing // the color of a pixel. - return (p.fX >= rect.fLeft - 1E-9 && // - p.fX <= rect.fRight + 1E-9 && // - p.fY >= rect.fTop - 1E-9 && // - p.fY <= rect.fBottom + 1E-9); + return (p.x >= rect.GetLeft() - 1E-9 && // + p.x <= rect.GetRight() + 1E-9 && // + p.y >= rect.GetTop() - 1E-9 && // + p.y <= rect.GetBottom() + 1E-9); } -static bool containsInclusive(const SkRect rect, const SkPoint quad[4]) { +static bool containsInclusive(const DlRect rect, const DlQuad quad) { return (containsInclusive(rect, quad[0]) && // containsInclusive(rect, quad[1]) && // containsInclusive(rect, quad[2]) && // containsInclusive(rect, quad[3])); } -static bool containsInclusive(const SkIRect rect, const SkPoint quad[4]) { - return containsInclusive(SkRect::Make(rect), quad); +static bool containsInclusive(const DlIRect rect, const DlQuad quad) { + return containsInclusive(DlRect::Make(rect), quad); } -static bool containsInclusive(const SkIRect rect, const SkRect bounds) { - return (bounds.fLeft >= rect.fLeft - 1E-9 && - bounds.fTop >= rect.fTop - 1E-9 && - bounds.fRight <= rect.fRight + 1E-9 && - bounds.fBottom <= rect.fBottom + 1E-9); +static bool containsInclusive(const DlIRect rect, const DlRect bounds) { + return (bounds.GetLeft() >= rect.GetLeft() - 1E-9 && + bounds.GetTop() >= rect.GetTop() - 1E-9 && + bounds.GetRight() <= rect.GetRight() + 1E-9 && + bounds.GetBottom() <= rect.GetBottom() + 1E-9); } // Used to verify that the expected output bounds and reverse-engineered @@ -62,43 +66,61 @@ static bool containsInclusive(const SkIRect rect, const SkRect bounds) { // returned from the various bounds computation methods under the specified // matrix. static void TestBoundsWithMatrix(const DlImageFilter& filter, - const SkMatrix& matrix, - const SkRect& sourceBounds, - const SkPoint expectedLocalOutputQuad[4]) { - SkRect device_input_bounds = matrix.mapRect(sourceBounds); - SkPoint expected_output_quad[4]; - matrix.mapPoints(expected_output_quad, expectedLocalOutputQuad, 4); - - SkIRect device_filter_ibounds; - ASSERT_EQ(filter.map_device_bounds(device_input_bounds.roundOut(), matrix, - device_filter_ibounds), + const DlMatrix& matrix, + const DlRect& sourceBounds, + const DlQuad& expectedLocalOutputQuad) { + DlRect device_input_bounds = sourceBounds.TransformAndClipBounds(matrix); + DlQuad expected_output_quad = matrix.Transform(expectedLocalOutputQuad); + + DlIRect device_filter_ibounds; + ASSERT_EQ(filter.map_device_bounds(DlIRect::RoundOut(device_input_bounds), + matrix, device_filter_ibounds), &device_filter_ibounds); - ASSERT_TRUE(containsInclusive(device_filter_ibounds, expected_output_quad)); - - SkIRect reverse_input_ibounds; + EXPECT_TRUE(containsInclusive(device_filter_ibounds, expected_output_quad)) + << filter << std::endl + << sourceBounds << ", {" << std::endl + << " " << expectedLocalOutputQuad[0] << ", " << std::endl + << " " << expectedLocalOutputQuad[1] << ", " << std::endl + << " " << expectedLocalOutputQuad[2] << ", " << std::endl + << " " << expectedLocalOutputQuad[3] << std::endl + << "}, " << matrix << ", " << std::endl + << device_filter_ibounds << std::endl + << device_input_bounds << ", {" << std::endl + << " " << expected_output_quad[0] << ", " << std::endl + << " " << expected_output_quad[1] << ", " << std::endl + << " " << expected_output_quad[2] << ", " << std::endl + << " " << expected_output_quad[3] << std::endl + << "}"; + + DlIRect reverse_input_ibounds; ASSERT_EQ(filter.get_input_device_bounds(device_filter_ibounds, matrix, reverse_input_ibounds), &reverse_input_ibounds); - ASSERT_TRUE(containsInclusive(reverse_input_ibounds, device_input_bounds)); + EXPECT_TRUE(containsInclusive(reverse_input_ibounds, device_input_bounds)) + << filter << std::endl + << matrix << ", " << std::endl + << reverse_input_ibounds << ", " << std::endl + << device_input_bounds; } static void TestInvalidBounds(const DlImageFilter& filter, - const SkMatrix& matrix, - const SkRect& localInputBounds) { - SkIRect device_input_bounds = matrix.mapRect(localInputBounds).roundOut(); + const DlMatrix& matrix, + const DlRect& localInputBounds) { + DlIRect device_input_bounds = + DlIRect::RoundOut(localInputBounds.TransformBounds(matrix)); - SkRect local_filter_bounds; + DlRect local_filter_bounds; ASSERT_EQ(filter.map_local_bounds(localInputBounds, local_filter_bounds), nullptr); ASSERT_EQ(local_filter_bounds, localInputBounds); - SkIRect device_filter_ibounds; + DlIRect device_filter_ibounds; ASSERT_EQ(filter.map_device_bounds(device_input_bounds, matrix, device_filter_ibounds), nullptr); ASSERT_EQ(device_filter_ibounds, device_input_bounds); - SkIRect reverse_input_ibounds; + DlIRect reverse_input_ibounds; ASSERT_EQ(filter.get_input_device_bounds(device_input_bounds, matrix, reverse_input_ibounds), nullptr); @@ -114,26 +136,30 @@ static void TestInvalidBounds(const DlImageFilter& filter, // be assumed to be unable to perform their computations for the given // image filter and will be returning null. static void TestBounds(const DlImageFilter& filter, - const SkRect& sourceBounds, - const SkPoint expectedLocalOutputQuad[4]) { - SkRect local_filter_bounds; + const DlRect& sourceBounds, + const DlQuad& expectedLocalOutputQuad) { + DlRect local_filter_bounds; ASSERT_EQ(filter.map_local_bounds(sourceBounds, local_filter_bounds), &local_filter_bounds); ASSERT_TRUE(containsInclusive(local_filter_bounds, expectedLocalOutputQuad)); - for (int scale = 1; scale <= 4; scale++) { - for (int skew = 0; skew < 8; skew++) { + for (int i_scale = 1; i_scale <= 4; i_scale++) { + DlScalar scale = i_scale; + for (int skew_eighths = 0; skew_eighths < 7; skew_eighths++) { + DlScalar skew = skew_eighths / 8.0f; for (int degrees = 0; degrees <= 360; degrees += 15) { - SkMatrix matrix; - matrix.setScale(scale, scale); - matrix.postSkew(skew / 8.0, skew / 8.0); - matrix.postRotate(degrees); - ASSERT_TRUE(matrix.invert(nullptr)); + DlMatrix matrix; + matrix = matrix.Scale({scale, scale, 1}); + matrix = DlMatrix::MakeSkew(skew, skew) * matrix; + matrix = DlMatrix::MakeRotationZ(DlDegrees(degrees)) * matrix; + ASSERT_TRUE(matrix.IsInvertible()) << matrix; + ASSERT_FALSE(matrix.HasPerspective2D()) << matrix; TestBoundsWithMatrix(filter, matrix, sourceBounds, expectedLocalOutputQuad); - matrix.setPerspX(0.001); - matrix.setPerspY(0.001); - ASSERT_TRUE(matrix.invert(nullptr)); + matrix.m[3] = 0.001f; + matrix.m[7] = 0.001f; + ASSERT_TRUE(matrix.IsInvertible()) << matrix; + ASSERT_TRUE(matrix.HasPerspective2D()) << matrix; TestBoundsWithMatrix(filter, matrix, sourceBounds, expectedLocalOutputQuad); } @@ -142,10 +168,10 @@ static void TestBounds(const DlImageFilter& filter, } static void TestBounds(const DlImageFilter& filter, - const SkRect& sourceBounds, - const SkRect& expectedLocalOutputBounds) { - SkPoint expected_local_output_quad[4]; - expectedLocalOutputBounds.toQuad(expected_local_output_quad); + const DlRect& sourceBounds, + const DlRect& expectedLocalOutputBounds) { + DlQuad expected_local_output_quad = expectedLocalOutputBounds.GetPoints(); + ASSERT_EQ(expected_local_output_quad.size(), 4u); // Only 0u when empty TestBounds(filter, sourceBounds, expected_local_output_quad); } @@ -186,7 +212,7 @@ TEST(DisplayListImageFilter, BlurWithLocalMatrixEquals) { DlBlurImageFilter filter1(5.0, 6.0, DlTileMode::kMirror); DlBlurImageFilter filter2(5.0, 6.0, DlTileMode::kMirror); - SkMatrix local_matrix = SkMatrix::Translate(10, 10); + DlMatrix local_matrix = DlMatrix::MakeTranslation({10, 10}); TestEquals(*filter1.makeWithLocalMatrix(local_matrix), *filter2.makeWithLocalMatrix(local_matrix)); } @@ -204,25 +230,25 @@ TEST(DisplayListImageFilter, BlurNotEquals) { TEST(DisplayListImageFilter, BlurBounds) { DlBlurImageFilter filter = DlBlurImageFilter(5, 10, DlTileMode::kDecal); - SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80); - SkRect expected_output_bounds = input_bounds.makeOutset(15, 30); + DlRect input_bounds = DlRect::MakeLTRB(20, 20, 80, 80); + DlRect expected_output_bounds = input_bounds.Expand(15, 30); TestBounds(filter, input_bounds, expected_output_bounds); } TEST(DisplayListImageFilter, BlurZeroSigma) { std::shared_ptr filter = - DlBlurImageFilter::Make(0, 0, DlTileMode::kMirror); + DlImageFilter::MakeBlur(0, 0, DlTileMode::kMirror); ASSERT_EQ(filter, nullptr); - filter = DlBlurImageFilter::Make(3, SK_ScalarNaN, DlTileMode::kMirror); + filter = DlImageFilter::MakeBlur(3, SK_ScalarNaN, DlTileMode::kMirror); ASSERT_EQ(filter, nullptr); - filter = DlBlurImageFilter::Make(SK_ScalarNaN, 3, DlTileMode::kMirror); + filter = DlImageFilter::MakeBlur(SK_ScalarNaN, 3, DlTileMode::kMirror); ASSERT_EQ(filter, nullptr); filter = - DlBlurImageFilter::Make(SK_ScalarNaN, SK_ScalarNaN, DlTileMode::kMirror); + DlImageFilter::MakeBlur(SK_ScalarNaN, SK_ScalarNaN, DlTileMode::kMirror); ASSERT_EQ(filter, nullptr); - filter = DlBlurImageFilter::Make(3, 0, DlTileMode::kMirror); + filter = DlImageFilter::MakeBlur(3, 0, DlTileMode::kMirror); ASSERT_NE(filter, nullptr); - filter = DlBlurImageFilter::Make(0, 3, DlTileMode::kMirror); + filter = DlImageFilter::MakeBlur(0, 3, DlTileMode::kMirror); ASSERT_NE(filter, nullptr); } @@ -262,7 +288,7 @@ TEST(DisplayListImageFilter, DilateWithLocalMatrixEquals) { DlDilateImageFilter filter1(5.0, 6.0); DlDilateImageFilter filter2(5.0, 6.0); - SkMatrix local_matrix = SkMatrix::Translate(10, 10); + DlMatrix local_matrix = DlMatrix::MakeTranslation({10, 10}); TestEquals(*filter1.makeWithLocalMatrix(local_matrix), *filter2.makeWithLocalMatrix(local_matrix)); } @@ -278,8 +304,8 @@ TEST(DisplayListImageFilter, DilateNotEquals) { TEST(DisplayListImageFilter, DilateBounds) { DlDilateImageFilter filter = DlDilateImageFilter(5, 10); - SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80); - SkRect expected_output_bounds = input_bounds.makeOutset(5, 10); + DlRect input_bounds = DlRect::MakeLTRB(20, 20, 80, 80); + DlRect expected_output_bounds = input_bounds.Expand(5, 10); TestBounds(filter, input_bounds, expected_output_bounds); } @@ -319,7 +345,7 @@ TEST(DisplayListImageFilter, ErodeWithLocalMatrixEquals) { DlErodeImageFilter filter1(5.0, 6.0); DlErodeImageFilter filter2(5.0, 6.0); - SkMatrix local_matrix = SkMatrix::Translate(10, 10); + DlMatrix local_matrix = DlMatrix::MakeTranslation({10, 10}); TestEquals(*filter1.makeWithLocalMatrix(local_matrix), *filter2.makeWithLocalMatrix(local_matrix)); } @@ -335,22 +361,24 @@ TEST(DisplayListImageFilter, ErodeNotEquals) { TEST(DisplayListImageFilter, ErodeBounds) { DlErodeImageFilter filter = DlErodeImageFilter(5, 10); - SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80); - SkRect expected_output_bounds = input_bounds.makeInset(5, 10); + DlRect input_bounds = DlRect::MakeLTRB(20, 20, 80, 80); + DlRect expected_output_bounds = input_bounds.Expand(-5, -10); TestBounds(filter, input_bounds, expected_output_bounds); } TEST(DisplayListImageFilter, MatrixConstructor) { - DlMatrixImageFilter filter(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter filter(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); } TEST(DisplayListImageFilter, MatrixShared) { - DlMatrixImageFilter filter(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter filter(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); ASSERT_NE(filter.shared().get(), &filter); @@ -358,9 +386,10 @@ TEST(DisplayListImageFilter, MatrixShared) { } TEST(DisplayListImageFilter, MatrixAsMatrix) { - DlMatrixImageFilter filter(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter filter(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); ASSERT_NE(filter.asMatrix(), nullptr); @@ -368,9 +397,10 @@ TEST(DisplayListImageFilter, MatrixAsMatrix) { } TEST(DisplayListImageFilter, MatrixContents) { - SkMatrix matrix = SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1); + DlMatrix matrix = DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0); DlMatrixImageFilter filter(matrix, DlImageSampling::kLinear); ASSERT_EQ(filter.matrix(), matrix); @@ -378,9 +408,10 @@ TEST(DisplayListImageFilter, MatrixContents) { } TEST(DisplayListImageFilter, MatrixEquals) { - SkMatrix matrix = SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1); + DlMatrix matrix = DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0); DlMatrixImageFilter filter1(matrix, DlImageSampling::kLinear); DlMatrixImageFilter filter2(matrix, DlImageSampling::kLinear); @@ -388,24 +419,27 @@ TEST(DisplayListImageFilter, MatrixEquals) { } TEST(DisplayListImageFilter, MatrixWithLocalMatrixEquals) { - SkMatrix matrix = SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1); + DlMatrix matrix = DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0); DlMatrixImageFilter filter1(matrix, DlImageSampling::kLinear); DlMatrixImageFilter filter2(matrix, DlImageSampling::kLinear); - SkMatrix local_matrix = SkMatrix::Translate(10, 10); + DlMatrix local_matrix = DlMatrix::MakeTranslation({10, 10}); TestEquals(*filter1.makeWithLocalMatrix(local_matrix), *filter2.makeWithLocalMatrix(local_matrix)); } TEST(DisplayListImageFilter, MatrixNotEquals) { - SkMatrix matrix1 = SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1); - SkMatrix matrix2 = SkMatrix::MakeAll(5.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1); + DlMatrix matrix1 = DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0); + DlMatrix matrix2 = DlMatrix::MakeRow(5.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0); DlMatrixImageFilter filter1(matrix1, DlImageSampling::kLinear); DlMatrixImageFilter filter2(matrix2, DlImageSampling::kLinear); DlMatrixImageFilter filter3(matrix1, DlImageSampling::kNearestNeighbor); @@ -415,35 +449,37 @@ TEST(DisplayListImageFilter, MatrixNotEquals) { } TEST(DisplayListImageFilter, MatrixBounds) { - SkMatrix matrix = SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 7, // - 0.0, 0.0, 1); - SkMatrix inverse; - ASSERT_TRUE(matrix.invert(&inverse)); + DlMatrix matrix = DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 7, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0); + EXPECT_TRUE(matrix.IsInvertible()); DlMatrixImageFilter filter(matrix, DlImageSampling::kLinear); - SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80); - SkPoint expectedOutputQuad[4] = { - {50, 77}, // (20,20) => (20*2 + 10, 20/2 + 20*3 + 7) == (50, 77) - {50, 257}, // (20,80) => (20*2 + 10, 20/2 + 80*3 + 7) == (50, 257) - {170, 287}, // (80,80) => (80*2 + 10, 80/2 + 80*3 + 7) == (170, 287) - {170, 107}, // (80,20) => (80*2 + 10, 80/2 + 20*3 + 7) == (170, 107) + DlRect input_bounds = DlRect::MakeLTRB(20, 20, 80, 80); + DlQuad expectedOutputQuad = { + DlPoint(50, 77), // == (20,20) => (20*2 + 10, 20/2 + 20*3 + 7) + DlPoint(50, 257), // == (20,80) => (20*2 + 10, 20/2 + 80*3 + 7) + DlPoint(170, 287), // == (80,80) => (80*2 + 10, 80/2 + 80*3 + 7) + DlPoint(170, 107), // == (80,20) => (80*2 + 10, 80/2 + 20*3 + 7) }; TestBounds(filter, input_bounds, expectedOutputQuad); } TEST(DisplayListImageFilter, ComposeConstructor) { - DlMatrixImageFilter outer(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter outer(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror); DlComposeImageFilter filter(outer, inner); } TEST(DisplayListImageFilter, ComposeShared) { - DlMatrixImageFilter outer(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter outer(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror); DlComposeImageFilter filter(outer, inner); @@ -453,9 +489,10 @@ TEST(DisplayListImageFilter, ComposeShared) { } TEST(DisplayListImageFilter, ComposeAsCompose) { - DlMatrixImageFilter outer(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter outer(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror); DlComposeImageFilter filter(outer, inner); @@ -465,9 +502,10 @@ TEST(DisplayListImageFilter, ComposeAsCompose) { } TEST(DisplayListImageFilter, ComposeContents) { - DlMatrixImageFilter outer(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter outer(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror); DlComposeImageFilter filter(outer, inner); @@ -477,16 +515,18 @@ TEST(DisplayListImageFilter, ComposeContents) { } TEST(DisplayListImageFilter, ComposeEquals) { - DlMatrixImageFilter outer1(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter outer1(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); DlBlurImageFilter inner1(5.0, 6.0, DlTileMode::kMirror); DlComposeImageFilter filter1(outer1, inner1); - DlMatrixImageFilter outer2(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter outer2(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); DlBlurImageFilter inner2(5.0, 6.0, DlTileMode::kMirror); DlComposeImageFilter filter2(outer1, inner1); @@ -495,35 +535,39 @@ TEST(DisplayListImageFilter, ComposeEquals) { } TEST(DisplayListImageFilter, ComposeWithLocalMatrixEquals) { - DlMatrixImageFilter outer1(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter outer1(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); DlBlurImageFilter inner1(5.0, 6.0, DlTileMode::kMirror); DlComposeImageFilter filter1(outer1, inner1); - DlMatrixImageFilter outer2(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter outer2(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); DlBlurImageFilter inner2(5.0, 6.0, DlTileMode::kMirror); DlComposeImageFilter filter2(outer1, inner1); - SkMatrix local_matrix = SkMatrix::Translate(10, 10); + DlMatrix local_matrix = DlMatrix::MakeTranslation({10, 10}); TestEquals(*filter1.makeWithLocalMatrix(local_matrix), *filter2.makeWithLocalMatrix(local_matrix)); } TEST(DisplayListImageFilter, ComposeNotEquals) { - DlMatrixImageFilter outer1(SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter outer1(DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); DlBlurImageFilter inner1(5.0, 6.0, DlTileMode::kMirror); - DlMatrixImageFilter outer2(SkMatrix::MakeAll(5.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1), + DlMatrixImageFilter outer2(DlMatrix::MakeRow(5.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0), DlImageSampling::kLinear); DlBlurImageFilter inner2(7.0, 6.0, DlTileMode::kMirror); @@ -539,35 +583,34 @@ TEST(DisplayListImageFilter, ComposeBounds) { DlDilateImageFilter outer = DlDilateImageFilter(5, 10); DlBlurImageFilter inner = DlBlurImageFilter(12, 5, DlTileMode::kDecal); DlComposeImageFilter filter = DlComposeImageFilter(outer, inner); - SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80); - SkRect expected_output_bounds = - input_bounds.makeOutset(36, 15).makeOutset(5, 10); + DlRect input_bounds = DlRect::MakeLTRB(20, 20, 80, 80); + DlRect expected_output_bounds = input_bounds.Expand(36, 15).Expand(5, 10); TestBounds(filter, input_bounds, expected_output_bounds); } static void TestUnboundedBounds(DlImageFilter& filter, - const SkRect& sourceBounds, - const SkRect& expectedOutputBounds, - const SkRect& expectedInputBounds) { - SkRect bounds; + const DlRect& sourceBounds, + const DlRect& expectedOutputBounds, + const DlRect& expectedInputBounds) { + DlRect bounds; EXPECT_EQ(filter.map_local_bounds(sourceBounds, bounds), nullptr); EXPECT_EQ(bounds, expectedOutputBounds); - SkIRect ibounds; - EXPECT_EQ( - filter.map_device_bounds(sourceBounds.roundOut(), SkMatrix::I(), ibounds), - nullptr); - EXPECT_EQ(ibounds, expectedOutputBounds.roundOut()); + DlIRect ibounds; + EXPECT_EQ(filter.map_device_bounds(DlIRect::RoundOut(sourceBounds), + DlMatrix(), ibounds), + nullptr); + EXPECT_EQ(ibounds, DlIRect::RoundOut(expectedOutputBounds)); - EXPECT_EQ(filter.get_input_device_bounds(sourceBounds.roundOut(), - SkMatrix::I(), ibounds), + EXPECT_EQ(filter.get_input_device_bounds(DlIRect::RoundOut(sourceBounds), + DlMatrix(), ibounds), nullptr); - EXPECT_EQ(ibounds, expectedInputBounds.roundOut()); + EXPECT_EQ(ibounds, DlIRect::RoundOut(expectedInputBounds)); } TEST(DisplayListImageFilter, ComposeBoundsWithUnboundedInner) { - auto input_bounds = SkRect::MakeLTRB(20, 20, 80, 80); - auto expected_bounds = SkRect::MakeLTRB(5, 2, 95, 98); + auto input_bounds = DlRect::MakeLTRB(20, 20, 80, 80); + auto expected_bounds = DlRect::MakeLTRB(5, 2, 95, 98); DlBlendColorFilter color_filter(DlColor::kRed(), DlBlendMode::kSrcOver); auto outer = DlBlurImageFilter(5.0, 6.0, DlTileMode::kRepeat); @@ -578,8 +621,8 @@ TEST(DisplayListImageFilter, ComposeBoundsWithUnboundedInner) { } TEST(DisplayListImageFilter, ComposeBoundsWithUnboundedOuter) { - auto input_bounds = SkRect::MakeLTRB(20, 20, 80, 80); - auto expected_bounds = SkRect::MakeLTRB(5, 2, 95, 98); + auto input_bounds = DlRect::MakeLTRB(20, 20, 80, 80); + auto expected_bounds = DlRect::MakeLTRB(5, 2, 95, 98); DlBlendColorFilter color_filter(DlColor::kRed(), DlBlendMode::kSrcOver); auto outer = DlColorFilterImageFilter(color_filter.shared()); @@ -590,7 +633,7 @@ TEST(DisplayListImageFilter, ComposeBoundsWithUnboundedOuter) { } TEST(DisplayListImageFilter, ComposeBoundsWithUnboundedInnerAndOuter) { - auto input_bounds = SkRect::MakeLTRB(20, 20, 80, 80); + auto input_bounds = DlRect::MakeLTRB(20, 20, 80, 80); auto expected_bounds = input_bounds; DlBlendColorFilter color_filter1(DlColor::kRed(), DlBlendMode::kSrcOver); @@ -604,18 +647,17 @@ TEST(DisplayListImageFilter, ComposeBoundsWithUnboundedInnerAndOuter) { // See https://github.com/flutter/flutter/issues/108433 TEST(DisplayListImageFilter, Issue108433) { - auto input_bounds = SkIRect::MakeLTRB(20, 20, 80, 80); - auto expected_bounds = SkIRect::MakeLTRB(5, 2, 95, 98); + auto input_bounds = DlIRect::MakeLTRB(20, 20, 80, 80); + auto expected_bounds = DlIRect::MakeLTRB(5, 2, 95, 98); DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcOver); auto dl_outer = DlBlurImageFilter(5.0, 6.0, DlTileMode::kRepeat); auto dl_inner = DlColorFilterImageFilter(dl_color_filter.shared()); auto dl_compose = DlComposeImageFilter(dl_outer, dl_inner); - SkIRect dl_bounds; - ASSERT_EQ( - dl_compose.map_device_bounds(input_bounds, SkMatrix::I(), dl_bounds), - nullptr); + DlIRect dl_bounds; + ASSERT_EQ(dl_compose.map_device_bounds(input_bounds, DlMatrix(), dl_bounds), + nullptr); ASSERT_EQ(dl_bounds, expected_bounds); } @@ -663,7 +705,7 @@ TEST(DisplayListImageFilter, ColorFilterWithLocalMatrixEquals) { DlBlendColorFilter dl_color_filter2(DlColor::kRed(), DlBlendMode::kLighten); DlColorFilterImageFilter filter2(dl_color_filter2); - SkMatrix local_matrix = SkMatrix::Translate(10, 10); + DlMatrix local_matrix = DlMatrix::MakeTranslation({10, 10}); TestEquals(*filter1.makeWithLocalMatrix(local_matrix), *filter2.makeWithLocalMatrix(local_matrix)); } @@ -685,28 +727,30 @@ TEST(DisplayListImageFilter, ColorFilterNotEquals) { TEST(DisplayListImageFilter, ColorFilterBounds) { DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcIn); DlColorFilterImageFilter filter(dl_color_filter); - SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80); + DlRect input_bounds = DlRect::MakeLTRB(20, 20, 80, 80); TestBounds(filter, input_bounds, input_bounds); } TEST(DisplayListImageFilter, ColorFilterModifiesTransparencyBounds) { DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcOver); DlColorFilterImageFilter filter(dl_color_filter); - SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80); - TestInvalidBounds(filter, SkMatrix::I(), input_bounds); + DlRect input_bounds = DlRect::MakeLTRB(20, 20, 80, 80); + TestInvalidBounds(filter, DlMatrix(), input_bounds); } TEST(DisplayListImageFilter, LocalImageFilterBounds) { - auto filter_matrix = SkMatrix::MakeAll(2.0, 0.0, 10, // - 0.5, 3.0, 15, // - 0.0, 0.0, 1); + auto filter_matrix = DlMatrix::MakeRow(2.0, 0.0, 0.0, 10, // + 0.5, 3.0, 0.0, 15, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0); std::vector> sk_filters{ SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr), SkImageFilters::ColorFilter( SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcOver), nullptr), SkImageFilters::Dilate(5.0, 10.0, nullptr), - SkImageFilters::MatrixTransform( - filter_matrix, SkSamplingOptions(SkFilterMode::kLinear), nullptr), + SkImageFilters::MatrixTransform(ToSkMatrix(filter_matrix), + SkSamplingOptions(SkFilterMode::kLinear), + nullptr), SkImageFilters::Compose( SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr), SkImageFilters::ColorFilter( @@ -715,47 +759,58 @@ TEST(DisplayListImageFilter, LocalImageFilterBounds) { DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcOver); std::vector> dl_filters{ - std::make_shared(5.0, 6.0, DlTileMode::kRepeat), - std::make_shared(dl_color_filter.shared()), - std::make_shared(5, 10), - std::make_shared(filter_matrix, - DlImageSampling::kLinear), - std::make_shared( - std::make_shared(5.0, 6.0, DlTileMode::kRepeat), - std::make_shared( - dl_color_filter.shared()))}; + DlImageFilter::MakeBlur(5.0, 6.0, DlTileMode::kRepeat), + DlImageFilter::MakeColorFilter(dl_color_filter.shared()), + DlImageFilter::MakeDilate(5, 10), + DlImageFilter::MakeMatrix(filter_matrix, DlImageSampling::kLinear), + DlImageFilter::MakeCompose( + DlImageFilter::MakeBlur(5.0, 6.0, DlTileMode::kRepeat), + DlImageFilter::MakeColorFilter(dl_color_filter.shared())), + }; auto persp = SkMatrix::I(); persp.setPerspY(0.001); - std::vector matrices = { + std::vector sk_matrices = { SkMatrix::Translate(10.0, 10.0), SkMatrix::Scale(2.0, 2.0).preTranslate(10.0, 10.0), - SkMatrix::RotateDeg(45).preTranslate(5.0, 5.0), persp}; - std::vector bounds_matrices{SkMatrix::Translate(5.0, 10.0), - SkMatrix::Scale(2.0, 2.0)}; + SkMatrix::RotateDeg(45).preTranslate(5.0, 5.0), // + persp}; + std::vector dl_matrices = { + DlMatrix::MakeTranslation({10.0, 10.0}), + DlMatrix::MakeScale({2.0, 2.0, 1.0}).Translate({10.0, 10.0}), + DlMatrix::MakeRotationZ(DlDegrees(45)).Translate({5.0, 5.0}), + ToDlMatrix(persp)}; + std::vector sk_bounds_matrices{ + SkMatrix::Translate(5.0, 10.0), + SkMatrix::Scale(2.0, 2.0), + }; + std::vector dl_bounds_matrices{ + DlMatrix::MakeTranslation({5.0, 10.0}), + DlMatrix::MakeScale({2.0, 2.0, 1.0}), + }; - for (unsigned j = 0; j < matrices.size(); j++) { - DlLocalMatrixImageFilter filter(matrices[j], nullptr); + for (unsigned j = 0; j < dl_matrices.size(); j++) { + DlLocalMatrixImageFilter filter(dl_matrices[j], nullptr); { - const auto input_bounds = SkRect::MakeLTRB(20, 20, 80, 80); - SkRect output_bounds; + const auto input_bounds = DlRect::MakeLTRB(20, 20, 80, 80); + DlRect output_bounds; EXPECT_EQ(filter.map_local_bounds(input_bounds, output_bounds), &output_bounds); EXPECT_EQ(input_bounds, output_bounds); } - for (unsigned k = 0; k < bounds_matrices.size(); k++) { - auto& bounds_matrix = bounds_matrices[k]; + for (unsigned k = 0; k < dl_bounds_matrices.size(); k++) { + auto& bounds_matrix = dl_bounds_matrices[k]; { - const auto input_bounds = SkIRect::MakeLTRB(20, 20, 80, 80); - SkIRect output_bounds; + const auto input_bounds = DlIRect::MakeLTRB(20, 20, 80, 80); + DlIRect output_bounds; EXPECT_EQ(filter.map_device_bounds(input_bounds, bounds_matrix, output_bounds), &output_bounds); EXPECT_EQ(input_bounds, output_bounds); } { - const auto output_bounds = SkIRect::MakeLTRB(20, 20, 80, 80); - SkIRect input_bounds; + const auto output_bounds = DlIRect::MakeLTRB(20, 20, 80, 80); + DlIRect input_bounds; EXPECT_EQ(filter.get_input_device_bounds(output_bounds, bounds_matrix, input_bounds), &input_bounds); @@ -765,15 +820,15 @@ TEST(DisplayListImageFilter, LocalImageFilterBounds) { } for (unsigned i = 0; i < sk_filters.size(); i++) { - for (unsigned j = 0; j < matrices.size(); j++) { - for (unsigned k = 0; k < bounds_matrices.size(); k++) { + for (unsigned j = 0; j < dl_matrices.size(); j++) { + for (unsigned k = 0; k < dl_bounds_matrices.size(); k++) { auto desc = "filter " + std::to_string(i + 1) // + ", filter matrix " + std::to_string(j + 1) // + ", bounds matrix " + std::to_string(k + 1); - auto& m = matrices[j]; - auto& bounds_matrix = bounds_matrices[k]; - auto sk_local_filter = sk_filters[i]->makeWithLocalMatrix(m); - auto dl_local_filter = dl_filters[i]->makeWithLocalMatrix(m); + auto sk_local_filter = + sk_filters[i]->makeWithLocalMatrix(sk_matrices[j]); + auto dl_local_filter = + dl_filters[i]->makeWithLocalMatrix(dl_matrices[j]); if (!sk_local_filter || !dl_local_filter) { // Temporarily relax the equivalence testing to allow Skia to expand // their behavior. Once the Skia fixes are rolled in, the @@ -784,13 +839,14 @@ TEST(DisplayListImageFilter, LocalImageFilterBounds) { } { auto input_bounds = SkIRect::MakeLTRB(20, 20, 80, 80); - SkIRect sk_rect, dl_rect; + SkIRect sk_rect; + DlIRect dl_rect; sk_rect = sk_local_filter->filterBounds( - input_bounds, bounds_matrix, + input_bounds, sk_bounds_matrices[k], SkImageFilter::MapDirection::kForward_MapDirection); - if (dl_local_filter->map_device_bounds(input_bounds, bounds_matrix, - dl_rect)) { - ASSERT_EQ(sk_rect, dl_rect) << desc; + if (dl_local_filter->map_device_bounds( + ToDlIRect(input_bounds), dl_bounds_matrices[k], dl_rect)) { + ASSERT_EQ(sk_rect, ToSkIRect(dl_rect)) << desc; } else { ASSERT_TRUE(dl_local_filter->modifies_transparent_black()) << desc; ASSERT_FALSE(sk_local_filter->canComputeFastBounds()) << desc; @@ -806,13 +862,14 @@ TEST(DisplayListImageFilter, LocalImageFilterBounds) { continue; } auto outset_bounds = SkIRect::MakeLTRB(20, 20, 80, 80); - SkIRect sk_rect, dl_rect; + SkIRect sk_rect; + DlIRect dl_rect; sk_rect = sk_local_filter->filterBounds( - outset_bounds, bounds_matrix, + outset_bounds, sk_bounds_matrices[k], SkImageFilter::MapDirection::kReverse_MapDirection); if (dl_local_filter->get_input_device_bounds( - outset_bounds, bounds_matrix, dl_rect)) { - ASSERT_EQ(sk_rect, dl_rect) << desc; + ToDlIRect(outset_bounds), dl_bounds_matrices[k], dl_rect)) { + ASSERT_EQ(sk_rect, ToSkIRect(dl_rect)) << desc; } else { ASSERT_TRUE(dl_local_filter->modifies_transparent_black()); ASSERT_FALSE(sk_local_filter->canComputeFastBounds()); @@ -823,5 +880,90 @@ TEST(DisplayListImageFilter, LocalImageFilterBounds) { } } +TEST(DisplayListImageFilter, RuntimeEffectEquality) { + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr}, + std::make_shared>()); + DlRuntimeEffectImageFilter filter_b(nullptr, {nullptr}, + std::make_shared>()); + + EXPECT_EQ(filter_a, filter_b); + + DlRuntimeEffectImageFilter filter_c( + nullptr, {nullptr}, std::make_shared>(1)); + + EXPECT_NE(filter_a, filter_c); +} + +TEST(DisplayListImageFilter, RuntimeEffectEqualityWithSamplers) { + auto image_a = + DlColorSource::MakeImage(nullptr, DlTileMode::kClamp, DlTileMode::kDecal); + auto image_b = + DlColorSource::MakeImage(nullptr, DlTileMode::kClamp, DlTileMode::kClamp); + + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr, image_a}, + std::make_shared>()); + DlRuntimeEffectImageFilter filter_b(nullptr, {nullptr, image_a}, + std::make_shared>()); + + EXPECT_EQ(filter_a, filter_b); + + DlRuntimeEffectImageFilter filter_c(nullptr, {nullptr, image_b}, + std::make_shared>()); + + EXPECT_NE(filter_a, filter_c); +} + +TEST(DisplayListImageFilter, RuntimeEffectMapDeviceBounds) { + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr}, + std::make_shared>()); + + auto input_bounds = DlIRect::MakeLTRB(0, 0, 100, 100); + DlMatrix identity; + DlIRect output_bounds; + DlIRect* result = + filter_a.map_device_bounds(input_bounds, identity, output_bounds); + + EXPECT_NE(result, nullptr); + EXPECT_EQ(result, &output_bounds); + EXPECT_EQ(output_bounds, input_bounds); +} + +TEST(DisplayListImageFilter, RuntimeEffectMapInputBounds) { + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr}, + std::make_shared>()); + + auto input_bounds = DlRect::MakeLTRB(0, 0, 100, 100); + + DlRect output_bounds; + DlRect* result = filter_a.map_local_bounds(input_bounds, output_bounds); + + EXPECT_NE(result, nullptr); + EXPECT_EQ(result, &output_bounds); + EXPECT_EQ(output_bounds, input_bounds); +} + +TEST(DisplayListImageFilter, RuntimeEffectGetInputDeviceBounds) { + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr}, + std::make_shared>()); + + auto output_bounds = DlIRect::MakeLTRB(0, 0, 100, 100); + + DlMatrix identity; + DlIRect input_bounds; + DlIRect* result = + filter_a.get_input_device_bounds(output_bounds, identity, input_bounds); + + EXPECT_NE(result, nullptr); + EXPECT_EQ(result, &input_bounds); + EXPECT_EQ(output_bounds, input_bounds); +} + +TEST(DisplayListImageFilter, RuntimeEffectModifiesTransparentBlack) { + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr}, + std::make_shared>()); + + EXPECT_FALSE(filter_a.modifies_transparent_black()); +} + } // namespace testing } // namespace flutter diff --git a/display_list/effects/dl_image_filters.h b/display_list/effects/dl_image_filters.h new file mode 100644 index 0000000000000..e3a7f48f7a69d --- /dev/null +++ b/display_list/effects/dl_image_filters.h @@ -0,0 +1,17 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_DL_IMAGE_FILTERS_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_DL_IMAGE_FILTERS_H_ + +#include "flutter/display_list/effects/image_filters/dl_blur_image_filter.h" +#include "flutter/display_list/effects/image_filters/dl_color_filter_image_filter.h" +#include "flutter/display_list/effects/image_filters/dl_compose_image_filter.h" +#include "flutter/display_list/effects/image_filters/dl_dilate_image_filter.h" +#include "flutter/display_list/effects/image_filters/dl_erode_image_filter.h" +#include "flutter/display_list/effects/image_filters/dl_local_matrix_image_filter.h" +#include "flutter/display_list/effects/image_filters/dl_matrix_image_filter.h" +#include "flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.h" + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_DL_IMAGE_FILTERS_H_ diff --git a/display_list/effects/image_filters/dl_blur_image_filter.cc b/display_list/effects/image_filters/dl_blur_image_filter.cc new file mode 100644 index 0000000000000..5b1e61b96912f --- /dev/null +++ b/display_list/effects/image_filters/dl_blur_image_filter.cc @@ -0,0 +1,52 @@ +// 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. + +#include "flutter/display_list/effects/image_filters/dl_blur_image_filter.h" + +namespace flutter { + +std::shared_ptr DlBlurImageFilter::Make(DlScalar sigma_x, + DlScalar sigma_y, + DlTileMode tile_mode) { + if (!std::isfinite(sigma_x) || !std::isfinite(sigma_y)) { + return nullptr; + } + if (sigma_x < SK_ScalarNearlyZero && sigma_y < SK_ScalarNearlyZero) { + return nullptr; + } + sigma_x = (sigma_x < SK_ScalarNearlyZero) ? 0 : sigma_x; + sigma_y = (sigma_y < SK_ScalarNearlyZero) ? 0 : sigma_y; + return std::make_shared(sigma_x, sigma_y, tile_mode); +} + +DlRect* DlBlurImageFilter::map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const { + output_bounds = input_bounds.Expand(sigma_x_ * 3.0f, sigma_y_ * 3.0f); + return &output_bounds; +} + +DlIRect* DlBlurImageFilter::map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const { + return outset_device_bounds(input_bounds, sigma_x_ * 3.0f, sigma_y_ * 3.0f, + ctm, output_bounds); +} + +DlIRect* DlBlurImageFilter::get_input_device_bounds( + const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const { + // Blurs are symmetric in terms of output-for-input and input-for-output + return map_device_bounds(output_bounds, ctm, input_bounds); +} + +bool DlBlurImageFilter::equals_(const DlImageFilter& other) const { + FML_DCHECK(other.type() == DlImageFilterType::kBlur); + auto that = static_cast(&other); + return (DlScalarNearlyEqual(sigma_x_, that->sigma_x_) && + DlScalarNearlyEqual(sigma_y_, that->sigma_y_) && + tile_mode_ == that->tile_mode_); +} + +} // namespace flutter diff --git a/display_list/effects/image_filters/dl_blur_image_filter.h b/display_list/effects/image_filters/dl_blur_image_filter.h new file mode 100644 index 0000000000000..6853a9354f6ac --- /dev/null +++ b/display_list/effects/image_filters/dl_blur_image_filter.h @@ -0,0 +1,66 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_BLUR_IMAGE_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_BLUR_IMAGE_FILTER_H_ + +#include "flutter/display_list/effects/dl_image_filter.h" + +#include "flutter/display_list/dl_tile_mode.h" + +namespace flutter { + +class DlBlurImageFilter final : public DlImageFilter { + public: + DlBlurImageFilter(DlScalar sigma_x, DlScalar sigma_y, DlTileMode tile_mode) + : sigma_x_(sigma_x), sigma_y_(sigma_y), tile_mode_(tile_mode) {} + explicit DlBlurImageFilter(const DlBlurImageFilter* filter) + : DlBlurImageFilter(filter->sigma_x_, + filter->sigma_y_, + filter->tile_mode_) {} + DlBlurImageFilter(const DlBlurImageFilter& filter) + : DlBlurImageFilter(&filter) {} + + static std::shared_ptr Make(DlScalar sigma_x, + DlScalar sigma_y, + DlTileMode tile_mode); + + std::shared_ptr shared() const override { + return std::make_shared(this); + } + + DlImageFilterType type() const override { return DlImageFilterType::kBlur; } + size_t size() const override { return sizeof(*this); } + + const DlBlurImageFilter* asBlur() const override { return this; } + + bool modifies_transparent_black() const override { return false; } + + DlRect* map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const override; + + DlIRect* map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const override; + + DlIRect* get_input_device_bounds(const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const override; + + DlScalar sigma_x() const { return sigma_x_; } + DlScalar sigma_y() const { return sigma_y_; } + DlTileMode tile_mode() const { return tile_mode_; } + + protected: + bool equals_(const DlImageFilter& other) const override; + + private: + DlScalar sigma_x_; + DlScalar sigma_y_; + DlTileMode tile_mode_; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_BLUR_IMAGE_FILTER_H_ diff --git a/display_list/effects/image_filters/dl_color_filter_image_filter.cc b/display_list/effects/image_filters/dl_color_filter_image_filter.cc new file mode 100644 index 0000000000000..92ada43cf9ccd --- /dev/null +++ b/display_list/effects/image_filters/dl_color_filter_image_filter.cc @@ -0,0 +1,54 @@ +// 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. + +#include "flutter/display_list/effects/image_filters/dl_color_filter_image_filter.h" + +#include "flutter/display_list/utils/dl_comparable.h" + +namespace flutter { + +std::shared_ptr DlColorFilterImageFilter::Make( + const std::shared_ptr& filter) { + if (filter) { + return std::make_shared(filter); + } + return nullptr; +} + +bool DlColorFilterImageFilter::modifies_transparent_black() const { + if (color_filter_) { + return color_filter_->modifies_transparent_black(); + } + return false; +} + +DlRect* DlColorFilterImageFilter::map_local_bounds( + const DlRect& input_bounds, + DlRect& output_bounds) const { + output_bounds = input_bounds; + return modifies_transparent_black() ? nullptr : &output_bounds; +} + +DlIRect* DlColorFilterImageFilter::map_device_bounds( + const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const { + output_bounds = input_bounds; + return modifies_transparent_black() ? nullptr : &output_bounds; +} + +DlIRect* DlColorFilterImageFilter::get_input_device_bounds( + const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const { + return map_device_bounds(output_bounds, ctm, input_bounds); +} + +bool DlColorFilterImageFilter::equals_(const DlImageFilter& other) const { + FML_DCHECK(other.type() == DlImageFilterType::kColorFilter); + auto that = static_cast(&other); + return Equals(color_filter_, that->color_filter_); +} + +} // namespace flutter diff --git a/display_list/effects/image_filters/dl_color_filter_image_filter.h b/display_list/effects/image_filters/dl_color_filter_image_filter.h new file mode 100644 index 0000000000000..602770d87c471 --- /dev/null +++ b/display_list/effects/image_filters/dl_color_filter_image_filter.h @@ -0,0 +1,73 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_COLOR_FILTER_IMAGE_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_COLOR_FILTER_IMAGE_FILTER_H_ + +#include "display_list/effects/dl_image_filter.h" + +#include "flutter/display_list/effects/dl_color_filter.h" + +namespace flutter { + +class DlColorFilterImageFilter final : public DlImageFilter { + public: + explicit DlColorFilterImageFilter(std::shared_ptr filter) + : color_filter_(std::move(filter)) {} + explicit DlColorFilterImageFilter(const DlColorFilter* filter) + : color_filter_(filter->shared()) {} + explicit DlColorFilterImageFilter(const DlColorFilter& filter) + : color_filter_(filter.shared()) {} + explicit DlColorFilterImageFilter(const DlColorFilterImageFilter* filter) + : DlColorFilterImageFilter(filter->color_filter_) {} + DlColorFilterImageFilter(const DlColorFilterImageFilter& filter) + : DlColorFilterImageFilter(&filter) {} + + static std::shared_ptr Make( + const std::shared_ptr& filter); + + std::shared_ptr shared() const override { + return std::make_shared(color_filter_); + } + + DlImageFilterType type() const override { + return DlImageFilterType::kColorFilter; + } + size_t size() const override { return sizeof(*this); } + + const std::shared_ptr color_filter() const { + return color_filter_; + } + + const DlColorFilterImageFilter* asColorFilter() const override { + return this; + } + + bool modifies_transparent_black() const override; + + DlRect* map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const override; + + DlIRect* map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const override; + + DlIRect* get_input_device_bounds(const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const override; + + MatrixCapability matrix_capability() const override { + return MatrixCapability::kComplex; + } + + protected: + bool equals_(const DlImageFilter& other) const override; + + private: + std::shared_ptr color_filter_; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_COLOR_FILTER_IMAGE_FILTER_H_ diff --git a/display_list/effects/image_filters/dl_compose_image_filter.cc b/display_list/effects/image_filters/dl_compose_image_filter.cc new file mode 100644 index 0000000000000..13ab97882d25e --- /dev/null +++ b/display_list/effects/image_filters/dl_compose_image_filter.cc @@ -0,0 +1,107 @@ +// 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. + +#include "flutter/display_list/effects/image_filters/dl_compose_image_filter.h" + +#include "flutter/display_list/utils/dl_comparable.h" + +namespace flutter { + +std::shared_ptr DlComposeImageFilter::Make( + const std::shared_ptr& outer, + const std::shared_ptr& inner) { + if (!outer) { + return inner; + } + if (!inner) { + return outer; + } + return std::make_shared(outer, inner); +} + +bool DlComposeImageFilter::modifies_transparent_black() const { + if (inner_ && inner_->modifies_transparent_black()) { + return true; + } + if (outer_ && outer_->modifies_transparent_black()) { + return true; + } + return false; +} + +DlRect* DlComposeImageFilter::map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const { + DlRect cur_bounds = input_bounds; + DlRect* ret = &output_bounds; + // We set this result in case neither filter is present. + output_bounds = input_bounds; + if (inner_) { + if (!inner_->map_local_bounds(cur_bounds, output_bounds)) { + ret = nullptr; + } + cur_bounds = output_bounds; + } + if (outer_) { + if (!outer_->map_local_bounds(cur_bounds, output_bounds)) { + ret = nullptr; + } + } + return ret; +} + +DlIRect* DlComposeImageFilter::map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const { + DlIRect cur_bounds = input_bounds; + DlIRect* ret = &output_bounds; + // We set this result in case neither filter is present. + output_bounds = input_bounds; + if (inner_) { + if (!inner_->map_device_bounds(cur_bounds, ctm, output_bounds)) { + ret = nullptr; + } + cur_bounds = output_bounds; + } + if (outer_) { + if (!outer_->map_device_bounds(cur_bounds, ctm, output_bounds)) { + ret = nullptr; + } + } + return ret; +} + +DlIRect* DlComposeImageFilter::get_input_device_bounds( + const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const { + DlIRect cur_bounds = output_bounds; + DlIRect* ret = &input_bounds; + // We set this result in case neither filter is present. + input_bounds = output_bounds; + if (outer_) { + if (!outer_->get_input_device_bounds(cur_bounds, ctm, input_bounds)) { + ret = nullptr; + } + cur_bounds = input_bounds; + } + if (inner_) { + if (!inner_->get_input_device_bounds(cur_bounds, ctm, input_bounds)) { + ret = nullptr; + } + } + return ret; +} + +DlImageFilter::MatrixCapability DlComposeImageFilter::matrix_capability() + const { + return std::min(outer_->matrix_capability(), inner_->matrix_capability()); +} + +bool DlComposeImageFilter::equals_(const DlImageFilter& other) const { + FML_DCHECK(other.type() == DlImageFilterType::kCompose); + auto that = static_cast(&other); + return (Equals(outer_, that->outer_) && Equals(inner_, that->inner_)); +} + +} // namespace flutter diff --git a/display_list/effects/image_filters/dl_compose_image_filter.h b/display_list/effects/image_filters/dl_compose_image_filter.h new file mode 100644 index 0000000000000..5ce5f6c668c9b --- /dev/null +++ b/display_list/effects/image_filters/dl_compose_image_filter.h @@ -0,0 +1,69 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_COMPOSE_IMAGE_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_COMPOSE_IMAGE_FILTER_H_ + +#include "display_list/effects/dl_image_filter.h" + +namespace flutter { + +class DlComposeImageFilter final : public DlImageFilter { + public: + DlComposeImageFilter(const std::shared_ptr& outer, + const std::shared_ptr& inner) + : outer_(outer), inner_(inner) {} + DlComposeImageFilter(const DlImageFilter* outer, const DlImageFilter* inner) + : outer_(outer->shared()), inner_(inner->shared()) {} + DlComposeImageFilter(const DlImageFilter& outer, const DlImageFilter& inner) + : DlComposeImageFilter(&outer, &inner) {} + explicit DlComposeImageFilter(const DlComposeImageFilter* filter) + : DlComposeImageFilter(filter->outer_, filter->inner_) {} + DlComposeImageFilter(const DlComposeImageFilter& filter) + : DlComposeImageFilter(&filter) {} + + static std::shared_ptr Make( + const std::shared_ptr& outer, + const std::shared_ptr& inner); + + std::shared_ptr shared() const override { + return std::make_shared(this); + } + + DlImageFilterType type() const override { + return DlImageFilterType::kCompose; + } + size_t size() const override { return sizeof(*this); } + + std::shared_ptr outer() const { return outer_; } + std::shared_ptr inner() const { return inner_; } + + const DlComposeImageFilter* asCompose() const override { return this; } + + bool modifies_transparent_black() const override; + + DlRect* map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const override; + + DlIRect* map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const override; + + DlIRect* get_input_device_bounds(const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const override; + + MatrixCapability matrix_capability() const override; + + protected: + bool equals_(const DlImageFilter& other) const override; + + private: + const std::shared_ptr outer_; + const std::shared_ptr inner_; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_COMPOSE_IMAGE_FILTER_H_ diff --git a/display_list/effects/image_filters/dl_dilate_image_filter.cc b/display_list/effects/image_filters/dl_dilate_image_filter.cc new file mode 100644 index 0000000000000..da192176fe508 --- /dev/null +++ b/display_list/effects/image_filters/dl_dilate_image_filter.cc @@ -0,0 +1,45 @@ +// 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. + +#include "flutter/display_list/effects/image_filters/dl_dilate_image_filter.h" + +namespace flutter { + +std::shared_ptr DlDilateImageFilter::Make(DlScalar radius_x, + DlScalar radius_y) { + if (std::isfinite(radius_x) && radius_x > SK_ScalarNearlyZero && + std::isfinite(radius_y) && radius_y > SK_ScalarNearlyZero) { + return std::make_shared(radius_x, radius_y); + } + return nullptr; +} + +DlRect* DlDilateImageFilter::map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const { + output_bounds = input_bounds.Expand(radius_x_, radius_y_); + return &output_bounds; +} + +DlIRect* DlDilateImageFilter::map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const { + return outset_device_bounds(input_bounds, radius_x_, radius_y_, ctm, + output_bounds); +} + +DlIRect* DlDilateImageFilter::get_input_device_bounds( + const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const { + return inset_device_bounds(output_bounds, radius_x_, radius_y_, ctm, + input_bounds); +} + +bool DlDilateImageFilter::equals_(const DlImageFilter& other) const { + FML_DCHECK(other.type() == DlImageFilterType::kDilate); + auto that = static_cast(&other); + return (radius_x_ == that->radius_x_ && radius_y_ == that->radius_y_); +} + +} // namespace flutter diff --git a/display_list/effects/image_filters/dl_dilate_image_filter.h b/display_list/effects/image_filters/dl_dilate_image_filter.h new file mode 100644 index 0000000000000..b0dd3de28ebb9 --- /dev/null +++ b/display_list/effects/image_filters/dl_dilate_image_filter.h @@ -0,0 +1,59 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_DILATE_IMAGE_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_DILATE_IMAGE_FILTER_H_ + +#include "display_list/effects/dl_image_filter.h" + +namespace flutter { + +class DlDilateImageFilter final : public DlImageFilter { + public: + DlDilateImageFilter(DlScalar radius_x, DlScalar radius_y) + : radius_x_(radius_x), radius_y_(radius_y) {} + explicit DlDilateImageFilter(const DlDilateImageFilter* filter) + : DlDilateImageFilter(filter->radius_x_, filter->radius_y_) {} + DlDilateImageFilter(const DlDilateImageFilter& filter) + : DlDilateImageFilter(&filter) {} + + static std::shared_ptr Make(DlScalar radius_x, + DlScalar radius_y); + + std::shared_ptr shared() const override { + return std::make_shared(this); + } + + DlImageFilterType type() const override { return DlImageFilterType::kDilate; } + size_t size() const override { return sizeof(*this); } + + const DlDilateImageFilter* asDilate() const override { return this; } + + bool modifies_transparent_black() const override { return false; } + + DlRect* map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const override; + + DlIRect* map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const override; + + DlIRect* get_input_device_bounds(const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const override; + + DlScalar radius_x() const { return radius_x_; } + DlScalar radius_y() const { return radius_y_; } + + protected: + bool equals_(const DlImageFilter& other) const override; + + private: + DlScalar radius_x_; + DlScalar radius_y_; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_DILATE_IMAGE_FILTER_H_ diff --git a/display_list/effects/image_filters/dl_erode_image_filter.cc b/display_list/effects/image_filters/dl_erode_image_filter.cc new file mode 100644 index 0000000000000..0b58af6e6f5ff --- /dev/null +++ b/display_list/effects/image_filters/dl_erode_image_filter.cc @@ -0,0 +1,45 @@ +// 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. + +#include "flutter/display_list/effects/image_filters/dl_erode_image_filter.h" + +namespace flutter { + +std::shared_ptr DlErodeImageFilter::Make(DlScalar radius_x, + DlScalar radius_y) { + if (std::isfinite(radius_x) && radius_x > SK_ScalarNearlyZero && + std::isfinite(radius_y) && radius_y > SK_ScalarNearlyZero) { + return std::make_shared(radius_x, radius_y); + } + return nullptr; +} + +DlRect* DlErodeImageFilter::map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const { + output_bounds = input_bounds.Expand(-radius_x_, -radius_y_); + return &output_bounds; +} + +DlIRect* DlErodeImageFilter::map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const { + return inset_device_bounds(input_bounds, radius_x_, radius_y_, ctm, + output_bounds); +} + +DlIRect* DlErodeImageFilter::get_input_device_bounds( + const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const { + return outset_device_bounds(output_bounds, radius_x_, radius_y_, ctm, + input_bounds); +} + +bool DlErodeImageFilter::equals_(const DlImageFilter& other) const { + FML_DCHECK(other.type() == DlImageFilterType::kErode); + auto that = static_cast(&other); + return (radius_x_ == that->radius_x_ && radius_y_ == that->radius_y_); +} + +} // namespace flutter diff --git a/display_list/effects/image_filters/dl_erode_image_filter.h b/display_list/effects/image_filters/dl_erode_image_filter.h new file mode 100644 index 0000000000000..6bbae385740ea --- /dev/null +++ b/display_list/effects/image_filters/dl_erode_image_filter.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_ERODE_IMAGE_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_ERODE_IMAGE_FILTER_H_ + +#include + +#include "display_list/effects/dl_image_filter.h" + +namespace flutter { + +class DlErodeImageFilter final : public DlImageFilter { + public: + DlErodeImageFilter(DlScalar radius_x, DlScalar radius_y) + : radius_x_(radius_x), radius_y_(radius_y) {} + explicit DlErodeImageFilter(const DlErodeImageFilter* filter) + : DlErodeImageFilter(filter->radius_x_, filter->radius_y_) {} + DlErodeImageFilter(const DlErodeImageFilter& filter) + : DlErodeImageFilter(&filter) {} + + static std::shared_ptr Make(DlScalar radius_x, + DlScalar radius_y); + + std::shared_ptr shared() const override { + return std::make_shared(this); + } + + DlImageFilterType type() const override { return DlImageFilterType::kErode; } + size_t size() const override { return sizeof(*this); } + + const DlErodeImageFilter* asErode() const override { return this; } + + bool modifies_transparent_black() const override { return false; } + + DlRect* map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const override; + + DlIRect* map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const override; + + DlIRect* get_input_device_bounds(const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const override; + + DlScalar radius_x() const { return radius_x_; } + DlScalar radius_y() const { return radius_y_; } + + protected: + bool equals_(const DlImageFilter& other) const override; + + private: + DlScalar radius_x_; + DlScalar radius_y_; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_ERODE_IMAGE_FILTER_H_ diff --git a/display_list/effects/image_filters/dl_local_matrix_image_filter.cc b/display_list/effects/image_filters/dl_local_matrix_image_filter.cc new file mode 100644 index 0000000000000..68c0e9c784028 --- /dev/null +++ b/display_list/effects/image_filters/dl_local_matrix_image_filter.cc @@ -0,0 +1,65 @@ +// 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. + +#include "flutter/display_list/effects/image_filters/dl_local_matrix_image_filter.h" + +#include "flutter/display_list/utils/dl_comparable.h" + +namespace flutter { + +std::shared_ptr DlLocalMatrixImageFilter::Make( + const DlMatrix& matrix, + const std::shared_ptr& filter) { + return std::make_shared(matrix, filter); +} + +bool DlLocalMatrixImageFilter::modifies_transparent_black() const { + if (!image_filter_) { + return false; + } + return image_filter_->modifies_transparent_black(); +} + +DlRect* DlLocalMatrixImageFilter::map_local_bounds( + const DlRect& input_bounds, + DlRect& output_bounds) const { + if (!image_filter_) { + output_bounds = input_bounds; + return &output_bounds; + } + return image_filter_->map_local_bounds(input_bounds, output_bounds); +} + +DlIRect* DlLocalMatrixImageFilter::map_device_bounds( + const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const { + if (!image_filter_) { + output_bounds = input_bounds; + return &output_bounds; + } + return image_filter_->map_device_bounds(input_bounds, ctm * matrix_, + output_bounds); +} + +DlIRect* DlLocalMatrixImageFilter::get_input_device_bounds( + const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const { + if (!image_filter_) { + input_bounds = output_bounds; + return &input_bounds; + } + return image_filter_->get_input_device_bounds(output_bounds, ctm * matrix_, + input_bounds); +} + +bool DlLocalMatrixImageFilter::equals_(const DlImageFilter& other) const { + FML_DCHECK(other.type() == DlImageFilterType::kLocalMatrix); + auto that = static_cast(&other); + return (matrix_ == that->matrix_ && + Equals(image_filter_, that->image_filter_)); +} + +} // namespace flutter diff --git a/display_list/effects/image_filters/dl_local_matrix_image_filter.h b/display_list/effects/image_filters/dl_local_matrix_image_filter.h new file mode 100644 index 0000000000000..94a88ac25caaf --- /dev/null +++ b/display_list/effects/image_filters/dl_local_matrix_image_filter.h @@ -0,0 +1,69 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_LOCAL_MATRIX_IMAGE_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_LOCAL_MATRIX_IMAGE_FILTER_H_ + +#include "display_list/effects/dl_image_filter.h" + +namespace flutter { + +class DlLocalMatrixImageFilter final : public DlImageFilter { + public: + explicit DlLocalMatrixImageFilter( + const DlMatrix& matrix, + const std::shared_ptr& filter) + : matrix_(matrix), image_filter_(filter) {} + explicit DlLocalMatrixImageFilter(const DlLocalMatrixImageFilter* filter) + : DlLocalMatrixImageFilter(filter->matrix_, filter->image_filter_) {} + DlLocalMatrixImageFilter(const DlLocalMatrixImageFilter& filter) + : DlLocalMatrixImageFilter(&filter) {} + + std::shared_ptr shared() const override { + return std::make_shared(this); + } + + static std::shared_ptr Make( + const DlMatrix& matrix, + const std::shared_ptr& filter); + + DlImageFilterType type() const override { + return DlImageFilterType::kLocalMatrix; + } + size_t size() const override { return sizeof(*this); } + + const DlMatrix& matrix() const { return matrix_; } + + const std::shared_ptr image_filter() const { + return image_filter_; + } + + const DlLocalMatrixImageFilter* asLocalMatrix() const override { + return this; + } + + bool modifies_transparent_black() const override; + + DlRect* map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const override; + + DlIRect* map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const override; + + DlIRect* get_input_device_bounds(const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const override; + + protected: + bool equals_(const DlImageFilter& other) const override; + + private: + DlMatrix matrix_; + std::shared_ptr image_filter_; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_LOCAL_MATRIX_IMAGE_FILTER_H_ diff --git a/display_list/effects/image_filters/dl_matrix_image_filter.cc b/display_list/effects/image_filters/dl_matrix_image_filter.cc new file mode 100644 index 0000000000000..4d9bc95efcf34 --- /dev/null +++ b/display_list/effects/image_filters/dl_matrix_image_filter.cc @@ -0,0 +1,60 @@ +// 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. + +#include "flutter/display_list/effects/image_filters/dl_matrix_image_filter.h" + +namespace flutter { + +std::shared_ptr DlMatrixImageFilter::Make( + const DlMatrix& matrix, + DlImageSampling sampling) { + if (matrix.IsFinite() && !matrix.IsIdentity()) { + return std::make_shared(matrix, sampling); + } + return nullptr; +} + +DlRect* DlMatrixImageFilter::map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const { + output_bounds = input_bounds.TransformAndClipBounds(matrix_); + return &output_bounds; +} + +DlIRect* DlMatrixImageFilter::map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const { + if (!ctm.IsInvertible()) { + output_bounds = input_bounds; + return nullptr; + } + DlMatrix matrix = ctm * matrix_ * ctm.Invert(); + DlRect device_rect = + DlRect::Make(input_bounds).TransformAndClipBounds(matrix); + output_bounds = DlIRect::RoundOut(device_rect); + return &output_bounds; +} + +DlIRect* DlMatrixImageFilter::get_input_device_bounds( + const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const { + DlMatrix matrix = ctm * matrix_; + if (!matrix.IsInvertible()) { + input_bounds = output_bounds; + return nullptr; + } + DlMatrix inverse = ctm * matrix.Invert(); + DlRect bounds = DlRect::Make(output_bounds); + bounds = bounds.TransformAndClipBounds(inverse); + input_bounds = DlIRect::RoundOut(bounds); + return &input_bounds; +} + +bool DlMatrixImageFilter::equals_(const DlImageFilter& other) const { + FML_DCHECK(other.type() == DlImageFilterType::kMatrix); + auto that = static_cast(&other); + return (matrix_ == that->matrix_ && sampling_ == that->sampling_); +} + +} // namespace flutter diff --git a/display_list/effects/image_filters/dl_matrix_image_filter.h b/display_list/effects/image_filters/dl_matrix_image_filter.h new file mode 100644 index 0000000000000..ee98e9c2d11a3 --- /dev/null +++ b/display_list/effects/image_filters/dl_matrix_image_filter.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_MATRIX_IMAGE_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_MATRIX_IMAGE_FILTER_H_ + +#include "display_list/effects/dl_image_filter.h" + +#include "flutter/display_list/dl_sampling_options.h" + +namespace flutter { + +class DlMatrixImageFilter final : public DlImageFilter { + public: + DlMatrixImageFilter(const DlMatrix& matrix, DlImageSampling sampling) + : matrix_(matrix), sampling_(sampling) {} + explicit DlMatrixImageFilter(const DlMatrixImageFilter* filter) + : DlMatrixImageFilter(filter->matrix_, filter->sampling_) {} + DlMatrixImageFilter(const DlMatrixImageFilter& filter) + : DlMatrixImageFilter(&filter) {} + + static std::shared_ptr Make(const DlMatrix& matrix, + DlImageSampling sampling); + + std::shared_ptr shared() const override { + return std::make_shared(this); + } + + DlImageFilterType type() const override { return DlImageFilterType::kMatrix; } + size_t size() const override { return sizeof(*this); } + + const DlMatrix& matrix() const { return matrix_; } + DlImageSampling sampling() const { return sampling_; } + + const DlMatrixImageFilter* asMatrix() const override { return this; } + + bool modifies_transparent_black() const override { return false; } + + DlRect* map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const override; + + DlIRect* map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const override; + + DlIRect* get_input_device_bounds(const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const override; + + protected: + bool equals_(const DlImageFilter& other) const override; + + private: + DlMatrix matrix_; + DlImageSampling sampling_; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_MATRIX_IMAGE_FILTER_H_ diff --git a/display_list/effects/image_filters/dl_runtime_effect_image_filter.cc b/display_list/effects/image_filters/dl_runtime_effect_image_filter.cc new file mode 100644 index 0000000000000..9c6c285532d61 --- /dev/null +++ b/display_list/effects/image_filters/dl_runtime_effect_image_filter.cc @@ -0,0 +1,61 @@ +// 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. + +#include "flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.h" + +namespace flutter { + +std::shared_ptr DlRuntimeEffectImageFilter::Make( + sk_sp runtime_effect, + std::vector> samplers, + std::shared_ptr> uniform_data) { + return std::make_shared( + std::move(runtime_effect), std::move(samplers), std::move(uniform_data)); +} + +DlRect* DlRuntimeEffectImageFilter::map_local_bounds( + const DlRect& input_bounds, + DlRect& output_bounds) const { + output_bounds = input_bounds; + return &output_bounds; +} + +DlIRect* DlRuntimeEffectImageFilter::map_device_bounds( + const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const { + output_bounds = input_bounds; + return &output_bounds; +} + +DlIRect* DlRuntimeEffectImageFilter::get_input_device_bounds( + const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const { + input_bounds = output_bounds; + return &input_bounds; +} + +bool DlRuntimeEffectImageFilter::equals_(const DlImageFilter& other) const { + FML_DCHECK(other.type() == DlImageFilterType::kRuntimeEffect); + auto that = static_cast(&other); + if (runtime_effect_ != that->runtime_effect_ || + samplers_.size() != that->samplers().size() || + uniform_data_->size() != that->uniform_data()->size()) { + return false; + } + for (auto i = 0u; i < samplers_.size(); i++) { + if (samplers_[i] != that->samplers()[i]) { + return false; + } + } + for (auto i = 0u; i < uniform_data_->size(); i++) { + if (uniform_data_->at(i) != that->uniform_data()->at(i)) { + return false; + } + } + return true; +} + +} // namespace flutter diff --git a/display_list/effects/image_filters/dl_runtime_effect_image_filter.h b/display_list/effects/image_filters/dl_runtime_effect_image_filter.h new file mode 100644 index 0000000000000..391cfd9f475f9 --- /dev/null +++ b/display_list/effects/image_filters/dl_runtime_effect_image_filter.h @@ -0,0 +1,80 @@ +// 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. + +#ifndef FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_RUNTIME_EFFECT_IMAGE_FILTER_H_ +#define FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_RUNTIME_EFFECT_IMAGE_FILTER_H_ + +#include "display_list/effects/dl_image_filter.h" + +#include "flutter/display_list/effects/dl_color_source.h" +#include "flutter/display_list/effects/dl_runtime_effect.h" + +namespace flutter { + +class DlRuntimeEffectImageFilter final : public DlImageFilter { + public: + explicit DlRuntimeEffectImageFilter( + sk_sp runtime_effect, + std::vector> samplers, + std::shared_ptr> uniform_data) + : runtime_effect_(std::move(runtime_effect)), + samplers_(std::move(samplers)), + uniform_data_(std::move(uniform_data)) {} + + std::shared_ptr shared() const override { + return std::make_shared( + this->runtime_effect_, this->samplers_, this->uniform_data_); + } + + static std::shared_ptr Make( + sk_sp runtime_effect, + std::vector> samplers, + std::shared_ptr> uniform_data); + + DlImageFilterType type() const override { + return DlImageFilterType::kRuntimeEffect; + } + size_t size() const override { return sizeof(*this); } + + bool modifies_transparent_black() const override { return false; } + + DlRect* map_local_bounds(const DlRect& input_bounds, + DlRect& output_bounds) const override; + + DlIRect* map_device_bounds(const DlIRect& input_bounds, + const DlMatrix& ctm, + DlIRect& output_bounds) const override; + + DlIRect* get_input_device_bounds(const DlIRect& output_bounds, + const DlMatrix& ctm, + DlIRect& input_bounds) const override; + + const DlRuntimeEffectImageFilter* asRuntimeEffectFilter() const override { + return this; + } + + const sk_sp runtime_effect() const { + return runtime_effect_; + } + + const std::vector>& samplers() const { + return samplers_; + } + + const std::shared_ptr>& uniform_data() const { + return uniform_data_; + } + + protected: + bool equals_(const DlImageFilter& other) const override; + + private: + sk_sp runtime_effect_; + std::vector> samplers_; + std::shared_ptr> uniform_data_; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_EFFECTS_IMAGE_FILTERS_DL_RUNTIME_EFFECT_IMAGE_FILTER_H_ diff --git a/display_list/geometry/dl_geometry_types.h b/display_list/geometry/dl_geometry_types.h index 66ef9a7428464..0021a2b90964d 100644 --- a/display_list/geometry/dl_geometry_types.h +++ b/display_list/geometry/dl_geometry_types.h @@ -23,6 +23,7 @@ using DlDegrees = impeller::Degrees; using DlRadians = impeller::Radians; using DlPoint = impeller::Point; +using DlVector2 = impeller::Vector2; using DlIPoint = impeller::IPoint32; using DlSize = impeller::Size; using DlISize = impeller::ISize32; @@ -30,6 +31,7 @@ using DlRect = impeller::Rect; using DlIRect = impeller::IRect32; using DlRoundRect = impeller::RoundRect; using DlMatrix = impeller::Matrix; +using DlQuad = impeller::Quad; static_assert(sizeof(SkPoint) == sizeof(DlPoint)); static_assert(sizeof(SkIPoint) == sizeof(DlIPoint)); @@ -39,6 +41,19 @@ static_assert(sizeof(SkRect) == sizeof(DlRect)); static_assert(sizeof(SkIRect) == sizeof(DlIRect)); static_assert(sizeof(SkVector) == sizeof(DlSize)); +static constexpr DlScalar kEhCloseEnough = impeller::kEhCloseEnough; + +constexpr inline bool DlScalarNearlyZero(DlScalar x, + DlScalar tolerance = kEhCloseEnough) { + return impeller::ScalarNearlyZero(x, tolerance); +} + +constexpr inline bool DlScalarNearlyEqual(DlScalar x, + DlScalar y, + DlScalar tolerance = kEhCloseEnough) { + return impeller::ScalarNearlyEqual(x, y, tolerance); +} + inline const DlPoint& ToDlPoint(const SkPoint& point) { return *reinterpret_cast(&point); } @@ -131,7 +146,7 @@ inline const SkRect* ToSkRect(const DlRect* rect) { return rect == nullptr ? nullptr : reinterpret_cast(rect); } -inline const SkRect* ToSkRect(std::optional& rect) { +inline const SkRect* ToSkRect(const std::optional& rect) { return rect.has_value() ? &ToSkRect(rect.value()) : nullptr; } diff --git a/display_list/skia/dl_sk_canvas.cc b/display_list/skia/dl_sk_canvas.cc index 3746a88c3e038..6d66c466745e6 100644 --- a/display_list/skia/dl_sk_canvas.cc +++ b/display_list/skia/dl_sk_canvas.cc @@ -6,6 +6,7 @@ #include "flutter/display_list/skia/dl_sk_canvas.h" +#include "flutter/display_list/effects/image_filters/dl_blur_image_filter.h" #include "flutter/display_list/skia/dl_sk_conversions.h" #include "flutter/display_list/skia/dl_sk_dispatcher.h" #include "flutter/fml/trace_event.h" @@ -52,7 +53,7 @@ void DlSkCanvasAdapter::Save() { delegate_->save(); } -void DlSkCanvasAdapter::SaveLayer(std::optional& bounds, +void DlSkCanvasAdapter::SaveLayer(const std::optional& bounds, const DlPaint* paint, const DlImageFilter* backdrop, std::optional backdrop_id) { diff --git a/display_list/skia/dl_sk_canvas.h b/display_list/skia/dl_sk_canvas.h index 70b0ffa97b95b..3a42c3a61f2e5 100644 --- a/display_list/skia/dl_sk_canvas.h +++ b/display_list/skia/dl_sk_canvas.h @@ -30,7 +30,7 @@ class DlSkCanvasAdapter final : public virtual DlCanvas { SkImageInfo GetImageInfo() const override; void Save() override; - void SaveLayer(std::optional& bounds, + void SaveLayer(const std::optional& bounds, const DlPaint* paint = nullptr, const DlImageFilter* backdrop = nullptr, std::optional backdrop_id = std::nullopt) override; diff --git a/display_list/skia/dl_sk_conversions.cc b/display_list/skia/dl_sk_conversions.cc index f891fa83e59a4..16b3455f9dc0b 100644 --- a/display_list/skia/dl_sk_conversions.cc +++ b/display_list/skia/dl_sk_conversions.cc @@ -4,6 +4,9 @@ #include "flutter/display_list/skia/dl_sk_conversions.h" +#include "flutter/display_list/effects/dl_color_filters.h" +#include "flutter/display_list/effects/dl_color_sources.h" +#include "flutter/display_list/effects/dl_image_filters.h" #include "third_party/skia/include/core/SkColorFilter.h" #include "third_party/skia/include/effects/SkGradientShader.h" #include "third_party/skia/include/effects/SkImageFilters.h" @@ -69,6 +72,7 @@ sk_sp ToSk(const DlColorSource* source) { if (!source) { return nullptr; } + SkMatrix scratch; static auto ToSkColors = [](const DlGradientColorSourceBase* gradient) -> std::vector { std::vector sk_colors; @@ -79,11 +83,6 @@ sk_sp ToSk(const DlColorSource* source) { return sk_colors; }; switch (source->type()) { - case DlColorSourceType::kColor: { - const DlColorColorSource* color_source = source->asColor(); - FML_DCHECK(color_source != nullptr); - return SkShaders::Color(ToSk(color_source->color())); - } case DlColorSourceType::kImage: { const DlImageColorSource* image_source = source->asImage(); FML_DCHECK(image_source != nullptr); @@ -94,51 +93,53 @@ sk_sp ToSk(const DlColorSource* source) { return image->skia_image()->makeShader( ToSk(image_source->horizontal_tile_mode()), ToSk(image_source->vertical_tile_mode()), - ToSk(image_source->sampling()), image_source->matrix_ptr()); + ToSk(image_source->sampling()), + ToSk(image_source->matrix_ptr(), scratch)); } case DlColorSourceType::kLinearGradient: { const DlLinearGradientColorSource* linear_source = source->asLinearGradient(); FML_DCHECK(linear_source != nullptr); - SkPoint pts[] = {linear_source->start_point(), - linear_source->end_point()}; + SkPoint pts[] = {ToSkPoint(linear_source->start_point()), + ToSkPoint(linear_source->end_point())}; std::vector skcolors = ToSkColors(linear_source); return SkGradientShader::MakeLinear( pts, skcolors.data(), linear_source->stops(), linear_source->stop_count(), ToSk(linear_source->tile_mode()), 0, - linear_source->matrix_ptr()); + ToSk(linear_source->matrix_ptr(), scratch)); } case DlColorSourceType::kRadialGradient: { const DlRadialGradientColorSource* radial_source = source->asRadialGradient(); FML_DCHECK(radial_source != nullptr); return SkGradientShader::MakeRadial( - radial_source->center(), radial_source->radius(), + ToSkPoint(radial_source->center()), radial_source->radius(), ToSkColors(radial_source).data(), radial_source->stops(), radial_source->stop_count(), ToSk(radial_source->tile_mode()), 0, - radial_source->matrix_ptr()); + ToSk(radial_source->matrix_ptr(), scratch)); } case DlColorSourceType::kConicalGradient: { const DlConicalGradientColorSource* conical_source = source->asConicalGradient(); FML_DCHECK(conical_source != nullptr); return SkGradientShader::MakeTwoPointConical( - conical_source->start_center(), conical_source->start_radius(), - conical_source->end_center(), conical_source->end_radius(), + ToSkPoint(conical_source->start_center()), + conical_source->start_radius(), + ToSkPoint(conical_source->end_center()), conical_source->end_radius(), ToSkColors(conical_source).data(), conical_source->stops(), conical_source->stop_count(), ToSk(conical_source->tile_mode()), 0, - conical_source->matrix_ptr()); + ToSk(conical_source->matrix_ptr(), scratch)); } case DlColorSourceType::kSweepGradient: { const DlSweepGradientColorSource* sweep_source = source->asSweepGradient(); FML_DCHECK(sweep_source != nullptr); return SkGradientShader::MakeSweep( - sweep_source->center().x(), sweep_source->center().y(), + sweep_source->center().x, sweep_source->center().y, ToSkColors(sweep_source).data(), sweep_source->stops(), sweep_source->stop_count(), ToSk(sweep_source->tile_mode()), sweep_source->start(), sweep_source->end(), 0, - sweep_source->matrix_ptr()); + ToSk(sweep_source->matrix_ptr(), scratch)); } case DlColorSourceType::kRuntimeEffect: { const DlRuntimeEffectColorSource* runtime_source = @@ -203,7 +204,8 @@ sk_sp ToSk(const DlImageFilter* filter) { const DlMatrixImageFilter* matrix_filter = filter->asMatrix(); FML_DCHECK(matrix_filter != nullptr); return SkImageFilters::MatrixTransform( - matrix_filter->matrix(), ToSk(matrix_filter->sampling()), nullptr); + ToSkMatrix(matrix_filter->matrix()), ToSk(matrix_filter->sampling()), + nullptr); } case DlImageFilterType::kCompose: { const DlComposeImageFilter* compose_filter = filter->asCompose(); @@ -229,8 +231,11 @@ sk_sp ToSk(const DlImageFilter* filter) { if (!skia_filter) { return nullptr; } - return skia_filter->makeWithLocalMatrix(lm_filter->matrix()); + return skia_filter->makeWithLocalMatrix(ToSkMatrix(lm_filter->matrix())); } + case DlImageFilterType::kRuntimeEffect: + // UNSUPPORTED. + return nullptr; } } diff --git a/display_list/skia/dl_sk_conversions.h b/display_list/skia/dl_sk_conversions.h index 808d9842efd43..f3d5d7320b173 100644 --- a/display_list/skia/dl_sk_conversions.h +++ b/display_list/skia/dl_sk_conversions.h @@ -86,8 +86,7 @@ inline sk_sp ToSk(const DlColorSource& source) { } extern sk_sp ToSk(const DlImageFilter* filter); -inline sk_sp ToSk( - const std::shared_ptr& filter) { +inline sk_sp ToSk(const std::shared_ptr& filter) { return ToSk(filter.get()); } inline sk_sp ToSk(const DlImageFilter& filter) { @@ -112,6 +111,13 @@ inline sk_sp ToSk(const DlMaskFilter& filter) { return ToSk(&filter); } +inline SkMatrix* ToSk(const DlMatrix* matrix, SkMatrix& scratch) { + return matrix ? &scratch.setAll(matrix->m[0], matrix->m[4], matrix->m[12], // + matrix->m[1], matrix->m[5], matrix->m[13], // + matrix->m[3], matrix->m[7], matrix->m[15]) + : nullptr; +} + extern sk_sp ToSk(const std::shared_ptr& vertices); } // namespace flutter diff --git a/display_list/skia/dl_sk_conversions_unittests.cc b/display_list/skia/dl_sk_conversions_unittests.cc index 575113b9f64ba..81606246e76c4 100644 --- a/display_list/skia/dl_sk_conversions_unittests.cc +++ b/display_list/skia/dl_sk_conversions_unittests.cc @@ -7,7 +7,9 @@ #include "flutter/display_list/dl_sampling_options.h" #include "flutter/display_list/dl_tile_mode.h" #include "flutter/display_list/dl_vertices.h" -#include "flutter/display_list/effects/dl_color_source.h" +#include "flutter/display_list/effects/dl_color_filters.h" +#include "flutter/display_list/effects/dl_color_sources.h" +#include "flutter/display_list/effects/dl_image_filters.h" #include "flutter/display_list/skia/dl_sk_conversions.h" #include "gtest/gtest.h" #include "third_party/skia/include/core/SkColorSpace.h" @@ -18,10 +20,9 @@ namespace flutter { namespace testing { TEST(DisplayListImageFilter, LocalImageSkiaNull) { - auto blur_filter = - std::make_shared(0, 0, DlTileMode::kClamp); - DlLocalMatrixImageFilter dl_local_matrix_filter(SkMatrix::RotateDeg(45), - blur_filter); + auto blur_filter = DlImageFilter::MakeBlur(0, 0, DlTileMode::kClamp); + DlLocalMatrixImageFilter dl_local_matrix_filter( + DlMatrix::MakeRotationZ(DlDegrees(45)), blur_filter); // With sigmas set to zero on the blur filter, Skia will return a null filter. // The local matrix filter should return nullptr instead of crashing. ASSERT_EQ(ToSk(dl_local_matrix_filter), nullptr); @@ -158,7 +159,7 @@ TEST(DisplayListSkConversions, BlendColorFilterModifiesTransparency) { DlBlendColorFilter filter(color, mode); auto srgb = SkColorSpace::MakeSRGB(); if (filter.modifies_transparent_black()) { - auto dl_filter = DlBlendColorFilter::Make(color, mode); + auto dl_filter = DlColorFilter::MakeBlend(color, mode); auto sk_filter = ToSk(filter); ASSERT_NE(dl_filter, nullptr) << desc; ASSERT_NE(sk_filter, nullptr) << desc; @@ -167,7 +168,7 @@ TEST(DisplayListSkConversions, BlendColorFilterModifiesTransparency) { SkColors::kTransparent) << desc; } else { - auto dl_filter = DlBlendColorFilter::Make(color, mode); + auto dl_filter = DlColorFilter::MakeBlend(color, mode); auto sk_filter = ToSk(filter); EXPECT_EQ(dl_filter == nullptr, sk_filter == nullptr) << desc; ASSERT_TRUE(sk_filter == nullptr || @@ -230,15 +231,12 @@ TEST(DisplayListColorSource, ConvertRuntimeEffect) { SkRuntimeEffect::MakeForShader( SkString("vec4 main(vec2 p) { return vec4(1); }")) .effect); - std::shared_ptr source1 = - DlColorSource::MakeRuntimeEffect( - kTestRuntimeEffect1, {}, std::make_shared>()); - std::shared_ptr source2 = - DlColorSource::MakeRuntimeEffect( - kTestRuntimeEffect2, {}, std::make_shared>()); - std::shared_ptr source3 = - DlColorSource::MakeRuntimeEffect( - nullptr, {}, std::make_shared>()); + std::shared_ptr source1 = DlColorSource::MakeRuntimeEffect( + kTestRuntimeEffect1, {}, std::make_shared>()); + std::shared_ptr source2 = DlColorSource::MakeRuntimeEffect( + kTestRuntimeEffect2, {}, std::make_shared>()); + std::shared_ptr source3 = DlColorSource::MakeRuntimeEffect( + nullptr, {}, std::make_shared>()); ASSERT_NE(ToSk(source1), nullptr); ASSERT_NE(ToSk(source2), nullptr); @@ -250,10 +248,8 @@ TEST(DisplayListColorSource, ConvertRuntimeEffectWithNullSampler) { SkRuntimeEffect::MakeForShader( SkString("vec4 main(vec2 p) { return vec4(0); }")) .effect); - std::shared_ptr source1 = - DlColorSource::MakeRuntimeEffect( - kTestRuntimeEffect1, {nullptr}, - std::make_shared>()); + std::shared_ptr source1 = DlColorSource::MakeRuntimeEffect( + kTestRuntimeEffect1, {nullptr}, std::make_shared>()); ASSERT_EQ(ToSk(source1), nullptr); } @@ -272,7 +268,7 @@ TEST(DisplayListSkConversions, MatrixColorFilterModifiesTransparency) { "matrix[" + std::to_string(element) + "] = " + std::to_string(value); matrix[element] = value; DlMatrixColorFilter filter(matrix); - auto dl_filter = DlMatrixColorFilter::Make(matrix); + auto dl_filter = DlColorFilter::MakeMatrix(matrix); auto sk_filter = ToSk(filter); auto srgb = SkColorSpace::MakeSRGB(); EXPECT_EQ(dl_filter == nullptr, sk_filter == nullptr); @@ -305,9 +301,8 @@ TEST(DisplayListSkConversions, ToSkDitheringEnabledForGradients) { DlPaint dl_paint; // Set the paint to be a gradient. - dl_paint.setColorSource(DlColorSource::MakeLinear(SkPoint::Make(0, 0), - SkPoint::Make(100, 100), 0, - 0, 0, DlTileMode::kClamp)); + dl_paint.setColorSource(DlColorSource::MakeLinear( + DlPoint(0, 0), DlPoint(100, 100), 0, 0, 0, DlTileMode::kClamp)); { SkPaint sk_paint = ToSk(dl_paint); diff --git a/display_list/skia/dl_sk_dispatcher.cc b/display_list/skia/dl_sk_dispatcher.cc index c79542d01e1f9..0f9ffda98879a 100644 --- a/display_list/skia/dl_sk_dispatcher.cc +++ b/display_list/skia/dl_sk_dispatcher.cc @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/display_list/skia/dl_sk_dispatcher.h" #include +#include "flutter/display_list/skia/dl_sk_dispatcher.h" + #include "flutter/display_list/dl_blend_mode.h" +#include "flutter/display_list/effects/image_filters/dl_blur_image_filter.h" #include "flutter/display_list/skia/dl_sk_conversions.h" #include "flutter/display_list/skia/dl_sk_types.h" #include "flutter/fml/trace_event.h" diff --git a/display_list/skia/dl_sk_paint_dispatcher_unittests.cc b/display_list/skia/dl_sk_paint_dispatcher_unittests.cc index 808204dd33db2..f0bd925c1c5b2 100644 --- a/display_list/skia/dl_sk_paint_dispatcher_unittests.cc +++ b/display_list/skia/dl_sk_paint_dispatcher_unittests.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "display_list/effects/dl_color_source.h" +#include "flutter/display_list/effects/dl_color_source.h" #include "flutter/display_list/skia/dl_sk_paint_dispatcher.h" #include "flutter/display_list/skia/dl_sk_dispatcher.h" @@ -28,8 +28,8 @@ static const DlColor kTestColors[2] = {DlColor(0xFF000000), DlColor(0xFFFFFFFF)}; static const float kTestStops[2] = {0.0f, 1.0f}; static const auto kTestLinearGradient = - DlColorSource::MakeLinear(SkPoint::Make(0.0f, 0.0f), - SkPoint::Make(100.0f, 100.0f), + DlColorSource::MakeLinear(DlPoint(0.0f, 0.0f), + DlPoint(100.0f, 100.0f), 2, kTestColors, kTestStops, @@ -64,12 +64,7 @@ TEST(DisplayListUtils, SetColorSourceDoesNotDitherIfNotGradient) { EXPECT_FALSE(helper.paint(true).isDither()); EXPECT_FALSE(helper.paint(false).isDither()); - DlColorColorSource color_color_source(DlColor::kBlue()); - helper.setColorSource(&color_color_source); - EXPECT_FALSE(helper.paint(true).isDither()); - EXPECT_FALSE(helper.paint(false).isDither()); - - helper.setColorSource(&kTestSource1); + helper.setColorSource(kTestSource1.get()); EXPECT_FALSE(helper.paint(true).isDither()); EXPECT_FALSE(helper.paint(false).isDither()); } @@ -98,14 +93,7 @@ TEST(DisplayListUtils, SkDispatcherSetColorSourceDoesNotDitherIfNotGradient) { EXPECT_FALSE(dispatcher.safe_paint(true)->isDither()); // Calling safe_paint(false) returns a nullptr - DlColorColorSource color_color_source(DlColor::kBlue()); - dispatcher.setColorSource(&color_color_source); - EXPECT_FALSE(dispatcher.paint(true).isDither()); - EXPECT_FALSE(dispatcher.paint(false).isDither()); - EXPECT_FALSE(dispatcher.safe_paint(true)->isDither()); - // Calling safe_paint(false) returns a nullptr - - dispatcher.setColorSource(&kTestSource1); + dispatcher.setColorSource(kTestSource1.get()); EXPECT_FALSE(dispatcher.paint(true).isDither()); EXPECT_FALSE(dispatcher.paint(false).isDither()); EXPECT_FALSE(dispatcher.safe_paint(true)->isDither()); diff --git a/display_list/testing/BUILD.gn b/display_list/testing/BUILD.gn index f94799b9729af..fc48324c76e0c 100644 --- a/display_list/testing/BUILD.gn +++ b/display_list/testing/BUILD.gn @@ -86,6 +86,7 @@ source_set("display_list_surface_provider") { if (surface_provider_include_software) { sources += [ + "dl_test_surface_provider_software.cc", "dl_test_surface_software.cc", "dl_test_surface_software.h", ] @@ -95,14 +96,16 @@ source_set("display_list_surface_provider") { sources += [ "dl_test_surface_gl.cc", "dl_test_surface_gl.h", + "dl_test_surface_provider_gl.cc", ] deps += [ "//flutter/testing:opengl" ] } if (surface_provider_include_metal) { sources += [ - "dl_test_surface_metal.cc", "dl_test_surface_metal.h", + "dl_test_surface_metal.mm", + "dl_test_surface_provider_metal.mm", ] deps += [ "//flutter/impeller/display_list", diff --git a/display_list/testing/dl_rendering_unittests.cc b/display_list/testing/dl_rendering_unittests.cc index 2bca5d6b1ef44..bf752442fed9d 100644 --- a/display_list/testing/dl_rendering_unittests.cc +++ b/display_list/testing/dl_rendering_unittests.cc @@ -8,6 +8,8 @@ #include "flutter/display_list/dl_builder.h" #include "flutter/display_list/dl_op_flags.h" #include "flutter/display_list/dl_sampling_options.h" +#include "flutter/display_list/effects/color_filters/dl_matrix_color_filter.h" +#include "flutter/display_list/effects/dl_image_filter.h" #include "flutter/display_list/skia/dl_sk_canvas.h" #include "flutter/display_list/skia/dl_sk_conversions.h" #include "flutter/display_list/skia/dl_sk_dispatcher.h" @@ -861,7 +863,7 @@ class TestParameters { bool impeller_compatible(const DlPaint& paint) const { if (is_draw_text_blob()) { // Non-color text is rendered as paths - if (paint.getColorSourcePtr() && !paint.getColorSourcePtr()->asColor()) { + if (paint.getColorSourcePtr()) { return false; } // Non-filled text (stroke or stroke and fill) is rendered as paths @@ -1386,7 +1388,8 @@ class CanvasCompareTester { 0, 0, 0, 0.5, 0, }; // clang-format on - DlMatrixColorFilter dl_alpha_rotate_filter(rotate_alpha_color_matrix); + auto dl_alpha_rotate_filter = + DlColorFilter::MakeMatrix(rotate_alpha_color_matrix); auto sk_alpha_rotate_filter = SkColorFilters::Matrix(rotate_alpha_color_matrix); { @@ -1401,7 +1404,7 @@ class CanvasCompareTester { }, [=](const DlSetupContext& ctx) { DlPaint save_p; - save_p.setColorFilter(&dl_alpha_rotate_filter); + save_p.setColorFilter(dl_alpha_rotate_filter); ctx.canvas->SaveLayer(nullptr, &save_p); ctx.paint.setStrokeWidth(5.0); }) @@ -1419,7 +1422,7 @@ class CanvasCompareTester { }, [=](const DlSetupContext& ctx) { DlPaint save_p; - save_p.setColorFilter(&dl_alpha_rotate_filter); + save_p.setColorFilter(dl_alpha_rotate_filter); ctx.canvas->SaveLayer(&kRenderBounds, &save_p); ctx.paint.setStrokeWidth(5.0); }) @@ -1436,8 +1439,8 @@ class CanvasCompareTester { 0, 0, 0, 1, 0, }; // clang-format on - DlMatrixColorFilter dl_color_filter(color_matrix); - DlColorFilterImageFilter dl_cf_image_filter(dl_color_filter); + auto dl_color_filter = DlColorFilter::MakeMatrix(color_matrix); + auto dl_cf_image_filter = DlImageFilter::MakeColorFilter(dl_color_filter); auto sk_cf_image_filter = SkImageFilters::ColorFilter( SkColorFilters::Matrix(color_matrix), nullptr); { @@ -1452,7 +1455,7 @@ class CanvasCompareTester { }, [=](const DlSetupContext& ctx) { DlPaint save_p; - save_p.setImageFilter(&dl_cf_image_filter); + save_p.setImageFilter(dl_cf_image_filter); ctx.canvas->SaveLayer(nullptr, &save_p); ctx.paint.setStrokeWidth(5.0); }) @@ -1470,7 +1473,7 @@ class CanvasCompareTester { }, [=](const DlSetupContext& ctx) { DlPaint save_p; - save_p.setImageFilter(&dl_cf_image_filter); + save_p.setImageFilter(dl_cf_image_filter); ctx.canvas->SaveLayer(&kRenderBounds, &save_p); ctx.paint.setStrokeWidth(5.0); }) @@ -1708,7 +1711,7 @@ class CanvasCompareTester { 1.0, 1.0, 1.0, 1.0, 0, }; // clang-format on - DlMatrixColorFilter dl_color_filter(rotate_color_matrix); + auto dl_color_filter = DlColorFilter::MakeMatrix(rotate_color_matrix); auto sk_color_filter = SkColorFilters::Matrix(rotate_color_matrix); { DlColor bg = DlColor::kWhite(); @@ -1721,7 +1724,7 @@ class CanvasCompareTester { }, [=](const DlSetupContext& ctx) { ctx.paint.setColor(DlColor::kYellow()); - ctx.paint.setColorFilter(&dl_color_filter); + ctx.paint.setColorFilter(dl_color_filter); }) .with_bg(bg)); } @@ -1764,10 +1767,14 @@ class CanvasCompareTester { } { - SkPoint end_points[] = { + SkPoint sk_end_points[] = { SkPoint::Make(kRenderBounds.fLeft, kRenderBounds.fTop), SkPoint::Make(kRenderBounds.fRight, kRenderBounds.fBottom), }; + DlPoint dl_end_points[] = { + DlPoint(kRenderBounds.fLeft, kRenderBounds.fTop), + DlPoint(kRenderBounds.fRight, kRenderBounds.fBottom), + }; DlColor dl_colors[] = { DlColor::kGreen(), DlColor::kYellow().withAlpha(0x7f), @@ -1784,10 +1791,10 @@ class CanvasCompareTester { 1.0, }; auto dl_gradient = - DlColorSource::MakeLinear(end_points[0], end_points[1], 3, dl_colors, - stops, DlTileMode::kMirror); + DlColorSource::MakeLinear(dl_end_points[0], dl_end_points[1], 3, + dl_colors, stops, DlTileMode::kMirror); auto sk_gradient = SkGradientShader::MakeLinear( - end_points, sk_colors, stops, 3, SkTileMode::kMirror, 0, nullptr); + sk_end_points, sk_colors, stops, 3, SkTileMode::kMirror, 0, nullptr); { RenderWith(testP, env, tolerance, CaseParameters( @@ -3897,7 +3904,7 @@ TEST_F(DisplayListRendering, SaveLayerClippedContentStillFilters) { }, [=](const DlRenderContext& ctx) { auto layer_filter = - DlBlurImageFilter::Make(10.0f, 10.0f, DlTileMode::kDecal); + DlImageFilter::MakeBlur(10.0f, 10.0f, DlTileMode::kDecal); DlPaint layer_paint; layer_paint.setImageFilter(layer_filter); ctx.canvas->Save(); @@ -3939,28 +3946,28 @@ TEST_F(DisplayListRendering, SaveLayerConsolidation) { 0, 0, 0, .7, 0, // clang-format on }; - SkMatrix contract_matrix; - contract_matrix.setScale(0.9f, 0.9f, kRenderCenterX, kRenderCenterY); + DlMatrix contract_matrix; + contract_matrix.Translate({kRenderCenterX, kRenderCenterY}); + contract_matrix.Scale({0.9f, 0.9f}); + contract_matrix.Translate({kRenderCenterX, kRenderCenterY}); std::vector opacities = { 0, 0.5f, SK_Scalar1, }; - std::vector> color_filters = { - std::make_shared(DlColor::kCyan(), - DlBlendMode::kSrcATop), - std::make_shared(commutable_color_matrix), - std::make_shared(non_commutable_color_matrix), - DlSrgbToLinearGammaColorFilter::kInstance, - DlLinearToSrgbGammaColorFilter::kInstance, + std::vector> color_filters = { + DlColorFilter::MakeBlend(DlColor::kCyan(), DlBlendMode::kSrcATop), + DlColorFilter::MakeMatrix(commutable_color_matrix), + DlColorFilter::MakeMatrix(non_commutable_color_matrix), + DlColorFilter::MakeSrgbToLinearGamma(), + DlColorFilter::MakeLinearToSrgbGamma(), }; std::vector> image_filters = { - std::make_shared(5.0f, 5.0f, DlTileMode::kDecal), - std::make_shared(5.0f, 5.0f), - std::make_shared(5.0f, 5.0f), - std::make_shared(contract_matrix, - DlImageSampling::kLinear), + DlImageFilter::MakeBlur(5.0f, 5.0f, DlTileMode::kDecal), + DlImageFilter::MakeDilate(5.0f, 5.0f), + DlImageFilter::MakeErode(5.0f, 5.0f), + DlImageFilter::MakeMatrix(contract_matrix, DlImageSampling::kLinear), }; auto render_content = [](DisplayListBuilder& builder) { @@ -4125,8 +4132,13 @@ TEST_F(DisplayListRendering, MatrixColorFilterModifyTransparencyCheck) { "matrix[" + std::to_string(element) + "] = " + std::to_string(value); float original_value = matrix[element]; matrix[element] = value; + // Here we instantiate a DlMatrixColorFilter directly so that it is + // not affected by the "NOP" detection in the factory. We sould not + // need to do this if we tested by just rendering the filter color + // over the source color with the filter blend mode instead of + // rendering via a ColorFilter, but this test is more "black box". DlMatrixColorFilter filter(matrix); - auto dl_filter = DlMatrixColorFilter::Make(matrix); + auto dl_filter = DlColorFilter::MakeMatrix(matrix); bool is_identity = (dl_filter == nullptr || original_value == value); DlPaint paint(DlColor(0x7f7f7f7f)); @@ -4194,7 +4206,7 @@ TEST_F(DisplayListRendering, MatrixColorFilterOpacityCommuteCheck) { std::string desc = "matrix[" + std::to_string(element) + "] = " + std::to_string(value); matrix[element] = value; - auto filter = DlMatrixColorFilter::Make(matrix); + auto filter = DlColorFilter::MakeMatrix(matrix); EXPECT_EQ(std::isfinite(value), filter != nullptr); DlPaint paint(DlColor(0x80808080)); @@ -4302,7 +4314,7 @@ TEST_F(DisplayListRendering, BlendColorFilterModifyTransparencyCheck) { std::string desc = desc_str.str(); DlBlendColorFilter filter(color, mode); if (filter.modifies_transparent_black()) { - ASSERT_NE(DlBlendColorFilter::Make(color, mode), nullptr) << desc; + ASSERT_NE(DlColorFilter::MakeBlend(color, mode), nullptr) << desc; } DlPaint paint(DlColor(0x7f7f7f7f)); @@ -4363,7 +4375,7 @@ TEST_F(DisplayListRendering, BlendColorFilterOpacityCommuteCheck) { // If it can commute with opacity, then it might also be a NOP, // so we won't necessarily get a non-null return from |::Make()| } else { - ASSERT_NE(DlBlendColorFilter::Make(color, mode), nullptr) << desc; + ASSERT_NE(DlColorFilter::MakeBlend(color, mode), nullptr) << desc; } DlPaint paint(DlColor(0x80808080)); @@ -4463,8 +4475,8 @@ class DisplayListNopTest : public DisplayListRendering { 0.0001, 0.0001, 0.0001, 0.9997, 0.0, // 0.0001, 0.0001, 0.0001, 0.9997, 0.1, // }; - color_filter_nomtb = DlMatrixColorFilter::Make(color_filter_matrix_nomtb); - color_filter_mtb = DlMatrixColorFilter::Make(color_filter_matrix_mtb); + color_filter_nomtb = DlColorFilter::MakeMatrix(color_filter_matrix_nomtb); + color_filter_mtb = DlColorFilter::MakeMatrix(color_filter_matrix_mtb); EXPECT_FALSE(color_filter_nomtb->modifies_transparent_black()); EXPECT_TRUE(color_filter_mtb->modifies_transparent_black()); @@ -4520,8 +4532,8 @@ class DisplayListNopTest : public DisplayListRendering { std::vector test_src_colors; std::vector test_dst_colors; - std::shared_ptr color_filter_nomtb; - std::shared_ptr color_filter_mtb; + std::shared_ptr color_filter_nomtb; + std::shared_ptr color_filter_mtb; // A 1-row image containing every color in test_dst_colors std::unique_ptr test_data; @@ -4603,7 +4615,7 @@ class DisplayListNopTest : public DisplayListRendering { desc_stream << BlendModeToString(mode); desc_stream << "/" << color; std::string desc = desc_stream.str(); - DisplayListBuilder builder({0.0f, 0.0f, 100.0f, 100.0f}); + DisplayListBuilder builder(DlRect::MakeWH(100.0f, 100.0f)); DlPaint paint = DlPaint(color).setBlendMode(mode); builder.DrawRect(SkRect{0.0f, 0.0f, 10.0f, 10.0f}, paint); auto dl = builder.Build(); @@ -4676,7 +4688,7 @@ class DisplayListNopTest : public DisplayListRendering { void test_attributes_image(DlBlendMode mode, DlColor color, - DlColorFilter* color_filter, + const DlColorFilter* color_filter, DlImageFilter* image_filter) { // if (true) { return; } std::stringstream desc_stream; @@ -4697,7 +4709,7 @@ class DisplayListNopTest : public DisplayListRendering { desc_stream << ", IF: " << if_mtb; std::string desc = desc_stream.str(); - DisplayListBuilder builder({0.0f, 0.0f, 100.0f, 100.0f}); + DisplayListBuilder builder(DlRect::MakeWH(100.0f, 100.0f)); DlPaint paint = DlPaint(color) // .setBlendMode(mode) // .setColorFilter(color_filter) // diff --git a/display_list/testing/dl_test_equality.h b/display_list/testing/dl_test_equality.h index 84dcaff5e2814..5fd8c6b370059 100644 --- a/display_list/testing/dl_test_equality.h +++ b/display_list/testing/dl_test_equality.h @@ -12,8 +12,8 @@ namespace flutter { namespace testing { -template -static void TestEquals(T& source1, T& source2) { +template +static void TestEquals(const T& source1, const U& source2) { ASSERT_TRUE(source1 == source2); ASSERT_TRUE(source2 == source1); ASSERT_FALSE(source1 != source2); @@ -24,8 +24,8 @@ static void TestEquals(T& source1, T& source2) { ASSERT_TRUE(Equals(&source2, &source1)); } -template -static void TestNotEquals(T& source1, T& source2, const std::string& label) { +template +static void TestNotEquals(T& source1, U& source2, const std::string& label) { ASSERT_FALSE(source1 == source2) << label; ASSERT_FALSE(source2 == source1) << label; ASSERT_TRUE(source1 != source2) << label; diff --git a/display_list/testing/dl_test_snippets.cc b/display_list/testing/dl_test_snippets.cc index 3ce6e7ce1b11b..7e23751f756fc 100644 --- a/display_list/testing/dl_test_snippets.cc +++ b/display_list/testing/dl_test_snippets.cc @@ -136,14 +136,15 @@ std::vector CreateAllAttributesOps() { }}, {"SetColorSource", { - {0, 96, 0, [](DlOpReceiver& r) { r.setColorSource(&kTestSource1); }}, - {0, 152, 0, + {0, 104, 0, + [](DlOpReceiver& r) { r.setColorSource(kTestSource1.get()); }}, + {0, 176, 0, [](DlOpReceiver& r) { r.setColorSource(kTestSource2.get()); }}, - {0, 152, 0, + {0, 176, 0, [](DlOpReceiver& r) { r.setColorSource(kTestSource3.get()); }}, - {0, 160, 0, + {0, 184, 0, [](DlOpReceiver& r) { r.setColorSource(kTestSource4.get()); }}, - {0, 152, 0, + {0, 176, 0, [](DlOpReceiver& r) { r.setColorSource(kTestSource5.get()); }}, // Reset attribute to default as last entry @@ -177,15 +178,15 @@ std::vector CreateAllAttributesOps() { [](DlOpReceiver& r) { r.setImageFilter(&kTestErodeImageFilter2); }}, {0, 24, 0, [](DlOpReceiver& r) { r.setImageFilter(&kTestErodeImageFilter3); }}, - {0, 64, 0, + {0, 88, 0, [](DlOpReceiver& r) { r.setImageFilter(&kTestMatrixImageFilter1); }}, - {0, 64, 0, + {0, 88, 0, [](DlOpReceiver& r) { r.setImageFilter(&kTestMatrixImageFilter2); }}, - {0, 64, 0, + {0, 88, 0, [](DlOpReceiver& r) { r.setImageFilter(&kTestMatrixImageFilter3); }}, @@ -209,7 +210,7 @@ std::vector CreateAllAttributesOps() { [](DlOpReceiver& r) { r.setImageFilter( kTestBlurImageFilter1 - .makeWithLocalMatrix(SkMatrix::Translate(2, 2)) + .makeWithLocalMatrix(DlMatrix::MakeTranslation({2, 2})) .get()); }}, @@ -219,26 +220,32 @@ std::vector CreateAllAttributesOps() { {"SetColorFilter", { {0, 40, 0, - [](DlOpReceiver& r) { r.setColorFilter(&kTestBlendColorFilter1); }}, + [](DlOpReceiver& r) { + r.setColorFilter(kTestBlendColorFilter1.get()); + }}, {0, 40, 0, - [](DlOpReceiver& r) { r.setColorFilter(&kTestBlendColorFilter2); }}, + [](DlOpReceiver& r) { + r.setColorFilter(kTestBlendColorFilter2.get()); + }}, {0, 40, 0, - [](DlOpReceiver& r) { r.setColorFilter(&kTestBlendColorFilter3); }}, + [](DlOpReceiver& r) { + r.setColorFilter(kTestBlendColorFilter3.get()); + }}, {0, 96, 0, [](DlOpReceiver& r) { - r.setColorFilter(&kTestMatrixColorFilter1); + r.setColorFilter(kTestMatrixColorFilter1.get()); }}, {0, 96, 0, [](DlOpReceiver& r) { - r.setColorFilter(&kTestMatrixColorFilter2); + r.setColorFilter(kTestMatrixColorFilter2.get()); }}, {0, 16, 0, [](DlOpReceiver& r) { - r.setColorFilter(DlSrgbToLinearGammaColorFilter::kInstance.get()); + r.setColorFilter(DlColorFilter::MakeSrgbToLinearGamma().get()); }}, {0, 16, 0, [](DlOpReceiver& r) { - r.setColorFilter(DlLinearToSrgbGammaColorFilter::kInstance.get()); + r.setColorFilter(DlColorFilter::MakeLinearToSrgbGamma().get()); }}, // Reset attribute to default as last entry diff --git a/display_list/testing/dl_test_snippets.h b/display_list/testing/dl_test_snippets.h index 055d7725e3eb5..08e08e4c64056 100644 --- a/display_list/testing/dl_test_snippets.h +++ b/display_list/testing/dl_test_snippets.h @@ -7,6 +7,9 @@ #include "flutter/display_list/display_list.h" #include "flutter/display_list/dl_builder.h" +#include "flutter/display_list/effects/color_filters/dl_blend_color_filter.h" +#include "flutter/display_list/effects/dl_color_sources.h" +#include "flutter/display_list/effects/dl_image_filters.h" #include "flutter/testing/testing.h" #include "third_party/skia/include/core/SkCanvas.h" @@ -23,7 +26,7 @@ sk_sp GetSampleNestedDisplayList(); typedef const std::function DlInvoker; -constexpr SkPoint kEndPoints[] = { +constexpr DlPoint kEndPoints[] = { {0, 0}, {100, 100}, }; @@ -88,10 +91,11 @@ static auto TestImage1 = MakeTestImage(40, 40, 5); static auto TestImage2 = MakeTestImage(50, 50, 5); static auto TestSkImage = MakeTestImage(30, 30, 5)->skia_image(); -static const DlImageColorSource kTestSource1(TestImage1, - DlTileMode::kClamp, - DlTileMode::kMirror, - kLinearSampling); +static const std::shared_ptr kTestSource1 = + DlColorSource::MakeImage(TestImage1, + DlTileMode::kClamp, + DlTileMode::kMirror, + kLinearSampling); static const std::shared_ptr kTestSource2 = DlColorSource::MakeLinear(kEndPoints[0], kEndPoints[1], @@ -123,14 +127,18 @@ static const std::shared_ptr kTestSource5 = kColors, kStops, DlTileMode::kDecal); -static const DlBlendColorFilter kTestBlendColorFilter1(DlColor::kRed(), - DlBlendMode::kDstATop); -static const DlBlendColorFilter kTestBlendColorFilter2(DlColor::kBlue(), - DlBlendMode::kDstATop); -static const DlBlendColorFilter kTestBlendColorFilter3(DlColor::kRed(), - DlBlendMode::kDstIn); -static const DlMatrixColorFilter kTestMatrixColorFilter1(kRotateColorMatrix); -static const DlMatrixColorFilter kTestMatrixColorFilter2(kInvertColorMatrix); + +static const auto kTestBlendColorFilter1 = + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kDstATop); +static const auto kTestBlendColorFilter2 = + DlColorFilter::MakeBlend(DlColor::kBlue(), DlBlendMode::kDstATop); +static const auto kTestBlendColorFilter3 = + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kDstOver); +static const auto kTestMatrixColorFilter1 = + DlColorFilter::MakeMatrix(kRotateColorMatrix); +static const auto kTestMatrixColorFilter2 = + DlColorFilter::MakeMatrix(kInvertColorMatrix); + static const DlBlurImageFilter kTestBlurImageFilter1(5.0, 5.0, DlTileMode::kClamp); @@ -150,13 +158,13 @@ static const DlErodeImageFilter kTestErodeImageFilter1(4.0, 4.0); static const DlErodeImageFilter kTestErodeImageFilter2(4.0, 3.0); static const DlErodeImageFilter kTestErodeImageFilter3(3.0, 4.0); static const DlMatrixImageFilter kTestMatrixImageFilter1( - SkMatrix::RotateDeg(45), + DlMatrix::MakeRotationZ(DlDegrees(45)), kNearestSampling); static const DlMatrixImageFilter kTestMatrixImageFilter2( - SkMatrix::RotateDeg(85), + DlMatrix::MakeRotationZ(DlDegrees(85)), kNearestSampling); static const DlMatrixImageFilter kTestMatrixImageFilter3( - SkMatrix::RotateDeg(45), + DlMatrix::MakeRotationZ(DlDegrees(45)), kLinearSampling); static const DlComposeImageFilter kTestComposeImageFilter1( kTestBlurImageFilter1, diff --git a/display_list/testing/dl_test_surface_metal.cc b/display_list/testing/dl_test_surface_metal.mm similarity index 65% rename from display_list/testing/dl_test_surface_metal.cc rename to display_list/testing/dl_test_surface_metal.mm index 90ac9b7eb5637..d90e5e9f0ae67 100644 --- a/display_list/testing/dl_test_surface_metal.cc +++ b/display_list/testing/dl_test_surface_metal.mm @@ -15,22 +15,17 @@ namespace testing { class DlMetalSurfaceInstance : public DlSurfaceInstance { public: - explicit DlMetalSurfaceInstance( - std::unique_ptr metal_surface) + explicit DlMetalSurfaceInstance(std::unique_ptr metal_surface) : metal_surface_(std::move(metal_surface)) {} ~DlMetalSurfaceInstance() = default; - sk_sp sk_surface() const override { - return metal_surface_->GetSurface(); - } + sk_sp sk_surface() const override { return metal_surface_->GetSurface(); } private: std::unique_ptr metal_surface_; }; -bool DlMetalSurfaceProvider::InitializeSurface(size_t width, - size_t height, - PixelFormat format) { +bool DlMetalSurfaceProvider::InitializeSurface(size_t width, size_t height, PixelFormat format) { if (format != kN32PremulPixelFormat) { return false; } @@ -39,8 +34,7 @@ bool DlMetalSurfaceProvider::InitializeSurface(size_t width, return true; } -std::shared_ptr DlMetalSurfaceProvider::GetPrimarySurface() - const { +std::shared_ptr DlMetalSurfaceProvider::GetPrimarySurface() const { if (!metal_surface_) { return nullptr; } @@ -51,16 +45,14 @@ std::shared_ptr DlMetalSurfaceProvider::MakeOffscreenSurface( size_t width, size_t height, PixelFormat format) const { - auto surface = - TestMetalSurface::Create(*metal_context_, SkISize::Make(width, height)); + auto surface = TestMetalSurface::Create(*metal_context_, SkISize::Make(width, height)); surface->GetSurface()->getCanvas()->clear(SK_ColorTRANSPARENT); return std::make_shared(std::move(surface)); } class DlMetalPixelData : public DlPixelData { public: - explicit DlMetalPixelData( - std::unique_ptr screenshot) + explicit DlMetalPixelData(std::unique_ptr screenshot) : screenshot_(std::move(screenshot)), addr_(reinterpret_cast(screenshot_->GetBytes())), ints_per_row_(screenshot_->GetBytesPerRow() / 4) { @@ -68,14 +60,10 @@ class DlMetalPixelData : public DlPixelData { } ~DlMetalPixelData() override = default; - const uint32_t* addr32(int x, int y) const override { - return addr_ + (y * ints_per_row_) + x; - } + const uint32_t* addr32(int x, int y) const override { return addr_ + (y * ints_per_row_) + x; } size_t width() const override { return screenshot_->GetWidth(); } size_t height() const override { return screenshot_->GetHeight(); } - void write(const std::string& path) const override { - screenshot_->WriteToPNG(path); - } + void write(const std::string& path) const override { screenshot_->WriteToPNG(path); } private: std::unique_ptr screenshot_; @@ -83,19 +71,16 @@ class DlMetalPixelData : public DlPixelData { const uint32_t ints_per_row_; }; -sk_sp DlMetalSurfaceProvider::ImpellerSnapshot( - const sk_sp& list, - int width, - int height) const { +sk_sp DlMetalSurfaceProvider::ImpellerSnapshot(const sk_sp& list, + int width, + int height) const { auto texture = DisplayListToTexture(list, {width, height}, *aiks_context_); - return sk_make_sp( - snapshotter_->MakeScreenshot(*aiks_context_, texture)); + return sk_make_sp(snapshotter_->MakeScreenshot(*aiks_context_, texture)); } -sk_sp DlMetalSurfaceProvider::MakeImpellerImage( - const sk_sp& list, - int width, - int height) const { +sk_sp DlMetalSurfaceProvider::MakeImpellerImage(const sk_sp& list, + int width, + int height) const { InitScreenShotter(); return impeller::DlImageImpeller::Make( DisplayListToTexture(list, {width, height}, *aiks_context_)); @@ -105,8 +90,8 @@ void DlMetalSurfaceProvider::InitScreenShotter() const { if (!snapshotter_) { snapshotter_.reset(new MetalScreenshotter(/*enable_wide_gamut=*/false)); auto typographer = impeller::TypographerContextSkia::Make(); - aiks_context_.reset(new impeller::AiksContext( - snapshotter_->GetPlayground().GetContext(), typographer)); + aiks_context_.reset( + new impeller::AiksContext(snapshotter_->GetPlayground().GetContext(), typographer)); } } diff --git a/display_list/testing/dl_test_surface_provider.cc b/display_list/testing/dl_test_surface_provider.cc index ab7d75c0901e7..545b3cfa0de1f 100644 --- a/display_list/testing/dl_test_surface_provider.cc +++ b/display_list/testing/dl_test_surface_provider.cc @@ -10,50 +10,29 @@ #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/encode/SkPngEncoder.h" -#ifdef ENABLE_SOFTWARE_BENCHMARKS -#include "flutter/display_list/testing/dl_test_surface_software.h" -#endif -#ifdef ENABLE_OPENGL_BENCHMARKS -#include "flutter/display_list/testing/dl_test_surface_gl.h" -#endif -#ifdef ENABLE_METAL_BENCHMARKS -#include "flutter/display_list/testing/dl_test_surface_metal.h" -#endif - -namespace flutter { -namespace testing { +namespace flutter::testing { std::string DlSurfaceProvider::BackendName(BackendType type) { switch (type) { - case kMetalBackend: - return "Metal"; - case kOpenGlBackend: - return "OpenGL"; case kSoftwareBackend: return "Software"; + case kOpenGlBackend: + return "OpenGL"; + case kMetalBackend: + return "Metal"; } } std::unique_ptr DlSurfaceProvider::Create( BackendType backend_type) { switch (backend_type) { -#ifdef ENABLE_SOFTWARE_BENCHMARKS case kSoftwareBackend: - return std::make_unique(); -#endif -#ifdef ENABLE_OPENGL_BENCHMARKS - case kOpenGLBackend: - return std::make_unique(); -#endif -#ifdef ENABLE_METAL_BENCHMARKS + return CreateSoftware(); + case kOpenGlBackend: + return CreateOpenGL(); case kMetalBackend: - return std::make_unique(); -#endif - default: - return nullptr; + return CreateMetal(); } - - return nullptr; } bool DlSurfaceProvider::Snapshot(std::string& filename) const { @@ -78,5 +57,20 @@ bool DlSurfaceProvider::Snapshot(std::string& filename) const { #endif } -} // namespace testing -} // namespace flutter +#ifndef ENABLE_SOFTWARE_BENCHMARKS +std::unique_ptr DlSurfaceProvider::CreateSoftware() { + return nullptr; +} +#endif +#ifndef ENABLE_OPENGL_BENCHMARKS +std::unique_ptr DlSurfaceProvider::CreateOpenGL() { + return nullptr; +} +#endif +#ifndef ENABLE_METAL_BENCHMARKS +std::unique_ptr DlSurfaceProvider::CreateMetal() { + return nullptr; +} +#endif + +} // namespace flutter::testing diff --git a/display_list/testing/dl_test_surface_provider.h b/display_list/testing/dl_test_surface_provider.h index 1ad4af789cb43..5bfc710502391 100644 --- a/display_list/testing/dl_test_surface_provider.h +++ b/display_list/testing/dl_test_surface_provider.h @@ -97,6 +97,11 @@ class DlSurfaceProvider { protected: DlSurfaceProvider() = default; + + private: + static std::unique_ptr CreateSoftware(); + static std::unique_ptr CreateMetal(); + static std::unique_ptr CreateOpenGL(); }; } // namespace testing diff --git a/display_list/testing/dl_test_surface_provider_gl.cc b/display_list/testing/dl_test_surface_provider_gl.cc new file mode 100644 index 0000000000000..0d258d0c6b29b --- /dev/null +++ b/display_list/testing/dl_test_surface_provider_gl.cc @@ -0,0 +1,15 @@ +// 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. + +#include "flutter/display_list/testing/dl_test_surface_provider.h" + +#include "flutter/display_list/testing/dl_test_surface_gl.h" + +namespace flutter::testing { + +std::unique_ptr DlSurfaceProvider::CreateOpenGL() { + return std::make_unique(); +} + +} // namespace flutter::testing diff --git a/display_list/testing/dl_test_surface_provider_metal.mm b/display_list/testing/dl_test_surface_provider_metal.mm new file mode 100644 index 0000000000000..95de93dee08a5 --- /dev/null +++ b/display_list/testing/dl_test_surface_provider_metal.mm @@ -0,0 +1,15 @@ +// 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. + +#include "flutter/display_list/testing/dl_test_surface_provider.h" + +#include "flutter/display_list/testing/dl_test_surface_metal.h" + +namespace flutter::testing { + +std::unique_ptr DlSurfaceProvider::CreateMetal() { + return std::make_unique(); +} + +} // namespace flutter::testing diff --git a/display_list/testing/dl_test_surface_provider_software.cc b/display_list/testing/dl_test_surface_provider_software.cc new file mode 100644 index 0000000000000..6da75c41ab845 --- /dev/null +++ b/display_list/testing/dl_test_surface_provider_software.cc @@ -0,0 +1,15 @@ +// 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. + +#include "flutter/display_list/testing/dl_test_surface_provider.h" + +#include "flutter/display_list/testing/dl_test_surface_software.h" + +namespace flutter::testing { + +std::unique_ptr DlSurfaceProvider::CreateSoftware() { + return std::make_unique(); +} + +} // namespace flutter::testing diff --git a/display_list/utils/dl_comparable.h b/display_list/utils/dl_comparable.h index 6273bc703f70f..008da8d9942c3 100644 --- a/display_list/utils/dl_comparable.h +++ b/display_list/utils/dl_comparable.h @@ -15,8 +15,8 @@ namespace flutter { // Any combination of shared_ptr or T* are supported and null pointers // are not equal to anything but another null pointer. -template -bool Equals(const T* a, const T* b) { +template +bool Equals(const T* a, const U* b) { if (a == b) { return true; } @@ -26,88 +26,88 @@ bool Equals(const T* a, const T* b) { return *a == *b; } -template -bool Equals(std::shared_ptr a, const T* b) { +template +bool Equals(std::shared_ptr a, const U* b) { return Equals(a.get(), b); } -template -bool Equals(std::shared_ptr a, const T* b) { +template +bool Equals(std::shared_ptr a, const U* b) { return Equals(a.get(), b); } -template -bool Equals(const T* a, std::shared_ptr b) { +template +bool Equals(const T* a, std::shared_ptr b) { return Equals(a, b.get()); } -template -bool Equals(const T* a, std::shared_ptr b) { +template +bool Equals(const T* a, std::shared_ptr b) { return Equals(a, b.get()); } -template -bool Equals(std::shared_ptr a, std::shared_ptr b) { +template +bool Equals(std::shared_ptr a, std::shared_ptr b) { return Equals(a.get(), b.get()); } -template -bool Equals(std::shared_ptr a, std::shared_ptr b) { +template +bool Equals(std::shared_ptr a, std::shared_ptr b) { return Equals(a.get(), b.get()); } -template -bool Equals(std::shared_ptr a, std::shared_ptr b) { +template +bool Equals(std::shared_ptr a, std::shared_ptr b) { return Equals(a.get(), b.get()); } -template -bool Equals(std::shared_ptr a, std::shared_ptr b) { +template +bool Equals(std::shared_ptr a, std::shared_ptr b) { return Equals(a.get(), b.get()); } -template -bool NotEquals(const T* a, const T* b) { - return !Equals(a, b); +template +bool NotEquals(const T* a, const U* b) { + return !Equals(a, b); } -template -bool NotEquals(std::shared_ptr a, const T* b) { +template +bool NotEquals(std::shared_ptr a, const U* b) { return !Equals(a.get(), b); } -template -bool NotEquals(std::shared_ptr a, const T* b) { +template +bool NotEquals(std::shared_ptr a, const U* b) { return !Equals(a.get(), b); } -template -bool NotEquals(const T* a, std::shared_ptr b) { +template +bool NotEquals(const T* a, std::shared_ptr b) { return !Equals(a, b.get()); } -template -bool NotEquals(const T* a, std::shared_ptr b) { +template +bool NotEquals(const T* a, std::shared_ptr b) { return !Equals(a, b.get()); } -template -bool NotEquals(std::shared_ptr a, std::shared_ptr b) { +template +bool NotEquals(std::shared_ptr a, std::shared_ptr b) { return !Equals(a.get(), b.get()); } -template -bool NotEquals(std::shared_ptr a, std::shared_ptr b) { +template +bool NotEquals(std::shared_ptr a, std::shared_ptr b) { return !Equals(a.get(), b.get()); } -template -bool NotEquals(std::shared_ptr a, std::shared_ptr b) { +template +bool NotEquals(std::shared_ptr a, std::shared_ptr b) { return !Equals(a.get(), b.get()); } -template -bool NotEquals(std::shared_ptr a, std::shared_ptr b) { +template +bool NotEquals(std::shared_ptr a, std::shared_ptr b) { return !Equals(a.get(), b.get()); } diff --git a/display_list/utils/dl_matrix_clip_tracker.cc b/display_list/utils/dl_matrix_clip_tracker.cc index 76d8fd4e64d95..a7f4c316190f7 100644 --- a/display_list/utils/dl_matrix_clip_tracker.cc +++ b/display_list/utils/dl_matrix_clip_tracker.cc @@ -241,7 +241,7 @@ DlRect DisplayListMatrixClipState::GetLocalCullCoverage() const { // We could do a 4-point long-form conversion, but since this is // only used for culling, let's just return a non-constricting // cull rect. - return ToDlRect(DisplayListBuilder::kMaxCullRect); + return DisplayListBuilder::kMaxCullRect; } DlMatrix inverse = matrix_.Invert(); // We eliminated perspective above so we can use the cheaper non-clipping diff --git a/display_list/utils/dl_matrix_clip_tracker.h b/display_list/utils/dl_matrix_clip_tracker.h index 344a54bd57ff8..1bcf67b6978d9 100644 --- a/display_list/utils/dl_matrix_clip_tracker.h +++ b/display_list/utils/dl_matrix_clip_tracker.h @@ -53,7 +53,7 @@ class DisplayListMatrixClipState { } bool using_4x4_matrix() const { return !matrix_.IsAffine(); } - bool is_matrix_invertable() const { return matrix_.GetDeterminant() != 0.0f; } + bool is_matrix_invertable() const { return matrix_.IsInvertible(); } bool has_perspective() const { return matrix_.HasPerspective(); } const DlMatrix& matrix() const { return matrix_; } diff --git a/docs/JIT-Release-Modes.md b/docs/JIT-Release-Modes.md deleted file mode 100644 index a6b991b7774a8..0000000000000 --- a/docs/JIT-Release-Modes.md +++ /dev/null @@ -1,15 +0,0 @@ -Normally Flutter runs in JIT for faster compilation/debugging support in `debug` mode and AOT mode for better performance in `profile` and `release` mode. For platforms that Flutter cannot produce AOT artifacts for, such as Android x86 (32 bit), a JIT release build may be used instead. The advantage of this mode over a regular debug build is that it removes debugging support and disables assertions which makes the final artifact smaller and more performant, though less so than a full AOT build. - - -JIT release mode can be used with a local engine configuration. For example, to setup an Android x86 jit_release and host build you can use the GN command below. Both device and host artifacts need to be built, except in cases where they are the same such as the Desktop shells. - -```shell -./flutter/tools/gn --runtime-mode=jit_release --android --android-cpu=x86 -ninja -C out/android_jit_release_x86 -./flutter/tools/gn --runtime-mode=jit_release -ninja -C out/host_jit_release -``` - -This can be used with the flutter tool [via the `--local-engine`](Debugging-the-engine.md#running-a-flutter-app-with-a-local-engine) flag to produce a bundle containing the jit release artifacts using the `flutter assemble` command. By default, flutter.gradle does not know how to package this artifacts so it requires custom integration into a build pipeline. Nevertheless, the artifact structure should be identical to a debug build, but with asserts disabled and product mode enabled. - -jit_release is not supported on iOS devices. Applications built in JIT mode cannot be distributed on the Apple App Store. \ No newline at end of file diff --git a/docs/ci/Engine-Clang-Tidy-Linter.md b/docs/ci/Engine-Clang-Tidy-Linter.md index 05879f82181e6..e3d8e9fb327ec 100644 --- a/docs/ci/Engine-Clang-Tidy-Linter.md +++ b/docs/ci/Engine-Clang-Tidy-Linter.md @@ -6,7 +6,7 @@ In May 2020, [`clang-tidy`](https://clang.llvm.org/extra/clang-tidy/) was added If a file has `// FLUTTER_NOLINT` at the top, it has issues with the lint that haven't been addressed and the linter will ignore it. As the issues are fixed the comments should be removed. -You can run the linter locally by running `flutter/ci/lint.sh`. +You can run the linter locally by running `flutter/ci/clang_tidy.sh`. ## CI background information diff --git a/docs/monorepo/engine_binary_hashing.md b/docs/monorepo/engine_binary_hashing.md index add5cf9463954..4b764bac0a631 100644 --- a/docs/monorepo/engine_binary_hashing.md +++ b/docs/monorepo/engine_binary_hashing.md @@ -61,7 +61,7 @@ git ls-tree -r HEAD engine DEPS | git hash-object --stdin When developing a pull request (PR), your branch might contain multiple commits. To enable A/B testing against the engine version at the time of branching, we can modify the hash calculation to use the merge-base. This ensures that the generated hash reflects the engine state at the branch point, facilitating accurate comparisons. ```bash -git ls-tree -R $(git merge-base HEAD master) engine DEPS | git hash-object --stdin +git ls-tree -r $(git merge-base HEAD master) engine DEPS | git hash-object --stdin ``` ## Recommended Formula and Implementation @@ -69,7 +69,7 @@ git ls-tree -R $(git merge-base HEAD master) engine DEPS | git hash-object --std For now, the recommended formula for calculating the engine hash is: ```bash -git ls-tree -R $(git merge-base HEAD master) engine DEPS | git hash-object --stdin +git ls-tree -r $(git merge-base HEAD master) engine DEPS | git hash-object --stdin ``` To ensure backwards compatibility and allow for future updates, this formula should be implemented in both `.sh` and `.bat` scripts checked into the repository. This approach enables controlled updates to the hash calculation logic without disrupting existing workflows. diff --git a/flow/diff_context.cc b/flow/diff_context.cc index 6bcf444f008b7..aca12f6e8538b 100644 --- a/flow/diff_context.cc +++ b/flow/diff_context.cc @@ -166,6 +166,10 @@ bool DiffContext::PushCullRect(const SkRect& clip) { return !state_.matrix_clip.device_cull_rect().isEmpty(); } +const DlMatrix& DiffContext::GetMatrix() const { + return state_.matrix_clip.matrix(); +} + SkMatrix DiffContext::GetTransform3x3() const { return state_.matrix_clip.matrix_3x3(); } diff --git a/flow/diff_context.h b/flow/diff_context.h index 0eb536c49cbe4..9195ce0af7237 100644 --- a/flow/diff_context.h +++ b/flow/diff_context.h @@ -88,6 +88,9 @@ class DiffContext { // Instruct DiffContext that current layer will paint with integral transform. void WillPaintWithIntegralTransform() { state_.integral_transform = true; } + // Returns current transform as DlMatrix. + const DlMatrix& GetMatrix() const; + // Returns current transform as SkMatrix. SkMatrix GetTransform3x3() const; diff --git a/flow/embedded_views.cc b/flow/embedded_views.cc index b0d17870a707b..cb0faf33dae82 100644 --- a/flow/embedded_views.cc +++ b/flow/embedded_views.cc @@ -84,7 +84,7 @@ void MutatorsStack::PushOpacity(const int& alpha) { } void MutatorsStack::PushBackdropFilter( - const std::shared_ptr& filter, + const std::shared_ptr& filter, const SkRect& filter_rect) { std::shared_ptr element = std::make_shared(filter, filter_rect); diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 028fbd216a4aa..594ef93cdaa68 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -50,7 +50,7 @@ enum MutatorType { // https://github.com/flutter/flutter/issues/108470 class ImageFilterMutation { public: - ImageFilterMutation(std::shared_ptr filter, + ImageFilterMutation(std::shared_ptr filter, const SkRect& filter_rect) : filter_(std::move(filter)), filter_rect_(filter_rect) {} @@ -66,7 +66,7 @@ class ImageFilterMutation { } private: - std::shared_ptr filter_; + std::shared_ptr filter_; const SkRect filter_rect_; }; @@ -111,7 +111,7 @@ class Mutator { explicit Mutator(const SkMatrix& matrix) : type_(kTransform), matrix_(matrix) {} explicit Mutator(const int& alpha) : type_(kOpacity), alpha_(alpha) {} - explicit Mutator(const std::shared_ptr& filter, + explicit Mutator(const std::shared_ptr& filter, const SkRect& filter_rect) : type_(kBackdropFilter), filter_mutation_( @@ -197,7 +197,7 @@ class MutatorsStack { void PushTransform(const SkMatrix& matrix); void PushOpacity(const int& alpha); // `filter_rect` is in global coordinates. - void PushBackdropFilter(const std::shared_ptr& filter, + void PushBackdropFilter(const std::shared_ptr& filter, const SkRect& filter_rect); // Removes the `Mutator` on the top of the stack @@ -296,7 +296,7 @@ class EmbeddedViewParams { // Pushes the stored DlImageFilter object to the mutators stack. // // `filter_rect` is in global coordinates. - void PushImageFilter(const std::shared_ptr& filter, + void PushImageFilter(const std::shared_ptr& filter, const SkRect& filter_rect) { mutators_stack_.PushBackdropFilter(filter, filter_rect); } @@ -510,7 +510,7 @@ class ExternalViewEmbedder { // See also: |PushVisitedPlatformView| for pushing platform view ids to the // visited platform views list. virtual void PushFilterToVisitedPlatformViews( - const std::shared_ptr& filter, + const std::shared_ptr& filter, const SkRect& filter_rect) {} private: diff --git a/flow/layers/backdrop_filter_layer.cc b/flow/layers/backdrop_filter_layer.cc index aec04bafae872..7941101202416 100644 --- a/flow/layers/backdrop_filter_layer.cc +++ b/flow/layers/backdrop_filter_layer.cc @@ -7,12 +7,10 @@ namespace flutter { BackdropFilterLayer::BackdropFilterLayer( - std::shared_ptr filter, + const std::shared_ptr& filter, DlBlendMode blend_mode, std::optional backdrop_id) - : filter_(std::move(filter)), - blend_mode_(blend_mode), - backdrop_id_(backdrop_id) {} + : filter_(filter), blend_mode_(blend_mode), backdrop_id_(backdrop_id) {} void BackdropFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { DiffContext::AutoSubtreeRestore subtree(context); @@ -31,10 +29,11 @@ void BackdropFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { if (filter_) { paint_bounds = context->MapRect(paint_bounds); auto filter_target_bounds = paint_bounds.roundOut(); - SkIRect filter_input_bounds; // in screen coordinates - filter_->get_input_device_bounds( - filter_target_bounds, context->GetTransform3x3(), filter_input_bounds); - context->AddReadbackRegion(filter_target_bounds, filter_input_bounds); + DlIRect filter_input_bounds; // in screen coordinates + filter_->get_input_device_bounds(ToDlIRect(filter_target_bounds), + context->GetMatrix(), filter_input_bounds); + context->AddReadbackRegion(filter_target_bounds, + ToSkIRect(filter_input_bounds)); } DiffChildren(context, prev); diff --git a/flow/layers/backdrop_filter_layer.h b/flow/layers/backdrop_filter_layer.h index 180c5a641341a..76c61975e986d 100644 --- a/flow/layers/backdrop_filter_layer.h +++ b/flow/layers/backdrop_filter_layer.h @@ -6,13 +6,12 @@ #define FLUTTER_FLOW_LAYERS_BACKDROP_FILTER_LAYER_H_ #include "flutter/flow/layers/container_layer.h" -#include "third_party/skia/include/core/SkImageFilter.h" namespace flutter { class BackdropFilterLayer : public ContainerLayer { public: - BackdropFilterLayer(std::shared_ptr filter, + BackdropFilterLayer(const std::shared_ptr& filter, DlBlendMode blend_mode, std::optional backdrop_id = std::nullopt); @@ -23,7 +22,7 @@ class BackdropFilterLayer : public ContainerLayer { void Paint(PaintContext& context) const override; private: - std::shared_ptr filter_; + std::shared_ptr filter_; DlBlendMode blend_mode_; std::optional backdrop_id_; diff --git a/flow/layers/backdrop_filter_layer_unittests.cc b/flow/layers/backdrop_filter_layer_unittests.cc index a796bffb2a0cb..01a6b54cadc26 100644 --- a/flow/layers/backdrop_filter_layer_unittests.cc +++ b/flow/layers/backdrop_filter_layer_unittests.cc @@ -3,8 +3,8 @@ // found in the LICENSE file. #include "flutter/flow/layers/backdrop_filter_layer.h" -#include "flutter/flow/layers/clip_rect_layer.h" +#include "flutter/display_list/effects/dl_image_filter.h" #include "flutter/flow/layers/clip_rect_layer.h" #include "flutter/flow/layers/transform_layer.h" #include "flutter/flow/testing/diff_context_test.h" @@ -19,9 +19,9 @@ using BackdropFilterLayerTest = LayerTest; #ifndef NDEBUG TEST_F(BackdropFilterLayerTest, PaintingEmptyLayerDies) { - auto filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp); - auto layer = std::make_shared(filter.shared(), - DlBlendMode::kSrcOver); + auto filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); + auto layer = + std::make_shared(filter, DlBlendMode::kSrcOver); auto parent = std::make_shared(kEmptyRect, Clip::kHardEdge); parent->Add(layer); @@ -38,9 +38,9 @@ TEST_F(BackdropFilterLayerTest, PaintBeforePrerollDies) { const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); const SkPath child_path = SkPath().addRect(child_bounds); auto mock_layer = std::make_shared(child_path); - auto filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp); - auto layer = std::make_shared(filter.shared(), - DlBlendMode::kSrcOver); + auto filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); + auto layer = + std::make_shared(filter, DlBlendMode::kSrcOver); layer->Add(mock_layer); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); @@ -100,8 +100,7 @@ TEST_F(BackdropFilterLayerTest, SimpleFilter) { const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); const SkPath child_path = SkPath().addRect(child_bounds); const DlPaint child_paint = DlPaint(DlColor::kYellow()); - auto layer_filter = - std::make_shared(2.5, 3.2, DlTileMode::kClamp); + auto layer_filter = DlImageFilter::MakeBlur(2.5, 3.2, DlTileMode::kClamp); auto mock_layer = std::make_shared(child_path, child_paint); auto layer = std::make_shared(layer_filter, DlBlendMode::kSrcOver); @@ -148,8 +147,7 @@ TEST_F(BackdropFilterLayerTest, NonSrcOverBlend) { const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); const SkPath child_path = SkPath().addRect(child_bounds); const DlPaint child_paint = DlPaint(DlColor::kYellow()); - auto layer_filter = - std::make_shared(2.5, 3.2, DlTileMode::kClamp); + auto layer_filter = DlImageFilter::MakeBlur(2.5, 3.2, DlTileMode::kClamp); auto mock_layer = std::make_shared(child_path, child_paint); auto layer = std::make_shared(layer_filter, DlBlendMode::kSrc); @@ -205,8 +203,7 @@ TEST_F(BackdropFilterLayerTest, MultipleChildren) { const DlPaint child_paint2 = DlPaint(DlColor::kCyan()); SkRect children_bounds = child_path1.getBounds(); children_bounds.join(child_path2.getBounds()); - auto layer_filter = - std::make_shared(2.5, 3.2, DlTileMode::kClamp); + auto layer_filter = DlImageFilter::MakeBlur(2.5, 3.2, DlTileMode::kClamp); auto mock_layer1 = std::make_shared(child_path1, child_paint1); auto mock_layer2 = std::make_shared(child_path2, child_paint2); auto layer = std::make_shared(layer_filter, @@ -269,10 +266,8 @@ TEST_F(BackdropFilterLayerTest, Nested) { const DlPaint child_paint2 = DlPaint(DlColor::kCyan()); SkRect children_bounds = child_path1.getBounds(); children_bounds.join(child_path2.getBounds()); - auto layer_filter1 = - std::make_shared(2.5, 3.2, DlTileMode::kClamp); - auto layer_filter2 = - std::make_shared(2.7, 3.1, DlTileMode::kDecal); + auto layer_filter1 = DlImageFilter::MakeBlur(2.5, 3.2, DlTileMode::kClamp); + auto layer_filter2 = DlImageFilter::MakeBlur(2.7, 3.1, DlTileMode::kDecal); auto mock_layer1 = std::make_shared(child_path1, child_paint1); auto mock_layer2 = std::make_shared(child_path2, child_paint2); auto layer1 = std::make_shared(layer_filter1, @@ -343,11 +338,11 @@ TEST_F(BackdropFilterLayerTest, Nested) { TEST_F(BackdropFilterLayerTest, Readback) { std::shared_ptr no_filter; - auto layer_filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp); + auto layer_filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); auto initial_transform = SkMatrix(); // BDF with filter always reads from surface - auto layer1 = std::make_shared(layer_filter.shared(), + auto layer1 = std::make_shared(layer_filter, DlBlendMode::kSrcOver); preroll_context()->surface_needs_readback = false; preroll_context()->state_stack.set_preroll_delegate(initial_transform); @@ -376,14 +371,14 @@ TEST_F(BackdropFilterLayerTest, Readback) { } TEST_F(BackdropFilterLayerTest, OpacityInheritance) { - auto backdrop_filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp); + auto backdrop_filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); const SkPath mock_path = SkPath().addRect(SkRect::MakeLTRB(0, 0, 10, 10)); const DlPaint mock_paint = DlPaint(DlColor::kRed()); const SkRect clip_rect = SkRect::MakeLTRB(0, 0, 100, 100); auto clip = std::make_shared(clip_rect, Clip::kHardEdge); auto parent = std::make_shared(128, SkPoint::Make(0, 0)); - auto layer = std::make_shared(backdrop_filter.shared(), + auto layer = std::make_shared(backdrop_filter, DlBlendMode::kSrcOver); auto child = std::make_shared(mock_path, mock_paint); layer->Add(child); @@ -404,7 +399,8 @@ TEST_F(BackdropFilterLayerTest, OpacityInheritance) { /* BackdropFilterLayer::Paint */ { DlPaint save_paint; save_paint.setAlpha(128); - expected_builder.SaveLayer(&clip_rect, &save_paint, &backdrop_filter); + expected_builder.SaveLayer(&clip_rect, &save_paint, + backdrop_filter.get()); { /* MockLayer::Paint */ { DlPaint child_paint; @@ -424,20 +420,20 @@ TEST_F(BackdropFilterLayerTest, OpacityInheritance) { using BackdropLayerDiffTest = DiffContextTest; TEST_F(BackdropLayerDiffTest, BackdropLayer) { - auto filter = DlBlurImageFilter(10, 10, DlTileMode::kClamp); + auto filter = DlImageFilter::MakeBlur(10, 10, DlTileMode::kClamp); { // tests later assume 30px readback area, fail early if that's not the case - SkIRect readback; - EXPECT_EQ(filter.get_input_device_bounds(SkIRect::MakeWH(10, 10), - SkMatrix::I(), readback), + DlIRect readback; + EXPECT_EQ(filter->get_input_device_bounds(DlIRect::MakeWH(10, 10), + DlMatrix(), readback), &readback); - EXPECT_EQ(readback, SkIRect::MakeLTRB(-30, -30, 40, 40)); + EXPECT_EQ(readback, DlIRect::MakeLTRB(-30, -30, 40, 40)); } MockLayerTree l1(SkISize::Make(100, 100)); - l1.root()->Add(std::make_shared(filter.shared(), - DlBlendMode::kSrcOver)); + l1.root()->Add( + std::make_shared(filter, DlBlendMode::kSrcOver)); // no clip, effect over entire surface auto damage = DiffLayerTree(l1, MockLayerTree(SkISize::Make(100, 100))); @@ -447,8 +443,8 @@ TEST_F(BackdropLayerDiffTest, BackdropLayer) { auto clip = std::make_shared(SkRect::MakeLTRB(20, 20, 60, 60), Clip::kHardEdge); - clip->Add(std::make_shared(filter.shared(), - DlBlendMode::kSrcOver)); + clip->Add( + std::make_shared(filter, DlBlendMode::kSrcOver)); l2.root()->Add(clip); damage = DiffLayerTree(l2, MockLayerTree(SkISize::Make(100, 100))); @@ -482,15 +478,15 @@ TEST_F(BackdropLayerDiffTest, BackdropLayer) { } TEST_F(BackdropLayerDiffTest, ReadbackOutsideOfPaintArea) { - auto filter = DlMatrixImageFilter(SkMatrix::Translate(50, 50), - DlImageSampling::kLinear); + auto filter = DlImageFilter::MakeMatrix(DlMatrix::MakeTranslation({50, 50}), + DlImageSampling::kLinear); MockLayerTree l1(SkISize::Make(100, 100)); auto clip = std::make_shared(SkRect::MakeLTRB(60, 60, 80, 80), Clip::kHardEdge); - clip->Add(std::make_shared(filter.shared(), - DlBlendMode::kSrcOver)); + clip->Add( + std::make_shared(filter, DlBlendMode::kSrcOver)); l1.root()->Add(clip); auto damage = DiffLayerTree(l1, MockLayerTree(SkISize::Make(100, 100))); @@ -507,15 +503,15 @@ TEST_F(BackdropLayerDiffTest, ReadbackOutsideOfPaintArea) { } TEST_F(BackdropLayerDiffTest, BackdropLayerInvalidTransform) { - auto filter = DlBlurImageFilter(10, 10, DlTileMode::kClamp); + auto filter = DlImageFilter::MakeBlur(10, 10, DlTileMode::kClamp); { // tests later assume 30px readback area, fail early if that's not the case - SkIRect readback; - EXPECT_EQ(filter.get_input_device_bounds(SkIRect::MakeWH(10, 10), - SkMatrix::I(), readback), + DlIRect readback; + EXPECT_EQ(filter->get_input_device_bounds(DlIRect::MakeWH(10, 10), + DlMatrix(), readback), &readback); - EXPECT_EQ(readback, SkIRect::MakeLTRB(-30, -30, 40, 40)); + EXPECT_EQ(readback, DlIRect::MakeLTRB(-30, -30, 40, 40)); } MockLayerTree l1(SkISize::Make(100, 100)); @@ -525,8 +521,8 @@ TEST_F(BackdropLayerDiffTest, BackdropLayerInvalidTransform) { auto transform_layer = std::make_shared(transform); l1.root()->Add(transform_layer); - transform_layer->Add(std::make_shared( - filter.shared(), DlBlendMode::kSrcOver)); + transform_layer->Add( + std::make_shared(filter, DlBlendMode::kSrcOver)); auto damage = DiffLayerTree(l1, MockLayerTree(SkISize::Make(100, 100))); EXPECT_EQ(damage.frame_damage, SkIRect::MakeWH(100, 100)); diff --git a/flow/layers/color_filter_layer_unittests.cc b/flow/layers/color_filter_layer_unittests.cc index 209ca2b22733a..c43ca5d6f9ac6 100644 --- a/flow/layers/color_filter_layer_unittests.cc +++ b/flow/layers/color_filter_layer_unittests.cc @@ -89,7 +89,7 @@ TEST_F(ColorFilterLayerTest, SimpleFilter) { const SkPath child_path = SkPath().addRect(child_bounds); const DlPaint child_paint = DlPaint(DlColor::kYellow()); - auto dl_color_filter = DlLinearToSrgbGammaColorFilter::kInstance; + auto dl_color_filter = DlColorFilter::MakeLinearToSrgbGamma(); auto mock_layer = std::make_shared(child_path, child_paint); auto layer = std::make_shared(dl_color_filter); layer->Add(mock_layer); @@ -129,7 +129,7 @@ TEST_F(ColorFilterLayerTest, MultipleChildren) { const DlPaint child_paint2 = DlPaint(DlColor::kCyan()); auto mock_layer1 = std::make_shared(child_path1, child_paint1); auto mock_layer2 = std::make_shared(child_path2, child_paint2); - auto dl_color_filter = DlSrgbToLinearGammaColorFilter::kInstance; + auto dl_color_filter = DlColorFilter::MakeSrgbToLinearGamma(); auto layer = std::make_shared(dl_color_filter); layer->Add(mock_layer1); layer->Add(mock_layer2); @@ -179,7 +179,7 @@ TEST_F(ColorFilterLayerTest, Nested) { const DlPaint child_paint2 = DlPaint(DlColor::kCyan()); auto mock_layer1 = std::make_shared(child_path1, child_paint1); auto mock_layer2 = std::make_shared(child_path2, child_paint2); - auto dl_color_filter = DlSrgbToLinearGammaColorFilter::kInstance; + auto dl_color_filter = DlColorFilter::MakeSrgbToLinearGamma(); auto layer1 = std::make_shared(dl_color_filter); auto layer2 = std::make_shared(dl_color_filter); @@ -239,7 +239,7 @@ TEST_F(ColorFilterLayerTest, Readback) { // ColorFilterLayer does not read from surface auto layer = std::make_shared( - DlLinearToSrgbGammaColorFilter::kInstance); + DlColorFilter::MakeLinearToSrgbGamma()); preroll_context()->surface_needs_readback = false; preroll_context()->state_stack.set_preroll_delegate(initial_transform); layer->Preroll(preroll_context()); @@ -255,7 +255,7 @@ TEST_F(ColorFilterLayerTest, Readback) { } TEST_F(ColorFilterLayerTest, CacheChild) { - auto layer_filter = DlSrgbToLinearGammaColorFilter::kInstance; + auto layer_filter = DlColorFilter::MakeSrgbToLinearGamma(); auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); @@ -296,7 +296,7 @@ TEST_F(ColorFilterLayerTest, CacheChild) { } TEST_F(ColorFilterLayerTest, CacheChildren) { - auto layer_filter = DlSrgbToLinearGammaColorFilter::kInstance; + auto layer_filter = DlColorFilter::MakeSrgbToLinearGamma(); auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); const SkPath child_path1 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); @@ -342,7 +342,7 @@ TEST_F(ColorFilterLayerTest, CacheChildren) { } TEST_F(ColorFilterLayerTest, CacheColorFilterLayerSelf) { - auto layer_filter = DlSrgbToLinearGammaColorFilter::kInstance; + auto layer_filter = DlColorFilter::MakeSrgbToLinearGamma(); auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); const SkPath child_path1 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); @@ -408,12 +408,12 @@ TEST_F(ColorFilterLayerTest, OpacityInheritance) { 0, 0, 0, 1, 0, }; // clang-format on - auto layer_filter = DlMatrixColorFilter(matrix); + auto layer_filter = DlColorFilter::MakeMatrix(matrix); auto initial_transform = SkMatrix::Translate(50.0, 25.5); const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); auto mock_layer = std::make_shared(child_path); - auto color_filter_layer = std::make_shared( - std::make_shared(matrix)); + auto color_filter_layer = + std::make_shared(DlColorFilter::MakeMatrix(matrix)); color_filter_layer->Add(mock_layer); PrerollContext* context = preroll_context(); @@ -440,7 +440,7 @@ TEST_F(ColorFilterLayerTest, OpacityInheritance) { /* ColorFilterLayer::Paint() */ { DlPaint dl_paint; dl_paint.setColor(DlColor(opacity_alpha << 24)); - dl_paint.setColorFilter(&layer_filter); + dl_paint.setColorFilter(layer_filter); expected_builder.SaveLayer(&child_path.getBounds(), &dl_paint); /* MockLayer::Paint() */ { expected_builder.DrawPath(child_path, DlPaint(DlColor(0xFF000000))); diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index 31c1f3a8e0be1..1fa13acd47d6c 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -10,12 +10,12 @@ namespace flutter { -ImageFilterLayer::ImageFilterLayer(std::shared_ptr filter, +ImageFilterLayer::ImageFilterLayer(const std::shared_ptr& filter, const SkPoint& offset) : CacheableContainerLayer( RasterCacheUtil::kMinimumRendersBeforeCachingFilterLayer), offset_(offset), - filter_(std::move(filter)), + filter_(filter), transformed_filter_(nullptr) {} void ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { @@ -34,14 +34,14 @@ void ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { } if (filter_) { - auto filter = filter_->makeWithLocalMatrix(context->GetTransform3x3()); + auto filter = filter_->makeWithLocalMatrix(context->GetMatrix()); if (filter) { // This transform will be applied to every child rect in the subtree context->PushFilterBoundsAdjustment([filter](SkRect rect) { - SkIRect filter_out_bounds; - filter->map_device_bounds(rect.roundOut(), SkMatrix::I(), + DlIRect filter_out_bounds; + filter->map_device_bounds(ToDlIRect(rect.roundOut()), DlMatrix(), filter_out_bounds); - return SkRect::Make(filter_out_bounds); + return SkRect::Make(ToSkIRect(filter_out_bounds)); }); } } @@ -78,11 +78,10 @@ void ImageFilterLayer::Preroll(PrerollContext* context) { (LayerStateStack::kCallerCanApplyOpacity | LayerStateStack::kCallerCanApplyColorFilter); - const SkIRect filter_in_bounds = child_bounds.roundOut(); - SkIRect filter_out_bounds; - filter_->map_device_bounds(filter_in_bounds, SkMatrix::I(), - filter_out_bounds); - child_bounds.set(filter_out_bounds); + const DlIRect filter_in_bounds = ToDlIRect(child_bounds.roundOut()); + DlIRect filter_out_bounds; + filter_->map_device_bounds(filter_in_bounds, DlMatrix(), filter_out_bounds); + child_bounds.set(ToSkIRect(filter_out_bounds)); child_bounds.offset(offset_); set_paint_bounds(child_bounds); @@ -94,7 +93,7 @@ void ImageFilterLayer::Preroll(PrerollContext* context) { #endif // !SLIMPELLER transformed_filter_ = - filter_->makeWithLocalMatrix(context->state_stack.transform_3x3()); + filter_->makeWithLocalMatrix(context->state_stack.matrix()); #if !SLIMPELLER if (transformed_filter_) { diff --git a/flow/layers/image_filter_layer.h b/flow/layers/image_filter_layer.h index b96937da1425a..bd8172c295e13 100644 --- a/flow/layers/image_filter_layer.h +++ b/flow/layers/image_filter_layer.h @@ -12,7 +12,7 @@ namespace flutter { class ImageFilterLayer : public CacheableContainerLayer { public: - explicit ImageFilterLayer(std::shared_ptr filter, + explicit ImageFilterLayer(const std::shared_ptr& filter, const SkPoint& offset = SkPoint::Make(0, 0)); void Diff(DiffContext* context, const Layer* old_layer) override; @@ -23,8 +23,8 @@ class ImageFilterLayer : public CacheableContainerLayer { private: SkPoint offset_; - std::shared_ptr filter_; - std::shared_ptr transformed_filter_; + const std::shared_ptr filter_; + std::shared_ptr transformed_filter_; FML_DISALLOW_COPY_AND_ASSIGN(ImageFilterLayer); }; diff --git a/flow/layers/image_filter_layer_unittests.cc b/flow/layers/image_filter_layer_unittests.cc index 17ed929dd3b08..cfbeb17ba2f8b 100644 --- a/flow/layers/image_filter_layer_unittests.cc +++ b/flow/layers/image_filter_layer_unittests.cc @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/display_list/dl_tile_mode.h" #include "flutter/flow/layers/image_filter_layer.h" +#include "flutter/display_list/dl_tile_mode.h" +#include "flutter/display_list/effects/dl_image_filter.h" #include "flutter/flow/layers/layer_tree.h" #include "flutter/flow/layers/transform_layer.h" #include "flutter/flow/testing/diff_context_test.h" @@ -82,14 +83,14 @@ TEST_F(ImageFilterLayerTest, SimpleFilter) { const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); const SkPath child_path = SkPath().addRect(child_bounds); const DlPaint child_paint = DlPaint(DlColor::kYellow()); - auto dl_image_filter = std::make_shared( - SkMatrix(), DlImageSampling::kMipmapLinear); + auto dl_image_filter = DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({1.0, 2.0}), DlImageSampling::kMipmapLinear); auto mock_layer = std::make_shared(child_path, child_paint); auto layer = std::make_shared(dl_image_filter); layer->Add(mock_layer); const SkRect child_rounded_bounds = - SkRect::MakeLTRB(5.0f, 6.0f, 21.0f, 22.0f); + SkRect::MakeLTRB(6.0f, 8.0f, 22.0f, 24.0f); preroll_context()->state_stack.set_preroll_delegate(initial_transform); layer->Preroll(preroll_context()); @@ -123,8 +124,8 @@ TEST_F(ImageFilterLayerTest, SimpleFilterWithOffset) { const SkPath child_path = SkPath().addRect(child_bounds); const DlPaint child_paint = DlPaint(DlColor::kYellow()); const SkPoint layer_offset = SkPoint::Make(5.5, 6.5); - auto dl_image_filter = std::make_shared( - SkMatrix(), DlImageSampling::kMipmapLinear); + auto dl_image_filter = DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({1.0, 2.0}), DlImageSampling::kMipmapLinear); auto mock_layer = std::make_shared(child_path, child_paint); auto layer = std::make_shared(dl_image_filter, layer_offset); @@ -133,7 +134,7 @@ TEST_F(ImageFilterLayerTest, SimpleFilterWithOffset) { SkMatrix child_matrix = initial_transform; child_matrix.preTranslate(layer_offset.fX, layer_offset.fY); const SkRect child_rounded_bounds = - SkRect::MakeLTRB(10.5f, 12.5f, 26.5f, 28.5f); + SkRect::MakeLTRB(11.5f, 14.5f, 27.5f, 30.5f); preroll_context()->state_stack.set_preroll_delegate(initial_cull_rect, initial_transform); @@ -173,9 +174,9 @@ TEST_F(ImageFilterLayerTest, SimpleFilterBounds) { const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); const SkPath child_path = SkPath().addRect(child_bounds); const DlPaint child_paint = DlPaint(DlColor::kYellow()); - const SkMatrix filter_transform = SkMatrix::Scale(2.0, 2.0); + const DlMatrix filter_transform = DlMatrix::MakeScale({2.0, 2.0, 1}); - auto dl_image_filter = std::make_shared( + auto dl_image_filter = DlImageFilter::MakeMatrix( filter_transform, DlImageSampling::kMipmapLinear); auto mock_layer = std::make_shared(child_path, child_paint); auto layer = std::make_shared(dl_image_filter); @@ -216,8 +217,8 @@ TEST_F(ImageFilterLayerTest, MultipleChildren) { SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); const DlPaint child_paint1 = DlPaint(DlColor::kYellow()); const DlPaint child_paint2 = DlPaint(DlColor::kCyan()); - auto dl_image_filter = std::make_shared( - SkMatrix(), DlImageSampling::kMipmapLinear); + auto dl_image_filter = DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({1.0, 2.0}), DlImageSampling::kMipmapLinear); auto mock_layer1 = std::make_shared(child_path1, child_paint1); auto mock_layer2 = std::make_shared(child_path2, child_paint2); auto layer = std::make_shared(dl_image_filter); @@ -226,7 +227,8 @@ TEST_F(ImageFilterLayerTest, MultipleChildren) { SkRect children_bounds = child_path1.getBounds(); children_bounds.join(child_path2.getBounds()); - SkRect children_rounded_bounds = SkRect::Make(children_bounds.roundOut()); + SkRect children_rounded_bounds = + SkRect::Make(children_bounds.roundOut()).makeOffset(1.0f, 2.0f); preroll_context()->state_stack.set_preroll_delegate(initial_transform); layer->Preroll(preroll_context()); @@ -269,10 +271,10 @@ TEST_F(ImageFilterLayerTest, Nested) { SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); const DlPaint child_paint1 = DlPaint(DlColor::kYellow()); const DlPaint child_paint2 = DlPaint(DlColor::kCyan()); - auto dl_image_filter1 = std::make_shared( - SkMatrix(), DlImageSampling::kMipmapLinear); - auto dl_image_filter2 = std::make_shared( - SkMatrix(), DlImageSampling::kMipmapLinear); + auto dl_image_filter1 = DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({1.0, 2.0}), DlImageSampling::kMipmapLinear); + auto dl_image_filter2 = DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({3.0, 4.0}), DlImageSampling::kMipmapLinear); auto mock_layer1 = std::make_shared(child_path1, child_paint1); auto mock_layer2 = std::make_shared(child_path2, child_paint2); auto layer1 = std::make_shared(dl_image_filter1); @@ -281,20 +283,31 @@ TEST_F(ImageFilterLayerTest, Nested) { layer1->Add(mock_layer1); layer1->Add(layer2); - SkRect children_bounds = child_path1.getBounds(); - children_bounds.join(SkRect::Make(child_path2.getBounds().roundOut())); - const SkRect children_rounded_bounds = - SkRect::Make(children_bounds.roundOut()); - const SkRect mock_layer2_rounded_bounds = - SkRect::Make(child_path2.getBounds().roundOut()); + // Filter(translate by 1, 2) + // / | + // Mock(child_path1) Filter(translate by 3, 4) + // | + // Mock(child_path2 (shifted (3, 0))) + + SkRect filter2_bounds = SkRect::Make( // + child_path2 + .getBounds() // includes shift(3, 0) on child_path2 + .makeOffset(3.0, 4.0) // filter2 translation + .roundOut()); + SkRect filter1_child_bounds = child_path1.getBounds(); + filter1_child_bounds.join(filter2_bounds); + SkRect filter1_bounds = SkRect::Make( // + filter1_child_bounds // no shift on child_path1 + .makeOffset(1.0, 2.0) // filter1 translation + .roundOut()); preroll_context()->state_stack.set_preroll_delegate(initial_transform); layer1->Preroll(preroll_context()); EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); - EXPECT_EQ(layer1->paint_bounds(), children_rounded_bounds); - EXPECT_EQ(layer1->child_paint_bounds(), children_bounds); - EXPECT_EQ(layer2->paint_bounds(), mock_layer2_rounded_bounds); + EXPECT_EQ(layer1->paint_bounds(), filter1_bounds); + EXPECT_EQ(layer1->child_paint_bounds(), filter1_child_bounds); + EXPECT_EQ(layer2->paint_bounds(), filter2_bounds); EXPECT_EQ(layer2->child_paint_bounds(), child_path2.getBounds()); EXPECT_TRUE(mock_layer1->needs_painting(paint_context())); EXPECT_TRUE(mock_layer2->needs_painting(paint_context())); @@ -307,7 +320,7 @@ TEST_F(ImageFilterLayerTest, Nested) { /* ImageFilterLayer::Paint() */ { DlPaint dl_paint; dl_paint.setImageFilter(dl_image_filter1.get()); - expected_builder.SaveLayer(&children_bounds, &dl_paint); + expected_builder.SaveLayer(&filter1_child_bounds, &dl_paint); { /* MockLayer::Paint() */ { expected_builder.DrawPath(child_path1, DlPaint(DlColor::kYellow())); @@ -331,8 +344,8 @@ TEST_F(ImageFilterLayerTest, Nested) { } TEST_F(ImageFilterLayerTest, Readback) { - auto dl_image_filter = std::make_shared( - SkMatrix(), DlImageSampling::kLinear); + auto dl_image_filter = DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({1.0, 2.0}), DlImageSampling::kLinear); // ImageFilterLayer does not read from surface auto layer = std::make_shared(dl_image_filter); @@ -350,8 +363,8 @@ TEST_F(ImageFilterLayerTest, Readback) { } TEST_F(ImageFilterLayerTest, CacheChild) { - auto dl_image_filter = std::make_shared( - SkMatrix(), DlImageSampling::kMipmapLinear); + auto dl_image_filter = DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({1.0, 2.0}), DlImageSampling::kMipmapLinear); auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); @@ -384,6 +397,9 @@ TEST_F(ImageFilterLayerTest, CacheChild) { // his children EXPECT_EQ(cacheable_image_filter_item->cache_state(), RasterCacheItem::CacheState::kChildren); + // We assert here because the lines after it will crash if this is not true + // (It will generally only fail if another EXPECT test above also fails) + ASSERT_TRUE(cacheable_image_filter_item->GetId().has_value()); EXPECT_TRUE(raster_cache()->Draw(cacheable_image_filter_item->GetId().value(), cache_canvas, &paint)); EXPECT_FALSE(raster_cache()->Draw( @@ -391,8 +407,8 @@ TEST_F(ImageFilterLayerTest, CacheChild) { } TEST_F(ImageFilterLayerTest, CacheChildren) { - auto dl_image_filter = std::make_shared( - SkMatrix(), DlImageSampling::kMipmapLinear); + auto dl_image_filter = DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({1.0, 2.0}), DlImageSampling::kMipmapLinear); auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); DlPaint paint; @@ -431,6 +447,9 @@ TEST_F(ImageFilterLayerTest, CacheChildren) { // children EXPECT_EQ(cacheable_image_filter_item->cache_state(), RasterCacheItem::CacheState::kChildren); + // We assert here because the lines after it will crash if this is not true + // (It will generally only fail if another EXPECT test above also fails) + ASSERT_TRUE(cacheable_image_filter_item->GetId().has_value()); EXPECT_TRUE(raster_cache()->Draw(cacheable_image_filter_item->GetId().value(), cache_canvas, &paint)); EXPECT_FALSE(raster_cache()->Draw( @@ -446,7 +465,8 @@ TEST_F(ImageFilterLayerTest, CacheChildren) { 0, 0, 1); SkMatrix cache_matrix = initial_transform; cache_matrix.preConcat(snapped_matrix); - auto transformed_filter = dl_image_filter->makeWithLocalMatrix(cache_matrix); + auto transformed_filter = + dl_image_filter->makeWithLocalMatrix(ToDlMatrix(cache_matrix)); layer->Paint(display_list_paint_context()); DisplayListBuilder expected_builder; @@ -468,8 +488,8 @@ TEST_F(ImageFilterLayerTest, CacheChildren) { } TEST_F(ImageFilterLayerTest, CacheImageFilterLayerSelf) { - auto dl_image_filter = std::make_shared( - SkMatrix(), DlImageSampling::kMipmapLinear); + auto dl_image_filter = DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({1.0, 2.0}), DlImageSampling::kMipmapLinear); auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); @@ -570,8 +590,8 @@ TEST_F(ImageFilterLayerTest, OpacityInheritance) { const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); const SkPath child_path = SkPath().addRect(child_bounds); const DlPaint child_paint = DlPaint(DlColor::kYellow()); - auto dl_image_filter = std::make_shared( - SkMatrix(), DlImageSampling::kMipmapLinear); + auto dl_image_filter = DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({1.0, 2.0}), DlImageSampling::kMipmapLinear); // The mock_layer child will not be compatible with opacity auto mock_layer = MockLayer::Make(child_path, child_paint); @@ -623,14 +643,13 @@ TEST_F(ImageFilterLayerTest, OpacityInheritance) { using ImageFilterLayerDiffTest = DiffContextTest; TEST_F(ImageFilterLayerDiffTest, ImageFilterLayer) { - auto dl_blur_filter = - std::make_shared(10, 10, DlTileMode::kClamp); + auto dl_blur_filter = DlImageFilter::MakeBlur(10, 10, DlTileMode::kClamp); { // tests later assume 30px paint area, fail early if that's not the case - SkIRect input_bounds; - dl_blur_filter->get_input_device_bounds(SkIRect::MakeWH(10, 10), - SkMatrix::I(), input_bounds); - EXPECT_EQ(input_bounds, SkIRect::MakeLTRB(-30, -30, 40, 40)); + DlIRect input_bounds; + dl_blur_filter->get_input_device_bounds(DlIRect::MakeWH(10, 10), DlMatrix(), + input_bounds); + EXPECT_EQ(input_bounds, DlIRect::MakeLTRB(-30, -30, 40, 40)); } MockLayerTree l1; @@ -670,15 +689,14 @@ TEST_F(ImageFilterLayerDiffTest, ImageFilterLayer) { } TEST_F(ImageFilterLayerDiffTest, ImageFilterLayerInflatestChildSize) { - auto dl_blur_filter = - std::make_shared(10, 10, DlTileMode::kClamp); + auto dl_blur_filter = DlImageFilter::MakeBlur(10, 10, DlTileMode::kClamp); { // tests later assume 30px paint area, fail early if that's not the case - SkIRect input_bounds; - dl_blur_filter->get_input_device_bounds(SkIRect::MakeWH(10, 10), - SkMatrix::I(), input_bounds); - EXPECT_EQ(input_bounds, SkIRect::MakeLTRB(-30, -30, 40, 40)); + DlIRect input_bounds; + dl_blur_filter->get_input_device_bounds(DlIRect::MakeWH(10, 10), DlMatrix(), + input_bounds); + EXPECT_EQ(input_bounds, DlIRect::MakeLTRB(-30, -30, 40, 40)); } MockLayerTree l1; diff --git a/flow/layers/layer_state_stack.cc b/flow/layers/layer_state_stack.cc index 15cf79548edde..07ab3cc864b18 100644 --- a/flow/layers/layer_state_stack.cc +++ b/flow/layers/layer_state_stack.cc @@ -35,6 +35,10 @@ class DummyDelegate : public LayerStateStack::Delegate { error(); return {}; } + DlMatrix matrix() const override { + error(); + return dummy_matrix_; + } SkM44 matrix_4x4() const override { error(); return {}; @@ -69,6 +73,7 @@ class DummyDelegate : public LayerStateStack::Delegate { static void error() { FML_DCHECK(false) << "LayerStateStack state queried without a delegate"; } + const DlMatrix dummy_matrix_; }; const std::shared_ptr DummyDelegate::kInstance = std::make_shared(); @@ -88,6 +93,7 @@ class DlCanvasDelegate : public LayerStateStack::Delegate { SkRect device_cull_rect() const override { return canvas_->GetDestinationClipBounds(); } + DlMatrix matrix() const override { return canvas_->GetMatrix(); } SkM44 matrix_4x4() const override { return canvas_->GetTransformFullPerspective(); } @@ -147,6 +153,7 @@ class PrerollDelegate : public LayerStateStack::Delegate { void decommission() override {} + DlMatrix matrix() const override { return state().matrix(); } SkM44 matrix_4x4() const override { return state().matrix_4x4(); } SkMatrix matrix_3x3() const override { return state().matrix_3x3(); } SkRect local_cull_rect() const override { return state().local_cull_rect(); } @@ -282,7 +289,7 @@ class OpacityEntry : public LayerStateStack::StateEntry { class ImageFilterEntry : public LayerStateStack::StateEntry { public: ImageFilterEntry(const SkRect& bounds, - const std::shared_ptr& filter, + const std::shared_ptr& filter, const LayerStateStack::RenderingAttributes& prev) : bounds_(bounds), filter_(filter), @@ -304,8 +311,8 @@ class ImageFilterEntry : public LayerStateStack::StateEntry { private: const SkRect bounds_; - const std::shared_ptr filter_; - const std::shared_ptr old_filter_; + const std::shared_ptr filter_; + const std::shared_ptr old_filter_; const SkRect old_bounds_; FML_DISALLOW_COPY_ASSIGN_AND_MOVE(ImageFilterEntry); @@ -346,7 +353,7 @@ class ColorFilterEntry : public LayerStateStack::StateEntry { class BackdropFilterEntry : public SaveLayerEntry { public: BackdropFilterEntry(const SkRect& bounds, - const std::shared_ptr& filter, + const std::shared_ptr& filter, DlBlendMode blend_mode, std::optional backdrop_id, const LayerStateStack::RenderingAttributes& prev) @@ -373,7 +380,7 @@ class BackdropFilterEntry : public SaveLayerEntry { } private: - const std::shared_ptr filter_; + const std::shared_ptr filter_; std::optional backdrop_id_; FML_DISALLOW_COPY_ASSIGN_AND_MOVE(BackdropFilterEntry); @@ -550,7 +557,7 @@ void MutatorContext::applyOpacity(const SkRect& bounds, SkScalar opacity) { void MutatorContext::applyImageFilter( const SkRect& bounds, - const std::shared_ptr& filter) { + const std::shared_ptr& filter) { if (filter) { layer_state_stack_->push_image_filter(bounds, filter); } @@ -566,7 +573,7 @@ void MutatorContext::applyColorFilter( void MutatorContext::applyBackdropFilter( const SkRect& bounds, - const std::shared_ptr& filter, + const std::shared_ptr& filter, DlBlendMode blend_mode, std::optional backdrop_id) { layer_state_stack_->push_backdrop(bounds, filter, blend_mode, backdrop_id); @@ -706,7 +713,7 @@ void LayerStateStack::push_color_filter( void LayerStateStack::push_image_filter( const SkRect& bounds, - const std::shared_ptr& filter) { + const std::shared_ptr& filter) { maybe_save_layer(filter); state_stack_.emplace_back( std::make_unique(bounds, filter, outstanding_)); @@ -715,7 +722,7 @@ void LayerStateStack::push_image_filter( void LayerStateStack::push_backdrop( const SkRect& bounds, - const std::shared_ptr& filter, + const std::shared_ptr& filter, DlBlendMode blend_mode, std::optional backdrop_id) { state_stack_.emplace_back(std::make_unique( @@ -828,7 +835,7 @@ void LayerStateStack::maybe_save_layer( } void LayerStateStack::maybe_save_layer( - const std::shared_ptr& filter) { + const std::shared_ptr& filter) { if (outstanding_.image_filter) { // TBD: compose the 2 image filters together. save_layer(outstanding_.save_layer_bounds); diff --git a/flow/layers/layer_state_stack.h b/flow/layers/layer_state_stack.h index a5cec34ed1cc4..ad505c09340aa 100644 --- a/flow/layers/layer_state_stack.h +++ b/flow/layers/layer_state_stack.h @@ -177,7 +177,7 @@ class LayerStateStack { // outstanding attributes. // (Currently only opacity is recorded for batching) void applyImageFilter(const SkRect& bounds, - const std::shared_ptr& filter); + const std::shared_ptr& filter); // Records the color filter for application at the next call to // saveLayer or applyState. A saveLayer may be executed at @@ -196,7 +196,7 @@ class LayerStateStack { // subsequent canvas or builder objects that are made delegates // will only see a saveLayer with the indicated blend_mode. void applyBackdropFilter(const SkRect& bounds, - const std::shared_ptr& filter, + const std::shared_ptr& filter, DlBlendMode blend_mode, std::optional backdrop_id); @@ -252,7 +252,7 @@ class LayerStateStack { return outstanding_.color_filter; } - std::shared_ptr outstanding_image_filter() const { + std::shared_ptr outstanding_image_filter() const { return outstanding_.image_filter; } @@ -278,6 +278,12 @@ class LayerStateStack { // This rectangle may be a conservative estimate of the true clip region. SkRect local_cull_rect() const { return delegate_->local_cull_rect(); } + // The transform from the local coordinates to the device coordinates + // in 4x4 DlMatrix representation. This matrix provides all information + // needed to compute bounds for a 2D rendering primitive, and it will + // accurately concatenate with other 4x4 matrices without losing information. + const DlMatrix matrix() const { return delegate_->matrix(); } + // The transform from the local coordinates to the device coordinates // in the most capable 4x4 matrix representation. This matrix may be // more information than is needed to compute bounds for a 2D rendering @@ -332,9 +338,9 @@ class LayerStateStack { void push_color_filter(const SkRect& bounds, const std::shared_ptr& filter); void push_image_filter(const SkRect& bounds, - const std::shared_ptr& filter); + const std::shared_ptr& filter); void push_backdrop(const SkRect& bounds, - const std::shared_ptr& filter, + const std::shared_ptr& filter, DlBlendMode blend_mode, std::optional backdrop_id); @@ -362,7 +368,7 @@ class LayerStateStack { void maybe_save_layer(int apply_flags); void maybe_save_layer(SkScalar opacity); void maybe_save_layer(const std::shared_ptr& filter); - void maybe_save_layer(const std::shared_ptr& filter); + void maybe_save_layer(const std::shared_ptr& filter); // --------------------- struct RenderingAttributes { @@ -379,7 +385,7 @@ class LayerStateStack { SkScalar opacity = SK_Scalar1; std::shared_ptr color_filter; - std::shared_ptr image_filter; + std::shared_ptr image_filter; DlPaint* fill(DlPaint& paint, DlBlendMode mode = DlBlendMode::kSrcOver) const; @@ -441,6 +447,7 @@ class LayerStateStack { virtual SkRect local_cull_rect() const = 0; virtual SkRect device_cull_rect() const = 0; + virtual DlMatrix matrix() const = 0; virtual SkM44 matrix_4x4() const = 0; virtual SkMatrix matrix_3x3() const = 0; virtual bool content_culled(const SkRect& content_bounds) const = 0; diff --git a/flow/layers/layer_state_stack_unittests.cc b/flow/layers/layer_state_stack_unittests.cc index 928961f3621c2..bffe21ccf7750 100644 --- a/flow/layers/layer_state_stack_unittests.cc +++ b/flow/layers/layer_state_stack_unittests.cc @@ -207,12 +207,10 @@ TEST(LayerStateStack, Opacity) { TEST(LayerStateStack, ColorFilter) { SkRect rect = {10, 10, 20, 20}; - std::shared_ptr outer_filter = - std::make_shared(DlColor::kYellow(), - DlBlendMode::kColorBurn); - std::shared_ptr inner_filter = - std::make_shared(DlColor::kRed(), - DlBlendMode::kColorBurn); + auto outer_filter = + DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kColorBurn); + auto inner_filter = + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kColorBurn); LayerStateStack state_stack; state_stack.set_preroll_delegate(SkRect::MakeLTRB(0, 0, 50, 50)); @@ -287,14 +285,15 @@ TEST(LayerStateStack, ColorFilter) { TEST(LayerStateStack, ImageFilter) { SkRect rect = {10, 10, 20, 20}; - std::shared_ptr outer_filter = - std::make_shared(2.0f, 2.0f, DlTileMode::kClamp); - std::shared_ptr inner_filter = - std::make_shared(3.0f, 3.0f, DlTileMode::kClamp); + std::shared_ptr outer_filter = + DlImageFilter::MakeBlur(2.0f, 2.0f, DlTileMode::kClamp); + std::shared_ptr inner_filter = + DlImageFilter::MakeBlur(3.0f, 3.0f, DlTileMode::kClamp); SkRect inner_src_rect = rect; - SkRect outer_src_rect; - ASSERT_EQ(inner_filter->map_local_bounds(rect, outer_src_rect), - &outer_src_rect); + DlRect dl_outer_src_rect; + ASSERT_EQ(inner_filter->map_local_bounds(ToDlRect(rect), dl_outer_src_rect), + &dl_outer_src_rect); + SkRect outer_src_rect = ToSkRect(dl_outer_src_rect); LayerStateStack state_stack; state_stack.set_preroll_delegate(SkRect::MakeLTRB(0, 0, 50, 50)); @@ -369,9 +368,8 @@ TEST(LayerStateStack, ImageFilter) { TEST(LayerStateStack, OpacityAndColorFilterInteraction) { SkRect rect = {10, 10, 20, 20}; - std::shared_ptr color_filter = - std::make_shared(DlColor::kYellow(), - DlBlendMode::kColorBurn); + auto color_filter = + DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kColorBurn); DisplayListBuilder builder; LayerStateStack state_stack; @@ -429,8 +427,8 @@ TEST(LayerStateStack, OpacityAndColorFilterInteraction) { TEST(LayerStateStack, OpacityAndImageFilterInteraction) { SkRect rect = {10, 10, 20, 20}; - std::shared_ptr image_filter = - std::make_shared(2.0f, 2.0f, DlTileMode::kClamp); + std::shared_ptr image_filter = + DlImageFilter::MakeBlur(2.0f, 2.0f, DlTileMode::kClamp); DisplayListBuilder builder; LayerStateStack state_stack; @@ -488,11 +486,9 @@ TEST(LayerStateStack, OpacityAndImageFilterInteraction) { TEST(LayerStateStack, ColorFilterAndImageFilterInteraction) { SkRect rect = {10, 10, 20, 20}; - std::shared_ptr color_filter = - std::make_shared(DlColor::kYellow(), - DlBlendMode::kColorBurn); - std::shared_ptr image_filter = - std::make_shared(2.0f, 2.0f, DlTileMode::kClamp); + auto color_filter = + DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kColorBurn); + auto image_filter = DlImageFilter::MakeBlur(2.0f, 2.0f, DlTileMode::kClamp); DisplayListBuilder builder; LayerStateStack state_stack; diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc index e9c0db8a9a714..0993fb13b5077 100644 --- a/flow/layers/opacity_layer_unittests.cc +++ b/flow/layers/opacity_layer_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/flow/layers/opacity_layer.h" +#include "flutter/display_list/effects/dl_image_filter.h" #include "flutter/flow/layers/clip_rect_layer.h" #include "flutter/flow/layers/image_filter_layer.h" #include "flutter/flow/layers/layer_tree.h" @@ -542,7 +543,7 @@ TEST_F(OpacityLayerTest, OpacityInheritanceThroughImageFilter) { auto opacity_layer = std::make_shared(128, SkPoint::Make(20, 20)); auto filter_layer = std::make_shared( - std::make_shared(5.0, 5.0, DlTileMode::kClamp)); + DlImageFilter::MakeBlur(5.0, 5.0, DlTileMode::kClamp)); auto mock_layer = MockLayer::MakeOpacityCompatible(SkPath()); filter_layer->Add(mock_layer); opacity_layer->Add(filter_layer); diff --git a/flow/layers/platform_view_layer_unittests.cc b/flow/layers/platform_view_layer_unittests.cc index 31eab1bd3f315..df8ccfc30c609 100644 --- a/flow/layers/platform_view_layer_unittests.cc +++ b/flow/layers/platform_view_layer_unittests.cc @@ -132,7 +132,7 @@ TEST_F(PlatformViewLayerTest, StateTransfer) { transform_layer2->Add(child2); auto embedder = MockViewEmbedder(); - DisplayListBuilder builder({0, 0, 500, 500}); + DisplayListBuilder builder(DlRect::MakeWH(500, 500)); embedder.AddCanvas(&builder); PrerollContext* preroll_ctx = preroll_context(); diff --git a/flow/layers/shader_mask_layer_unittests.cc b/flow/layers/shader_mask_layer_unittests.cc index 933ba70664835..5cd676e485e98 100644 --- a/flow/layers/shader_mask_layer_unittests.cc +++ b/flow/layers/shader_mask_layer_unittests.cc @@ -30,8 +30,8 @@ static std::shared_ptr MakeFilter(DlColor color) { 0, 1, }; - return DlColorSource::MakeLinear(SkPoint::Make(0, 0), SkPoint::Make(10, 10), - 2, colors, stops, DlTileMode::kRepeat); + return DlColorSource::MakeLinear(DlPoint(0, 0), DlPoint(10, 10), 2, colors, + stops, DlTileMode::kRepeat); } #ifndef NDEBUG diff --git a/flow/mutators_stack_unittests.cc b/flow/mutators_stack_unittests.cc index 42ce6363d47ff..42969c330835f 100644 --- a/flow/mutators_stack_unittests.cc +++ b/flow/mutators_stack_unittests.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flutter/display_list/effects/dl_image_filters.h" #include "flutter/flow/embedded_views.h" #include "gtest/gtest.h" @@ -93,7 +94,7 @@ TEST(MutatorsStack, PushBackdropFilter) { MutatorsStack stack; const int num_of_mutators = 10; for (int i = 0; i < num_of_mutators; i++) { - auto filter = std::make_shared(i, 5, DlTileMode::kClamp); + auto filter = DlImageFilter::MakeBlur(i, 5, DlTileMode::kClamp); stack.PushBackdropFilter(filter, SkRect::MakeXYWH(i, i, i, i)); } @@ -168,7 +169,7 @@ TEST(MutatorsStack, Equality) { stack.PushClipPath(path); int alpha = 240; stack.PushOpacity(alpha); - auto filter = std::make_shared(5, 5, DlTileMode::kClamp); + auto filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); stack.PushBackdropFilter(filter, SkRect::MakeEmpty()); MutatorsStack stack_other; @@ -182,8 +183,7 @@ TEST(MutatorsStack, Equality) { stack_other.PushClipPath(other_path); int other_alpha = 240; stack_other.PushOpacity(other_alpha); - auto other_filter = - std::make_shared(5, 5, DlTileMode::kClamp); + auto other_filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); stack_other.PushBackdropFilter(other_filter, SkRect::MakeEmpty()); ASSERT_TRUE(stack == stack_other); @@ -215,7 +215,7 @@ TEST(Mutator, Initialization) { Mutator mutator5 = Mutator(alpha); ASSERT_TRUE(mutator5.GetType() == MutatorType::kOpacity); - auto filter = std::make_shared(5, 5, DlTileMode::kClamp); + auto filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); Mutator mutator6 = Mutator(filter, SkRect::MakeEmpty()); ASSERT_TRUE(mutator6.GetType() == MutatorType::kBackdropFilter); ASSERT_TRUE(mutator6.GetFilterMutation().GetFilter() == *filter); @@ -248,7 +248,7 @@ TEST(Mutator, CopyConstructor) { Mutator copy5 = Mutator(mutator5); ASSERT_TRUE(mutator5 == copy5); - auto filter = std::make_shared(5, 5, DlTileMode::kClamp); + auto filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); Mutator mutator6 = Mutator(filter, SkRect::MakeEmpty()); Mutator copy6 = Mutator(mutator6); ASSERT_TRUE(mutator6 == copy6); @@ -281,8 +281,8 @@ TEST(Mutator, Equality) { Mutator other_mutator5 = Mutator(alpha); ASSERT_TRUE(mutator5 == other_mutator5); - auto filter1 = std::make_shared(5, 5, DlTileMode::kClamp); - auto filter2 = std::make_shared(5, 5, DlTileMode::kClamp); + auto filter1 = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); + auto filter2 = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); Mutator mutator6 = Mutator(filter1, SkRect::MakeEmpty()); Mutator other_mutator6 = Mutator(filter2, SkRect::MakeEmpty()); ASSERT_TRUE(mutator6 == other_mutator6); @@ -302,9 +302,8 @@ TEST(Mutator, UnEquality) { Mutator other_mutator2 = Mutator(alpha2); ASSERT_TRUE(mutator2 != other_mutator2); - auto filter = std::make_shared(5, 5, DlTileMode::kClamp); - auto filter2 = - std::make_shared(10, 10, DlTileMode::kClamp); + auto filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); + auto filter2 = DlImageFilter::MakeBlur(10, 10, DlTileMode::kClamp); Mutator mutator3 = Mutator(filter, SkRect::MakeEmpty()); Mutator other_mutator3 = Mutator(filter2, SkRect::MakeEmpty()); ASSERT_TRUE(mutator3 != other_mutator3); diff --git a/flow/paint_region.h b/flow/paint_region.h index 52f29762f67ad..34b7ac1e9bbeb 100644 --- a/flow/paint_region.h +++ b/flow/paint_region.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_FLOW_PAINT_REGION_H_ #define FLUTTER_FLOW_PAINT_REGION_H_ +#include #include #include #include "flutter/fml/logging.h" diff --git a/flow/paint_utils.cc b/flow/paint_utils.cc index eab62daad1d7a..815ab2c51bec0 100644 --- a/flow/paint_utils.cc +++ b/flow/paint_utils.cc @@ -22,9 +22,9 @@ std::shared_ptr CreateCheckerboardShader(SkColor c1, bm.eraseArea(SkIRect::MakeLTRB(0, 0, size, size), c2); bm.eraseArea(SkIRect::MakeLTRB(size, size, 2 * size, 2 * size), c2); auto image = DlImage::Make(SkImages::RasterFromBitmap(bm)); - return std::make_shared( - image, DlTileMode::kRepeat, DlTileMode::kRepeat, - DlImageSampling::kNearestNeighbor); + return DlColorSource::MakeImage(image, DlTileMode::kRepeat, + DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor); } } // anonymous namespace diff --git a/flow/raster_cache_unittests.cc b/flow/raster_cache_unittests.cc index 255509952daf5..ea814184fa0ca 100644 --- a/flow/raster_cache_unittests.cc +++ b/flow/raster_cache_unittests.cc @@ -553,8 +553,7 @@ TEST(RasterCache, PrepareLayerTransform) { SkRect child_bounds = SkRect::MakeLTRB(10, 10, 50, 50); SkPath child_path = SkPath().addOval(child_bounds); auto child_layer = MockLayer::Make(child_path); - auto blur_filter = - std::make_shared(5, 5, DlTileMode::kClamp); + auto blur_filter = DlBlurImageFilter::Make(5, 5, DlTileMode::kClamp); auto blur_layer = std::make_shared(blur_filter); SkMatrix matrix = SkMatrix::Scale(2, 2); auto transform_layer = std::make_shared(matrix); diff --git a/flutter_frontend_server/pubspec.yaml b/flutter_frontend_server/pubspec.yaml index 3cb88d8a9aff1..4fb0fc42d1c5e 100644 --- a/flutter_frontend_server/pubspec.yaml +++ b/flutter_frontend_server/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/flutter_vma/include/README.md b/flutter_vma/include/README.md new file mode 100644 index 0000000000000..373e37ad14d6f --- /dev/null +++ b/flutter_vma/include/README.md @@ -0,0 +1 @@ +This folder is here to match Angle's expectations for the VMA directory. diff --git a/fml/BUILD.gn b/fml/BUILD.gn index 2dc48033fbb65..ecfe636d5a0d5 100644 --- a/fml/BUILD.gn +++ b/fml/BUILD.gn @@ -134,12 +134,6 @@ source_set("fml") { libs = [] - if (is_ios || is_mac) { - sources += [ "platform/darwin/concurrent_message_loop_factory.mm" ] - } else { - sources += [ "concurrent_message_loop_factory.cc" ] - } - if (is_ios || is_mac) { cflags_objc = flutter_cflags_objc cflags_objcc = flutter_cflags_objcc @@ -147,26 +141,21 @@ source_set("fml") { sources += [ "platform/darwin/cf_utils.cc", "platform/darwin/cf_utils.h", + "platform/darwin/concurrent_message_loop_factory.mm", "platform/darwin/message_loop_darwin.h", "platform/darwin/message_loop_darwin.mm", "platform/darwin/paths_darwin.mm", "platform/darwin/platform_version.h", "platform/darwin/platform_version.mm", - "platform/darwin/scoped_block.h", - "platform/darwin/scoped_block.mm", "platform/darwin/scoped_nsautorelease_pool.cc", "platform/darwin/scoped_nsautorelease_pool.h", - "platform/darwin/scoped_nsobject.h", - "platform/darwin/scoped_nsobject.mm", - "platform/darwin/scoped_policy.h", - "platform/darwin/scoped_typeref.h", "platform/darwin/string_range_sanitization.h", "platform/darwin/string_range_sanitization.mm", - "platform/darwin/weak_nsobject.h", - "platform/darwin/weak_nsobject.mm", ] frameworks = [ "Foundation.framework" ] + } else { + sources += [ "concurrent_message_loop_factory.cc" ] } if (is_android) { @@ -369,20 +358,16 @@ if (enable_unittests) { "time/time_unittest.cc", ] - if (is_mac) { + if (is_mac || is_ios) { + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + sources += [ "platform/darwin/cf_utils_unittests.mm", "platform/darwin/string_range_sanitization_unittests.mm", ] } - if (is_mac || is_ios) { - sources += [ - "platform/darwin/scoped_nsobject_unittests.mm", - "platform/darwin/weak_nsobject_unittests.mm", - ] - } - if (is_fuchsia) { sources += [ "platform/fuchsia/log_interest_listener_unittests.cc" ] } @@ -413,21 +398,4 @@ if (enable_unittests) { libs = [ "${fuchsia_arch_root}/sysroot/lib/libzircon.so" ] } } - - executable("fml_arc_unittests") { - testonly = true - if (is_mac || is_ios) { - cflags_objcc = flutter_cflags_objc_arc - sources = [ - "platform/darwin/scoped_nsobject_arc_unittests.mm", - "platform/darwin/weak_nsobject_arc_unittests.mm", - ] - } - - deps = [ - ":fml_fixtures", - "//flutter/fml", - "//flutter/testing", - ] - } } diff --git a/fml/cpu_affinity.cc b/fml/cpu_affinity.cc index 0342c85f0433e..a580c9d04c52c 100644 --- a/fml/cpu_affinity.cc +++ b/fml/cpu_affinity.cc @@ -5,6 +5,7 @@ #include "flutter/fml/cpu_affinity.h" #include "flutter/fml/build_config.h" +#include #include #include #include diff --git a/fml/cpu_affinity.h b/fml/cpu_affinity.h index c1d8498e6524a..c644472965d41 100644 --- a/fml/cpu_affinity.h +++ b/fml/cpu_affinity.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_FML_CPU_AFFINITY_H_ #define FLUTTER_FML_CPU_AFFINITY_H_ +#include #include #include #include diff --git a/fml/platform/darwin/cf_utils.h b/fml/platform/darwin/cf_utils.h index 02fd3229d7c66..71bbb2bb8d35f 100644 --- a/fml/platform/darwin/cf_utils.h +++ b/fml/platform/darwin/cf_utils.h @@ -11,54 +11,107 @@ namespace fml { +/// Default retain and release implementations for CFRef. +template +struct CFRefTraits { + static constexpr T kNullValue = nullptr; + static void Retain(T instance) { CFRetain(instance); } + static void Release(T instance) { CFRelease(instance); } +}; + +/// RAII-based smart pointer wrapper for CoreFoundation objects. +/// +/// CFRef takes over ownership of the object it wraps and ensures that retain +/// and release are called as appropriate on creation, assignment, and disposal. template class CFRef { public: - CFRef() : instance_(nullptr) {} + /// Creates a new null CFRef. + CFRef() : instance_(CFRefTraits::kNullValue) {} - // NOLINTNEXTLINE(google-explicit-constructor) - CFRef(T instance) : instance_(instance) {} + /// Takes over ownership of `instance`, which is expected to be already + /// retained. + explicit CFRef(T instance) : instance_(instance) {} + /// Copy ctor: Creates a retained copy of the CoreFoundation object owned by + /// `other`. CFRef(const CFRef& other) : instance_(other.instance_) { if (instance_) { - CFRetain(instance_); + CFRefTraits::Retain(instance_); } } + /// Move ctor: Takes over ownership of the CoreFoundation object owned + /// by `other`. The object owned by `other` is set to null. CFRef(CFRef&& other) : instance_(other.instance_) { - other.instance_ = nullptr; + other.instance_ = CFRefTraits::kNullValue; } + /// Takes over ownership of the CoreFoundation object owned by `other`. CFRef& operator=(CFRef&& other) { Reset(other.Release()); return *this; } + /// Releases the underlying CoreFoundation object, if non-null. ~CFRef() { - if (instance_ != nullptr) { - CFRelease(instance_); + if (instance_) { + CFRefTraits::Release(instance_); } - instance_ = nullptr; + instance_ = CFRefTraits::kNullValue; } - void Reset(T instance = nullptr) { - if (instance_ != nullptr) { - CFRelease(instance_); + /// Takes over ownership of `instance`, null by default. The object is + /// expected to be already retained if non-null. + /// + /// Releases the previous object, if non-null. + void Reset(T instance = CFRefTraits::kNullValue) { + if (instance_) { + CFRefTraits::Release(instance_); } - instance_ = instance; } + /// Retains a shared copy of `instance`. The previous object is released if + /// non-null. Has no effect if `instance` is the currently-held object. + void Retain(T instance = CFRefTraits::kNullValue) { + if (instance_ == instance) { + return; + } + if (instance) { + CFRefTraits::Retain(instance); + } + Reset(instance); + } + + /// Returns and transfers ownership of the underlying CoreFoundation object + /// to the caller. The caller is responsible for calling `CFRelease` when done + /// with the object. [[nodiscard]] T Release() { auto instance = instance_; - instance_ = nullptr; + instance_ = CFRefTraits::kNullValue; return instance; } + /// Returns the underlying CoreFoundation object. Ownership of the returned + /// object follows The Get Rule. + /// + /// See: + /// https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/uid/20001148-SW1 + T Get() const { return instance_; } + + /// Returns the underlying CoreFoundation object. Ownership of the returned + /// object follows The Get Rule. + /// + /// See: + /// https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/uid/20001148-SW1 // NOLINTNEXTLINE(google-explicit-constructor) operator T() const { return instance_; } - explicit operator bool() const { return instance_ != nullptr; } + /// Returns true if the underlying CoreFoundation object is non-null. + explicit operator bool() const { + return instance_ != CFRefTraits::kNullValue; + } private: T instance_; diff --git a/fml/platform/darwin/cf_utils_unittests.mm b/fml/platform/darwin/cf_utils_unittests.mm index 902850d03e9f3..d18ab0dad60cd 100644 --- a/fml/platform/darwin/cf_utils_unittests.mm +++ b/fml/platform/darwin/cf_utils_unittests.mm @@ -7,6 +7,21 @@ #include "flutter/testing/testing.h" namespace fml { + +// Test state used in CFTest.SupportsCustomRetainRelease. +struct CFRefTestState { + bool retain_called; + bool release_called; +}; + +// Template specialization used in CFTest.SupportsCustomRetainRelease. +template <> +struct CFRefTraits { + static constexpr CFRefTestState* kNullValue = nullptr; + static void Retain(CFRefTestState* instance) { instance->retain_called = true; } + static void Release(CFRefTestState* instance) { instance->release_called = true; } +}; + namespace testing { TEST(CFTest, CanCreateRefs) { @@ -60,5 +75,46 @@ } } +TEST(CFTest, GetReturnsUnderlyingObject) { + CFMutableStringRef cf_string = CFStringCreateMutable(kCFAllocatorDefault, 100u); + const CFIndex ref_count_before = CFGetRetainCount(cf_string); + CFRef string_ref(cf_string); + + CFMutableStringRef returned_string = string_ref.Get(); + const CFIndex ref_count_after = CFGetRetainCount(cf_string); + EXPECT_EQ(cf_string, returned_string); + EXPECT_EQ(ref_count_before, ref_count_after); +} + +TEST(CFTest, RetainSharesOwnership) { + CFMutableStringRef cf_string = CFStringCreateMutable(kCFAllocatorDefault, 100u); + const CFIndex ref_count_before = CFGetRetainCount(cf_string); + + CFRef string_ref; + string_ref.Retain(cf_string); + const CFIndex ref_count_after = CFGetRetainCount(cf_string); + + EXPECT_EQ(cf_string, string_ref); + EXPECT_EQ(ref_count_before + 1u, ref_count_after); +} + +TEST(CFTest, SupportsCustomRetainRelease) { + CFRefTestState instance{}; + CFRef ref(&instance); + ASSERT_EQ(ref.Get(), &instance); + ASSERT_FALSE(instance.retain_called); + ASSERT_FALSE(instance.release_called); + ref.Reset(); + ASSERT_EQ(ref.Get(), nullptr); + ASSERT_FALSE(instance.retain_called); + ASSERT_TRUE(instance.release_called); + + CFRefTestState other_instance{}; + ref.Retain(&other_instance); + ASSERT_EQ(ref.Get(), &other_instance); + ASSERT_TRUE(other_instance.retain_called); + ASSERT_FALSE(other_instance.release_called); +} + } // namespace testing } // namespace fml diff --git a/fml/platform/darwin/scoped_block.h b/fml/platform/darwin/scoped_block.h deleted file mode 100644 index 2e7cc91cc23e9..0000000000000 --- a/fml/platform/darwin/scoped_block.h +++ /dev/null @@ -1,46 +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. - -#ifndef FLUTTER_FML_PLATFORM_DARWIN_SCOPED_BLOCK_H_ -#define FLUTTER_FML_PLATFORM_DARWIN_SCOPED_BLOCK_H_ - -#include - -#include "flutter/fml/platform/darwin/scoped_typeref.h" - -#if defined(__has_feature) && __has_feature(objc_arc) -#define BASE_MAC_BRIDGE_CAST(TYPE, VALUE) (__bridge TYPE)(VALUE) -#else -#define BASE_MAC_BRIDGE_CAST(TYPE, VALUE) VALUE -#endif - -namespace fml { - -namespace internal { - -template -struct ScopedBlockTraits { - static B InvalidValue() { return nullptr; } - static B Retain(B block) { - return BASE_MAC_BRIDGE_CAST( - B, Block_copy(BASE_MAC_BRIDGE_CAST(const void*, block))); - } - static void Release(B block) { - Block_release(BASE_MAC_BRIDGE_CAST(const void*, block)); - } -}; - -} // namespace internal - -// ScopedBlock<> is patterned after ScopedCFTypeRef<>, but uses Block_copy() and -// Block_release() instead of CFRetain() and CFRelease(). - -template -using ScopedBlock = ScopedTypeRef>; - -} // namespace fml - -#undef BASE_MAC_BRIDGE_CAST - -#endif // FLUTTER_FML_PLATFORM_DARWIN_SCOPED_BLOCK_H_ diff --git a/fml/platform/darwin/scoped_nsobject.h b/fml/platform/darwin/scoped_nsobject.h deleted file mode 100644 index b6540c4a94a0d..0000000000000 --- a/fml/platform/darwin/scoped_nsobject.h +++ /dev/null @@ -1,248 +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. - -#ifndef FLUTTER_FML_PLATFORM_DARWIN_SCOPED_NSOBJECT_H_ -#define FLUTTER_FML_PLATFORM_DARWIN_SCOPED_NSOBJECT_H_ - -#include -#include - -// Include NSObject.h directly because Foundation.h pulls in many dependencies. -// (Approx 100k lines of code versus 1.5k for NSObject.h). scoped_nsobject gets -// singled out because it is most typically included from other header files. -#import - -#include "flutter/fml/macros.h" -#include "flutter/fml/platform/darwin/scoped_typeref.h" - -#if !defined(__has_feature) || !__has_feature(objc_arc) -@class NSAutoreleasePool; -#endif - -namespace fml { - -// scoped_nsobject<> is patterned after scoped_ptr<>, but maintains ownership -// of an NSObject subclass object. Style deviations here are solely for -// compatibility with scoped_ptr<>'s interface, with which everyone is already -// familiar. -// -// scoped_nsobject<> takes ownership of an object (in the constructor or in -// reset()) by taking over the caller's existing ownership claim. The caller -// must own the object it gives to scoped_nsobject<>, and relinquishes an -// ownership claim to that object. scoped_nsobject<> does not call -retain, -// callers have to call this manually if appropriate. -// -// scoped_nsprotocol<> has the same behavior as scoped_nsobject, but can be used -// with protocols. -// -// scoped_nsobject<> is not to be used for NSAutoreleasePools. For -// NSAutoreleasePools use ScopedNSAutoreleasePool from -// scoped_nsautorelease_pool.h instead. -// We check for bad uses of scoped_nsobject and NSAutoreleasePool at compile -// time with a template specialization (see below). -// -// If Automatic Reference Counting (aka ARC) is enabled then the ownership -// policy is not controllable by the user as ARC make it really difficult to -// transfer ownership (the reference passed to scoped_nsobject constructor is -// sunk by ARC and __attribute((ns_consumed)) appears to not work correctly -// with Objective-C++ see https://llvm.org/bugs/show_bug.cgi?id=27887). Due to -// that, the policy is always to |RETAIN| when using ARC. - -namespace internal { - -id ScopedNSProtocolTraitsRetain(__unsafe_unretained id obj) - __attribute((ns_returns_not_retained)); -id ScopedNSProtocolTraitsAutoRelease(__unsafe_unretained id obj) - __attribute((ns_returns_not_retained)); -void ScopedNSProtocolTraitsRelease(__unsafe_unretained id obj); - -// Traits for ScopedTypeRef<>. As this class may be compiled from file with -// Automatic Reference Counting enable or not all methods have annotation to -// enforce the same code generation in both case (in particular, the Retain -// method uses ns_returns_not_retained to prevent ARC to insert a -release -// call on the returned value and thus defeating the -retain). -template -struct ScopedNSProtocolTraits { - static NST InvalidValue() __attribute((ns_returns_not_retained)) { - return nil; - } - static NST Retain(__unsafe_unretained NST nst) - __attribute((ns_returns_not_retained)) { - return ScopedNSProtocolTraitsRetain(nst); - } - static void Release(__unsafe_unretained NST nst) { - ScopedNSProtocolTraitsRelease(nst); - } -}; - -} // namespace internal - -template -class scoped_nsprotocol - : public ScopedTypeRef> { - public: - using Traits = internal::ScopedNSProtocolTraits; - -#if !defined(__has_feature) || !__has_feature(objc_arc) - explicit scoped_nsprotocol(NST object = Traits::InvalidValue(), - scoped_policy::OwnershipPolicy policy = - scoped_policy::OwnershipPolicy::kAssume) - : ScopedTypeRef(object, policy) {} -#else - explicit scoped_nsprotocol(NST object = Traits::InvalidValue()) - : ScopedTypeRef(object, - scoped_policy::OwnershipPolicy::kRetain) {} -#endif - - // NOLINTNEXTLINE(google-explicit-constructor) - scoped_nsprotocol(const scoped_nsprotocol& that) - : ScopedTypeRef(that) {} - - template - explicit scoped_nsprotocol(const scoped_nsprotocol& that_as_subclass) - : ScopedTypeRef(that_as_subclass) {} - - // NOLINTNEXTLINE(google-explicit-constructor) - scoped_nsprotocol(scoped_nsprotocol&& that) - : ScopedTypeRef(std::move(that)) {} - - scoped_nsprotocol& operator=(const scoped_nsprotocol& that) { - ScopedTypeRef::operator=(that); - return *this; - } - -#if !defined(__has_feature) || !__has_feature(objc_arc) - void reset(NST object = Traits::InvalidValue(), - scoped_policy::OwnershipPolicy policy = - scoped_policy::OwnershipPolicy::kAssume) { - ScopedTypeRef::reset(object, policy); - } -#else - void reset(NST object = Traits::InvalidValue()) { - ScopedTypeRef::reset(object, - scoped_policy::OwnershipPolicy::kRetain); - } -#endif - - // Shift reference to the autorelease pool to be released later. - NST autorelease() __attribute((ns_returns_not_retained)) { - return internal::ScopedNSProtocolTraitsAutoRelease(this->release()); - } -}; - -// Free functions -template -void swap(scoped_nsprotocol& p1, scoped_nsprotocol& p2) { - p1.swap(p2); -} - -template -bool operator==(C p1, const scoped_nsprotocol& p2) { - return p1 == p2.get(); -} - -template -bool operator!=(C p1, const scoped_nsprotocol& p2) { - return p1 != p2.get(); -} - -template -class scoped_nsobject : public scoped_nsprotocol { - public: - using Traits = typename scoped_nsprotocol::Traits; - -#if !defined(__has_feature) || !__has_feature(objc_arc) - explicit scoped_nsobject(NST* object = Traits::InvalidValue(), - scoped_policy::OwnershipPolicy policy = - scoped_policy::OwnershipPolicy::kAssume) - : scoped_nsprotocol(object, policy) {} -#else - explicit scoped_nsobject(NST* object = Traits::InvalidValue()) - : scoped_nsprotocol(object) {} -#endif - - // NOLINTNEXTLINE(google-explicit-constructor) - scoped_nsobject(const scoped_nsobject& that) - : scoped_nsprotocol(that) {} - - template - explicit scoped_nsobject(const scoped_nsobject& that_as_subclass) - : scoped_nsprotocol(that_as_subclass) {} - - // NOLINTNEXTLINE(google-explicit-constructor) - scoped_nsobject(scoped_nsobject&& that) - : scoped_nsprotocol(std::move(that)) {} - - scoped_nsobject& operator=(const scoped_nsobject& that) { - scoped_nsprotocol::operator=(that); - return *this; - } - -#if !defined(__has_feature) || !__has_feature(objc_arc) - void reset(NST* object = Traits::InvalidValue(), - scoped_policy::OwnershipPolicy policy = - scoped_policy::OwnershipPolicy::kAssume) { - scoped_nsprotocol::reset(object, policy); - } -#else - void reset(NST* object = Traits::InvalidValue()) { - scoped_nsprotocol::reset(object); - } -#endif - -#if !defined(__has_feature) || !__has_feature(objc_arc) - static_assert(std::is_same::value == false, - "Use ScopedNSAutoreleasePool instead"); -#endif -}; - -// Specialization to make scoped_nsobject work. -template <> -class scoped_nsobject : public scoped_nsprotocol { - public: - using Traits = typename scoped_nsprotocol::Traits; - -#if !defined(__has_feature) || !__has_feature(objc_arc) - explicit scoped_nsobject(id object = Traits::InvalidValue(), - scoped_policy::OwnershipPolicy policy = - scoped_policy::OwnershipPolicy::kAssume) - : scoped_nsprotocol(object, policy) {} -#else - explicit scoped_nsobject(id object = Traits::InvalidValue()) - : scoped_nsprotocol(object) {} -#endif - - // NOLINTNEXTLINE(google-explicit-constructor) - scoped_nsobject(const scoped_nsobject& that) - : scoped_nsprotocol(that) {} - - template - explicit scoped_nsobject(const scoped_nsobject& that_as_subclass) - : scoped_nsprotocol(that_as_subclass) {} - - // NOLINTNEXTLINE(google-explicit-constructor) - scoped_nsobject(scoped_nsobject&& that) - : scoped_nsprotocol(std::move(that)) {} - - scoped_nsobject& operator=(const scoped_nsobject& that) { - scoped_nsprotocol::operator=(that); - return *this; - } - -#if !defined(__has_feature) || !__has_feature(objc_arc) - void reset(id object = Traits::InvalidValue(), - scoped_policy::OwnershipPolicy policy = - scoped_policy::OwnershipPolicy::kAssume) { - scoped_nsprotocol::reset(object, policy); - } -#else - void reset(id object = Traits::InvalidValue()) { - scoped_nsprotocol::reset(object); - } -#endif -}; - -} // namespace fml - -#endif // FLUTTER_FML_PLATFORM_DARWIN_SCOPED_NSOBJECT_H_ diff --git a/fml/platform/darwin/scoped_nsobject.mm b/fml/platform/darwin/scoped_nsobject.mm deleted file mode 100644 index 0645420b32df4..0000000000000 --- a/fml/platform/darwin/scoped_nsobject.mm +++ /dev/null @@ -1,24 +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. - -#include "flutter/fml/platform/darwin/scoped_nsobject.h" - -namespace fml { - -namespace internal { - -id ScopedNSProtocolTraitsRetain(id obj) { - return [obj retain]; -} - -id ScopedNSProtocolTraitsAutoRelease(id obj) { - return [obj autorelease]; -} - -void ScopedNSProtocolTraitsRelease(id obj) { - return [obj release]; -} - -} // namespace internal -} // namespace fml diff --git a/fml/platform/darwin/scoped_nsobject_arc_unittests.mm b/fml/platform/darwin/scoped_nsobject_arc_unittests.mm deleted file mode 100644 index cde666a00f354..0000000000000 --- a/fml/platform/darwin/scoped_nsobject_arc_unittests.mm +++ /dev/null @@ -1,131 +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. - -#include - -#import - -#include "flutter/fml/platform/darwin/scoped_nsobject.h" -#include "fml/logging.h" -#include "gtest/gtest.h" - -#if !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -namespace { - -template -CFIndex GetRetainCount(const fml::scoped_nsobject& nst) { - @autoreleasepool { - return CFGetRetainCount((__bridge CFTypeRef)nst.get()) - 1; - } -} - -#if __has_feature(objc_arc_weak) -TEST(ScopedNSObjectTestARC, DefaultPolicyIsRetain) { - __weak id o; - @autoreleasepool { - fml::scoped_nsprotocol p([[NSObject alloc] init]); - o = p.get(); - EXPECT_EQ(o, p.get()); - } - EXPECT_EQ(o, nil); -} -#endif - -TEST(ScopedNSObjectTestARC, ScopedNSObject) { - fml::scoped_nsobject p1([[NSObject alloc] init]); - @autoreleasepool { - EXPECT_TRUE(p1.get()); - EXPECT_TRUE(p1.get()); - } - EXPECT_EQ(1, GetRetainCount(p1)); - EXPECT_EQ(1, GetRetainCount(p1)); - fml::scoped_nsobject p2(p1); - @autoreleasepool { - EXPECT_EQ(p1.get(), p2.get()); - } - EXPECT_EQ(2, GetRetainCount(p1)); - p2.reset(); - EXPECT_EQ(nil, p2.get()); - EXPECT_EQ(1, GetRetainCount(p1)); - { - fml::scoped_nsobject p3 = p1; - @autoreleasepool { - EXPECT_EQ(p1.get(), p3.get()); - } - EXPECT_EQ(2, GetRetainCount(p1)); - p3 = p1; - @autoreleasepool { - EXPECT_EQ(p1.get(), p3.get()); - } - EXPECT_EQ(2, GetRetainCount(p1)); - } - EXPECT_EQ(1, GetRetainCount(p1)); - fml::scoped_nsobject p4; - @autoreleasepool { - p4 = fml::scoped_nsobject(p1.get()); - } - EXPECT_EQ(2, GetRetainCount(p1)); - @autoreleasepool { - EXPECT_TRUE(p1 == p1.get()); - EXPECT_TRUE(p1 == p1); - EXPECT_FALSE(p1 != p1); - EXPECT_FALSE(p1 != p1.get()); - } - fml::scoped_nsobject p5([[NSObject alloc] init]); - @autoreleasepool { - EXPECT_TRUE(p1 != p5); - EXPECT_TRUE(p1 != p5.get()); - EXPECT_FALSE(p1 == p5); - EXPECT_FALSE(p1 == p5.get()); - } - - fml::scoped_nsobject p6 = p1; - EXPECT_EQ(3, GetRetainCount(p6)); - @autoreleasepool { - p6.autorelease(); - EXPECT_EQ(nil, p6.get()); - } - EXPECT_EQ(2, GetRetainCount(p1)); -} - -TEST(ScopedNSObjectTestARC, ScopedNSObjectInContainer) { - fml::scoped_nsobject p([[NSObject alloc] init]); - @autoreleasepool { - EXPECT_TRUE(p.get()); - } - EXPECT_EQ(1, GetRetainCount(p)); - @autoreleasepool { - std::vector> objects; - objects.push_back(p); - EXPECT_EQ(2, GetRetainCount(p)); - @autoreleasepool { - EXPECT_EQ(p.get(), objects[0].get()); - } - objects.push_back(fml::scoped_nsobject([[NSObject alloc] init])); - @autoreleasepool { - EXPECT_TRUE(objects[1].get()); - } - EXPECT_EQ(1, GetRetainCount(objects[1])); - } - EXPECT_EQ(1, GetRetainCount(p)); -} - -TEST(ScopedNSObjectTestARC, ScopedNSObjectFreeFunctions) { - fml::scoped_nsobject p1([[NSObject alloc] init]); - id o1 = p1.get(); - EXPECT_TRUE(o1 == p1); - EXPECT_FALSE(o1 != p1); - fml::scoped_nsobject p2([[NSObject alloc] init]); - EXPECT_TRUE(o1 != p2); - EXPECT_FALSE(o1 == p2); - id o2 = p2.get(); - swap(p1, p2); - EXPECT_EQ(o2, p1.get()); - EXPECT_EQ(o1, p2.get()); -} - -} // namespace diff --git a/fml/platform/darwin/scoped_nsobject_unittests.mm b/fml/platform/darwin/scoped_nsobject_unittests.mm deleted file mode 100644 index 566fcfd988923..0000000000000 --- a/fml/platform/darwin/scoped_nsobject_unittests.mm +++ /dev/null @@ -1,106 +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. - -#include - -#include "flutter/fml/platform/darwin/scoped_nsautorelease_pool.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" -#include "gtest/gtest.h" - -namespace { - -// This is to suppress the bugprone-use-after-move warning. -// This strategy is recommanded here: -// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/use-after-move.html#silencing-erroneous-warnings -template -void IS_INITIALIZED(T&) {} - -TEST(ScopedNSObjectTest, ScopedNSObject) { - fml::scoped_nsobject p1([[NSObject alloc] init]); - ASSERT_TRUE(p1.get()); - ASSERT_EQ(1u, [p1 retainCount]); - fml::scoped_nsobject p2(p1); - ASSERT_EQ(p1.get(), p2.get()); - ASSERT_EQ(2u, [p1 retainCount]); - p2.reset(); - ASSERT_EQ(nil, p2.get()); - ASSERT_EQ(1u, [p1 retainCount]); - { - fml::scoped_nsobject p3 = p1; - ASSERT_EQ(p1.get(), p3.get()); - ASSERT_EQ(2u, [p1 retainCount]); - p3 = p1; - ASSERT_EQ(p1.get(), p3.get()); - ASSERT_EQ(2u, [p1 retainCount]); - } - ASSERT_EQ(1u, [p1 retainCount]); - fml::scoped_nsobject p4([p1.get() retain]); - ASSERT_EQ(2u, [p1 retainCount]); - ASSERT_TRUE(p1 == p1.get()); - ASSERT_TRUE(p1 == p1); - ASSERT_FALSE(p1 != p1); - ASSERT_FALSE(p1 != p1.get()); - fml::scoped_nsobject p5([[NSObject alloc] init]); - ASSERT_TRUE(p1 != p5); - ASSERT_TRUE(p1 != p5.get()); - ASSERT_FALSE(p1 == p5); - ASSERT_FALSE(p1 == p5.get()); - - fml::scoped_nsobject p6 = p1; - ASSERT_EQ(3u, [p6 retainCount]); - { - fml::ScopedNSAutoreleasePool pool; - p6.autorelease(); - ASSERT_EQ(nil, p6.get()); - ASSERT_EQ(3u, [p1 retainCount]); - } - ASSERT_EQ(2u, [p1 retainCount]); - - fml::scoped_nsobject p7([[NSObject alloc] init]); - fml::scoped_nsobject p8(std::move(p7)); - ASSERT_TRUE(p8); - ASSERT_EQ(1u, [p8 retainCount]); - IS_INITIALIZED(p7); - ASSERT_FALSE(p7.get()); -} - -// Instantiating scoped_nsobject<> with T=NSAutoreleasePool should trip a -// static_assert. -#if 0 -TEST(ScopedNSObjectTest, FailToCreateScopedNSObjectAutoreleasePool) { - fml::scoped_nsobject pool; -} -#endif - -TEST(ScopedNSObjectTest, ScopedNSObjectInContainer) { - fml::scoped_nsobject p([[NSObject alloc] init]); - ASSERT_TRUE(p.get()); - ASSERT_EQ(1u, [p retainCount]); - { - std::vector> objects; - objects.push_back(p); - ASSERT_EQ(2u, [p retainCount]); - ASSERT_EQ(p.get(), objects[0].get()); - objects.push_back(fml::scoped_nsobject([[NSObject alloc] init])); - ASSERT_TRUE(objects[1].get()); - ASSERT_EQ(1u, [objects[1] retainCount]); - } - ASSERT_EQ(1u, [p retainCount]); -} - -TEST(ScopedNSObjectTest, ScopedNSObjectFreeFunctions) { - fml::scoped_nsobject p1([[NSObject alloc] init]); - id o1 = p1.get(); - ASSERT_TRUE(o1 == p1); - ASSERT_FALSE(o1 != p1); - fml::scoped_nsobject p2([[NSObject alloc] init]); - ASSERT_TRUE(o1 != p2); - ASSERT_FALSE(o1 == p2); - id o2 = p2.get(); - swap(p1, p2); - ASSERT_EQ(o2, p1.get()); - ASSERT_EQ(o1, p2.get()); -} - -} // namespace diff --git a/fml/platform/darwin/scoped_policy.h b/fml/platform/darwin/scoped_policy.h deleted file mode 100644 index fb4985871adae..0000000000000 --- a/fml/platform/darwin/scoped_policy.h +++ /dev/null @@ -1,25 +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. - -#ifndef FLUTTER_FML_PLATFORM_DARWIN_SCOPED_POLICY_H_ -#define FLUTTER_FML_PLATFORM_DARWIN_SCOPED_POLICY_H_ - -namespace fml { -namespace scoped_policy { - -// Defines the ownership policy for a scoped object. -enum OwnershipPolicy { - // The scoped object takes ownership of an object by taking over an existing - // ownership claim. - kAssume, - - // The scoped object will retain the the object and any initial ownership is - // not changed. - kRetain -}; - -} // namespace scoped_policy -} // namespace fml - -#endif // FLUTTER_FML_PLATFORM_DARWIN_SCOPED_POLICY_H_ diff --git a/fml/platform/darwin/scoped_typeref.h b/fml/platform/darwin/scoped_typeref.h deleted file mode 100644 index bed7994487455..0000000000000 --- a/fml/platform/darwin/scoped_typeref.h +++ /dev/null @@ -1,146 +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. - -#ifndef FLUTTER_FML_PLATFORM_DARWIN_SCOPED_TYPEREF_H_ -#define FLUTTER_FML_PLATFORM_DARWIN_SCOPED_TYPEREF_H_ - -#include "flutter/fml/platform/darwin/scoped_policy.h" - -namespace fml { - -// ScopedTypeRef<> is patterned after std::unique_ptr<>, but maintains ownership -// of a reference to any type that is maintained by Retain and Release methods. -// -// The Traits structure must provide the Retain and Release methods for type T. -// A default ScopedTypeRefTraits is used but not defined, and should be defined -// for each type to use this interface. For example, an appropriate definition -// of ScopedTypeRefTraits for CGLContextObj would be: -// -// template<> -// struct ScopedTypeRefTraits { -// static CGLContextObj InvalidValue() { return nullptr; } -// static CGLContextObj Retain(CGLContextObj object) { -// CGLContextRetain(object); -// return object; -// } -// static void Release(CGLContextObj object) { CGLContextRelease(object); } -// }; -// -// For the many types that have pass-by-pointer create functions, the function -// InitializeInto() is provided to allow direct initialization and assumption -// of ownership of the object. For example, continuing to use the above -// CGLContextObj specialization: -// -// fml::ScopedTypeRef context; -// CGLCreateContext(pixel_format, share_group, context.InitializeInto()); -// -// For initialization with an existing object, the caller may specify whether -// the ScopedTypeRef<> being initialized is assuming the caller's existing -// ownership of the object (and should not call Retain in initialization) or if -// it should not assume this ownership and must create its own (by calling -// Retain in initialization). This behavior is based on the |policy| parameter, -// with |kAssume| for the former and |kRetain| for the latter. The default -// policy is to |kAssume|. - -template -struct ScopedTypeRefTraits; - -template > -class ScopedTypeRef { - public: - typedef T element_type; - - explicit ScopedTypeRef( - __unsafe_unretained T object = Traits::InvalidValue(), - fml::scoped_policy::OwnershipPolicy policy = fml::scoped_policy::kAssume) - : object_(object) { - if (object_ && policy == fml::scoped_policy::kRetain) { - object_ = Traits::Retain(object_); - } - } - - // NOLINTNEXTLINE(google-explicit-constructor) - ScopedTypeRef(const ScopedTypeRef& that) : object_(that.object_) { - if (object_) { - object_ = Traits::Retain(object_); - } - } - - // This allows passing an object to a function that takes its superclass. - template - explicit ScopedTypeRef(const ScopedTypeRef& that_as_subclass) - : object_(that_as_subclass.get()) { - if (object_) { - object_ = Traits::Retain(object_); - } - } - - // NOLINTNEXTLINE(google-explicit-constructor) - ScopedTypeRef(ScopedTypeRef&& that) : object_(that.object_) { - that.object_ = Traits::InvalidValue(); - } - - ~ScopedTypeRef() { - if (object_) { - Traits::Release(object_); - } - } - - ScopedTypeRef& operator=(const ScopedTypeRef& that) { - reset(that.get(), fml::scoped_policy::kRetain); - return *this; - } - - // This is to be used only to take ownership of objects that are created - // by pass-by-pointer create functions. To enforce this, require that the - // object be reset to NULL before this may be used. - [[nodiscard]] T* InitializeInto() { - FML_DCHECK(!object_); - return &object_; - } - - void reset(__unsafe_unretained T object = Traits::InvalidValue(), - fml::scoped_policy::OwnershipPolicy policy = - fml::scoped_policy::kAssume) { - if (object && policy == fml::scoped_policy::kRetain) { - object = Traits::Retain(object); - } - if (object_) { - Traits::Release(object_); - } - object_ = object; - } - - bool operator==(__unsafe_unretained T that) const { return object_ == that; } - - bool operator!=(__unsafe_unretained T that) const { return object_ != that; } - - // NOLINTNEXTLINE(google-explicit-constructor) - operator T() const __attribute((ns_returns_not_retained)) { return object_; } - - T get() const __attribute((ns_returns_not_retained)) { return object_; } - - void swap(ScopedTypeRef& that) { - __unsafe_unretained T temp = that.object_; - that.object_ = object_; - object_ = temp; - } - - protected: - // ScopedTypeRef<>::release() is like std::unique_ptr<>::release. It is NOT - // a wrapper for Release(). To force a ScopedTypeRef<> object to call - // Release(), use ScopedTypeRef<>::reset(). - [[nodiscard]] T release() __attribute((ns_returns_not_retained)) { - __unsafe_unretained T temp = object_; - object_ = Traits::InvalidValue(); - return temp; - } - - private: - __unsafe_unretained T object_; -}; - -} // namespace fml - -#endif // FLUTTER_FML_PLATFORM_DARWIN_SCOPED_TYPEREF_H_ diff --git a/fml/platform/darwin/weak_nsobject.h b/fml/platform/darwin/weak_nsobject.h deleted file mode 100644 index 92120e18ebb31..0000000000000 --- a/fml/platform/darwin/weak_nsobject.h +++ /dev/null @@ -1,282 +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. - -#ifndef FLUTTER_FML_PLATFORM_DARWIN_WEAK_NSOBJECT_H_ -#define FLUTTER_FML_PLATFORM_DARWIN_WEAK_NSOBJECT_H_ - -#import -#import - -#include - -#include - -#include "flutter/fml/logging.h" -#include "flutter/fml/memory/ref_counted.h" -#include "flutter/fml/memory/ref_ptr.h" -#include "flutter/fml/memory/thread_checker.h" - -namespace debug { -struct DebugThreadChecker { - FML_DECLARE_THREAD_CHECKER(checker); -}; -} // namespace debug - -// WeakNSObject<> is patterned after scoped_nsobject<>, but instead of -// maintaining ownership of an NSObject subclass object, it will nil itself out -// when the object is deallocated. -// -// WeakNSProtocol<> has the same behavior as WeakNSObject, but can be used -// with protocols. -// -// Example usage (fml::WeakNSObject): -// WeakNSObjectFactory factory([[Foo alloc] init]); -// WeakNSObject weak_foo; // No pointer -// weak_foo = factory.GetWeakNSObject() // Now a weak reference is kept. -// [weak_foo description]; // Returns [foo description]. -// foo.reset(); // The reference is released. -// [weak_foo description]; // Returns nil, as weak_foo is pointing to nil. -// -// -// Implementation wise a WeakNSObject keeps a reference to a refcounted -// WeakContainer. There is one unique instance of a WeakContainer per watched -// NSObject, this relationship is maintained via the ObjectiveC associated -// object API, indirectly via an ObjectiveC CRBWeakNSProtocolSentinel class. -// -// Threading restrictions: -// - Several WeakNSObject pointing to the same underlying object must all be -// created and dereferenced on the same thread; -// - thread safety is enforced by the implementation, except: -// - it is allowed to destroy a WeakNSObject on any thread; -// - the implementation assumes that the tracked object will be released on the -// same thread that the WeakNSObject is created on. -// -// fml specifics: -// WeakNSObjects can only originate from a |WeakNSObjectFactory| (see below), though WeakNSObjects -// are copyable and movable. -// -// WeakNSObjects are not in general thread-safe. They may only be *used* on -// a single thread, namely the same thread as the "originating" -// |WeakNSObjectFactory| (which can invalidate the WeakNSObjects that it -// generates). -// -// However, WeakNSObject may be passed to other threads, reset on other -// threads, or destroyed on other threads. They may also be reassigned on -// other threads (in which case they should then only be used on the thread -// corresponding to the new "originating" |WeakNSObjectFactory|). -namespace fml { - -// Forward declaration, so |WeakNSObject| can friend it. -template -class WeakNSObjectFactory; - -// WeakContainer keeps a weak pointer to an object and clears it when it -// receives nullify() from the object's sentinel. -class WeakContainer : public fml::RefCountedThreadSafe { - public: - explicit WeakContainer(id object, const debug::DebugThreadChecker& checker); - - id object() { - CheckThreadSafety(); - return object_; - } - - void nullify() { object_ = nil; } - - private: - friend fml::RefCountedThreadSafe; - ~WeakContainer(); - - __unsafe_unretained id object_; - - void CheckThreadSafety() const { FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker); } - -// checker_ is unused in non-unopt mode. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-private-field" - debug::DebugThreadChecker checker_; -#pragma clang diagnostic pop -}; - -} // namespace fml - -// Sentinel for observing the object contained in the weak pointer. The object -// will be deleted when the weak object is deleted and will notify its -// container. -@interface CRBWeakNSProtocolSentinel : NSObject -// Return the only associated container for this object. There can be only one. -// Will return null if object is nil . -+ (fml::RefPtr)containerForObject:(id)object - threadChecker:(debug::DebugThreadChecker)checker; -@end - -namespace fml { - -// Base class for all WeakNSObject derivatives. -template -class WeakNSProtocol { - public: - WeakNSProtocol() = default; - - // A WeakNSProtocol object can be copied on one thread and used on - // another. - WeakNSProtocol(const WeakNSProtocol& that) - : container_(that.container_), checker_(that.checker_) {} - - ~WeakNSProtocol() = default; - - void reset() { - container_ = [CRBWeakNSProtocolSentinel containerForObject:nil threadChecker:checker_]; - } - - NST get() const { - CheckThreadSafety(); - if (!container_.get()) { - return nil; - } - return container_->object(); - } - - WeakNSProtocol& operator=(const WeakNSProtocol& that) { - // A WeakNSProtocol object can be copied on one thread and used on - // another. - container_ = that.container_; - checker_ = that.checker_; - return *this; - } - - bool operator==(NST that) const { - CheckThreadSafety(); - return get() == that; - } - - bool operator!=(NST that) const { - CheckThreadSafety(); - return get() != that; - } - - // This appears to be intentional to allow truthiness? - // NOLINTNEXTLINE(google-explicit-constructor) - operator NST() const { - CheckThreadSafety(); - return get(); - } - - protected: - friend class WeakNSObjectFactory; - - explicit WeakNSProtocol(RefPtr container, - const debug::DebugThreadChecker& checker) - : container_(std::move(container)), checker_(checker) {} - - // Refecounted reference to the container tracking the ObjectiveC object this - // class encapsulates. - RefPtr container_; - void CheckThreadSafety() const { FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker); } - - debug::DebugThreadChecker checker_; -}; - -// Free functions -template -bool operator==(NST p1, const WeakNSProtocol& p2) { - return p1 == p2.get(); -} - -template -bool operator!=(NST p1, const WeakNSProtocol& p2) { - return p1 != p2.get(); -} - -template -class WeakNSObject : public WeakNSProtocol { - public: - WeakNSObject() = default; - WeakNSObject(const WeakNSObject& that) : WeakNSProtocol(that) {} - - WeakNSObject& operator=(const WeakNSObject& that) { - WeakNSProtocol::operator=(that); - return *this; - } - - private: - friend class WeakNSObjectFactory; - - explicit WeakNSObject(RefPtr container, debug::DebugThreadChecker checker) - : WeakNSProtocol(container, checker) {} -}; - -// Specialization to make WeakNSObject work. -template <> -class WeakNSObject : public WeakNSProtocol { - public: - WeakNSObject() = default; - WeakNSObject(const WeakNSObject& that) : WeakNSProtocol(that) {} - - WeakNSObject& operator=(const WeakNSObject& that) { - WeakNSProtocol::operator=(that); - return *this; - } - - private: - friend class WeakNSObjectFactory; - - explicit WeakNSObject(const RefPtr& container, - const debug::DebugThreadChecker& checker) - : WeakNSProtocol(container, checker) {} -}; - -// Class that produces (valid) |WeakNSObject|s. Typically, this is used as a -// member variable of |NST| (preferably the last one -- see below), and |NST|'s -// methods control how WeakNSObjects to it are vended. This class is not -// thread-safe, and should only be created, destroyed and used on a single -// thread. -// -// Example: -// -// ```objc -// @implementation Controller { -// std::unique_ptr> _weakFactory; -// } -// -// - (instancetype)init { -// self = [super init]; -// _weakFactory = std::make_unique>(self) -// } - -// - (fml::WeakNSObject) { -// return _weakFactory->GetWeakNSObject() -// } -// -// @end -// ``` -template -class WeakNSObjectFactory { - public: - explicit WeakNSObjectFactory(NST* object) { - FML_DCHECK(object); - container_ = [CRBWeakNSProtocolSentinel containerForObject:object threadChecker:checker_]; - } - - ~WeakNSObjectFactory() { CheckThreadSafety(); } - - // Gets a new weak pointer, which will be valid until this object is - // destroyed.e - WeakNSObject GetWeakNSObject() const { return WeakNSObject(container_, checker_); } - - private: - // Refecounted reference to the container tracking the ObjectiveC object this - // class encapsulates. - RefPtr container_; - - void CheckThreadSafety() const { FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker); } - - debug::DebugThreadChecker checker_; - - FML_DISALLOW_COPY_AND_ASSIGN(WeakNSObjectFactory); -}; - -} // namespace fml - -#endif // FLUTTER_FML_PLATFORM_DARWIN_WEAK_NSOBJECT_H_ diff --git a/fml/platform/darwin/weak_nsobject.mm b/fml/platform/darwin/weak_nsobject.mm deleted file mode 100644 index e255515cafa21..0000000000000 --- a/fml/platform/darwin/weak_nsobject.mm +++ /dev/null @@ -1,67 +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. - -#include "flutter/fml/platform/darwin/weak_nsobject.h" -#include "flutter/fml/platform/darwin/scoped_nsautorelease_pool.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" - -namespace { -// The key needed by objc_setAssociatedObject. -char sentinelObserverKey_; -} // namespace - -namespace fml { - -WeakContainer::WeakContainer(id object, const debug::DebugThreadChecker& checker) - : object_(object), checker_(checker) {} - -WeakContainer::~WeakContainer() {} - -} // namespace fml - -@interface CRBWeakNSProtocolSentinel () -// Container to notify on dealloc. -@property(readonly, assign) fml::RefPtr container; -// Designed initializer. -- (id)initWithContainer:(fml::RefPtr)container; -@end - -@implementation CRBWeakNSProtocolSentinel - -+ (fml::RefPtr)containerForObject:(id)object - threadChecker:(debug::DebugThreadChecker)checker { - if (object == nil) { - return nullptr; - } - // The autoreleasePool is needed here as the call to objc_getAssociatedObject - // returns an autoreleased object which is better released sooner than later. - fml::ScopedNSAutoreleasePool pool; - CRBWeakNSProtocolSentinel* sentinel = objc_getAssociatedObject(object, &sentinelObserverKey_); - if (!sentinel) { - fml::scoped_nsobject newSentinel([[CRBWeakNSProtocolSentinel alloc] - initWithContainer:AdoptRef(new fml::WeakContainer(object, checker))]); - sentinel = newSentinel; - objc_setAssociatedObject(object, &sentinelObserverKey_, sentinel, OBJC_ASSOCIATION_RETAIN); - // The retain count is 2. One retain is due to the alloc, the other to the - // association with the weak object. - FML_DCHECK(2u == [sentinel retainCount]); - } - return [sentinel container]; -} - -- (id)initWithContainer:(fml::RefPtr)container { - FML_DCHECK(container.get()); - self = [super init]; - if (self) { - _container = container; - } - return self; -} - -- (void)dealloc { - _container->nullify(); - [super dealloc]; -} - -@end diff --git a/fml/platform/darwin/weak_nsobject_arc_unittests.mm b/fml/platform/darwin/weak_nsobject_arc_unittests.mm deleted file mode 100644 index 7e09b746d5d89..0000000000000 --- a/fml/platform/darwin/weak_nsobject_arc_unittests.mm +++ /dev/null @@ -1,121 +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. - -#include "flutter/fml/message_loop.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" -#include "flutter/fml/platform/darwin/weak_nsobject.h" -#include "flutter/fml/task_runner.h" -#include "flutter/fml/thread.h" -#include "gtest/gtest.h" - -#if !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -namespace fml { -namespace { - -TEST(WeakNSObjectTestARC, WeakNSObject) { - scoped_nsobject p1; - WeakNSObject w1; - @autoreleasepool { - p1.reset(([[NSObject alloc] init])); - WeakNSObjectFactory factory(p1.get()); - w1 = factory.GetWeakNSObject(); - EXPECT_TRUE(w1); - p1.reset(); - } - EXPECT_FALSE(w1); -} - -TEST(WeakNSObjectTestARC, MultipleWeakNSObject) { - scoped_nsobject p1; - WeakNSObject w1; - WeakNSObject w2; - @autoreleasepool { - p1.reset([[NSObject alloc] init]); - WeakNSObjectFactory factory(p1.get()); - w1 = factory.GetWeakNSObject(); - // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) - w2 = w1; - EXPECT_TRUE(w1); - EXPECT_TRUE(w2); - EXPECT_TRUE(w1.get() == w2.get()); - p1.reset(); - } - EXPECT_FALSE(w1); - EXPECT_FALSE(w2); -} - -TEST(WeakNSObjectTestARC, WeakNSObjectDies) { - scoped_nsobject p1([[NSObject alloc] init]); - WeakNSObjectFactory factory(p1.get()); - { - WeakNSObject w1 = factory.GetWeakNSObject(); - EXPECT_TRUE(w1); - } -} - -TEST(WeakNSObjectTestARC, WeakNSObjectReset) { - scoped_nsobject p1([[NSObject alloc] init]); - WeakNSObjectFactory factory(p1.get()); - WeakNSObject w1 = factory.GetWeakNSObject(); - EXPECT_TRUE(w1); - w1.reset(); - EXPECT_FALSE(w1); - EXPECT_TRUE(p1); - EXPECT_TRUE([p1 description]); -} - -TEST(WeakNSObjectTestARC, WeakNSObjectEmpty) { - scoped_nsobject p1; - WeakNSObject w1; - @autoreleasepool { - scoped_nsobject p1([[NSObject alloc] init]); - EXPECT_FALSE(w1); - WeakNSObjectFactory factory(p1.get()); - w1 = factory.GetWeakNSObject(); - EXPECT_TRUE(w1); - p1.reset(); - } - EXPECT_FALSE(w1); -} - -TEST(WeakNSObjectTestARC, WeakNSObjectCopy) { - scoped_nsobject p1; - WeakNSObject w1; - WeakNSObject w2; - @autoreleasepool { - scoped_nsobject p1([[NSObject alloc] init]); - WeakNSObjectFactory factory(p1.get()); - w1 = factory.GetWeakNSObject(); - // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) - w2 = w1; - EXPECT_TRUE(w1); - EXPECT_TRUE(w2); - p1.reset(); - } - EXPECT_FALSE(w1); - EXPECT_FALSE(w2); -} - -TEST(WeakNSObjectTestARC, WeakNSObjectAssignment) { - scoped_nsobject p1; - WeakNSObject w1; - WeakNSObject w2; - @autoreleasepool { - scoped_nsobject p1([[NSObject alloc] init]); - WeakNSObjectFactory factory(p1.get()); - w1 = factory.GetWeakNSObject(); - // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) - w2 = w1; - EXPECT_TRUE(w1); - EXPECT_TRUE(w2); - p1.reset(); - } - EXPECT_FALSE(w1); - EXPECT_FALSE(w2); -} -} // namespace -} // namespace fml diff --git a/fml/platform/darwin/weak_nsobject_unittests.mm b/fml/platform/darwin/weak_nsobject_unittests.mm deleted file mode 100644 index 0b93cdf449ec7..0000000000000 --- a/fml/platform/darwin/weak_nsobject_unittests.mm +++ /dev/null @@ -1,95 +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. - -#include "flutter/fml/message_loop.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" -#include "flutter/fml/platform/darwin/weak_nsobject.h" -#include "flutter/fml/task_runner.h" -#include "flutter/fml/thread.h" -#include "gtest/gtest.h" - -namespace fml { -namespace { - -TEST(WeakNSObjectTest, WeakNSObject) { - scoped_nsobject p1([[NSObject alloc] init]); - WeakNSObjectFactory factory(p1.get()); - WeakNSObject w1 = factory.GetWeakNSObject(); - EXPECT_TRUE(w1); - p1.reset(); - EXPECT_FALSE(w1); -} - -TEST(WeakNSObjectTest, MultipleWeakNSObject) { - scoped_nsobject p1([[NSObject alloc] init]); - WeakNSObjectFactory factory(p1.get()); - WeakNSObject w1 = factory.GetWeakNSObject(); - // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) - WeakNSObject w2(w1); - EXPECT_TRUE(w1); - EXPECT_TRUE(w2); - EXPECT_TRUE(w1.get() == w2.get()); - p1.reset(); - EXPECT_FALSE(w1); - EXPECT_FALSE(w2); -} - -TEST(WeakNSObjectTest, WeakNSObjectDies) { - scoped_nsobject p1([[NSObject alloc] init]); - WeakNSObjectFactory factory(p1.get()); - { - WeakNSObject w1 = factory.GetWeakNSObject(); - EXPECT_TRUE(w1); - } -} - -TEST(WeakNSObjectTest, WeakNSObjectReset) { - scoped_nsobject p1([[NSObject alloc] init]); - WeakNSObjectFactory factory(p1.get()); - WeakNSObject w1 = factory.GetWeakNSObject(); - EXPECT_TRUE(w1); - w1.reset(); - EXPECT_FALSE(w1); - EXPECT_TRUE(p1); - EXPECT_TRUE([p1 description]); -} - -TEST(WeakNSObjectTest, WeakNSObjectEmpty) { - scoped_nsobject p1([[NSObject alloc] init]); - WeakNSObject w1; - EXPECT_FALSE(w1); - WeakNSObjectFactory factory(p1.get()); - w1 = factory.GetWeakNSObject(); - EXPECT_TRUE(w1); - p1.reset(); - EXPECT_FALSE(w1); -} - -TEST(WeakNSObjectTest, WeakNSObjectCopy) { - scoped_nsobject p1([[NSObject alloc] init]); - WeakNSObjectFactory factory(p1.get()); - WeakNSObject w1 = factory.GetWeakNSObject(); - // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) - WeakNSObject w2(w1); - EXPECT_TRUE(w1); - EXPECT_TRUE(w2); - p1.reset(); - EXPECT_FALSE(w1); - EXPECT_FALSE(w2); -} - -TEST(WeakNSObjectTest, WeakNSObjectAssignment) { - scoped_nsobject p1([[NSObject alloc] init]); - WeakNSObjectFactory factory(p1.get()); - WeakNSObject w1 = factory.GetWeakNSObject(); - // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) - WeakNSObject w2 = w1; - EXPECT_TRUE(w1); - EXPECT_TRUE(w2); - p1.reset(); - EXPECT_FALSE(w1); - EXPECT_FALSE(w2); -} -} // namespace -} // namespace fml diff --git a/fml/synchronization/sync_switch.cc b/fml/synchronization/sync_switch.cc index 463b6a08d75ad..9421e30e0461b 100644 --- a/fml/synchronization/sync_switch.cc +++ b/fml/synchronization/sync_switch.cc @@ -5,6 +5,7 @@ #include "flutter/fml/synchronization/sync_switch.h" #include +#include namespace fml { diff --git a/fml/task_runner.cc b/fml/task_runner.cc index 5f9dd019c685e..8920a8f73ab25 100644 --- a/fml/task_runner.cc +++ b/fml/task_runner.cc @@ -52,6 +52,7 @@ bool TaskRunner::RunsTasksOnCurrentThread() { loop_queue_id); } +// static void TaskRunner::RunNowOrPostTask(const fml::RefPtr& runner, const fml::closure& task) { FML_DCHECK(runner); @@ -62,4 +63,20 @@ void TaskRunner::RunNowOrPostTask(const fml::RefPtr& runner, } } +// static +void TaskRunner::RunNowAndFlushMessages( + const fml::RefPtr& runner, + const fml::closure& task) { + FML_DCHECK(runner); + if (runner->RunsTasksOnCurrentThread()) { + task(); + // Post an empty task to make the UI message loop run its task observers. + // The observers will execute any Dart microtasks queued by the platform + // message handler. + runner->PostTask([] {}); + } else { + runner->PostTask(task); + } +} + } // namespace fml diff --git a/fml/task_runner.h b/fml/task_runner.h index 885c1b3efb566..6bc1c1a06cd8a 100644 --- a/fml/task_runner.h +++ b/fml/task_runner.h @@ -62,6 +62,14 @@ class TaskRunner : public fml::RefCountedThreadSafe, static void RunNowOrPostTask(const fml::RefPtr& runner, const fml::closure& task); + /// Like RunNowOrPostTask, except that if the task can be immediately + /// executed, an empty task will still be posted to the runner afterwards. + /// + /// This is used to ensure that messages posted to Dart from the platform + /// thread always flush the Dart event loop. + static void RunNowAndFlushMessages(const fml::RefPtr& runner, + const fml::closure& task); + protected: explicit TaskRunner(fml::RefPtr loop); diff --git a/impeller/BUILD.gn b/impeller/BUILD.gn index 3bd0a09c828a2..0f8b679abea20 100644 --- a/impeller/BUILD.gn +++ b/impeller/BUILD.gn @@ -60,7 +60,6 @@ impeller_component("impeller_unittests") { "core:allocator_unittests", "display_list:skia_conversions_unittests", "geometry:geometry_unittests", - "renderer/backend/metal:metal_unittests", "runtime_stage:runtime_stage_unittests", "shader_archive:shader_archive_unittests", "tessellator:tessellator_unittests", @@ -80,6 +79,10 @@ impeller_component("impeller_unittests") { ] } + if (impeller_enable_metal) { + deps += [ "//flutter/impeller/renderer/backend/metal:metal_unittests" ] + } + if (impeller_enable_vulkan) { deps += [ "//flutter/impeller/renderer/backend/vulkan:vulkan_unittests" ] } diff --git a/impeller/README.md b/impeller/README.md index 981a9b161c6b3..4cf82c5913f5e 100644 --- a/impeller/README.md +++ b/impeller/README.md @@ -229,6 +229,17 @@ To your `Info.plist` file, add under the top-level `` tag: ``` +## Embedding Standalone Impeller + +Impeller is designed to work best when used by Flutter. Most of the teams +efforts go into being great at that use-case. But, standalone Impeller can be +used to perform accelerated rendering in most environments without any Flutter +dependencies. + +Impeller provides a standalone SDK. The SDK exposes a single-header C API with +no platform dependencies. [Prebuilts for major platforms, documentation, +examples, are available](toolkit/interop/README.md). + ## Documentation, References, and Additional Reading * [Frequently Asked Questions](docs/faq.md) diff --git a/impeller/compiler/code_gen_template.h b/impeller/compiler/code_gen_template.h index 6c6b90e763fc2..15246a28d5d67 100644 --- a/impeller/compiler/code_gen_template.h +++ b/impeller/compiler/code_gen_template.h @@ -159,7 +159,7 @@ struct {{camel_case(shader_name)}}{{camel_case(shader_stage)}}Shader { {% endfor %}) { return {{ proto.args.0.argument_name }}.BindResource({% for arg in proto.args %} {% if loop.is_first %} -{{to_shader_stage(shader_stage)}}, {{ proto.descriptor_type }}, kResource{{ proto.name }}, kMetadata{{ proto.name }}, {% else %} +{{to_shader_stage(shader_stage)}}, {{ proto.descriptor_type }}, kResource{{ proto.name }}, &kMetadata{{ proto.name }}, {% else %} std::move({{ arg.argument_name }}){% if not loop.is_last %}, {% endif %} {% endif %} {% endfor %}); diff --git a/impeller/compiler/compiler.cc b/impeller/compiler/compiler.cc index 6123ad3f15982..9d7fdbf152f24 100644 --- a/impeller/compiler/compiler.cc +++ b/impeller/compiler/compiler.cc @@ -26,6 +26,12 @@ namespace impeller { namespace compiler { +namespace { +constexpr const char* kEGLImageExternalExtension = "GL_OES_EGL_image_external"; +constexpr const char* kEGLImageExternalExtension300 = + "GL_OES_EGL_image_external_essl3"; +} // namespace + static uint32_t ParseMSLVersion(const std::string& msl_version) { std::stringstream sstream(msl_version); std::string version_part; @@ -147,7 +153,11 @@ static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR& ir, // incompatible with ES 310+. for (auto& id : ir.ids_for_constant_or_variable) { if (StringStartsWith(ir.get_name(id), kExternalTexturePrefix)) { - gl_compiler->require_extension("GL_OES_EGL_image_external"); + if (source_options.gles_language_version >= 300) { + gl_compiler->require_extension(kEGLImageExternalExtension300); + } else { + gl_compiler->require_extension(kEGLImageExternalExtension); + } break; } } @@ -156,11 +166,15 @@ static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR& ir, sl_options.force_zero_initialized_variables = true; sl_options.vertex.fixup_clipspace = true; if (source_options.target_platform == TargetPlatform::kOpenGLES || - source_options.target_platform == TargetPlatform::kRuntimeStageGLES) { + source_options.target_platform == TargetPlatform::kRuntimeStageGLES || + source_options.target_platform == TargetPlatform::kRuntimeStageGLES3) { sl_options.version = source_options.gles_language_version > 0 ? source_options.gles_language_version : 100; sl_options.es = true; + if (source_options.target_platform == TargetPlatform::kRuntimeStageGLES3) { + sl_options.version = 300; + } if (source_options.require_framebuffer_fetch && source_options.type == SourceType::kFragmentShader) { gl_compiler->remap_ext_framebuffer_fetch(0, 0, true); @@ -202,6 +216,7 @@ static bool EntryPointMustBeNamedMain(TargetPlatform platform) { case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kRuntimeStageGLES3: return true; } FML_UNREACHABLE(); @@ -224,6 +239,7 @@ static CompilerBackend CreateCompiler(const spirv_cross::ParsedIR& ir, case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kRuntimeStageGLES3: compiler = CreateGLSLCompiler(ir, source_options); break; case TargetPlatform::kSkSL: @@ -317,7 +333,8 @@ Compiler::Compiler(const std::shared_ptr& source_mapping, spirv_options.target = target; } break; case TargetPlatform::kRuntimeStageMetal: - case TargetPlatform::kRuntimeStageGLES: { + case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kRuntimeStageGLES3: { SPIRVCompilerTargetEnv target; target.env = shaderc_target_env::shaderc_target_env_opengl; diff --git a/impeller/compiler/impellerc_main.cc b/impeller/compiler/impellerc_main.cc index a0535a2882b04..e4b54350378f0 100644 --- a/impeller/compiler/impellerc_main.cc +++ b/impeller/compiler/impellerc_main.cc @@ -207,6 +207,7 @@ static bool OutputDepfile(const Compiler& compiler, const Switches& switches) { case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageMetal: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kRuntimeStageGLES3: case TargetPlatform::kRuntimeStageVulkan: case TargetPlatform::kSkSL: case TargetPlatform::kVulkan: diff --git a/impeller/compiler/reflector.cc b/impeller/compiler/reflector.cc index 23792ea6924ba..5f9ba45eb1f48 100644 --- a/impeller/compiler/reflector.cc +++ b/impeller/compiler/reflector.cc @@ -312,6 +312,8 @@ static std::optional GetRuntimeStageBackend( return RuntimeStageBackend::kMetal; case TargetPlatform::kRuntimeStageGLES: return RuntimeStageBackend::kOpenGLES; + case TargetPlatform::kRuntimeStageGLES3: + return RuntimeStageBackend::kOpenGLES3; case TargetPlatform::kRuntimeStageVulkan: return RuntimeStageBackend::kVulkan; case TargetPlatform::kSkSL: diff --git a/impeller/compiler/runtime_stage_data.cc b/impeller/compiler/runtime_stage_data.cc index b45926305f55b..c7b28a6184fc4 100644 --- a/impeller/compiler/runtime_stage_data.cc +++ b/impeller/compiler/runtime_stage_data.cc @@ -204,6 +204,8 @@ static std::string RuntimeStageBackendToString(RuntimeStageBackend backend) { return "opengles"; case RuntimeStageBackend::kVulkan: return "vulkan"; + case RuntimeStageBackend::kOpenGLES3: + return "opengles3"; } } @@ -384,6 +386,9 @@ RuntimeStageData::CreateMultiStageFlatbuffer() const { case RuntimeStageBackend::kVulkan: runtime_stages->vulkan = std::move(runtime_stage); break; + case RuntimeStageBackend::kOpenGLES3: + runtime_stages->opengles3 = std::move(runtime_stage); + break; } } return runtime_stages; diff --git a/impeller/compiler/shader_lib/impeller/dithering.glsl b/impeller/compiler/shader_lib/impeller/dithering.glsl index e8e4e8439ce67..5494c4ecee45c 100644 --- a/impeller/compiler/shader_lib/impeller/dithering.glsl +++ b/impeller/compiler/shader_lib/impeller/dithering.glsl @@ -45,9 +45,6 @@ vec4 IPOrderedDither8x8(vec4 color, vec2 dest) { // Apply the dither to the color. color.rgb += dither * kDitherRate; - // Clamp the color values to [0,1]. - color.rgb = clamp(color.rgb, 0.0, 1.0); - return color; } diff --git a/impeller/compiler/switches.cc b/impeller/compiler/switches.cc index 31ae741707864..1dba36b211804 100644 --- a/impeller/compiler/switches.cc +++ b/impeller/compiler/switches.cc @@ -29,6 +29,7 @@ static const std::map kKnownRuntimeStages = { {"sksl", TargetPlatform::kSkSL}, {"runtime-stage-metal", TargetPlatform::kRuntimeStageMetal}, {"runtime-stage-gles", TargetPlatform::kRuntimeStageGLES}, + {"runtime-stage-gles3", TargetPlatform::kRuntimeStageGLES3}, {"runtime-stage-vulkan", TargetPlatform::kRuntimeStageVulkan}, }; diff --git a/impeller/compiler/types.cc b/impeller/compiler/types.cc index 2368bcd20fbec..85e197b99dbe3 100644 --- a/impeller/compiler/types.cc +++ b/impeller/compiler/types.cc @@ -89,6 +89,8 @@ std::string TargetPlatformToString(TargetPlatform platform) { return "RuntimeStageMetal"; case TargetPlatform::kRuntimeStageGLES: return "RuntimeStageGLES"; + case TargetPlatform::kRuntimeStageGLES3: + return "RuntimeStageGLES3"; case TargetPlatform::kRuntimeStageVulkan: return "RuntimeStageVulkan"; case TargetPlatform::kSkSL: @@ -146,6 +148,7 @@ bool TargetPlatformNeedsReflection(TargetPlatform platform) { case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageMetal: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kRuntimeStageGLES3: case TargetPlatform::kRuntimeStageVulkan: case TargetPlatform::kVulkan: return true; @@ -221,6 +224,7 @@ spirv_cross::CompilerMSL::Options::Platform TargetPlatformToMSLPlatform( case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kRuntimeStageGLES3: case TargetPlatform::kRuntimeStageVulkan: case TargetPlatform::kVulkan: case TargetPlatform::kUnknown: @@ -255,6 +259,7 @@ std::string TargetPlatformSLExtension(TargetPlatform platform) { case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kRuntimeStageGLES3: return "glsl"; case TargetPlatform::kVulkan: case TargetPlatform::kRuntimeStageVulkan: @@ -268,6 +273,7 @@ bool TargetPlatformIsOpenGL(TargetPlatform platform) { case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kRuntimeStageGLES3: return true; case TargetPlatform::kMetalDesktop: case TargetPlatform::kRuntimeStageMetal: @@ -292,6 +298,7 @@ bool TargetPlatformIsMetal(TargetPlatform platform) { case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kRuntimeStageGLES3: case TargetPlatform::kRuntimeStageVulkan: case TargetPlatform::kVulkan: return false; @@ -312,6 +319,7 @@ bool TargetPlatformIsVulkan(TargetPlatform platform) { case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kRuntimeStageGLES3: return false; } FML_UNREACHABLE(); @@ -322,6 +330,7 @@ bool TargetPlatformBundlesSkSL(TargetPlatform platform) { case TargetPlatform::kSkSL: case TargetPlatform::kRuntimeStageMetal: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kRuntimeStageGLES3: case TargetPlatform::kRuntimeStageVulkan: return true; case TargetPlatform::kMetalDesktop: diff --git a/impeller/compiler/types.h b/impeller/compiler/types.h index 81b631d49eec7..3878d0cef6d5c 100644 --- a/impeller/compiler/types.h +++ b/impeller/compiler/types.h @@ -34,6 +34,7 @@ enum class TargetPlatform { kVulkan, kRuntimeStageMetal, kRuntimeStageGLES, + kRuntimeStageGLES3, kRuntimeStageVulkan, kSkSL, }; diff --git a/impeller/core/BUILD.gn b/impeller/core/BUILD.gn index e526c46115313..bb7ef28cf15ca 100644 --- a/impeller/core/BUILD.gn +++ b/impeller/core/BUILD.gn @@ -18,6 +18,7 @@ impeller_component("core") { "formats.h", "host_buffer.cc", "host_buffer.h", + "idle_waiter.h", "platform.cc", "platform.h", "range.cc", @@ -50,7 +51,10 @@ impeller_component("core") { impeller_component("allocator_unittests") { testonly = true - sources = [ "allocator_unittests.cc" ] + sources = [ + "allocator_unittests.cc", + "buffer_view_unittests.cc", + ] deps = [ ":core", diff --git a/impeller/core/allocator.h b/impeller/core/allocator.h index a4b3509884ca5..50da86fe10bf1 100644 --- a/impeller/core/allocator.h +++ b/impeller/core/allocator.h @@ -8,6 +8,7 @@ #include "flutter/fml/mapping.h" #include "impeller/base/allocation_size.h" #include "impeller/core/device_buffer_descriptor.h" +#include "impeller/core/idle_waiter.h" #include "impeller/core/texture.h" #include "impeller/core/texture_descriptor.h" #include "impeller/geometry/size.h" diff --git a/impeller/core/buffer_view.cc b/impeller/core/buffer_view.cc index 8d0887a3b59d7..5ab3ee01fc8ff 100644 --- a/impeller/core/buffer_view.cc +++ b/impeller/core/buffer_view.cc @@ -6,6 +6,29 @@ namespace impeller { -// +BufferView::BufferView() : buffer_(nullptr), raw_buffer_(nullptr), range_({}) {} + +BufferView::BufferView(DeviceBuffer* buffer, Range range) + : buffer_(), raw_buffer_(buffer), range_(range) {} + +BufferView::BufferView(std::shared_ptr buffer, Range range) + : buffer_(std::move(buffer)), raw_buffer_(nullptr), range_(range) {} + +const DeviceBuffer* BufferView::GetBuffer() const { + return raw_buffer_ ? raw_buffer_ : buffer_.get(); +} + +std::shared_ptr BufferView::TakeBuffer() { + if (buffer_) { + raw_buffer_ = buffer_.get(); + return std::move(buffer_); + } else { + return nullptr; + } +} + +BufferView::operator bool() const { + return buffer_ || raw_buffer_; +} } // namespace impeller diff --git a/impeller/core/buffer_view.h b/impeller/core/buffer_view.h index e319f44f28359..f5a3cf1420a8b 100644 --- a/impeller/core/buffer_view.h +++ b/impeller/core/buffer_view.h @@ -12,11 +12,34 @@ namespace impeller { class DeviceBuffer; +/// A specific range in a DeviceBuffer. +/// +/// BufferView can maintain ownership over the DeviceBuffer or not depending on +/// if it is created with a std::shared_ptr or a raw pointer. struct BufferView { - std::shared_ptr buffer; - Range range; + public: + BufferView(); - constexpr explicit operator bool() const { return static_cast(buffer); } + BufferView(DeviceBuffer* buffer, Range range); + + BufferView(std::shared_ptr buffer, Range range); + + Range GetRange() const { return range_; } + + const DeviceBuffer* GetBuffer() const; + + std::shared_ptr TakeBuffer(); + + explicit operator bool() const; + + private: + std::shared_ptr buffer_; + /// This is a non-owned DeviceBuffer. Steps should be taken to make sure this + /// lives for the duration of the BufferView's life. Usually this is done + /// automatically by the graphics API or in the case of Vulkan the HostBuffer + /// or TrackedObjectsVK keeps it alive. + const DeviceBuffer* raw_buffer_; + Range range_; }; } // namespace impeller diff --git a/impeller/core/buffer_view_unittests.cc b/impeller/core/buffer_view_unittests.cc new file mode 100644 index 0000000000000..27c80247a9c48 --- /dev/null +++ b/impeller/core/buffer_view_unittests.cc @@ -0,0 +1,26 @@ +// 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. + +#include "flutter/testing/testing.h" +#include "impeller/core/buffer_view.h" + +namespace impeller { +namespace testing { + +TEST(BufferViewTest, Empty) { + BufferView buffer_view; + EXPECT_FALSE(buffer_view); +} + +TEST(BufferViewTest, TakeRaw) { + DeviceBuffer* buffer = reinterpret_cast(0xcafebabe); + BufferView buffer_view(buffer, {0, 123}); + EXPECT_TRUE(buffer_view); + std::shared_ptr taken = buffer_view.TakeBuffer(); + EXPECT_FALSE(taken); + EXPECT_EQ(buffer_view.GetBuffer(), buffer); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/core/device_buffer.cc b/impeller/core/device_buffer.cc index 1df3c03b36446..0d3f01afff808 100644 --- a/impeller/core/device_buffer.cc +++ b/impeller/core/device_buffer.cc @@ -16,10 +16,8 @@ void DeviceBuffer::Invalidate(std::optional range) const {} // static BufferView DeviceBuffer::AsBufferView(std::shared_ptr buffer) { - BufferView view; - view.buffer = std::move(buffer); - view.range = {0u, view.buffer->desc_.size}; - return view; + Range range = {0u, buffer->desc_.size}; + return BufferView(std::move(buffer), range); } const DeviceBufferDescriptor& DeviceBuffer::GetDeviceBufferDescriptor() const { diff --git a/impeller/core/host_buffer.cc b/impeller/core/host_buffer.cc index 1bee37599ad8a..1fef5df595a8b 100644 --- a/impeller/core/host_buffer.cc +++ b/impeller/core/host_buffer.cc @@ -19,12 +19,14 @@ namespace impeller { constexpr size_t kAllocatorBlockSize = 1024000; // 1024 Kb. std::shared_ptr HostBuffer::Create( - const std::shared_ptr& allocator) { - return std::shared_ptr(new HostBuffer(allocator)); + const std::shared_ptr& allocator, + const std::shared_ptr& idle_waiter) { + return std::shared_ptr(new HostBuffer(allocator, idle_waiter)); } -HostBuffer::HostBuffer(const std::shared_ptr& allocator) - : allocator_(allocator) { +HostBuffer::HostBuffer(const std::shared_ptr& allocator, + const std::shared_ptr& idle_waiter) + : allocator_(allocator), idle_waiter_(idle_waiter) { DeviceBufferDescriptor desc; desc.size = kAllocatorBlockSize; desc.storage_mode = StorageMode::kHostVisible; @@ -35,34 +37,52 @@ HostBuffer::HostBuffer(const std::shared_ptr& allocator) } } -HostBuffer::~HostBuffer() = default; +HostBuffer::~HostBuffer() { + if (idle_waiter_) { + // Since we hold on to DeviceBuffers we should make sure they aren't being + // used while we are deleting the HostBuffer. + idle_waiter_->WaitIdle(); + } +}; BufferView HostBuffer::Emplace(const void* buffer, size_t length, size_t align) { - auto [range, device_buffer] = EmplaceInternal(buffer, length, align); - if (!device_buffer) { + auto [range, device_buffer, raw_device_buffer] = + EmplaceInternal(buffer, length, align); + if (device_buffer) { + return BufferView(std::move(device_buffer), range); + } else if (raw_device_buffer) { + return BufferView(raw_device_buffer, range); + } else { return {}; } - return BufferView{std::move(device_buffer), range}; } BufferView HostBuffer::Emplace(const void* buffer, size_t length) { - auto [range, device_buffer] = EmplaceInternal(buffer, length); - if (!device_buffer) { + auto [range, device_buffer, raw_device_buffer] = + EmplaceInternal(buffer, length); + if (device_buffer) { + return BufferView(std::move(device_buffer), range); + } else if (raw_device_buffer) { + return BufferView(raw_device_buffer, range); + } else { return {}; } - return BufferView{std::move(device_buffer), range}; } BufferView HostBuffer::Emplace(size_t length, size_t align, const EmplaceProc& cb) { - auto [range, device_buffer] = EmplaceInternal(length, align, cb); - if (!device_buffer) { + auto [range, device_buffer, raw_device_buffer] = + EmplaceInternal(length, align, cb); + if (device_buffer) { + return BufferView(std::move(device_buffer), range); + } else if (raw_device_buffer) { + return BufferView(raw_device_buffer, range); + } else { return {}; } - return BufferView{std::move(device_buffer), range}; } HostBuffer::TestStateQuery HostBuffer::GetStateForTest() { @@ -90,10 +110,10 @@ bool HostBuffer::MaybeCreateNewBuffer() { return true; } -std::tuple> HostBuffer::EmplaceInternal( - size_t length, - size_t align, - const EmplaceProc& cb) { +std::tuple, DeviceBuffer*> +HostBuffer::EmplaceInternal(size_t length, + size_t align, + const EmplaceProc& cb) { if (!cb) { return {}; } @@ -113,7 +133,7 @@ std::tuple> HostBuffer::EmplaceInternal( cb(device_buffer->OnGetContents()); device_buffer->Flush(Range{0, length}); } - return std::make_tuple(Range{0, length}, std::move(device_buffer)); + return std::make_tuple(Range{0, length}, std::move(device_buffer), nullptr); } size_t padding = 0; @@ -135,12 +155,11 @@ std::tuple> HostBuffer::EmplaceInternal( current_buffer->Flush(output_range); offset_ += length; - return std::make_tuple(output_range, current_buffer); + return std::make_tuple(output_range, nullptr, current_buffer.get()); } -std::tuple> HostBuffer::EmplaceInternal( - const void* buffer, - size_t length) { +std::tuple, DeviceBuffer*> +HostBuffer::EmplaceInternal(const void* buffer, size_t length) { // If the requested allocation is bigger than the block size, create a one-off // device buffer and write to that. if (length > kAllocatorBlockSize) { @@ -158,7 +177,7 @@ std::tuple> HostBuffer::EmplaceInternal( return {}; } } - return std::make_tuple(Range{0, length}, std::move(device_buffer)); + return std::make_tuple(Range{0, length}, std::move(device_buffer), nullptr); } auto old_length = GetLength(); @@ -176,10 +195,11 @@ std::tuple> HostBuffer::EmplaceInternal( current_buffer->Flush(Range{old_length, length}); } offset_ += length; - return std::make_tuple(Range{old_length, length}, current_buffer); + return std::make_tuple(Range{old_length, length}, nullptr, + current_buffer.get()); } -std::tuple> +std::tuple, DeviceBuffer*> HostBuffer::EmplaceInternal(const void* buffer, size_t length, size_t align) { if (align == 0 || (GetLength() % align) == 0) { return EmplaceInternal(buffer, length); diff --git a/impeller/core/host_buffer.h b/impeller/core/host_buffer.h index 43c03c3a20908..d0c5dbf3f3b5b 100644 --- a/impeller/core/host_buffer.h +++ b/impeller/core/host_buffer.h @@ -28,7 +28,8 @@ static const constexpr size_t kHostBufferArenaSize = 4u; class HostBuffer { public: static std::shared_ptr Create( - const std::shared_ptr& allocator); + const std::shared_ptr& allocator, + const std::shared_ptr& idle_waiter); ~HostBuffer(); @@ -133,13 +134,13 @@ class HostBuffer { TestStateQuery GetStateForTest(); private: - [[nodiscard]] std::tuple> + [[nodiscard]] std::tuple, DeviceBuffer*> EmplaceInternal(const void* buffer, size_t length); - std::tuple> + std::tuple, DeviceBuffer*> EmplaceInternal(size_t length, size_t align, const EmplaceProc& cb); - std::tuple> + std::tuple, DeviceBuffer*> EmplaceInternal(const void* buffer, size_t length, size_t align); size_t GetLength() const { return offset_; } @@ -154,13 +155,15 @@ class HostBuffer { [[nodiscard]] BufferView Emplace(const void* buffer, size_t length); - explicit HostBuffer(const std::shared_ptr& allocator); + explicit HostBuffer(const std::shared_ptr& allocator, + const std::shared_ptr& idle_waiter); HostBuffer(const HostBuffer&) = delete; HostBuffer& operator=(const HostBuffer&) = delete; std::shared_ptr allocator_; + std::shared_ptr idle_waiter_; std::array>, kHostBufferArenaSize> device_buffers_; size_t current_buffer_ = 0u; diff --git a/impeller/core/idle_waiter.h b/impeller/core/idle_waiter.h new file mode 100644 index 0000000000000..f346375b2b0fe --- /dev/null +++ b/impeller/core/idle_waiter.h @@ -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. + +#ifndef FLUTTER_IMPELLER_CORE_IDLE_WAITER_H_ +#define FLUTTER_IMPELLER_CORE_IDLE_WAITER_H_ + +namespace impeller { + +/// Abstraction over waiting for the GPU to be idle. +/// +/// This is important for platforms like Vulkan where we need to make sure +/// we aren't deleting resources while the GPU is using them. +class IdleWaiter { + public: + virtual ~IdleWaiter() = default; + + /// Wait for the GPU tasks to finish. + /// This is a noop on some platforms, it's important for Vulkan. + virtual void WaitIdle() const = 0; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_CORE_IDLE_WAITER_H_ diff --git a/impeller/core/resource_binder.h b/impeller/core/resource_binder.h index 8f25ff0c7fe18..120d8c969634f 100644 --- a/impeller/core/resource_binder.h +++ b/impeller/core/resource_binder.h @@ -26,13 +26,13 @@ struct ResourceBinder { virtual bool BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, BufferView view) = 0; virtual bool BindResource(ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) = 0; }; diff --git a/impeller/core/runtime_types.h b/impeller/core/runtime_types.h index b38d61b0eb87a..84eb052991ab8 100644 --- a/impeller/core/runtime_types.h +++ b/impeller/core/runtime_types.h @@ -6,6 +6,7 @@ #define FLUTTER_IMPELLER_CORE_RUNTIME_TYPES_H_ #include +#include #include #include #include @@ -16,6 +17,7 @@ enum class RuntimeStageBackend { kSkSL, kMetal, kOpenGLES, + kOpenGLES3, kVulkan, }; diff --git a/impeller/core/sampler_descriptor.cc b/impeller/core/sampler_descriptor.cc index 9e99089813dd3..5f668bb1bd987 100644 --- a/impeller/core/sampler_descriptor.cc +++ b/impeller/core/sampler_descriptor.cc @@ -4,19 +4,17 @@ #include "impeller/core/sampler_descriptor.h" -#include "fml/logging.h" - namespace impeller { SamplerDescriptor::SamplerDescriptor() = default; -SamplerDescriptor::SamplerDescriptor(std::string label, +SamplerDescriptor::SamplerDescriptor(std::string_view label, MinMagFilter min_filter, MinMagFilter mag_filter, MipFilter mip_filter) : min_filter(min_filter), mag_filter(mag_filter), mip_filter(mip_filter), - label(std::move(label)) {} + label(label) {} } // namespace impeller diff --git a/impeller/core/sampler_descriptor.h b/impeller/core/sampler_descriptor.h index 0e92745ec311f..31e91c129a9db 100644 --- a/impeller/core/sampler_descriptor.h +++ b/impeller/core/sampler_descriptor.h @@ -21,11 +21,11 @@ struct SamplerDescriptor final : public Comparable { SamplerAddressMode height_address_mode = SamplerAddressMode::kClampToEdge; SamplerAddressMode depth_address_mode = SamplerAddressMode::kClampToEdge; - std::string label = "NN Clamp Sampler"; + std::string_view label = "NN Clamp Sampler"; SamplerDescriptor(); - SamplerDescriptor(std::string label, + SamplerDescriptor(std::string_view label, MinMagFilter min_filter, MinMagFilter mag_filter, MipFilter mip_filter); diff --git a/impeller/core/shader_types.h b/impeller/core/shader_types.h index f3759552f4877..9396e4b938aea 100644 --- a/impeller/core/shader_types.h +++ b/impeller/core/shader_types.h @@ -153,13 +153,15 @@ struct ShaderStageBufferLayout { } }; +// These enum values were chosen to match the same values +// in the VK Descriptor Type enum. enum class DescriptorType { - kUniformBuffer, - kStorageBuffer, - kSampledImage, - kImage, - kSampler, - kInputAttachment, + kSampler = 0, + kSampledImage = 1, + kImage = 2, + kUniformBuffer = 6, + kStorageBuffer = 7, + kInputAttachment = 10, }; struct DescriptorSetLayout { diff --git a/impeller/display_list/aiks_dl_basic_unittests.cc b/impeller/display_list/aiks_dl_basic_unittests.cc index cf9fcf0183ce9..281a33e7d09d6 100644 --- a/impeller/display_list/aiks_dl_basic_unittests.cc +++ b/impeller/display_list/aiks_dl_basic_unittests.cc @@ -58,7 +58,7 @@ TEST_P(AiksTest, CanRenderInvertedImageWithColorFilter) { DlPaint paint; paint.setColor(DlColor::kRed()); paint.setColorFilter( - DlBlendColorFilter::Make(DlColor::kYellow(), DlBlendMode::kSrcOver)); + DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kSrcOver)); paint.setInvertColors(true); auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg")); @@ -72,7 +72,7 @@ TEST_P(AiksTest, CanRenderColorFilterWithInvertColors) { DlPaint paint; paint.setColor(DlColor::kRed()); paint.setColorFilter( - DlBlendColorFilter::Make(DlColor::kYellow(), DlBlendMode::kSrcOver)); + DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kSrcOver)); paint.setInvertColors(true); builder.DrawRect(SkRect::MakeLTRB(0, 0, 100, 100), paint); @@ -84,7 +84,7 @@ TEST_P(AiksTest, CanRenderColorFilterWithInvertColorsDrawPaint) { DlPaint paint; paint.setColor(DlColor::kRed()); paint.setColorFilter( - DlBlendColorFilter::Make(DlColor::kYellow(), DlBlendMode::kSrcOver)); + DlColorFilter::MakeBlend(DlColor::kYellow(), DlBlendMode::kSrcOver)); paint.setInvertColors(true); builder.DrawPaint(paint); @@ -118,15 +118,14 @@ void CanRenderTiledTexture(AiksTest* aiks_test, /*enable_mipmapping=*/true); GenerateMipmap(context, texture, "table_mountain_nx"); auto image = DlImageImpeller::Make(texture); - SkMatrix sk_local_matrix = ToSkMatrix(local_matrix); - DlImageColorSource color_source(image, tile_mode, tile_mode, - DlImageSampling::kNearestNeighbor, - &sk_local_matrix); + auto color_source = DlColorSource::MakeImage( + image, tile_mode, tile_mode, DlImageSampling::kNearestNeighbor, + &local_matrix); DisplayListBuilder builder; DlPaint paint; paint.setColor(DlColor::kWhite()); - paint.setColorSource(&color_source); + paint.setColorSource(color_source); builder.Scale(aiks_test->GetContentScale().x, aiks_test->GetContentScale().y); builder.Translate(100.0f, 100.0f); @@ -233,6 +232,26 @@ TEST_P(AiksTest, CanRenderImageRect) { ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } +TEST_P(AiksTest, DrawImageRectSrcOutsideBounds) { + DisplayListBuilder builder; + auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg")); + + // Use a source rect that is partially outside the bounds of the image. + auto source_rect = SkRect::MakeXYWH( + image->dimensions().fWidth * 0.25f, image->dimensions().fHeight * 0.4f, + image->dimensions().fWidth, image->dimensions().fHeight); + + auto dest_rect = SkRect::MakeXYWH(100, 100, 600, 600); + + DlPaint paint; + paint.setColor(DlColor::kMidGrey()); + builder.DrawRect(dest_rect, paint); + + builder.DrawImageRect(image, source_rect, dest_rect, + DlImageSampling::kNearestNeighbor); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + TEST_P(AiksTest, CanRenderSimpleClips) { DisplayListBuilder builder; builder.Scale(GetContentScale().x, GetContentScale().y); @@ -309,10 +328,9 @@ TEST_P(AiksTest, CanRenderSimpleClips) { {500, 600}, 75, 7, gradient_colors, stops, DlTileMode::kMirror)); draw(paint, 0, 300); - DlImageColorSource image_source(image, DlTileMode::kRepeat, - DlTileMode::kRepeat, - DlImageSampling::kNearestNeighbor); - paint.setColorSource(&image_source); + paint.setColorSource( + DlColorSource::MakeImage(image, DlTileMode::kRepeat, DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor)); draw(paint, 300, 0); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); @@ -464,11 +482,10 @@ TEST_P(AiksTest, FilledCirclesRenderCorrectly) { {500, 600}, 75, 7, gradient_colors, stops, DlTileMode::kMirror)); builder.DrawCircle(SkPoint{500, 600}, 100, paint); - SkMatrix local_matrix = SkMatrix::Translate(700, 200); - DlImageColorSource image_source( + DlMatrix local_matrix = DlMatrix::MakeTranslation({700, 200}); + paint.setColorSource(DlColorSource::MakeImage( image, DlTileMode::kRepeat, DlTileMode::kRepeat, - DlImageSampling::kNearestNeighbor, &local_matrix); - paint.setColorSource(&image_source); + DlImageSampling::kNearestNeighbor, &local_matrix)); builder.DrawCircle(SkPoint{800, 300}, 100, paint); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); @@ -531,11 +548,10 @@ TEST_P(AiksTest, StrokedCirclesRenderCorrectly) { {500, 600}, 75, 7, gradient_colors, stops, DlTileMode::kMirror)); draw(builder, {500, 600}, 5, 10, 10); - SkMatrix local_matrix = SkMatrix::Translate(700, 200); - DlImageColorSource image_source( + DlMatrix local_matrix = DlMatrix::MakeTranslation({700, 200}); + paint.setColorSource(DlColorSource::MakeImage( image, DlTileMode::kRepeat, DlTileMode::kRepeat, - DlImageSampling::kNearestNeighbor, &local_matrix); - paint.setColorSource(&image_source); + DlImageSampling::kNearestNeighbor, &local_matrix)); draw(builder, {800, 300}, 5, 10, 10); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); @@ -604,11 +620,10 @@ TEST_P(AiksTest, FilledEllipsesRenderCorrectly) { builder.DrawOval(SkRect::MakeXYWH(200, 625, 200, 50), paint); builder.DrawOval(SkRect::MakeXYWH(275, 550, 50, 200), paint); - SkMatrix local_matrix = SkMatrix::Translate(610, 15); - DlImageColorSource image_source( + DlMatrix local_matrix = DlMatrix::MakeTranslation({610, 15}); + paint.setColorSource(DlColorSource::MakeImage( image, DlTileMode::kRepeat, DlTileMode::kRepeat, - DlImageSampling::kNearestNeighbor, &local_matrix); - paint.setColorSource(&image_source); + DlImageSampling::kNearestNeighbor, &local_matrix)); builder.DrawOval(SkRect::MakeXYWH(610, 90, 200, 50), paint); builder.DrawOval(SkRect::MakeXYWH(685, 15, 50, 200), paint); @@ -691,11 +706,10 @@ TEST_P(AiksTest, FilledRoundRectsRenderCorrectly) { SkRRect::MakeRectXY(SkRect::MakeLTRB(160, 550, 240, 750), 40, 40), paint); paint.setColor(DlColor::kWhite().modulateOpacity(0.1)); - SkMatrix local_matrix = SkMatrix::Translate(520, 20); - DlImageColorSource image_source( + DlMatrix local_matrix = DlMatrix::MakeTranslation({520, 20}); + paint.setColorSource(DlColorSource::MakeImage( image, DlTileMode::kRepeat, DlTileMode::kRepeat, - DlImageSampling::kNearestNeighbor, &local_matrix); - paint.setColorSource(&image_source); + DlImageSampling::kNearestNeighbor, &local_matrix)); for (int i = 1; i <= 10; i++) { int j = 11 - i; builder.DrawRRect( @@ -706,11 +720,10 @@ TEST_P(AiksTest, FilledRoundRectsRenderCorrectly) { } paint.setColor(DlColor::kWhite().modulateOpacity(0.5)); - local_matrix = SkMatrix::Translate(800, 300); - DlImageColorSource image_source2( + local_matrix = DlMatrix::MakeTranslation({800, 300}); + paint.setColorSource(DlColorSource::MakeImage( image, DlTileMode::kRepeat, DlTileMode::kRepeat, - DlImageSampling::kNearestNeighbor, &local_matrix); - paint.setColorSource(&image_source2); + DlImageSampling::kNearestNeighbor, &local_matrix)); builder.DrawRRect( SkRRect::MakeRectXY(SkRect::MakeLTRB(800, 410, 1000, 490), 40, 40), paint); @@ -815,8 +828,8 @@ TEST_P(AiksTest, CanRenderClippedBackdropFilter) { builder.ClipRRect(clip_rrect, DlCanvas::ClipOp::kIntersect); DlPaint save_paint; - auto backdrop_filter = std::make_shared( - DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kExclusion)); + auto backdrop_filter = DlImageFilter::MakeColorFilter( + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kExclusion)); builder.SaveLayer(&clip_rect, &save_paint, backdrop_filter.get()); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); @@ -896,9 +909,9 @@ TEST_P(AiksTest, ImageColorSourceEffectTransform) { // Translation { - SkMatrix matrix = SkMatrix::Translate(50, 50); + DlMatrix matrix = DlMatrix::MakeTranslation({50, 50}); DlPaint paint; - paint.setColorSource(std::make_shared( + paint.setColorSource(DlColorSource::MakeImage( texture, DlTileMode::kRepeat, DlTileMode::kRepeat, DlImageSampling::kNearestNeighbor, &matrix)); @@ -911,12 +924,11 @@ TEST_P(AiksTest, ImageColorSourceEffectTransform) { builder.Rotate(45); DlPaint paint; - Matrix impeller_matrix(1, -1, 0, 0, // - 1, 1, 0, 0, // - 0, 0, 1, 0, // - 0, 0, 0, 1); - SkMatrix matrix = SkM44::ColMajor(impeller_matrix.m).asM33(); - paint.setColorSource(std::make_shared( + Matrix matrix(1, -1, 0, 0, // + 1, 1, 0, 0, // + 0, 0, 1, 0, // + 0, 0, 0, 1); + paint.setColorSource(DlColorSource::MakeImage( texture, DlTileMode::kRepeat, DlTileMode::kRepeat, DlImageSampling::kNearestNeighbor, &matrix)); builder.DrawRect(SkRect::MakeLTRB(100, 0, 200, 100), paint); @@ -929,8 +941,8 @@ TEST_P(AiksTest, ImageColorSourceEffectTransform) { builder.Scale(100, 100); DlPaint paint; - SkMatrix matrix = SkMatrix::Scale(0.005, 0.005); - paint.setColorSource(std::make_shared( + DlMatrix matrix = DlMatrix::MakeScale({0.005, 0.005, 1}); + paint.setColorSource(DlColorSource::MakeImage( texture, DlTileMode::kRepeat, DlTileMode::kRepeat, DlImageSampling::kNearestNeighbor, &matrix)); @@ -976,9 +988,9 @@ TEST_P(AiksTest, MatrixImageFilterDoesntCullWhenTranslatedFromOffscreen) { // Draw a circle in a SaveLayer at -300, but move it back on-screen with a // +300 translation applied by a SaveLayer image filter. DlPaint paint; - SkMatrix translate = SkMatrix::Translate(300, 0); + DlMatrix translate = DlMatrix::MakeTranslation({300, 0}); paint.setImageFilter( - DlMatrixImageFilter::Make(translate, DlImageSampling::kLinear)); + DlImageFilter::MakeMatrix(translate, DlImageSampling::kLinear)); builder.SaveLayer(nullptr, &paint); DlPaint circle_paint; @@ -999,8 +1011,8 @@ TEST_P(AiksTest, // +300 translation applied by a SaveLayer image filter. DlPaint paint; - paint.setImageFilter(DlMatrixImageFilter::Make( - SkMatrix::Translate(300, 0) * SkMatrix::Scale(2, 2), + paint.setImageFilter(DlImageFilter::MakeMatrix( + DlMatrix::MakeTranslation({300, 0}) * DlMatrix::MakeScale({2, 2, 1}), DlImageSampling::kNearestNeighbor)); builder.SaveLayer(nullptr, &paint); @@ -1023,7 +1035,7 @@ TEST_P(AiksTest, ClearColorOptimizationWhenSubpassIsBiggerThanParentPass) { paint.setColor(DlColor::kRed()); builder.DrawRect(SkRect::MakeLTRB(200, 200, 300, 300), paint); - paint.setImageFilter(DlMatrixImageFilter::Make(SkMatrix::Scale(2, 2), + paint.setImageFilter(DlImageFilter::MakeMatrix(DlMatrix::MakeScale({2, 2, 1}), DlImageSampling::kLinear)); builder.SaveLayer(nullptr, &paint); // Draw a rectangle that would fully cover the parent pass size, but not @@ -1271,9 +1283,9 @@ TEST_P(AiksTest, FilledRoundRectPathsRenderCorrectly) { draw_rrect_as_path(SkRect::MakeLTRB(100, 610, 300, 690), 40, 40, paint); draw_rrect_as_path(SkRect::MakeLTRB(160, 550, 240, 750), 40, 40, paint); - auto matrix = SkMatrix::Translate(520, 20); + auto matrix = DlMatrix::MakeTranslation({520, 20}); paint.setColor(DlColor::kWhite().modulateOpacity(0.1)); - paint.setColorSource(std::make_shared( + paint.setColorSource(DlColorSource::MakeImage( texture, DlTileMode::kRepeat, DlTileMode::kRepeat, DlImageSampling::kMipmapLinear, &matrix)); for (int i = 1; i <= 10; i++) { @@ -1282,9 +1294,9 @@ TEST_P(AiksTest, FilledRoundRectPathsRenderCorrectly) { 720 + i * 20, 220 + j * 20), i * 10, j * 10, paint); } - matrix = SkMatrix::Translate(800, 300); + matrix = DlMatrix::MakeTranslation({800, 300}); paint.setColor(DlColor::kWhite().modulateOpacity(0.5)); - paint.setColorSource(std::make_shared( + paint.setColorSource(DlColorSource::MakeImage( texture, DlTileMode::kRepeat, DlTileMode::kRepeat, DlImageSampling::kMipmapLinear, &matrix)); @@ -1500,8 +1512,8 @@ TEST_P(AiksTest, PipelineBlendSingleParameter) { paint.setColor(DlColor::kGreen()); paint.setBlendMode(DlBlendMode::kSrcOver); - paint.setImageFilter(DlColorFilterImageFilter::Make( - DlBlendColorFilter::Make(DlColor::kWhite(), DlBlendMode::kDst))); + paint.setImageFilter(DlImageFilter::MakeColorFilter( + DlColorFilter::MakeBlend(DlColor::kWhite(), DlBlendMode::kDst))); builder.DrawCircle(SkPoint::Make(200, 200), 200, paint); builder.Restore(); } @@ -1518,8 +1530,8 @@ TEST_P(AiksTest, MassiveScalingMatrixImageFilter) { } DisplayListBuilder builder(SkRect::MakeSize(SkSize::Make(1000, 1000))); - auto filter = DlMatrixImageFilter::Make(SkMatrix::Scale(0.001, 0.001), - DlImageSampling::kLinear); + auto filter = DlImageFilter::MakeMatrix( + DlMatrix::MakeScale({0.001, 0.001, 1}), DlImageSampling::kLinear); DlPaint paint; paint.setImageFilter(filter); diff --git a/impeller/display_list/aiks_dl_blend_unittests.cc b/impeller/display_list/aiks_dl_blend_unittests.cc index f4691f63cc86f..062a61b3f2b20 100644 --- a/impeller/display_list/aiks_dl_blend_unittests.cc +++ b/impeller/display_list/aiks_dl_blend_unittests.cc @@ -66,7 +66,7 @@ TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer) { builder.ClipRect(layer_rect); DlPaint save_paint; - save_paint.setColorFilter(DlBlendColorFilter::Make( + save_paint.setColorFilter(DlColorFilter::MakeBlend( DlColor::RGBA(0, 1, 0, 0.5), DlBlendMode::kDifference)); builder.SaveLayer(&layer_rect, &save_paint); @@ -154,7 +154,7 @@ TEST_P(AiksTest, DrawAdvancedBlendPartlyOffscreen) { std::vector stops = {0.0, 1.0}; DlPaint paint; - SkMatrix matrix = SkMatrix::Scale(0.3, 0.3); + DlMatrix matrix = DlMatrix::MakeScale({0.3, 0.3, 1.0}); paint.setColorSource(DlColorSource::MakeLinear( /*start_point=*/{0, 0}, // /*end_point=*/{100, 100}, // @@ -233,7 +233,7 @@ TEST_P(AiksTest, ColorFilterBlend) { srcPaint.setBlendMode(blend_modes[i]); if (has_color_filter) { std::shared_ptr color_filter = - DlBlendColorFilter::Make(DlColor::RGBA(0.9, 0.5, 0.0, 1.0), + DlColorFilter::MakeBlend(DlColor::RGBA(0.9, 0.5, 0.0, 1.0), DlBlendMode::kSrcIn); srcPaint.setColorFilter(color_filter); } @@ -290,7 +290,7 @@ TEST_P(AiksTest, ColorFilterAdvancedBlend) { srcPaint.setBlendMode(blend_modes[i]); if (has_color_filter) { std::shared_ptr color_filter = - DlBlendColorFilter::Make(DlColor::RGBA(0.9, 0.5, 0.0, 1.0), + DlColorFilter::MakeBlend(DlColor::RGBA(0.9, 0.5, 0.0, 1.0), DlBlendMode::kSrcIn); srcPaint.setColorFilter(color_filter); } @@ -338,6 +338,7 @@ TEST_P(AiksTest, ColorFilterAdvancedBlendNoFbFetch) { FLT_FORWARD(mock_capabilities, old_capabilities, SupportsTriangleFan); FLT_FORWARD(mock_capabilities, old_capabilities, SupportsDecalSamplerAddressMode); + FLT_FORWARD(mock_capabilities, old_capabilities, SupportsPrimitiveRestart); ASSERT_TRUE(SetCapabilities(mock_capabilities).ok()); bool has_color_filter = true; @@ -381,7 +382,7 @@ TEST_P(AiksTest, ColorFilterAdvancedBlendNoFbFetch) { srcPaint.setBlendMode(blend_modes[i]); if (has_color_filter) { std::shared_ptr color_filter = - DlBlendColorFilter::Make(DlColor::RGBA(0.9, 0.5, 0.0, 1.0), + DlColorFilter::MakeBlend(DlColor::RGBA(0.9, 0.5, 0.0, 1.0), DlBlendMode::kMultiply); srcPaint.setColorFilter(color_filter); } @@ -444,7 +445,7 @@ TEST_P(AiksTest, BlendModePlusAlphaColorFilterWideGamut) { DlPaint save_paint; save_paint.setColorFilter( - DlBlendColorFilter::Make(DlColor::RGBA(1, 0, 0, 1), DlBlendMode::kPlus)); + DlColorFilter::MakeBlend(DlColor::RGBA(1, 0, 0, 1), DlBlendMode::kPlus)); builder.SaveLayer(nullptr, &save_paint); paint.setColor(DlColor::kRed()); @@ -470,7 +471,7 @@ TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) { DlPaint save_paint; save_paint.setColorFilter( - DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kColorDodge)); + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kColorDodge)); builder.SaveLayer(nullptr, &save_paint); builder.Translate(500, 300); @@ -720,7 +721,7 @@ TEST_P(AiksTest, ForegroundPipelineBlendAppliesTransformCorrectly) { builder.Rotate(30); DlPaint image_paint; - image_paint.setColorFilter(DlBlendColorFilter::Make( + image_paint.setColorFilter(DlColorFilter::MakeBlend( DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f), DlBlendMode::kSrcIn)); @@ -738,7 +739,7 @@ TEST_P(AiksTest, ForegroundAdvancedBlendAppliesTransformCorrectly) { builder.Rotate(30); DlPaint image_paint; - image_paint.setColorFilter(DlBlendColorFilter::Make( + image_paint.setColorFilter(DlColorFilter::MakeBlend( DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f), DlBlendMode::kColorDodge)); @@ -897,7 +898,7 @@ TEST_P(AiksTest, DestructiveBlendColorFilterFloodsClip) { DlPaint save_paint; save_paint.setColorFilter( - DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kSrc)); + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kSrc)); builder.SaveLayer(nullptr, &save_paint); builder.Restore(); @@ -905,5 +906,25 @@ TEST_P(AiksTest, DestructiveBlendColorFilterFloodsClip) { ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } +TEST_P(AiksTest, AdvancedBlendColorFilterWithDestinationOpacity) { + DisplayListBuilder builder; + + builder.DrawPaint(DlPaint(DlColor::kWhite())); + + DlPaint save_paint; + save_paint.setOpacity(0.3); + save_paint.setColorFilter(DlColorFilter::MakeBlend(DlColor::kTransparent(), + DlBlendMode::kSaturation)); + builder.SaveLayer(nullptr, &save_paint); + builder.DrawRect(SkRect::MakeXYWH(100, 100, 300, 300), + DlPaint(DlColor::kMaroon())); + builder.DrawRect(SkRect::MakeXYWH(200, 200, 300, 300), + DlPaint(DlColor::kBlue())); + builder.Restore(); + + // Should be solid red as the destructive color filter floods the clip. + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/display_list/aiks_dl_blur_unittests.cc b/impeller/display_list/aiks_dl_blur_unittests.cc index 96f852b49f65b..63a3d5443acb6 100644 --- a/impeller/display_list/aiks_dl_blur_unittests.cc +++ b/impeller/display_list/aiks_dl_blur_unittests.cc @@ -2,17 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "display_list/display_list.h" -#include "display_list/dl_blend_mode.h" -#include "display_list/dl_builder.h" -#include "display_list/dl_color.h" -#include "display_list/dl_paint.h" -#include "display_list/dl_sampling_options.h" -#include "display_list/dl_tile_mode.h" -#include "display_list/effects/dl_color_filter.h" -#include "display_list/effects/dl_color_source.h" -#include "display_list/effects/dl_image_filter.h" -#include "display_list/effects/dl_mask_filter.h" +#include "flutter/display_list/display_list.h" +#include "flutter/display_list/dl_blend_mode.h" +#include "flutter/display_list/dl_builder.h" +#include "flutter/display_list/dl_color.h" +#include "flutter/display_list/dl_paint.h" +#include "flutter/display_list/dl_sampling_options.h" +#include "flutter/display_list/dl_tile_mode.h" +#include "flutter/display_list/effects/dl_color_filter.h" +#include "flutter/display_list/effects/dl_color_source.h" +#include "flutter/display_list/effects/dl_image_filter.h" +#include "flutter/display_list/effects/dl_mask_filter.h" #include "flutter/impeller/display_list/aiks_unittests.h" #include "gmock/gmock.h" @@ -175,7 +175,7 @@ TEST_P(AiksTest, CanRenderForegroundBlendWithMaskBlur) { paint.setMaskFilter( DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma)); paint.setColorFilter( - DlBlendColorFilter::Make(DlColor::kGreen(), DlBlendMode::kSrc)); + DlColorFilter::MakeBlend(DlColor::kGreen(), DlBlendMode::kSrc)); builder.DrawCircle(SkPoint{400, 400}, 200, paint); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); @@ -195,7 +195,7 @@ TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) { paint.setMaskFilter( DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma)); paint.setColorFilter( - DlBlendColorFilter::Make(DlColor::kGreen(), DlBlendMode::kColor)); + DlColorFilter::MakeBlend(DlColor::kGreen(), DlBlendMode::kColor)); builder.DrawCircle(SkPoint{400, 400}, 200, paint); builder.Restore(); @@ -229,7 +229,7 @@ TEST_P(AiksTest, CanRenderBackdropBlurInteractive) { DlPaint save_paint; save_paint.setBlendMode(DlBlendMode::kSrc); - auto backdrop_filter = DlBlurImageFilter::Make(20, 20, DlTileMode::kClamp); + auto backdrop_filter = DlImageFilter::MakeBlur(20, 20, DlTileMode::kClamp); builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get()); builder.Restore(); @@ -261,7 +261,7 @@ TEST_P(AiksTest, CanRenderBackdropBlur) { DlPaint save_paint; save_paint.setBlendMode(DlBlendMode::kSrc); - auto backdrop_filter = DlBlurImageFilter::Make(30, 30, DlTileMode::kClamp); + auto backdrop_filter = DlImageFilter::MakeBlur(30, 30, DlTileMode::kClamp); builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get()); builder.Restore(); @@ -284,7 +284,7 @@ TEST_P(AiksTest, CanRenderBackdropBlurWithSingleBackdropId) { DlPaint save_paint; save_paint.setBlendMode(DlBlendMode::kSrc); - auto backdrop_filter = DlBlurImageFilter::Make(30, 30, DlTileMode::kClamp); + auto backdrop_filter = DlImageFilter::MakeBlur(30, 30, DlTileMode::kClamp); builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get(), /*backdrop_id=*/1); builder.Restore(); @@ -310,7 +310,7 @@ TEST_P(AiksTest, CanRenderMultipleBackdropBlurWithSingleBackdropId) { DlPaint save_paint; save_paint.setBlendMode(DlBlendMode::kSrc); - auto backdrop_filter = DlBlurImageFilter::Make(30, 30, DlTileMode::kClamp); + auto backdrop_filter = DlImageFilter::MakeBlur(30, 30, DlTileMode::kClamp); builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get(), /*backdrop_id=*/1); builder.Restore(); @@ -339,7 +339,7 @@ TEST_P(AiksTest, DlPaint save_paint; save_paint.setBlendMode(DlBlendMode::kSrc); auto backdrop_filter = - DlBlurImageFilter::Make(30 + i, 30, DlTileMode::kClamp); + DlImageFilter::MakeBlur(30 + i, 30, DlTileMode::kClamp); builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get(), /*backdrop_id=*/1); builder.Restore(); @@ -360,7 +360,7 @@ TEST_P(AiksTest, CanRenderBackdropBlurHugeSigma) { save_paint.setBlendMode(DlBlendMode::kSrc); auto backdrop_filter = - DlBlurImageFilter::Make(999999, 999999, DlTileMode::kClamp); + DlImageFilter::MakeBlur(999999, 999999, DlTileMode::kClamp); builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get()); builder.Restore(); @@ -373,7 +373,7 @@ TEST_P(AiksTest, CanRenderClippedBlur) { DlPaint paint; paint.setColor(DlColor::kGreen()); - paint.setImageFilter(DlBlurImageFilter::Make(20, 20, DlTileMode::kDecal)); + paint.setImageFilter(DlImageFilter::MakeBlur(20, 20, DlTileMode::kDecal)); builder.DrawCircle(SkPoint{400, 400}, 200, paint); builder.Restore(); @@ -579,7 +579,7 @@ static const std::map kPaintVariations = { {.style = DlBlurStyle::kSolid, .sigma = 8.0f, .alpha = 0.5f, - .image_filter = DlBlurImageFilter::Make(3, 3, DlTileMode::kClamp), + .image_filter = DlImageFilter::MakeBlur(3, 3, DlTileMode::kClamp), .invert_colors = true}}, // 6. Solid style, translucent, exclusion blended. {"SolidTranslucentExclusionBlend", @@ -595,7 +595,7 @@ static const std::map kPaintVariations = { {.style = DlBlurStyle::kInner, .sigma = 8.0f, .alpha = 0.5f, - .image_filter = DlBlurImageFilter::Make(3, 3, DlTileMode::kClamp)}}, + .image_filter = DlImageFilter::MakeBlur(3, 3, DlTileMode::kClamp)}}, // 9. Outer style, translucent. {"OuterTranslucent", {.style = DlBlurStyle::kOuter, .sigma = 8.0f, .alpha = 0.5f}}, @@ -603,7 +603,7 @@ static const std::map kPaintVariations = { {"OuterOpaqueWithBlurImageFilter", {.style = DlBlurStyle::kOuter, .sigma = 8.0f, - .image_filter = DlBlurImageFilter::Make(3, 3, DlTileMode::kClamp)}}, + .image_filter = DlImageFilter::MakeBlur(3, 3, DlTileMode::kClamp)}}, }; #define MASK_BLUR_VARIANT_TEST(config) \ @@ -756,7 +756,7 @@ TEST_P(AiksTest, MaskBlurDoesntStretchContents) { builder.Transform(SkMatrix::Translate(100, 100) * SkMatrix::Scale(0.5, 0.5)); - paint.setColorSource(std::make_shared( + paint.setColorSource(DlColorSource::MakeImage( DlImageImpeller::Make(boston), DlTileMode::kRepeat, DlTileMode::kRepeat, DlImageSampling::kMipmapLinear)); paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma)); @@ -790,7 +790,7 @@ TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) { DlPaint save_paint; save_paint.setBlendMode(DlBlendMode::kSrc); - auto backdrop_filter = DlBlurImageFilter::Make(20, 20, DlTileMode::kClamp); + auto backdrop_filter = DlImageFilter::MakeBlur(20, 20, DlTileMode::kClamp); builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get()); builder.Restore(); @@ -820,7 +820,7 @@ TEST_P(AiksTest, GaussianBlurAtPeripheryHorizontal) { DlPaint save_paint; save_paint.setBlendMode(DlBlendMode::kSrc); - auto backdrop_filter = DlBlurImageFilter::Make(20, 20, DlTileMode::kClamp); + auto backdrop_filter = DlImageFilter::MakeBlur(20, 20, DlTileMode::kClamp); builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get()); builder.Restore(); @@ -868,7 +868,7 @@ TEST_P(AiksTest, GaussianBlurAnimatedBackdrop) { paint.setBlendMode(DlBlendMode::kSrc); auto backdrop_filter = - DlBlurImageFilter::Make(sigma, sigma, DlTileMode::kClamp); + DlImageFilter::MakeBlur(sigma, sigma, DlTileMode::kClamp); builder.SaveLayer(nullptr, &paint, backdrop_filter.get()); count += 1; return builder.Build(); @@ -994,7 +994,7 @@ TEST_P(AiksTest, GaussianBlurScaledAndClipped) { Vector2 image_center = Vector2(bounds.GetSize() / 2); DlPaint paint; - paint.setImageFilter(DlBlurImageFilter::Make(20, 20, DlTileMode::kDecal)); + paint.setImageFilter(DlImageFilter::MakeBlur(20, 20, DlTileMode::kDecal)); Vector2 clip_size = {150, 75}; Vector2 center = Vector2(1024, 768) / 2; @@ -1046,7 +1046,7 @@ TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) { Vector2 image_center = Vector2(bounds.GetSize() / 2); DlPaint paint; paint.setImageFilter( - DlBlurImageFilter::Make(20, 20, tile_modes[selected_tile_mode])); + DlImageFilter::MakeBlur(20, 20, tile_modes[selected_tile_mode])); static PlaygroundPoint point_a(Point(362, 309), 20, Color::Red()); static PlaygroundPoint point_b(Point(662, 459), 20, Color::Red()); @@ -1086,7 +1086,7 @@ TEST_P(AiksTest, GaussianBlurOneDimension) { DlPaint paint; paint.setBlendMode(DlBlendMode::kSrc); - auto backdrop_filter = DlBlurImageFilter::Make(50, 0, DlTileMode::kClamp); + auto backdrop_filter = DlImageFilter::MakeBlur(50, 0, DlTileMode::kClamp); builder.SaveLayer(nullptr, &paint, backdrop_filter.get()); builder.Restore(); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); @@ -1104,7 +1104,7 @@ TEST_P(AiksTest, GaussianBlurRotatedAndClipped) { Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height); DlPaint paint; - paint.setImageFilter(DlBlurImageFilter::Make(20, 20, DlTileMode::kDecal)); + paint.setImageFilter(DlImageFilter::MakeBlur(20, 20, DlTileMode::kDecal)); Vector2 image_center = Vector2(bounds.GetSize() / 2); Vector2 clip_size = {150, 75}; @@ -1157,7 +1157,7 @@ TEST_P(AiksTest, GaussianBlurRotatedNonUniform) { DlPaint paint; paint.setColor(DlColor::kGreen()); paint.setImageFilter( - DlBlurImageFilter::Make(50, 0, tile_modes[selected_tile_mode])); + DlImageFilter::MakeBlur(50, 0, tile_modes[selected_tile_mode])); Vector2 center = Vector2(1024, 768) / 2; builder.Scale(GetContentScale().x, GetContentScale().y); @@ -1210,9 +1210,9 @@ TEST_P(AiksTest, BlurredRectangleWithShader) { auto texture = DisplayListToTexture(recorder_builder.Build(), {100, 100}, renderer); - auto image_source = std::make_shared( + auto image_source = DlColorSource::MakeImage( DlImageImpeller::Make(texture), DlTileMode::kRepeat, DlTileMode::kRepeat); - auto blur_filter = DlBlurImageFilter::Make(5, 5, DlTileMode::kDecal); + auto blur_filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kDecal); DlPaint paint; paint.setColor(DlColor::kDarkGreen()); @@ -1271,6 +1271,7 @@ TEST_P(AiksTest, GaussianBlurWithoutDecalSupport) { SupportsTextureToTextureBlits); FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultGlyphAtlasFormat); FLT_FORWARD(mock_capabilities, old_capabilities, SupportsTriangleFan); + FLT_FORWARD(mock_capabilities, old_capabilities, SupportsPrimitiveRestart); ASSERT_TRUE(SetCapabilities(mock_capabilities).ok()); auto texture = DlImageImpeller::Make(CreateTextureForFixture("boston.jpg")); @@ -1282,7 +1283,7 @@ TEST_P(AiksTest, GaussianBlurWithoutDecalSupport) { paint.setColor(DlColor::kBlack()); builder.DrawPaint(paint); - auto blur_filter = DlBlurImageFilter::Make(20, 20, DlTileMode::kDecal); + auto blur_filter = DlImageFilter::MakeBlur(20, 20, DlTileMode::kDecal); paint.setImageFilter(blur_filter); builder.DrawImage(texture, SkPoint::Make(200, 200), {}, &paint); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); @@ -1303,7 +1304,7 @@ TEST_P(AiksTest, GaussianBlurSolidColorTinyMipMap) { DlPaint paint; paint.setColor(DlColor::kChartreuse()); - auto blur_filter = DlBlurImageFilter::Make(0.1, 0.1, DlTileMode::kClamp); + auto blur_filter = DlImageFilter::MakeBlur(0.1, 0.1, DlTileMode::kClamp); paint.setImageFilter(blur_filter); builder.DrawPath(path, paint); @@ -1328,7 +1329,7 @@ TEST_P(AiksTest, GaussianBlurBackdropTinyMipMap) { DlPaint paint; paint.setColor(DlColor::kGreen()); - auto blur_filter = DlBlurImageFilter::Make(0.1, 0.1, DlTileMode::kDecal); + auto blur_filter = DlImageFilter::MakeBlur(0.1, 0.1, DlTileMode::kDecal); paint.setImageFilter(blur_filter); builder.DrawCircle(SkPoint{400, 400}, 200, paint); @@ -1362,7 +1363,7 @@ TEST_P(AiksTest, DlPaint save_paint; save_paint.setBlendMode(DlBlendMode::kSrc); - auto backdrop_filter = DlBlurImageFilter::Make(30, 30, DlTileMode::kClamp); + auto backdrop_filter = DlImageFilter::MakeBlur(30, 30, DlTileMode::kClamp); builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get(), /*backdrop_id=*/1); builder.Restore(); diff --git a/impeller/display_list/aiks_dl_gradient_unittests.cc b/impeller/display_list/aiks_dl_gradient_unittests.cc index 3390a1dec6db3..2f9c0f27ae65c 100644 --- a/impeller/display_list/aiks_dl_gradient_unittests.cc +++ b/impeller/display_list/aiks_dl_gradient_unittests.cc @@ -94,7 +94,7 @@ TEST_P(AiksTest, CanRenderLinearGradientDecalWithColorFilter) { // Overlay the gradient with 25% green. This should appear as the entire // rectangle being drawn with 25% green, including the border area outside the // decal gradient. - paint.setColorFilter(DlBlendColorFilter::Make(DlColor::kGreen().withAlpha(64), + paint.setColorFilter(DlColorFilter::MakeBlend(DlColor::kGreen().withAlpha(64), DlBlendMode::kSrcOver)); paint.setColor(DlColor::kWhite()); builder.DrawRect(SkRect::MakeXYWH(0, 0, 600, 600), paint); @@ -216,6 +216,123 @@ TEST_P(AiksTest, CanRenderLinearGradientWithOverlappingStopsClamp) { CanRenderLinearGradientWithOverlappingStops(this, DlTileMode::kClamp); } +namespace { +void CanRenderGradientWithIncompleteStops(AiksTest* aiks_test, + DlColorSourceType type) { + const DlTileMode tile_modes[4] = { + DlTileMode::kClamp, + DlTileMode::kRepeat, + DlTileMode::kMirror, + DlTileMode::kDecal, + }; + const DlScalar test_size = 250; + const DlScalar test_border = 25; + const DlScalar gradient_size = 50; + const DlScalar quadrant_size = test_size + test_border * 2; + + DisplayListBuilder builder; + builder.DrawRect(DlRect::MakeWH(quadrant_size * 2, quadrant_size * 2), + DlPaint().setColor(DlColor::kDarkGrey())); + + for (int quadrant = 0; quadrant < 4; quadrant++) { + builder.Save(); + builder.Translate((quadrant & 1) * quadrant_size + test_border, + (quadrant >> 1) * quadrant_size + test_border); + + if (type == DlColorSourceType::kLinearGradient) { + // Alignment lines for the gradient edges/repeats/mirrors/etc. + // (rendered under the gradient so as not to obscure it) + DlPoint center = DlPoint(test_size, test_size) * 0.5; + DlScalar ten_percent = gradient_size * 0.1; + for (int i = gradient_size / 2; i <= test_size / 2; i += gradient_size) { + auto draw_at = [=](DlCanvas& canvas, DlScalar offset, DlColor color) { + DlPaint line_paint; + line_paint.setColor(color); + // strokewidth of 2 straddles the dividing line + line_paint.setStrokeWidth(2.0f); + line_paint.setDrawStyle(DlDrawStyle::kStroke); + + DlPoint along(offset, offset); + DlScalar across_distance = test_size / 2 + 10 - offset; + DlPoint across(across_distance, -across_distance); + + canvas.DrawLine(center - along - across, // + center - along + across, // + line_paint); + canvas.DrawLine(center + along - across, // + center + along + across, // + line_paint); + }; + // White line is at the edge of the gradient + // Grey lines are where the 0.1 and 0.9 color stops land + draw_at(builder, i - ten_percent, DlColor::kMidGrey()); + draw_at(builder, i, DlColor::kWhite()); + draw_at(builder, i + ten_percent, DlColor::kMidGrey()); + } + } + + std::vector colors = { + DlColor::kGreen(), + DlColor::kPurple(), + DlColor::kOrange(), + DlColor::kBlue(), + }; + std::vector stops = {0.1, 0.3, 0.7, 0.9}; + + DlPaint paint; + switch (type) { + case DlColorSourceType::kLinearGradient: + paint.setColorSource(DlColorSource::MakeLinear( + {test_size / 2 - gradient_size / 2, + test_size / 2 - gradient_size / 2}, + {test_size / 2 + gradient_size / 2, + test_size / 2 + gradient_size / 2}, + stops.size(), colors.data(), stops.data(), tile_modes[quadrant])); + break; + case DlColorSourceType::kRadialGradient: + paint.setColorSource(DlColorSource::MakeRadial( + {test_size / 2, test_size / 2}, gradient_size, // + stops.size(), colors.data(), stops.data(), tile_modes[quadrant])); + break; + case DlColorSourceType::kConicalGradient: + paint.setColorSource(DlColorSource::MakeConical( + {test_size / 2, test_size / 2}, 0, + {test_size / 2 + 20, test_size / 2 - 10}, gradient_size, + stops.size(), colors.data(), stops.data(), tile_modes[quadrant])); + break; + case DlColorSourceType::kSweepGradient: + paint.setColorSource(DlColorSource::MakeSweep( + {test_size / 2, test_size / 2}, 0, 45, // + stops.size(), colors.data(), stops.data(), tile_modes[quadrant])); + break; + default: + FML_UNREACHABLE(); + } + + builder.DrawRect(SkRect::MakeXYWH(0, 0, test_size, test_size), paint); + builder.Restore(); + } + + ASSERT_TRUE(aiks_test->OpenPlaygroundHere(builder.Build())); +} +} // namespace + +TEST_P(AiksTest, CanRenderLinearGradientWithIncompleteStops) { + CanRenderGradientWithIncompleteStops(this, + DlColorSourceType::kLinearGradient); +} +TEST_P(AiksTest, CanRenderRadialGradientWithIncompleteStops) { + CanRenderGradientWithIncompleteStops(this, + DlColorSourceType::kRadialGradient); +} +TEST_P(AiksTest, CanRenderConicalGradientWithIncompleteStops) { + CanRenderGradientWithIncompleteStops(this, + DlColorSourceType::kConicalGradient); +} +TEST_P(AiksTest, CanRenderSweepGradientWithIncompleteStops) { + CanRenderGradientWithIncompleteStops(this, DlColorSourceType::kSweepGradient); +} + namespace { void CanRenderLinearGradientManyColors(AiksTest* aiks_test, DlTileMode tile_mode) { @@ -559,23 +676,23 @@ TEST_P(AiksTest, CanRenderConicalGradient) { DlColor(Color::MakeRGBA8(0x4c, 0xAF, 0x50, 0xFF).ToARGB()), DlColor(Color::MakeRGBA8(0x21, 0x96, 0xF3, 0xFF).ToARGB())}; std::vector stops = {0.0, 1.f / 3.f, 2.f / 3.f, 1.0}; - std::array, 8> array{ - std::make_tuple(SkPoint::Make(size / 2.f, size / 2.f), 0.f, - SkPoint::Make(size / 2.f, size / 2.f), size / 2.f), - std::make_tuple(SkPoint::Make(size / 2.f, size / 2.f), size / 4.f, - SkPoint::Make(size / 2.f, size / 2.f), size / 2.f), - std::make_tuple(SkPoint::Make(size / 4.f, size / 4.f), 0.f, - SkPoint::Make(size / 2.f, size / 2.f), size / 2.f), - std::make_tuple(SkPoint::Make(size / 4.f, size / 4.f), size / 2.f, - SkPoint::Make(size / 2.f, size / 2.f), 0), - std::make_tuple(SkPoint::Make(size / 4.f, size / 4.f), size / 4.f, - SkPoint::Make(size / 2.f, size / 2.f), size / 2.f), - std::make_tuple(SkPoint::Make(size / 4.f, size / 4.f), size / 16.f, - SkPoint::Make(size / 2.f, size / 2.f), size / 8.f), - std::make_tuple(SkPoint::Make(size / 4.f, size / 4.f), size / 8.f, - SkPoint::Make(size / 2.f, size / 2.f), size / 16.f), - std::make_tuple(SkPoint::Make(size / 8.f, size / 8.f), size / 8.f, - SkPoint::Make(size / 2.f, size / 2.f), size / 8.f), + std::array, 8> array{ + std::make_tuple(DlPoint(size / 2.f, size / 2.f), 0.f, + DlPoint(size / 2.f, size / 2.f), size / 2.f), + std::make_tuple(DlPoint(size / 2.f, size / 2.f), size / 4.f, + DlPoint(size / 2.f, size / 2.f), size / 2.f), + std::make_tuple(DlPoint(size / 4.f, size / 4.f), 0.f, + DlPoint(size / 2.f, size / 2.f), size / 2.f), + std::make_tuple(DlPoint(size / 4.f, size / 4.f), size / 2.f, + DlPoint(size / 2.f, size / 2.f), 0), + std::make_tuple(DlPoint(size / 4.f, size / 4.f), size / 4.f, + DlPoint(size / 2.f, size / 2.f), size / 2.f), + std::make_tuple(DlPoint(size / 4.f, size / 4.f), size / 16.f, + DlPoint(size / 2.f, size / 2.f), size / 8.f), + std::make_tuple(DlPoint(size / 4.f, size / 4.f), size / 8.f, + DlPoint(size / 2.f, size / 2.f), size / 16.f), + std::make_tuple(DlPoint(size / 8.f, size / 8.f), size / 8.f, + DlPoint(size / 2.f, size / 2.f), size / 8.f), }; for (int i = 0; i < 8; i++) { builder.Save(); diff --git a/impeller/display_list/aiks_dl_path_unittests.cc b/impeller/display_list/aiks_dl_path_unittests.cc index ce5806c97c004..953f193be9a6c 100644 --- a/impeller/display_list/aiks_dl_path_unittests.cc +++ b/impeller/display_list/aiks_dl_path_unittests.cc @@ -38,7 +38,7 @@ TEST_P(AiksTest, RotateColorFilteredPath) { arrow_head.moveTo({50, 120}).lineTo({120, 190}).lineTo({190, 120}); auto filter = - DlBlendColorFilter::Make(DlColor::kAliceBlue(), DlBlendMode::kSrcIn); + DlColorFilter::MakeBlend(DlColor::kAliceBlue(), DlBlendMode::kSrcIn); DlPaint paint; paint.setStrokeWidth(15.0); @@ -368,8 +368,8 @@ TEST_P(AiksTest, DrawLinesRenderCorrectly) { DlTileMode::kMirror)); draw(paint); - SkMatrix matrix = SkMatrix::Translate(-150, 75); - paint.setColorSource(std::make_shared( + DlMatrix matrix = DlMatrix::MakeTranslation({-150, 75}); + paint.setColorSource(DlColorSource::MakeImage( texture, DlTileMode::kRepeat, DlTileMode::kRepeat, DlImageSampling::kMipmapLinear, &matrix)); draw(paint); diff --git a/impeller/display_list/aiks_dl_runtime_effect_unittests.cc b/impeller/display_list/aiks_dl_runtime_effect_unittests.cc index e090dd6f5112c..29fc758ea94e8 100644 --- a/impeller/display_list/aiks_dl_runtime_effect_unittests.cc +++ b/impeller/display_list/aiks_dl_runtime_effect_unittests.cc @@ -4,11 +4,12 @@ #include -#include "display_list/effects/dl_color_source.h" -#include "flutter/impeller/display_list/aiks_unittests.h" - #include "flutter/display_list/dl_builder.h" #include "flutter/display_list/dl_paint.h" +#include "flutter/display_list/effects/dl_color_source.h" +#include "flutter/display_list/effects/dl_image_filter.h" +#include "flutter/display_list/effects/dl_runtime_effect.h" +#include "flutter/impeller/display_list/aiks_unittests.h" #include "include/core/SkPath.h" #include "include/core/SkRRect.h" @@ -19,7 +20,7 @@ namespace testing { using namespace flutter; namespace { -std::shared_ptr MakeRuntimeEffect( +std::shared_ptr MakeRuntimeEffect( AiksTest* test, std::string_view name, const std::shared_ptr>& uniform_data = {}, @@ -32,8 +33,8 @@ std::shared_ptr MakeRuntimeEffect( auto dl_runtime_effect = DlRuntimeEffect::MakeImpeller(runtime_stage); - return std::make_shared(dl_runtime_effect, - samplers, uniform_data); + return DlColorSource::MakeRuntimeEffect(dl_runtime_effect, samplers, + uniform_data); } } // namespace @@ -83,5 +84,32 @@ TEST_P(AiksTest, DrawPaintTransformsBounds) { ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } +TEST_P(AiksTest, CanRenderRuntimeEffectFilter) { + auto runtime_stages = + OpenAssetAsRuntimeStage("runtime_stage_filter_example.frag.iplr"); + + std::shared_ptr runtime_stage = + runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; + ASSERT_TRUE(runtime_stage); + ASSERT_TRUE(runtime_stage->IsDirty()); + + std::vector> sampler_inputs = { + nullptr, + }; + auto uniform_data = std::make_shared>(); + uniform_data->resize(sizeof(Vector2)); + + DlPaint paint; + paint.setColor(DlColor::kAqua()); + paint.setImageFilter(DlImageFilter::MakeRuntimeEffect( + DlRuntimeEffect::MakeImpeller(runtime_stage), sampler_inputs, + uniform_data)); + + DisplayListBuilder builder; + builder.DrawRect(SkRect::MakeXYWH(0, 0, 400, 400), paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/display_list/aiks_dl_text_unittests.cc b/impeller/display_list/aiks_dl_text_unittests.cc index 330c14089fc94..9c7932c1642db 100644 --- a/impeller/display_list/aiks_dl_text_unittests.cc +++ b/impeller/display_list/aiks_dl_text_unittests.cc @@ -7,11 +7,11 @@ #include "display_list/dl_tile_mode.h" #include "display_list/effects/dl_color_source.h" #include "display_list/effects/dl_mask_filter.h" -#include "flutter/impeller/display_list/aiks_unittests.h" - #include "flutter/display_list/dl_builder.h" #include "flutter/display_list/dl_color.h" #include "flutter/display_list/dl_paint.h" +#include "flutter/fml/build_config.h" +#include "flutter/impeller/display_list/aiks_unittests.h" #include "flutter/testing/testing.h" #include "impeller/geometry/matrix.h" #include "impeller/typographer/backends/skia/text_frame_skia.h" @@ -463,8 +463,8 @@ TEST_P(AiksTest, TextForegroundShaderWithTransform) { 1.0, }; text_paint.setColorSource(DlColorSource::MakeLinear( - /*start_point=*/{0, 0}, // - /*end_point=*/{100, 100}, // + /*start_point=*/DlPoint(0, 0), // + /*end_point=*/DlPoint(100, 100), // /*stop_count=*/2, // /*colors=*/colors.data(), // /*stops=*/stops.data(), // @@ -483,5 +483,81 @@ TEST_P(AiksTest, TextForegroundShaderWithTransform) { ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } +// Regression test for https://github.com/flutter/flutter/issues/157885. +TEST_P(AiksTest, DifferenceClipsMustRenderIdenticallyAcrossBackends) { + DisplayListBuilder builder; + + DlPaint paint; + DlColor clear_color(1.0, 0.5, 0.5, 0.5, DlColorSpace::kSRGB); + paint.setColor(clear_color); + builder.DrawPaint(paint); + + DlMatrix identity = { + 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + }; + builder.Save(); + builder.Transform(identity); + + DlRect frame = DlRect::MakeLTRB(1.0, 1.0, 1278.0, 763.0); + DlColor white(1.0, 1.0, 1.0, 1.0, DlColorSpace::kSRGB); + paint.setColor(white); + builder.DrawRect(frame, paint); + + builder.Save(); + builder.ClipRect(frame, DlCanvas::ClipOp::kIntersect); + + DlMatrix rect_xform = { + 0.8241262, 0.56640625, 0.0, 0.0, -0.56640625, 0.8241262, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 271.1137, 489.4733, 0.0, 1.0, + }; + builder.Save(); + builder.Transform(rect_xform); + + DlRect rect = DlRect::MakeLTRB(0.0, 0.0, 100.0, 100.0); + DlColor bluish(1.0, 0.184, 0.501, 0.929, DlColorSpace::kSRGB); + paint.setColor(bluish); + DlRoundRect rrect = DlRoundRect::MakeRectRadius(rect, 18.0); + builder.DrawRoundRect(rrect, paint); + + builder.Save(); + builder.ClipRect(rect, DlCanvas::ClipOp::kIntersect); + builder.Restore(); + + builder.Restore(); + + DlMatrix path_xform = { + 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 675.0, 279.5, 0.0, 1.0, + }; + builder.Save(); + builder.Transform(path_xform); + + SkPath path; + path.moveTo(87.5, 349.5); + path.lineTo(25.0, 29.5); + path.lineTo(150.0, 118.0); + path.lineTo(25.0, 118.0); + path.lineTo(150.0, 29.5); + path.close(); + + DlColor fill_color(1.0, 1.0, 0.0, 0.0, DlColorSpace::kSRGB); + DlColor stroke_color(1.0, 0.0, 0.0, 0.0, DlColorSpace::kSRGB); + paint.setColor(fill_color); + paint.setDrawStyle(DlDrawStyle::kFill); + builder.DrawPath(DlPath(path), paint); + + paint.setColor(stroke_color); + paint.setStrokeWidth(2.0); + paint.setDrawStyle(DlDrawStyle::kStroke); + builder.DrawPath(path, paint); + + builder.Restore(); + builder.Restore(); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/display_list/aiks_dl_unittests.cc b/impeller/display_list/aiks_dl_unittests.cc index 279b7bc00a48f..6c389981031f2 100644 --- a/impeller/display_list/aiks_dl_unittests.cc +++ b/impeller/display_list/aiks_dl_unittests.cc @@ -3,12 +3,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include #include "display_list/dl_sampling_options.h" #include "display_list/dl_tile_mode.h" #include "display_list/effects/dl_color_filter.h" #include "display_list/effects/dl_color_source.h" #include "display_list/effects/dl_image_filter.h" #include "display_list/geometry/dl_geometry_types.h" +#include "display_list/geometry/dl_path.h" #include "display_list/image/dl_image.h" #include "flutter/impeller/display_list/aiks_unittests.h" @@ -21,7 +23,9 @@ #include "impeller/display_list/dl_dispatcher.h" #include "impeller/display_list/dl_image_impeller.h" #include "impeller/geometry/scalar.h" +#include "include/core/SkCanvas.h" #include "include/core/SkMatrix.h" +#include "include/core/SkPath.h" #include "include/core/SkRSXform.h" #include "include/core/SkRefCnt.h" @@ -64,7 +68,7 @@ TEST_P(AiksTest, CollapsedDrawPaintInSubpassBackdropFilter) { paint.setBlendMode(DlBlendMode::kSrc); builder.DrawPaint(paint); - auto filter = DlBlurImageFilter::Make(20.0, 20.0, DlTileMode::kDecal); + auto filter = DlImageFilter::MakeBlur(20.0, 20.0, DlTileMode::kDecal); builder.SaveLayer(nullptr, nullptr, filter.get()); DlPaint draw_paint; @@ -83,7 +87,7 @@ TEST_P(AiksTest, ColorMatrixFilterSubpassCollapseOptimization) { 0, 0, -1.0, 1.0, 0, // 1.0, 1.0, 1.0, 1.0, 0 // }; - auto filter = DlMatrixColorFilter::Make(matrix); + auto filter = DlColorFilter::MakeMatrix(matrix); DlPaint paint; paint.setColorFilter(filter); @@ -103,7 +107,7 @@ TEST_P(AiksTest, LinearToSrgbFilterSubpassCollapseOptimization) { DisplayListBuilder builder(GetCullRect(GetWindowSize())); DlPaint paint; - paint.setColorFilter(DlLinearToSrgbGammaColorFilter::kInstance); + paint.setColorFilter(DlColorFilter::MakeLinearToSrgbGamma()); builder.SaveLayer(nullptr, &paint); builder.Translate(500, 300); @@ -120,7 +124,7 @@ TEST_P(AiksTest, SrgbToLinearFilterSubpassCollapseOptimization) { DisplayListBuilder builder(GetCullRect(GetWindowSize())); DlPaint paint; - paint.setColorFilter(DlLinearToSrgbGammaColorFilter::kInstance); + paint.setColorFilter(DlColorFilter::MakeLinearToSrgbGamma()); builder.SaveLayer(nullptr, &paint); builder.Translate(500, 300); @@ -159,7 +163,7 @@ TEST_P(AiksTest, TranslucentSaveLayerWithBlendColorFilterDrawsCorrectly) { DlPaint save_paint; paint.setColor(DlColor::kBlack().withAlpha(128)); paint.setColorFilter( - DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kDstOver)); + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kDstOver)); builder.Save(); builder.ClipRect(SkRect::MakeXYWH(100, 500, 300, 300)); builder.SaveLayer(nullptr, &paint); @@ -182,8 +186,8 @@ TEST_P(AiksTest, TranslucentSaveLayerWithBlendImageFilterDrawsCorrectly) { DlPaint save_paint; save_paint.setColor(DlColor::kBlack().withAlpha(128)); - save_paint.setImageFilter(DlColorFilterImageFilter::Make( - DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kDstOver))); + save_paint.setImageFilter(DlImageFilter::MakeColorFilter( + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kDstOver))); builder.SaveLayer(nullptr, &save_paint); @@ -205,7 +209,7 @@ TEST_P(AiksTest, TranslucentSaveLayerWithColorAndImageFilterDrawsCorrectly) { DlPaint save_paint; save_paint.setColor(DlColor::kBlack().withAlpha(128)); save_paint.setColorFilter( - DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kDstOver)); + DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kDstOver)); builder.Save(); builder.ClipRect(SkRect::MakeXYWH(100, 500, 300, 300)); builder.SaveLayer(nullptr, &save_paint); @@ -225,7 +229,7 @@ TEST_P(AiksTest, ImageFilteredUnboundedSaveLayerWithUnboundedContents) { DlPaint save_paint; save_paint.setImageFilter( - DlBlurImageFilter::Make(10.0, 10.0, DlTileMode::kDecal)); + DlImageFilter::MakeBlur(10.0, 10.0, DlTileMode::kDecal)); builder.SaveLayer(nullptr, &save_paint); { @@ -273,7 +277,7 @@ TEST_P(AiksTest, TranslucentSaveLayerWithColorMatrixColorFilterDrawsCorrectly) { }; DlPaint paint; paint.setColor(DlColor::kBlack().withAlpha(128)); - paint.setColorFilter(DlMatrixColorFilter::Make(matrix)); + paint.setColorFilter(DlColorFilter::MakeMatrix(matrix)); builder.SaveLayer(nullptr, &paint); builder.DrawImage(image, SkPoint{100, 500}, {}); builder.Restore(); @@ -295,7 +299,7 @@ TEST_P(AiksTest, TranslucentSaveLayerWithColorMatrixImageFilterDrawsCorrectly) { }; DlPaint paint; paint.setColor(DlColor::kBlack().withAlpha(128)); - paint.setColorFilter(DlMatrixColorFilter::Make(matrix)); + paint.setColorFilter(DlColorFilter::MakeMatrix(matrix)); builder.SaveLayer(nullptr, &paint); builder.DrawImage(image, SkPoint{100, 500}, {}); builder.Restore(); @@ -319,9 +323,9 @@ TEST_P(AiksTest, DlPaint paint; paint.setColor(DlColor::kBlack().withAlpha(128)); paint.setImageFilter( - DlColorFilterImageFilter::Make(DlMatrixColorFilter::Make(matrix))); + DlImageFilter::MakeColorFilter(DlColorFilter::MakeMatrix(matrix))); paint.setColorFilter( - DlBlendColorFilter::Make(DlColor::kGreen(), DlBlendMode::kModulate)); + DlColorFilter::MakeBlend(DlColor::kGreen(), DlBlendMode::kModulate)); builder.SaveLayer(nullptr, &paint); builder.DrawImage(image, SkPoint{100, 500}, {}); builder.Restore(); @@ -450,8 +454,8 @@ TEST_P(AiksTest, CanDrawPointsWithTextureMap) { {52, 52}, // }; - auto image_src = std::make_shared( - texture, DlTileMode::kClamp, DlTileMode::kClamp); + auto image_src = + DlColorSource::MakeImage(texture, DlTileMode::kClamp, DlTileMode::kClamp); DlPaint paint_round; paint_round.setStrokeCap(DlStrokeCap::kRound); @@ -601,7 +605,7 @@ TEST_P(AiksTest, ReleasesTextureOnTeardown) { builder.Translate(100.0f, 100.0f); DlPaint paint; - paint.setColorSource(std::make_shared( + paint.setColorSource(DlColorSource::MakeImage( DlImageImpeller::Make(texture), DlTileMode::kClamp, DlTileMode::kClamp, DlImageSampling::kLinear, nullptr)); @@ -637,10 +641,10 @@ TEST_P(AiksTest, MatrixImageFilterMagnify) { builder.Translate(600, -200); - SkMatrix matrix = SkMatrix::Scale(scale, scale); + DlMatrix matrix = DlMatrix::MakeScale({scale, scale, 1}); DlPaint paint; paint.setImageFilter( - DlMatrixImageFilter::Make(matrix, DlImageSampling::kLinear)); + DlImageFilter::MakeMatrix(matrix, DlImageSampling::kLinear)); builder.SaveLayer(nullptr, &paint); DlPaint rect_paint; @@ -659,7 +663,7 @@ TEST_P(AiksTest, ImageFilteredSaveLayerWithUnboundedContents) { DisplayListBuilder builder; builder.Scale(GetContentScale().x, GetContentScale().y); - auto test = [&builder](const std::shared_ptr& filter) { + auto test = [&builder](const std::shared_ptr& filter) { auto DrawLine = [&builder](const SkPoint& p0, const SkPoint& p1, const DlPaint& p) { DlPaint paint = p; @@ -692,22 +696,22 @@ TEST_P(AiksTest, ImageFilteredSaveLayerWithUnboundedContents) { builder.Restore(); }; - test(std::make_shared(10.0, 10.0, DlTileMode::kDecal)); + test(DlImageFilter::MakeBlur(10.0, 10.0, DlTileMode::kDecal)); builder.Translate(200.0, 0.0); - test(std::make_shared(10.0, 10.0)); + test(DlImageFilter::MakeDilate(10.0, 10.0)); builder.Translate(200.0, 0.0); - test(std::make_shared(10.0, 10.0)); + test(DlImageFilter::MakeErode(10.0, 10.0)); builder.Translate(-400.0, 200.0); - SkMatrix sk_matrix = SkMatrix::RotateDeg(10); + DlMatrix matrix = DlMatrix::MakeRotationZ(DlDegrees(10)); - auto rotate_filter = std::make_shared( - sk_matrix, DlImageSampling::kLinear); + auto rotate_filter = + DlImageFilter::MakeMatrix(matrix, DlImageSampling::kLinear); test(rotate_filter); builder.Translate(200.0, 0.0); @@ -718,29 +722,28 @@ TEST_P(AiksTest, ImageFilteredSaveLayerWithUnboundedContents) { 1, 0, 0, 0, 0, // 0, 0, 0, 1, 0 // }; - auto rgb_swap_filter = std::make_shared( - std::make_shared(m)); + auto rgb_swap_filter = + DlImageFilter::MakeColorFilter(DlColorFilter::MakeMatrix(m)); test(rgb_swap_filter); builder.Translate(200.0, 0.0); - test(DlComposeImageFilter::Make(rotate_filter, rgb_swap_filter)); + test(DlImageFilter::MakeCompose(rotate_filter, rgb_swap_filter)); builder.Translate(-400.0, 200.0); - test(std::make_shared( - SkMatrix::Translate(25.0, 25.0), rotate_filter)); + test(rotate_filter->makeWithLocalMatrix( + DlMatrix::MakeTranslation({25.0, 25.0}))); builder.Translate(200.0, 0.0); - test(std::make_shared( - SkMatrix::Translate(25.0, 25.0), rgb_swap_filter)); + test(rgb_swap_filter->makeWithLocalMatrix( + DlMatrix::MakeTranslation({25.0, 25.0}))); builder.Translate(200.0, 0.0); - test(std::make_shared( - SkMatrix::Translate(25.0, 25.0), - DlComposeImageFilter::Make(rotate_filter, rgb_swap_filter))); + test(DlImageFilter::MakeCompose(rotate_filter, rgb_swap_filter) + ->makeWithLocalMatrix(DlMatrix::MakeTranslation({25.0, 25.0}))); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } @@ -765,12 +768,12 @@ TEST_P(AiksTest, MatrixBackdropFilter) { builder.DrawCircle(SkPoint::Make(200, 200), 100, paint); // Should render a second circle, centered on the bottom-right-most edge of // the circle. - SkMatrix matrix = SkMatrix::Translate((100 + 100 * k1OverSqrt2), - (100 + 100 * k1OverSqrt2)) * - SkMatrix::Scale(0.5, 0.5) * - SkMatrix::Translate(-100, -100); + DlMatrix matrix = DlMatrix::MakeTranslation({(100 + 100 * k1OverSqrt2), + (100 + 100 * k1OverSqrt2)}) * + DlMatrix::MakeScale({0.5, 0.5, 1}) * + DlMatrix::MakeTranslation({-100, -100}); auto backdrop_filter = - DlMatrixImageFilter::Make(matrix, DlImageSampling::kLinear); + DlImageFilter::MakeMatrix(matrix, DlImageSampling::kLinear); builder.SaveLayer(nullptr, nullptr, backdrop_filter.get()); builder.Restore(); } @@ -793,13 +796,13 @@ TEST_P(AiksTest, MatrixSaveLayerFilter) { // Should render a second circle, centered on the bottom-right-most edge of // the circle. - SkMatrix matrix = SkMatrix::Translate((200 + 100 * k1OverSqrt2), - (200 + 100 * k1OverSqrt2)) * - SkMatrix::Scale(0.5, 0.5) * - SkMatrix::Translate(-200, -200); + DlMatrix matrix = DlMatrix::MakeTranslation({(200 + 100 * k1OverSqrt2), + (200 + 100 * k1OverSqrt2)}) * + DlMatrix::MakeScale({0.5, 0.5, 1}) * + DlMatrix::MakeTranslation({-200, -200}); DlPaint save_paint; save_paint.setImageFilter( - DlMatrixImageFilter::Make(matrix, DlImageSampling::kLinear)); + DlImageFilter::MakeMatrix(matrix, DlImageSampling::kLinear)); builder.SaveLayer(nullptr, &save_paint); @@ -919,7 +922,7 @@ TEST_P(AiksTest, BackdropRestoreUsesCorrectCoverageForFirstRestoredClip) { { // Create a save layer with a backdrop blur filter. auto backdrop_filter = - DlBlurImageFilter::Make(10.0, 10.0, DlTileMode::kDecal); + DlImageFilter::MakeBlur(10.0, 10.0, DlTileMode::kDecal); builder.SaveLayer(nullptr, nullptr, backdrop_filter.get()); } } @@ -982,5 +985,63 @@ TEST_P(AiksTest, CanEmptyPictureConvertToImage) { ASSERT_TRUE(OpenPlaygroundHere(recorder_builder.Build())); } +TEST_P(AiksTest, DepthValuesForLineMode) { + // Ensures that the additional draws created by line/polygon mode all + // have the same depth values. + DisplayListBuilder builder; + + SkPath path = SkPath::Circle(100, 100, 100); + + builder.DrawPath(path, DlPaint() + .setColor(DlColor::kRed()) + .setDrawStyle(DlDrawStyle::kStroke) + .setStrokeWidth(5)); + builder.Save(); + builder.ClipPath(path); + + std::vector points = { + DlPoint::MakeXY(0, -200), DlPoint::MakeXY(400, 200), + DlPoint::MakeXY(0, -100), DlPoint::MakeXY(400, 300), + DlPoint::MakeXY(0, 0), DlPoint::MakeXY(400, 400), + DlPoint::MakeXY(0, 100), DlPoint::MakeXY(400, 500), + DlPoint::MakeXY(0, 150), DlPoint::MakeXY(400, 600)}; + + builder.DrawPoints(DisplayListBuilder::PointMode::kLines, points.size(), + points.data(), + DlPaint().setColor(DlColor::kBlue()).setStrokeWidth(10)); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, DepthValuesForPolygonMode) { + // Ensures that the additional draws created by line/polygon mode all + // have the same depth values. + DisplayListBuilder builder; + + SkPath path = SkPath::Circle(100, 100, 100); + + builder.DrawPath(path, DlPaint() + .setColor(DlColor::kRed()) + .setDrawStyle(DlDrawStyle::kStroke) + .setStrokeWidth(5)); + builder.Save(); + builder.ClipPath(path); + + std::vector points = { + DlPoint::MakeXY(0, -200), DlPoint::MakeXY(400, 200), + DlPoint::MakeXY(0, -100), DlPoint::MakeXY(400, 300), + DlPoint::MakeXY(0, 0), DlPoint::MakeXY(400, 400), + DlPoint::MakeXY(0, 100), DlPoint::MakeXY(400, 500), + DlPoint::MakeXY(0, 150), DlPoint::MakeXY(400, 600)}; + + builder.DrawPoints(DisplayListBuilder::PointMode::kPolygon, points.size(), + points.data(), + DlPaint().setColor(DlColor::kBlue()).setStrokeWidth(10)); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/display_list/aiks_dl_vertices_unittests.cc b/impeller/display_list/aiks_dl_vertices_unittests.cc index d37dec045b6fa..869f137f53b67 100644 --- a/impeller/display_list/aiks_dl_vertices_unittests.cc +++ b/impeller/display_list/aiks_dl_vertices_unittests.cc @@ -52,8 +52,8 @@ TEST_P(AiksTest, VerticesGeometryUVPositionData) { DlImageImpeller::Make(CreateTextureForFixture("table_mountain_nx.png")); auto size = image->impeller_texture()->GetSize(); - paint.setColorSource(std::make_shared( - image, DlTileMode::kClamp, DlTileMode::kClamp)); + paint.setColorSource( + DlColorSource::MakeImage(image, DlTileMode::kClamp, DlTileMode::kClamp)); std::vector vertex_coordinates = {SkPoint::Make(0, 0), SkPoint::Make(size.width, 0), @@ -73,12 +73,10 @@ TEST_P(AiksTest, VerticesGeometryUVPositionDataWithTranslate) { DlImageImpeller::Make(CreateTextureForFixture("table_mountain_nx.png")); auto size = image->impeller_texture()->GetSize(); - SkMatrix matrix; - matrix.setTranslateX(100); - matrix.setTranslateY(100); - paint.setColorSource(std::make_shared( - image, DlTileMode::kClamp, DlTileMode::kClamp, DlImageSampling::kLinear, - &matrix)); + DlMatrix matrix = DlMatrix::MakeTranslation({100, 100}); + paint.setColorSource( + DlColorSource::MakeImage(image, DlTileMode::kClamp, DlTileMode::kClamp, + DlImageSampling::kLinear, &matrix)); std::vector positions = {SkPoint::Make(0, 0), SkPoint::Make(size.width, 0), @@ -98,8 +96,8 @@ TEST_P(AiksTest, VerticesGeometryColorUVPositionData) { DlImageImpeller::Make(CreateTextureForFixture("table_mountain_nx.png")); auto size = image->impeller_texture()->GetSize(); - paint.setColorSource(std::make_shared( - image, DlTileMode::kClamp, DlTileMode::kClamp)); + paint.setColorSource( + DlColorSource::MakeImage(image, DlTileMode::kClamp, DlTileMode::kClamp)); std::vector positions = { SkPoint::Make(0, 0), SkPoint::Make(size.width, 0), @@ -126,8 +124,8 @@ TEST_P(AiksTest, VerticesGeometryColorUVPositionDataAdvancedBlend) { DlImageImpeller::Make(CreateTextureForFixture("table_mountain_nx.png")); auto size = image->impeller_texture()->GetSize(); - paint.setColorSource(std::make_shared( - image, DlTileMode::kClamp, DlTileMode::kClamp)); + paint.setColorSource( + DlColorSource::MakeImage(image, DlTileMode::kClamp, DlTileMode::kClamp)); std::vector positions = { SkPoint::Make(0, 0), SkPoint::Make(size.width, 0), @@ -273,10 +271,10 @@ TEST_P(AiksTest, DrawVerticesImageSourceWithTextureCoordinates) { flutter::DisplayListBuilder builder; flutter::DlPaint paint; - auto image_source = flutter::DlImageColorSource( + auto image_source = flutter::DlColorSource::MakeImage( dl_image, flutter::DlTileMode::kRepeat, flutter::DlTileMode::kRepeat); - paint.setColorSource(&image_source); + paint.setColorSource(image_source); builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); @@ -302,10 +300,10 @@ TEST_P(AiksTest, flutter::DisplayListBuilder builder; flutter::DlPaint paint; - auto image_source = flutter::DlImageColorSource( + auto image_source = flutter::DlColorSource::MakeImage( dl_image, flutter::DlTileMode::kRepeat, flutter::DlTileMode::kRepeat); - paint.setColorSource(&image_source); + paint.setColorSource(image_source); builder.DrawVertices(vertices, flutter::DlBlendMode::kModulate, paint); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); diff --git a/impeller/display_list/canvas.cc b/impeller/display_list/canvas.cc index 77f6984fc28c9..11cc4d7598a9f 100644 --- a/impeller/display_list/canvas.cc +++ b/impeller/display_list/canvas.cc @@ -14,6 +14,7 @@ #include "flutter/fml/logging.h" #include "flutter/fml/trace_event.h" #include "impeller/base/validation.h" +#include "impeller/core/formats.h" #include "impeller/display_list/color_filter.h" #include "impeller/display_list/image_filter.h" #include "impeller/display_list/skia_conversions.h" @@ -36,6 +37,7 @@ #include "impeller/entity/geometry/point_field_geometry.h" #include "impeller/entity/geometry/rect_geometry.h" #include "impeller/entity/geometry/round_rect_geometry.h" +#include "impeller/entity/geometry/round_superellipse_geometry.h" #include "impeller/entity/geometry/stroke_path_geometry.h" #include "impeller/entity/save_layer_utils.h" #include "impeller/geometry/color.h" @@ -54,9 +56,7 @@ static bool UseColorSourceContents( if (vertices->HasVertexColors()) { return false; } - if (vertices->HasTextureCoordinates() && - (!paint.color_source || - paint.color_source->type() == flutter::DlColorSourceType::kColor)) { + if (vertices->HasTextureCoordinates() && !paint.color_source) { return true; } return !vertices->HasTextureCoordinates(); @@ -87,116 +87,6 @@ static void ApplyFramebufferBlend(Entity& entity) { entity.SetBlendMode(BlendMode::kSource); } -/// End the current render pass, saving the result as a texture, and then -/// restart it with the backdrop cleared to the previous contents. -/// -/// This method is used to set up the input for emulated advanced blends and -/// backdrop filters. -/// -/// Returns the previous render pass stored as a texture, or nullptr if there -/// was a validation failure. -/// -/// [should_remove_texture] defaults to false. If true, the render target -/// texture is removed from the entity pass target. This allows the texture to -/// be cached by the canvas dispatcher for usage in the backdrop filter reuse -/// mechanism. -static std::shared_ptr FlipBackdrop( - std::vector& render_passes, - Point global_pass_position, - EntityPassClipStack& clip_coverage_stack, - ContentContext& renderer, - bool should_remove_texture = false) { - LazyRenderingConfig rendering_config = std::move(render_passes.back()); - render_passes.pop_back(); - - // If the very first thing we render in this EntityPass is a subpass that - // happens to have a backdrop filter or advanced blend, than that backdrop - // filter/blend will sample from an uninitialized texture. - // - // By calling `pass_context.GetRenderPass` here, we force the texture to pass - // through at least one RenderPass with the correct clear configuration before - // any sampling occurs. - // - // In cases where there are no contents, we - // could instead check the clear color and initialize a 1x2 CPU texture - // instead of ending the pass. - rendering_config.inline_pass_context->GetRenderPass(); - if (!rendering_config.inline_pass_context->EndPass()) { - VALIDATION_LOG - << "Failed to end the current render pass in order to read from " - "the backdrop texture and apply an advanced blend or backdrop " - "filter."; - // Note: adding this render pass ensures there are no later crashes from - // unbalanced save layers. Ideally, this method would return false and the - // renderer could handle that by terminating dispatch. - render_passes.push_back(LazyRenderingConfig( - renderer, std::move(rendering_config.entity_pass_target), - std::move(rendering_config.inline_pass_context))); - return nullptr; - } - - const std::shared_ptr& input_texture = - rendering_config.inline_pass_context->GetTexture(); - - if (!input_texture) { - VALIDATION_LOG << "Failed to fetch the color texture in order to " - "apply an advanced blend or backdrop filter."; - - // Note: see above. - render_passes.push_back(LazyRenderingConfig( - renderer, std::move(rendering_config.entity_pass_target), - std::move(rendering_config.inline_pass_context))); - return nullptr; - } - - render_passes.push_back(LazyRenderingConfig( - renderer, std::move(rendering_config.entity_pass_target), - std::move(rendering_config.inline_pass_context))); - // If the current texture is being cached for a BDF we need to ensure we - // don't recycle it during recording; remove it from the entity pass target. - if (should_remove_texture) { - render_passes.back().entity_pass_target->RemoveSecondary(); - } - RenderPass& current_render_pass = - *render_passes.back().inline_pass_context->GetRenderPass(); - - // Eagerly restore the BDF contents. - - // If the pass context returns a backdrop texture, we need to draw it to the - // current pass. We do this because it's faster and takes significantly less - // memory than storing/loading large MSAA textures. Also, it's not possible - // to blit the non-MSAA resolve texture of the previous pass to MSAA - // textures (let alone a transient one). - Rect size_rect = Rect::MakeSize(input_texture->GetSize()); - auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect); - msaa_backdrop_contents->SetStencilEnabled(false); - msaa_backdrop_contents->SetLabel("MSAA backdrop"); - msaa_backdrop_contents->SetSourceRect(size_rect); - msaa_backdrop_contents->SetTexture(input_texture); - - Entity msaa_backdrop_entity; - msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents)); - msaa_backdrop_entity.SetBlendMode(BlendMode::kSource); - msaa_backdrop_entity.SetClipDepth(std::numeric_limits::max()); - if (!msaa_backdrop_entity.Render(renderer, current_render_pass)) { - VALIDATION_LOG << "Failed to render MSAA backdrop entity."; - return nullptr; - } - - // Restore any clips that were recorded before the backdrop filter was - // applied. - auto& replay_entities = clip_coverage_stack.GetReplayEntities(); - for (const auto& replay : replay_entities) { - SetClipScissor(replay.clip_coverage, current_render_pass, - global_pass_position); - if (!replay.entity.Render(renderer, current_render_pass)) { - VALIDATION_LOG << "Failed to render entity for clip restore."; - } - } - - return input_texture; -} - /// @brief Create the subpass restore contents, appling any filters or opacity /// from the provided paint object. static std::shared_ptr CreateContentsForSubpassTarget( @@ -271,7 +161,7 @@ static std::unique_ptr CreateRenderTarget( } // namespace Canvas::Canvas(ContentContext& renderer, - RenderTarget& render_target, + const RenderTarget& render_target, bool requires_readback) : renderer_(renderer), render_target_(render_target), @@ -283,7 +173,7 @@ Canvas::Canvas(ContentContext& renderer, } Canvas::Canvas(ContentContext& renderer, - RenderTarget& render_target, + const RenderTarget& render_target, bool requires_readback, Rect cull_rect) : renderer_(renderer), @@ -296,7 +186,7 @@ Canvas::Canvas(ContentContext& renderer, } Canvas::Canvas(ContentContext& renderer, - RenderTarget& render_target, + const RenderTarget& render_target, bool requires_readback, IRect cull_rect) : renderer_(renderer), @@ -420,9 +310,11 @@ void Canvas::DrawPaint(const Paint& paint) { bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, Size corner_radii, const Paint& paint) { - if (paint.color_source && - (paint.color_source->type() != flutter::DlColorSourceType::kColor || - paint.style != Paint::Style::kFill)) { + if (paint.style != Paint::Style::kFill) { + return false; + } + + if (paint.color_source) { return false; } @@ -535,14 +427,14 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, break; } case FilterContents::BlurStyle::kOuter: { - ClipGeometry(Geometry::MakeRoundRect(rect, corner_radii), - Entity::ClipOperation::kDifference); + RoundRectGeometry geom(rect, corner_radii); + ClipGeometry(geom, Entity::ClipOperation::kDifference); draw_blurred_rrect(); break; } case FilterContents::BlurStyle::kInner: { - ClipGeometry(Geometry::MakeRoundRect(rect, corner_radii), - Entity::ClipOperation::kIntersect); + RoundRectGeometry geom(rect, corner_radii); + ClipGeometry(geom, Entity::ClipOperation::kIntersect); draw_blurred_rrect(); break; } @@ -553,13 +445,17 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, return true; } -void Canvas::DrawLine(const Point& p0, const Point& p1, const Paint& paint) { +void Canvas::DrawLine(const Point& p0, + const Point& p1, + const Paint& paint, + bool reuse_depth) { Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); LineGeometry geom(p0, p1, paint.stroke_width, paint.stroke_cap); - AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint, + /*reuse_depth=*/reuse_depth); } void Canvas::DrawRect(const Rect& rect, const Paint& paint) { @@ -660,37 +556,89 @@ void Canvas::DrawCircle(const Point& center, } } -void Canvas::ClipGeometry(std::unique_ptr geometry, - Entity::ClipOperation clip_op) { - clip_geometry_.push_back(std::move(geometry)); +void Canvas::ClipGeometry(const Geometry& geometry, + Entity::ClipOperation clip_op, + bool is_aa) { + if (IsSkipping()) { + return; + } + + // Ideally the clip depth would be greater than the current rendering + // depth because any rendering calls that follow this clip operation will + // pre-increment the depth and then be rendering above our clip depth, + // but that case will be caught by the CHECK in AddRenderEntity above. + // In practice we sometimes have a clip set with no rendering after it + // and in such cases the current depth will equal the clip depth. + // Eventually the DisplayList should optimize these out, but it is hard + // to know if a clip will actually be used in advance of storing it in + // the DisplayList buffer. + // See https://github.com/flutter/flutter/issues/147021 + FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth) + << current_depth_ << " <=? " << transform_stack_.back().clip_depth; + uint32_t clip_depth = transform_stack_.back().clip_depth; + + const Matrix clip_transform = + Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * + GetCurrentTransform(); - auto contents = std::make_shared(); - contents->SetGeometry(clip_geometry_.back().get()); - contents->SetClipOperation(clip_op); + std::optional clip_coverage = geometry.GetCoverage(clip_transform); + if (!clip_coverage.has_value()) { + return; + } - Entity entity; - entity.SetTransform(GetCurrentTransform()); - entity.SetContents(std::move(contents)); + ClipContents clip_contents( + clip_coverage.value(), + /*is_axis_aligned_rect=*/geometry.IsAxisAlignedRect() && + GetCurrentTransform().IsTranslationScaleOnly()); + clip_contents.SetClipOperation(clip_op); + + EntityPassClipStack::ClipStateResult clip_state_result = + clip_coverage_stack_.RecordClip( + clip_contents, // + /*transform=*/clip_transform, // + /*global_pass_position=*/GetGlobalPassPosition(), // + /*clip_depth=*/clip_depth, // + /*clip_height_floor=*/GetClipHeightFloor(), // + /*is_aa=*/is_aa); - AddClipEntityToCurrentPass(entity); + if (clip_state_result.clip_did_change) { + // We only need to update the pass scissor if the clip state has changed. + SetClipScissor(clip_coverage_stack_.CurrentClipCoverage(), + *render_passes_.back().inline_pass_context->GetRenderPass(), + GetGlobalPassPosition()); + } ++transform_stack_.back().clip_height; ++transform_stack_.back().num_clips; -} -void Canvas::RestoreClip() { + if (!clip_state_result.should_render) { + return; + } + + // Note: this is a bit of a hack. Its not possible to construct a geometry + // result without begninning the render pass. We should refactor the geometry + // objects so that they only need a reference to the render pass size and/or + // orthographic transform. Entity entity; - entity.SetTransform(GetCurrentTransform()); - // This path is empty because ClipRestoreContents just generates a quad that - // takes up the full render target. - auto clip_restore = std::make_shared(); - clip_restore->SetRestoreHeight(GetClipHeight()); - entity.SetContents(std::move(clip_restore)); + entity.SetTransform(clip_transform); + entity.SetClipDepth(clip_depth); - AddRenderEntityToCurrentPass(entity); + GeometryResult geometry_result = geometry.GetPositionBuffer( + renderer_, // + entity, // + *render_passes_.back().inline_pass_context->GetRenderPass() // + ); + clip_contents.SetGeometry(geometry_result); + clip_coverage_stack_.GetLastReplayResult().clip_contents.SetGeometry( + geometry_result); + + clip_contents.Render( + renderer_, *render_passes_.back().inline_pass_context->GetRenderPass(), + clip_depth); } -void Canvas::DrawPoints(std::vector points, +void Canvas::DrawPoints(const Point points[], + uint32_t count, Scalar radius, const Paint& paint, PointStyle point_style) { @@ -702,7 +650,7 @@ void Canvas::DrawPoints(std::vector points, entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - PointFieldGeometry geom(std::move(points), radius, + PointFieldGeometry geom(points, count, radius, /*round=*/point_style == PointStyle::kRound); AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); } @@ -737,9 +685,23 @@ void Canvas::DrawImageRect(const std::shared_ptr& image, return; } + std::optional clipped_source = + source.Intersection(Rect::MakeSize(size)); + if (!clipped_source) { + return; + } + if (*clipped_source != source) { + Scalar sx = dest.GetWidth() / source.GetWidth(); + Scalar sy = dest.GetHeight() / source.GetHeight(); + Scalar tx = dest.GetLeft() - source.GetLeft() * sx; + Scalar ty = dest.GetTop() - source.GetTop() * sy; + Matrix src_to_dest = Matrix::MakeTranslateScale({sx, sy, 1}, {tx, ty, 0}); + dest = clipped_source->TransformBounds(src_to_dest); + } + auto texture_contents = TextureContents::MakeRect(dest); texture_contents->SetTexture(image); - texture_contents->SetSourceRect(source); + texture_contents->SetSourceRect(*clipped_source); texture_contents->SetStrictSourceRect(src_rect_constraint == SourceRectConstraint::kStrict); texture_contents->SetSamplerDescriptor(std::move(sampler)); @@ -773,8 +735,7 @@ void Canvas::DrawVertices(const std::shared_ptr& vertices, // Override the blend mode with kDestination in order to match the behavior // of Skia's SK_LEGACY_IGNORE_DRAW_VERTICES_BLEND_WITH_NO_SHADER flag, which // is enabled when the Flutter engine builds Skia. - if (!paint.color_source || - paint.color_source->type() == flutter::DlColorSourceType::kColor) { + if (!paint.color_source) { blend_mode = BlendMode::kDestination; } @@ -814,8 +775,7 @@ void Canvas::DrawVertices(const std::shared_ptr& vertices, static_cast(image_color_source->vertical_tile_mode()); auto sampler_descriptor = skia_conversions::ToSamplerDescriptor(image_color_source->sampling()); - auto effect_transform = - skia_conversions::ToMatrix(image_color_source->matrix()); + auto effect_transform = image_color_source->matrix(); auto contents = std::make_shared(); contents->SetBlendMode(blend_mode); @@ -824,6 +784,7 @@ void Canvas::DrawVertices(const std::shared_ptr& vertices, contents->SetEffectTransform(effect_transform); contents->SetTexture(texture); contents->SetTileMode(x_tile_mode, y_tile_mode); + contents->SetSamplerDescriptor(sampler_descriptor); entity.SetContents(paint.WithFilters(std::move(contents))); AddRenderEntityToCurrentPass(entity); @@ -894,7 +855,7 @@ void Canvas::DrawAtlas(const std::shared_ptr& atlas_contents, void Canvas::SetupRenderPass() { renderer_.GetRenderTargetCache()->Start(); - auto color0 = render_target_.GetColorAttachments().find(0u)->second; + ColorAttachment color0 = render_target_.GetColorAttachment(0); auto& stencil_attachment = render_target_.GetStencilAttachment(); auto& depth_attachment = render_target_.GetDepthAttachment(); @@ -1083,11 +1044,15 @@ void Canvas::SaveLayer(const Paint& paint, std::shared_ptr input_texture; - // If the backdrop ID is not the no-op id, and there is more than one usage + // If the backdrop ID is not nullopt and there is more than one usage // of it in the current scene, cache the backdrop texture and remove it from // the current entity pass flip. bool will_cache_backdrop_texture = false; BackdropData* backdrop_data = nullptr; + // If we've reached this point, there is at least one backdrop filter. But + // potentially more if there is a backdrop id. We may conditionally set this + // to a higher value in the if block below. + size_t backdrop_count = 1; if (backdrop_id.has_value()) { std::unordered_map::iterator backdrop_data_it = backdrop_data_.find(backdrop_id.value()); @@ -1095,16 +1060,24 @@ void Canvas::SaveLayer(const Paint& paint, backdrop_data = &backdrop_data_it->second; will_cache_backdrop_texture = backdrop_data_it->second.backdrop_count > 1; + backdrop_count = backdrop_data_it->second.backdrop_count; } } - if (!will_cache_backdrop_texture || - (will_cache_backdrop_texture && !backdrop_data->texture_slot)) { - input_texture = FlipBackdrop(render_passes_, // - GetGlobalPassPosition(), // - clip_coverage_stack_, // - renderer_, // - will_cache_backdrop_texture // + if (!will_cache_backdrop_texture || !backdrop_data->texture_slot) { + backdrop_count_ -= backdrop_count; + + // The onscreen texture can be flipped to if: + // 1. The device supports framebuffer fetch + // 2. There are no more backdrop filters + // 3. The current render pass is for the onscreen pass. + const bool should_use_onscreen = + renderer_.GetDeviceCapabilities().SupportsFramebufferFetch() && + backdrop_count_ == 0 && render_passes_.size() == 1u; + input_texture = FlipBackdrop( + GetGlobalPassPosition(), // + /*should_remove_texture=*/will_cache_backdrop_texture, // + /*should_use_onscreen=*/should_use_onscreen // ); if (!input_texture) { // Validation failures are logged in FlipBackdrop. @@ -1297,9 +1270,7 @@ bool Canvas::Restore() { // to the render target texture so far need to execute before it's bound // for blending (otherwise the blend pass will end up executing before // all the previous commands in the active pass). - auto input_texture = - FlipBackdrop(render_passes_, GetGlobalPassPosition(), - clip_coverage_stack_, renderer_); + auto input_texture = FlipBackdrop(GetGlobalPassPosition()); if (!input_texture) { return false; } @@ -1333,35 +1304,12 @@ bool Canvas::Restore() { transform_stack_.pop_back(); if (num_clips > 0) { - Entity entity; - entity.SetTransform( - Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * - GetCurrentTransform()); - // This path is empty because ClipRestoreContents just generates a quad that - // takes up the full render target. - auto clip_restore = std::make_shared(); - clip_restore->SetRestoreHeight(GetClipHeight()); - entity.SetContents(std::move(clip_restore)); - - auto current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage(); - if (current_clip_coverage.has_value()) { - // Entity transforms are relative to the current pass position, so we need - // to check clip coverage in the same space. - current_clip_coverage = - current_clip_coverage->Shift(-GetGlobalPassPosition()); - } - - auto clip_coverage = entity.GetClipCoverage(current_clip_coverage); - if (clip_coverage.coverage.has_value()) { - clip_coverage.coverage = - clip_coverage.coverage->Shift(GetGlobalPassPosition()); - } - EntityPassClipStack::ClipStateResult clip_state_result = - clip_coverage_stack_.ApplyClipState(clip_coverage, entity, - GetClipHeightFloor(), - GetGlobalPassPosition()); + clip_coverage_stack_.RecordRestore(GetGlobalPassPosition(), + GetClipHeight()); + // Clip restores are never required with depth based clipping. + FML_DCHECK(!clip_state_result.should_render); if (clip_state_result.clip_did_change) { // We only need to update the pass scissor if the clip state has changed. SetClipScissor( @@ -1370,12 +1318,6 @@ bool Canvas::Restore() { GetGlobalPassPosition() // ); } - - if (!clip_state_result.should_render) { - return true; - } - - entity.Render(renderer_, GetCurrentRenderPass()); } return true; @@ -1520,8 +1462,7 @@ void Canvas::AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth) { RenderTarget& render_target = render_passes_.back() .inline_pass_context->GetPassTarget() .GetRenderTarget(); - ColorAttachment attachment = - render_target.GetColorAttachments().find(0u)->second; + ColorAttachment attachment = render_target.GetColorAttachment(0); // Attachment.clear color needs to be premultiplied at all times, but the // Color::Blend function requires unpremultiplied colors. attachment.clear_color = attachment.clear_color.Unpremultiply() @@ -1555,11 +1496,7 @@ void Canvas::AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth) { // to the render target texture so far need to execute before it's bound // for blending (otherwise the blend pass will end up executing before // all the previous commands in the active pass). - auto input_texture = FlipBackdrop(render_passes_, // - GetGlobalPassPosition(), // - clip_coverage_stack_, // - renderer_ // - ); + auto input_texture = FlipBackdrop(GetGlobalPassPosition()); if (!input_texture) { return; } @@ -1594,67 +1531,130 @@ void Canvas::AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth) { entity.Render(renderer_, *result); } -void Canvas::AddClipEntityToCurrentPass(Entity& entity) { - if (IsSkipping()) { - return; - } +RenderPass& Canvas::GetCurrentRenderPass() const { + return *render_passes_.back().inline_pass_context->GetRenderPass(); +} - auto transform = entity.GetTransform(); - entity.SetTransform( - Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * transform); +void Canvas::SetBackdropData( + std::unordered_map backdrop_data, + size_t backdrop_count) { + backdrop_data_ = std::move(backdrop_data); + backdrop_count_ = backdrop_count; +} - // Ideally the clip depth would be greater than the current rendering - // depth because any rendering calls that follow this clip operation will - // pre-increment the depth and then be rendering above our clip depth, - // but that case will be caught by the CHECK in AddRenderEntity above. - // In practice we sometimes have a clip set with no rendering after it - // and in such cases the current depth will equal the clip depth. - // Eventually the DisplayList should optimize these out, but it is hard - // to know if a clip will actually be used in advance of storing it in - // the DisplayList buffer. - // See https://github.com/flutter/flutter/issues/147021 - FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth) - << current_depth_ << " <=? " << transform_stack_.back().clip_depth; - entity.SetClipDepth(transform_stack_.back().clip_depth); +std::shared_ptr Canvas::FlipBackdrop(Point global_pass_position, + bool should_remove_texture, + bool should_use_onscreen) { + LazyRenderingConfig rendering_config = std::move(render_passes_.back()); + render_passes_.pop_back(); - auto current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage(); - if (current_clip_coverage.has_value()) { - // Entity transforms are relative to the current pass position, so we need - // to check clip coverage in the same space. - current_clip_coverage = - current_clip_coverage->Shift(-GetGlobalPassPosition()); + // If the very first thing we render in this EntityPass is a subpass that + // happens to have a backdrop filter or advanced blend, than that backdrop + // filter/blend will sample from an uninitialized texture. + // + // By calling `pass_context.GetRenderPass` here, we force the texture to pass + // through at least one RenderPass with the correct clear configuration before + // any sampling occurs. + // + // In cases where there are no contents, we + // could instead check the clear color and initialize a 1x2 CPU texture + // instead of ending the pass. + rendering_config.inline_pass_context->GetRenderPass(); + if (!rendering_config.inline_pass_context->EndPass()) { + VALIDATION_LOG + << "Failed to end the current render pass in order to read from " + "the backdrop texture and apply an advanced blend or backdrop " + "filter."; + // Note: adding this render pass ensures there are no later crashes from + // unbalanced save layers. Ideally, this method would return false and the + // renderer could handle that by terminating dispatch. + render_passes_.push_back(LazyRenderingConfig( + renderer_, std::move(rendering_config.entity_pass_target), + std::move(rendering_config.inline_pass_context))); + return nullptr; } - auto clip_coverage = entity.GetClipCoverage(current_clip_coverage); - if (clip_coverage.coverage.has_value()) { - clip_coverage.coverage = - clip_coverage.coverage->Shift(GetGlobalPassPosition()); - } + const std::shared_ptr& input_texture = + rendering_config.inline_pass_context->GetTexture(); - EntityPassClipStack::ClipStateResult clip_state_result = - clip_coverage_stack_.ApplyClipState( - clip_coverage, entity, GetClipHeightFloor(), GetGlobalPassPosition()); + if (!input_texture) { + VALIDATION_LOG << "Failed to fetch the color texture in order to " + "apply an advanced blend or backdrop filter."; - if (clip_state_result.clip_did_change) { - // We only need to update the pass scissor if the clip state has changed. - SetClipScissor(clip_coverage_stack_.CurrentClipCoverage(), - GetCurrentRenderPass(), GetGlobalPassPosition()); + // Note: see above. + render_passes_.push_back(LazyRenderingConfig( + renderer_, std::move(rendering_config.entity_pass_target), + std::move(rendering_config.inline_pass_context))); + return nullptr; } - if (!clip_state_result.should_render) { - return; + if (should_use_onscreen) { + ColorAttachment color0 = render_target_.GetColorAttachment(0); + // When MSAA is being used, we end up overriding the entire backdrop by + // drawing the previous pass texture, and so we don't have to clear it and + // can use kDontCare. + color0.load_action = color0.resolve_texture != nullptr + ? LoadAction::kDontCare + : LoadAction::kLoad; + render_target_.SetColorAttachment(color0, 0); + + auto entity_pass_target = std::make_unique( + render_target_, // + renderer_.GetDeviceCapabilities().SupportsReadFromResolve(), // + renderer_.GetDeviceCapabilities().SupportsImplicitResolvingMSAA() // + ); + render_passes_.push_back( + LazyRenderingConfig(renderer_, std::move(entity_pass_target))); + requires_readback_ = false; + } else { + render_passes_.push_back(LazyRenderingConfig( + renderer_, std::move(rendering_config.entity_pass_target), + std::move(rendering_config.inline_pass_context))); + // If the current texture is being cached for a BDF we need to ensure we + // don't recycle it during recording; remove it from the entity pass target. + if (should_remove_texture) { + render_passes_.back().entity_pass_target->RemoveSecondary(); + } } + RenderPass& current_render_pass = + *render_passes_.back().inline_pass_context->GetRenderPass(); - entity.Render(renderer_, GetCurrentRenderPass()); -} + // Eagerly restore the BDF contents. -RenderPass& Canvas::GetCurrentRenderPass() const { - return *render_passes_.back().inline_pass_context->GetRenderPass(); -} + // If the pass context returns a backdrop texture, we need to draw it to the + // current pass. We do this because it's faster and takes significantly less + // memory than storing/loading large MSAA textures. Also, it's not possible + // to blit the non-MSAA resolve texture of the previous pass to MSAA + // textures (let alone a transient one). + Rect size_rect = Rect::MakeSize(input_texture->GetSize()); + auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect); + msaa_backdrop_contents->SetStencilEnabled(false); + msaa_backdrop_contents->SetLabel("MSAA backdrop"); + msaa_backdrop_contents->SetSourceRect(size_rect); + msaa_backdrop_contents->SetTexture(input_texture); -void Canvas::SetBackdropData( - std::unordered_map backdrop_data) { - backdrop_data_ = std::move(backdrop_data); + Entity msaa_backdrop_entity; + msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents)); + msaa_backdrop_entity.SetBlendMode(BlendMode::kSource); + msaa_backdrop_entity.SetClipDepth(std::numeric_limits::max()); + if (!msaa_backdrop_entity.Render(renderer_, current_render_pass)) { + VALIDATION_LOG << "Failed to render MSAA backdrop entity."; + return nullptr; + } + + // Restore any clips that were recorded before the backdrop filter was + // applied. + auto& replay_entities = clip_coverage_stack_.GetReplayEntities(); + for (const auto& replay : replay_entities) { + SetClipScissor(replay.clip_coverage, current_render_pass, + global_pass_position); + if (!replay.clip_contents.Render(renderer_, current_render_pass, + replay.clip_depth)) { + VALIDATION_LOG << "Failed to render entity for clip restore."; + } + } + + return input_texture; } bool Canvas::BlitToOnscreen() { diff --git a/impeller/display_list/canvas.h b/impeller/display_list/canvas.h index 0d72771bf3b1d..fbb4a8bb0abde 100644 --- a/impeller/display_list/canvas.h +++ b/impeller/display_list/canvas.h @@ -16,6 +16,7 @@ #include "impeller/core/sampler_descriptor.h" #include "impeller/display_list/paint.h" #include "impeller/entity/contents/atlas_contents.h" +#include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/entity.h" #include "impeller/entity/entity_pass_clip_stack.h" #include "impeller/entity/geometry/geometry.h" @@ -121,24 +122,25 @@ class Canvas { Entity::RenderingMode rendering_mode)>; Canvas(ContentContext& renderer, - RenderTarget& render_target, + const RenderTarget& render_target, bool requires_readback); explicit Canvas(ContentContext& renderer, - RenderTarget& render_target, + const RenderTarget& render_target, bool requires_readback, Rect cull_rect); explicit Canvas(ContentContext& renderer, - RenderTarget& render_target, + const RenderTarget& render_target, bool requires_readback, IRect cull_rect); ~Canvas() = default; /// @brief Update the backdrop data used to group together backdrop filters - /// within the same layer. - void SetBackdropData(std::unordered_map backdrop_data); + /// within the same layer + void SetBackdropData(std::unordered_map backdrop_data, + size_t backdrop_count); /// @brief Return the culling bounds of the current render target, or nullopt /// if there is no coverage. @@ -185,7 +187,10 @@ class Canvas { void DrawPaint(const Paint& paint); - void DrawLine(const Point& p0, const Point& p1, const Paint& paint); + void DrawLine(const Point& p0, + const Point& p1, + const Paint& paint, + bool reuse_depth = false); void DrawRect(const Rect& rect, const Paint& paint); @@ -195,7 +200,8 @@ class Canvas { void DrawCircle(const Point& center, Scalar radius, const Paint& paint); - void DrawPoints(std::vector points, + void DrawPoints(const Point points[], + uint32_t count, Scalar radius, const Paint& paint, PointStyle point_style); @@ -224,8 +230,9 @@ class Canvas { void DrawAtlas(const std::shared_ptr& atlas_contents, const Paint& paint); - void ClipGeometry(std::unique_ptr geometry, - Entity::ClipOperation clip_op); + void ClipGeometry(const Geometry& geometry, + Entity::ClipOperation clip_op, + bool is_aa = true); void EndReplay(); @@ -238,18 +245,37 @@ class Canvas { Rect coverage; }; + // Visible for testing. + bool RequiresReadback() const { return requires_readback_; } + private: ContentContext& renderer_; - RenderTarget& render_target_; - const bool requires_readback_; + RenderTarget render_target_; + bool requires_readback_; EntityPassClipStack clip_coverage_stack_; std::deque transform_stack_; std::optional initial_cull_rect_; std::vector render_passes_; std::vector save_layer_state_; + + /// Backdrop layers identified by an optional backdrop id. + /// + /// This is not the same as the [backdrop_count_] below as not + /// all backdrop filters will have an identified backdrop id. The + /// backdrop_count_ is also mutated during rendering. std::unordered_map backdrop_data_; + /// The remaining number of backdrop filters. + /// + /// This value is decremented while rendering. When it reaches 0, then + /// the FlipBackdrop can use the onscreen render target instead of + /// another offscreen. + /// + /// This optimization is disabled on devices that do not support framebuffer + /// fetch (iOS Simulator and certain OpenGLES devices). + size_t backdrop_count_ = 0u; + // All geometry objects created for regular draws can be stack allocated, // but clip geometries must be cached for record/replay for backdrop filters // and so must be kept alive longer. @@ -271,6 +297,27 @@ class Canvas { void SetupRenderPass(); + /// @brief Ends the current render pass, saving the result as a texture, and + /// thenrestart it with the backdrop cleared to the previous contents. + /// + /// The returned texture is used as the input for backdrop filters and + /// emulated advanced blends. Returns nullptr if there was a validation + /// failure. + /// + /// [should_remove_texture] defaults to false. If true, the render target + /// texture is removed from the entity pass target. This allows the texture to + /// be cached by the canvas dispatcher for usage in the backdrop filter reuse + /// mechanism. + /// + /// [should_use_onscreen] defaults to false. If true, the results are flipped + /// to the onscreen render target. This will set requires_readback_ to false. + /// This action is only safe to perform when there are no more backdrop + /// filters or advanced blends, or no more backdrop filters and the device + /// supports framebuffer fetch. + std::shared_ptr FlipBackdrop(Point global_pass_position, + bool should_remove_texture = false, + bool should_use_onscreen = false); + bool BlitToOnscreen(); size_t GetClipHeight() const; @@ -286,10 +333,6 @@ class Canvas { void AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth = false); - void AddClipEntityToCurrentPass(Entity& entity); - - void RestoreClip(); - bool AttemptDrawBlurredRRect(const Rect& rect, Size corner_radii, const Paint& paint); diff --git a/impeller/display_list/canvas_unittests.cc b/impeller/display_list/canvas_unittests.cc index dae5e25451b8a..09dad0565d26a 100644 --- a/impeller/display_list/canvas_unittests.cc +++ b/impeller/display_list/canvas_unittests.cc @@ -2,25 +2,64 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + +#include "flutter/display_list/dl_tile_mode.h" +#include "flutter/display_list/effects/dl_image_filter.h" +#include "flutter/display_list/geometry/dl_geometry_types.h" #include "flutter/testing/testing.h" +#include "gtest/gtest.h" +#include "impeller/core/formats.h" +#include "impeller/core/texture_descriptor.h" #include "impeller/display_list/aiks_unittests.h" #include "impeller/display_list/canvas.h" #include "impeller/geometry/geometry_asserts.h" +#include "impeller/renderer/render_target.h" namespace impeller { namespace testing { std::unique_ptr CreateTestCanvas( ContentContext& context, - std::optional cull_rect = std::nullopt) { - RenderTarget render_target = context.GetRenderTargetCache()->CreateOffscreen( - *context.GetContext(), {1, 1}, 1); + std::optional cull_rect = std::nullopt, + bool requires_readback = false) { + TextureDescriptor onscreen_desc; + onscreen_desc.size = {100, 100}; + onscreen_desc.format = + context.GetDeviceCapabilities().GetDefaultColorFormat(); + onscreen_desc.usage = TextureUsage::kRenderTarget; + onscreen_desc.storage_mode = StorageMode::kDevicePrivate; + onscreen_desc.sample_count = SampleCount::kCount1; + std::shared_ptr onscreen = + context.GetContext()->GetResourceAllocator()->CreateTexture( + onscreen_desc); + + ColorAttachment color0; + color0.load_action = LoadAction::kClear; + if (context.GetContext()->GetCapabilities()->SupportsOffscreenMSAA()) { + TextureDescriptor onscreen_msaa_desc = onscreen_desc; + onscreen_msaa_desc.sample_count = SampleCount::kCount4; + onscreen_msaa_desc.storage_mode = StorageMode::kDeviceTransient; + onscreen_msaa_desc.type = TextureType::kTexture2DMultisample; + + std::shared_ptr onscreen_msaa = + context.GetContext()->GetResourceAllocator()->CreateTexture( + onscreen_msaa_desc); + color0.resolve_texture = onscreen; + color0.texture = onscreen_msaa; + color0.store_action = StoreAction::kMultisampleResolve; + } else { + color0.texture = onscreen; + } + + RenderTarget render_target; + render_target.SetColorAttachment(color0, 0); if (cull_rect.has_value()) { - return std::make_unique(context, render_target, false, + return std::make_unique(context, render_target, requires_readback, cull_rect.value()); } - return std::make_unique(context, render_target, false); + return std::make_unique(context, render_target, requires_readback); } TEST_P(AiksTest, TransformMultipliesCorrectly) { @@ -93,5 +132,149 @@ TEST_P(AiksTest, CanvasCTMCanBeUpdated) { Matrix::MakeTranslation({100.0, 100.0, 0.0})); } +TEST_P(AiksTest, BackdropCountDownNormal) { + ContentContext context(GetContext(), nullptr); + if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) { + GTEST_SKIP() << "Test requires device with framebuffer fetch"; + } + auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100), + /*requires_readback=*/true); + // 3 backdrop filters + canvas->SetBackdropData({}, 3); + + auto blur = + flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp); + flutter::DlRect rect = flutter::DlRect::MakeLTRB(0, 0, 50, 50); + + EXPECT_TRUE(canvas->RequiresReadback()); + canvas->DrawRect(rect, {.color = Color::Azure()}); + canvas->SaveLayer({}, rect, blur.get(), + ContentBoundsPromise::kContainsContents, + /*total_content_depth=*/1); + canvas->Restore(); + EXPECT_TRUE(canvas->RequiresReadback()); + + canvas->SaveLayer({}, rect, blur.get(), + ContentBoundsPromise::kContainsContents, + /*total_content_depth=*/1); + canvas->Restore(); + EXPECT_TRUE(canvas->RequiresReadback()); + + canvas->SaveLayer({}, rect, blur.get(), + ContentBoundsPromise::kContainsContents, + /*total_content_depth=*/1); + canvas->Restore(); + EXPECT_FALSE(canvas->RequiresReadback()); +} + +TEST_P(AiksTest, BackdropCountDownBackdropId) { + ContentContext context(GetContext(), nullptr); + if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) { + GTEST_SKIP() << "Test requires device with framebuffer fetch"; + } + auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100), + /*requires_readback=*/true); + // 3 backdrop filters all with same id. + std::unordered_map data; + data[1] = BackdropData{.backdrop_count = 3}; + canvas->SetBackdropData(data, 3); + + auto blur = + flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp); + + EXPECT_TRUE(canvas->RequiresReadback()); + canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50), + {.color = Color::Azure()}); + canvas->SaveLayer({}, std::nullopt, blur.get(), + ContentBoundsPromise::kContainsContents, + /*total_content_depth=*/1, /*can_distribute_opacity=*/false, + /*backdrop_id=*/1); + canvas->Restore(); + EXPECT_FALSE(canvas->RequiresReadback()); + + canvas->SaveLayer({}, std::nullopt, blur.get(), + ContentBoundsPromise::kContainsContents, + /*total_content_depth=*/1, /*can_distribute_opacity=*/false, + /*backdrop_id=*/1); + canvas->Restore(); + EXPECT_FALSE(canvas->RequiresReadback()); + + canvas->SaveLayer({}, std::nullopt, blur.get(), + ContentBoundsPromise::kContainsContents, + /*total_content_depth=*/1, /*can_distribute_opacity=*/false, + /*backdrop_id=*/1); + canvas->Restore(); + EXPECT_FALSE(canvas->RequiresReadback()); +} + +TEST_P(AiksTest, BackdropCountDownBackdropIdMixed) { + ContentContext context(GetContext(), nullptr); + if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) { + GTEST_SKIP() << "Test requires device with framebuffer fetch"; + } + auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100), + /*requires_readback=*/true); + // 3 backdrop filters, 2 with same id. + std::unordered_map data; + data[1] = BackdropData{.backdrop_count = 2}; + canvas->SetBackdropData(data, 3); + + auto blur = + flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp); + + EXPECT_TRUE(canvas->RequiresReadback()); + canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50), + {.color = Color::Azure()}); + canvas->SaveLayer({}, std::nullopt, blur.get(), + ContentBoundsPromise::kContainsContents, 1, false); + canvas->Restore(); + EXPECT_TRUE(canvas->RequiresReadback()); + + canvas->SaveLayer({}, std::nullopt, blur.get(), + ContentBoundsPromise::kContainsContents, 1, false, 1); + canvas->Restore(); + EXPECT_FALSE(canvas->RequiresReadback()); + + canvas->SaveLayer({}, std::nullopt, blur.get(), + ContentBoundsPromise::kContainsContents, 1, false, 1); + canvas->Restore(); + EXPECT_FALSE(canvas->RequiresReadback()); +} + +// We only know the total number of backdrop filters, not the number of backdrop +// filters in the root pass. If we reach a count of 0 while in a nested +// saveLayer, we should not restore to the onscreen. +TEST_P(AiksTest, BackdropCountDownWithNestedSaveLayers) { + ContentContext context(GetContext(), nullptr); + if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) { + GTEST_SKIP() << "Test requires device with framebuffer fetch"; + } + auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100), + /*requires_readback=*/true); + + canvas->SetBackdropData({}, 2); + + auto blur = + flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp); + + EXPECT_TRUE(canvas->RequiresReadback()); + canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50), + {.color = Color::Azure()}); + canvas->SaveLayer({}, std::nullopt, blur.get(), + ContentBoundsPromise::kContainsContents, + /*total_content_depth=*/3); + + // This filter is nested in the first saveLayer. We cannot restore to onscreen + // here. + canvas->SaveLayer({}, std::nullopt, blur.get(), + ContentBoundsPromise::kContainsContents, + /*total_content_depth=*/1); + canvas->Restore(); + EXPECT_TRUE(canvas->RequiresReadback()); + + canvas->Restore(); + EXPECT_TRUE(canvas->RequiresReadback()); +} + } // namespace testing } // namespace impeller diff --git a/impeller/display_list/color_filter.cc b/impeller/display_list/color_filter.cc index e248ba6e09628..f5050672f3d14 100644 --- a/impeller/display_list/color_filter.cc +++ b/impeller/display_list/color_filter.cc @@ -4,7 +4,7 @@ #include "impeller/display_list/color_filter.h" -#include "display_list/effects/dl_color_filter.h" +#include "display_list/effects/dl_color_filters.h" #include "fml/logging.h" #include "impeller/display_list/skia_conversions.h" #include "impeller/entity/contents/filters/color_filter_contents.h" diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 804f344470237..3fb650d36a29d 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -10,7 +10,7 @@ #include #include -#include "display_list/effects/dl_color_source.h" +#include "display_list/dl_sampling_options.h" #include "display_list/effects/dl_image_filter.h" #include "flutter/fml/logging.h" #include "impeller/core/formats.h" @@ -25,7 +25,10 @@ #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" #include "impeller/entity/entity.h" -#include "impeller/entity/geometry/geometry.h" +#include "impeller/entity/geometry/ellipse_geometry.h" +#include "impeller/entity/geometry/fill_path_geometry.h" +#include "impeller/entity/geometry/rect_geometry.h" +#include "impeller/entity/geometry/round_rect_geometry.h" #include "impeller/geometry/color.h" #include "impeller/geometry/path.h" #include "impeller/geometry/path_builder.h" @@ -219,11 +222,7 @@ void DlDispatcherBase::setStrokeJoin(flutter::DlStrokeJoin join) { void DlDispatcherBase::setColorSource(const flutter::DlColorSource* source) { AUTO_DEPTH_WATCHER(0u); - if (!source || source->type() == flutter::DlColorSourceType::kColor) { - paint_.color_source = nullptr; - } else { - paint_.color_source = source; - } + paint_.color_source = source; } // |flutter::DlOpReceiver| @@ -434,7 +433,8 @@ void DlDispatcherBase::clipRect(const DlRect& rect, bool is_aa) { AUTO_DEPTH_WATCHER(0u); - GetCanvas().ClipGeometry(Geometry::MakeRect(rect), ToClipOperation(clip_op)); + RectGeometry geom(rect); + GetCanvas().ClipGeometry(geom, ToClipOperation(clip_op), /*is_aa=*/is_aa); } // |flutter::DlOpReceiver| @@ -443,8 +443,8 @@ void DlDispatcherBase::clipOval(const DlRect& bounds, bool is_aa) { AUTO_DEPTH_WATCHER(0u); - GetCanvas().ClipGeometry(Geometry::MakeOval(bounds), - ToClipOperation(clip_op)); + EllipseGeometry geom(bounds); + GetCanvas().ClipGeometry(geom, ToClipOperation(clip_op)); } // |flutter::DlOpReceiver| @@ -455,17 +455,17 @@ void DlDispatcherBase::clipRoundRect(const DlRoundRect& rrect, auto clip_op = ToClipOperation(sk_op); if (rrect.IsRect()) { - GetCanvas().ClipGeometry(Geometry::MakeRect(rrect.GetBounds()), clip_op); + RectGeometry geom(rrect.GetBounds()); + GetCanvas().ClipGeometry(geom, clip_op, /*is_aa=*/is_aa); } else if (rrect.IsOval()) { - GetCanvas().ClipGeometry(Geometry::MakeOval(rrect.GetBounds()), clip_op); + EllipseGeometry geom(rrect.GetBounds()); + GetCanvas().ClipGeometry(geom, clip_op); } else if (rrect.GetRadii().AreAllCornersSame()) { - GetCanvas().ClipGeometry( - Geometry::MakeRoundRect(rrect.GetBounds(), rrect.GetRadii().top_left), - clip_op); + RoundRectGeometry geom(rrect.GetBounds(), rrect.GetRadii().top_left); + GetCanvas().ClipGeometry(geom, clip_op); } else { - GetCanvas().ClipGeometry( - Geometry::MakeFillPath(PathBuilder{}.AddRoundRect(rrect).TakePath()), - clip_op); + FillPathGeometry geom(PathBuilder{}.AddRoundRect(rrect).TakePath()); + GetCanvas().ClipGeometry(geom, clip_op); } } @@ -477,19 +477,20 @@ void DlDispatcherBase::clipPath(const DlPath& path, ClipOp sk_op, bool is_aa) { DlRect rect; if (path.IsRect(&rect)) { - GetCanvas().ClipGeometry(Geometry::MakeRect(rect), clip_op); + RectGeometry geom(rect); + GetCanvas().ClipGeometry(geom, clip_op, /*is_aa=*/is_aa); } else if (path.IsOval(&rect)) { - GetCanvas().ClipGeometry(Geometry::MakeOval(rect), clip_op); + EllipseGeometry geom(rect); + GetCanvas().ClipGeometry(geom, clip_op); } else { SkRRect rrect; if (path.IsSkRRect(&rrect) && rrect.isSimple()) { - GetCanvas().ClipGeometry( - Geometry::MakeRoundRect( - skia_conversions::ToRect(rrect.rect()), - skia_conversions::ToSize(rrect.getSimpleRadii())), - clip_op); + RoundRectGeometry geom(skia_conversions::ToRect(rrect.rect()), + skia_conversions::ToSize(rrect.getSimpleRadii())); + GetCanvas().ClipGeometry(geom, clip_op); } else { - GetCanvas().ClipGeometry(Geometry::MakeFillPath(path.GetPath()), clip_op); + FillPathGeometry geom(path.GetPath()); + GetCanvas().ClipGeometry(geom, clip_op); } } } @@ -530,7 +531,8 @@ void DlDispatcherBase::drawDashedLine(const DlPoint& p0, // length is non-positive - drawLine will draw appropriate "dot" // off_length is non-positive - no gaps, drawLine will draw it solid // on_length is negative - invalid dashing - // Note that a 0 length "on" dash will draw "dot"s every "off" distance apart + // Note that a 0 length "on" dash will draw "dot"s every "off" distance + // apart if (length > 0.0f && on_length >= 0.0f && off_length > 0.0f) { Point delta = (p1 - p0) / length; // length > 0 already tested PathBuilder builder; @@ -656,20 +658,20 @@ void DlDispatcherBase::drawPoints(PointMode mode, switch (mode) { case flutter::DlCanvas::PointMode::kPoints: { // Cap::kButt is also treated as a square. - auto point_style = paint.stroke_cap == Cap::kRound ? PointStyle::kRound - : PointStyle::kSquare; - auto radius = paint.stroke_width; + PointStyle point_style = paint.stroke_cap == Cap::kRound + ? PointStyle::kRound + : PointStyle::kSquare; + Scalar radius = paint.stroke_width; if (radius > 0) { radius /= 2.0; } - GetCanvas().DrawPoints(skia_conversions::ToPoints(points, count), radius, - paint, point_style); + GetCanvas().DrawPoints(points, count, radius, paint, point_style); } break; case flutter::DlCanvas::PointMode::kLines: for (uint32_t i = 1; i < count; i += 2) { Point p0 = points[i - 1]; Point p1 = points[i]; - GetCanvas().DrawLine(p0, p1, paint); + GetCanvas().DrawLine(p0, p1, paint, /*reuse_depth=*/i > 1); } break; case flutter::DlCanvas::PointMode::kPolygon: @@ -677,7 +679,7 @@ void DlDispatcherBase::drawPoints(PointMode mode, Point p0 = points[0]; for (uint32_t i = 1; i < count; i++) { Point p1 = points[i]; - GetCanvas().DrawLine(p0, p1, paint); + GetCanvas().DrawLine(p0, p1, paint, /*reuse_depth=*/i > 1); p0 = p1; } } @@ -969,34 +971,36 @@ void CanvasDlDispatcher::drawVertices( } void CanvasDlDispatcher::SetBackdropData( - std::unordered_map backdrop) { - GetCanvas().SetBackdropData(std::move(backdrop)); + std::unordered_map backdrop, + size_t backdrop_count) { + GetCanvas().SetBackdropData(std::move(backdrop), backdrop_count); } //// Text Frame Dispatcher -TextFrameDispatcher::TextFrameDispatcher(const ContentContext& renderer, +FirstPassDispatcher::FirstPassDispatcher(const ContentContext& renderer, const Matrix& initial_matrix, const Rect cull_rect) : renderer_(renderer), matrix_(initial_matrix) { cull_rect_state_.push_back(cull_rect); } -TextFrameDispatcher::~TextFrameDispatcher() { +FirstPassDispatcher::~FirstPassDispatcher() { FML_DCHECK(cull_rect_state_.size() == 1); } -void TextFrameDispatcher::save() { +void FirstPassDispatcher::save() { stack_.emplace_back(matrix_); cull_rect_state_.push_back(cull_rect_state_.back()); } -void TextFrameDispatcher::saveLayer(const DlRect& bounds, +void FirstPassDispatcher::saveLayer(const DlRect& bounds, const flutter::SaveLayerOptions options, const flutter::DlImageFilter* backdrop, std::optional backdrop_id) { save(); + backdrop_count_ += (backdrop == nullptr ? 0 : 1); if (backdrop != nullptr && backdrop_id.has_value()) { std::shared_ptr shared_backdrop = backdrop->shared(); @@ -1031,31 +1035,31 @@ void TextFrameDispatcher::saveLayer(const DlRect& bounds, } } -void TextFrameDispatcher::restore() { +void FirstPassDispatcher::restore() { matrix_ = stack_.back(); stack_.pop_back(); cull_rect_state_.pop_back(); } -void TextFrameDispatcher::translate(DlScalar tx, DlScalar ty) { +void FirstPassDispatcher::translate(DlScalar tx, DlScalar ty) { matrix_ = matrix_.Translate({tx, ty}); } -void TextFrameDispatcher::scale(DlScalar sx, DlScalar sy) { +void FirstPassDispatcher::scale(DlScalar sx, DlScalar sy) { matrix_ = matrix_.Scale({sx, sy, 1.0f}); } -void TextFrameDispatcher::rotate(DlScalar degrees) { +void FirstPassDispatcher::rotate(DlScalar degrees) { matrix_ = matrix_ * Matrix::MakeRotationZ(Degrees(degrees)); } -void TextFrameDispatcher::skew(DlScalar sx, DlScalar sy) { +void FirstPassDispatcher::skew(DlScalar sx, DlScalar sy) { matrix_ = matrix_ * Matrix::MakeSkew(sx, sy); } // clang-format off // 2x3 2D affine subset of a 4x4 transform in row major order - void TextFrameDispatcher::transform2DAffine(DlScalar mxx, DlScalar mxy, DlScalar mxt, + void FirstPassDispatcher::transform2DAffine(DlScalar mxx, DlScalar mxy, DlScalar mxt, DlScalar myx, DlScalar myy, DlScalar myt) { matrix_ = matrix_ * Matrix::MakeColumn( mxx, myx, 0.0f, 0.0f, @@ -1066,7 +1070,7 @@ void TextFrameDispatcher::skew(DlScalar sx, DlScalar sy) { } // full 4x4 transform in row major order - void TextFrameDispatcher::transformFullPerspective( + void FirstPassDispatcher::transformFullPerspective( DlScalar mxx, DlScalar mxy, DlScalar mxz, DlScalar mxt, DlScalar myx, DlScalar myy, DlScalar myz, DlScalar myt, DlScalar mzx, DlScalar mzy, DlScalar mzz, DlScalar mzt, @@ -1080,11 +1084,11 @@ void TextFrameDispatcher::skew(DlScalar sx, DlScalar sy) { } // clang-format on -void TextFrameDispatcher::transformReset() { +void FirstPassDispatcher::transformReset() { matrix_ = Matrix(); } -void TextFrameDispatcher::drawTextFrame( +void FirstPassDispatcher::drawTextFrame( const std::shared_ptr& text_frame, DlScalar x, DlScalar y) { @@ -1101,16 +1105,20 @@ void TextFrameDispatcher::drawTextFrame( // we do not double-apply the alpha. properties.color = paint_.color.WithAlpha(1.0); } - auto scale = - (matrix_ * Matrix::MakeTranslation(Point(x, y))).GetMaxBasisLengthXY(); - renderer_.GetLazyGlyphAtlas()->AddTextFrame(*text_frame, // - scale, // - Point(x, y), // - properties // + auto scale = TextFrame::RoundScaledFontSize( + (matrix_ * Matrix::MakeTranslation(Point(x, y))).GetMaxBasisLengthXY()); + + renderer_.GetLazyGlyphAtlas()->AddTextFrame( + text_frame, // + scale, // + Point(x, y), // + (properties.stroke || text_frame->HasColor()) // + ? std::optional(properties) // + : std::nullopt // ); } -const Rect TextFrameDispatcher::GetCurrentLocalCullingBounds() const { +const Rect FirstPassDispatcher::GetCurrentLocalCullingBounds() const { auto cull_rect = cull_rect_state_.back(); if (!cull_rect.IsEmpty() && !cull_rect.IsMaximum()) { Matrix inverse = matrix_.Invert(); @@ -1119,7 +1127,7 @@ const Rect TextFrameDispatcher::GetCurrentLocalCullingBounds() const { return cull_rect; } -void TextFrameDispatcher::drawDisplayList( +void FirstPassDispatcher::drawDisplayList( const sk_sp display_list, DlScalar opacity) { [[maybe_unused]] size_t stack_depth = stack_.size(); @@ -1137,11 +1145,12 @@ void TextFrameDispatcher::drawDisplayList( display_list->Dispatch(*this); } else if (!local_cull_bounds.IsEmpty()) { IRect cull_rect = IRect::RoundOut(local_cull_bounds); - display_list->Dispatch(*this, SkIRect::MakeLTRB(cull_rect.GetLeft(), // - cull_rect.GetTop(), // - cull_rect.GetRight(), // - cull_rect.GetBottom() // - )); + display_list->Dispatch(*this, + SkIRect::MakeLTRB(cull_rect.GetLeft(), // + cull_rect.GetTop(), // + cull_rect.GetRight(), // + cull_rect.GetBottom() // + )); } } @@ -1152,27 +1161,27 @@ void TextFrameDispatcher::drawDisplayList( } // |flutter::DlOpReceiver| -void TextFrameDispatcher::setDrawStyle(flutter::DlDrawStyle style) { +void FirstPassDispatcher::setDrawStyle(flutter::DlDrawStyle style) { paint_.style = ToStyle(style); } // |flutter::DlOpReceiver| -void TextFrameDispatcher::setColor(flutter::DlColor color) { +void FirstPassDispatcher::setColor(flutter::DlColor color) { paint_.color = skia_conversions::ToColor(color); } // |flutter::DlOpReceiver| -void TextFrameDispatcher::setStrokeWidth(DlScalar width) { +void FirstPassDispatcher::setStrokeWidth(DlScalar width) { paint_.stroke_width = width; } // |flutter::DlOpReceiver| -void TextFrameDispatcher::setStrokeMiter(DlScalar limit) { +void FirstPassDispatcher::setStrokeMiter(DlScalar limit) { paint_.stroke_miter = limit; } // |flutter::DlOpReceiver| -void TextFrameDispatcher::setStrokeCap(flutter::DlStrokeCap cap) { +void FirstPassDispatcher::setStrokeCap(flutter::DlStrokeCap cap) { switch (cap) { case flutter::DlStrokeCap::kButt: paint_.stroke_cap = Cap::kButt; @@ -1187,7 +1196,7 @@ void TextFrameDispatcher::setStrokeCap(flutter::DlStrokeCap cap) { } // |flutter::DlOpReceiver| -void TextFrameDispatcher::setStrokeJoin(flutter::DlStrokeJoin join) { +void FirstPassDispatcher::setStrokeJoin(flutter::DlStrokeJoin join) { switch (join) { case flutter::DlStrokeJoin::kMiter: paint_.stroke_join = Join::kMiter; @@ -1202,7 +1211,7 @@ void TextFrameDispatcher::setStrokeJoin(flutter::DlStrokeJoin join) { } // |flutter::DlOpReceiver| -void TextFrameDispatcher::setImageFilter(const flutter::DlImageFilter* filter) { +void FirstPassDispatcher::setImageFilter(const flutter::DlImageFilter* filter) { if (filter == nullptr) { has_image_filter_ = false; } else { @@ -1210,11 +1219,11 @@ void TextFrameDispatcher::setImageFilter(const flutter::DlImageFilter* filter) { } } -std::unordered_map -TextFrameDispatcher::TakeBackdropData() { +std::pair, size_t> +FirstPassDispatcher::TakeBackdropData() { std::unordered_map temp; std::swap(temp, backdrop_data_); - return temp; + return std::make_pair(temp, backdrop_count_); } std::shared_ptr DisplayListToTexture( @@ -1254,7 +1263,7 @@ std::shared_ptr DisplayListToTexture( } SkIRect sk_cull_rect = SkIRect::MakeWH(size.width, size.height); - impeller::TextFrameDispatcher collector( + impeller::FirstPassDispatcher collector( context.GetContentContext(), impeller::Matrix(), Rect::MakeSize(size)); display_list->Dispatch(collector, sk_cull_rect); impeller::CanvasDlDispatcher impeller_dispatcher( @@ -1264,7 +1273,8 @@ std::shared_ptr DisplayListToTexture( display_list->max_root_blend_mode(), // impeller::IRect::MakeSize(size) // ); - impeller_dispatcher.SetBackdropData(collector.TakeBackdropData()); + const auto& [data, count] = collector.TakeBackdropData(); + impeller_dispatcher.SetBackdropData(data, count); display_list->Dispatch(impeller_dispatcher, sk_cull_rect); impeller_dispatcher.FinishRecording(); @@ -1272,6 +1282,7 @@ std::shared_ptr DisplayListToTexture( context.GetContentContext().GetTransientsBuffer().Reset(); } context.GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames(); + context.GetContext()->DisposeThreadLocalCachedResources(); return target.GetRenderTargetTexture(); } @@ -1283,7 +1294,7 @@ bool RenderToOnscreen(ContentContext& context, bool reset_host_buffer) { Rect ip_cull_rect = Rect::MakeLTRB(cull_rect.left(), cull_rect.top(), cull_rect.right(), cull_rect.bottom()); - TextFrameDispatcher collector(context, impeller::Matrix(), ip_cull_rect); + FirstPassDispatcher collector(context, impeller::Matrix(), ip_cull_rect); display_list->Dispatch(collector, cull_rect); impeller::CanvasDlDispatcher impeller_dispatcher( @@ -1293,7 +1304,8 @@ bool RenderToOnscreen(ContentContext& context, display_list->max_root_blend_mode(), // IRect::RoundOut(ip_cull_rect) // ); - impeller_dispatcher.SetBackdropData(collector.TakeBackdropData()); + const auto& [data, count] = collector.TakeBackdropData(); + impeller_dispatcher.SetBackdropData(data, count); display_list->Dispatch(impeller_dispatcher, cull_rect); impeller_dispatcher.FinishRecording(); if (reset_host_buffer) { diff --git a/impeller/display_list/dl_dispatcher.h b/impeller/display_list/dl_dispatcher.h index b8ee05f2fa8a6..ba43f1c15c9d8 100644 --- a/impeller/display_list/dl_dispatcher.h +++ b/impeller/display_list/dl_dispatcher.h @@ -260,7 +260,8 @@ class CanvasDlDispatcher : public DlDispatcherBase { ~CanvasDlDispatcher() = default; - void SetBackdropData(std::unordered_map backdrop); + void SetBackdropData(std::unordered_map backdrop, + size_t backdrop_count); // |flutter::DlOpReceiver| void save() override { @@ -294,16 +295,17 @@ class CanvasDlDispatcher : public DlDispatcherBase { Canvas& GetCanvas() override; }; -/// Performs a first pass over the display list to collect all text frames. -class TextFrameDispatcher : public flutter::IgnoreAttributeDispatchHelper, +/// Performs a first pass over the display list to collect infomation. +/// Collects things like text frames and backdrop filters. +class FirstPassDispatcher : public flutter::IgnoreAttributeDispatchHelper, public flutter::IgnoreClipDispatchHelper, public flutter::IgnoreDrawDispatchHelper { public: - TextFrameDispatcher(const ContentContext& renderer, + FirstPassDispatcher(const ContentContext& renderer, const Matrix& initial_matrix, const Rect cull_rect); - ~TextFrameDispatcher(); + ~FirstPassDispatcher(); void save() override; @@ -364,7 +366,7 @@ class TextFrameDispatcher : public flutter::IgnoreAttributeDispatchHelper, // |flutter::DlOpReceiver| void setImageFilter(const flutter::DlImageFilter* filter) override; - std::unordered_map TakeBackdropData(); + std::pair, size_t> TakeBackdropData(); private: const Rect GetCurrentLocalCullingBounds() const; @@ -376,6 +378,7 @@ class TextFrameDispatcher : public flutter::IgnoreAttributeDispatchHelper, // note: cull rects are always in the global coordinate space. std::vector cull_rect_state_; bool has_image_filter_ = false; + size_t backdrop_count_ = 0; Paint paint_; }; diff --git a/impeller/display_list/dl_golden_blur_unittests.cc b/impeller/display_list/dl_golden_blur_unittests.cc index 4dd070682bf8c..54784e69d9bf6 100644 --- a/impeller/display_list/dl_golden_blur_unittests.cc +++ b/impeller/display_list/dl_golden_blur_unittests.cc @@ -6,6 +6,7 @@ #include "flutter/display_list/dl_builder.h" #include "flutter/display_list/effects/dl_mask_filter.h" +#include "flutter/impeller/geometry/round_rect.h" #include "flutter/impeller/golden_tests/screenshot.h" #include "flutter/testing/testing.h" #include "gtest/gtest.h" @@ -164,9 +165,9 @@ TEST_P(DlGoldenTest, ShimmerTest) { DlImageSampling::kLinear, &paint); SkRect save_layer_bounds = SkRect::MakeLTRB(0, 0, 1024, 768); - DlBlurImageFilter blur(sigma, sigma, DlTileMode::kDecal); + auto blur = DlImageFilter::MakeBlur(sigma, sigma, DlTileMode::kDecal); canvas->ClipRect(SkRect::MakeLTRB(11.125, 10.3737, 911.25, 755.3333)); - canvas->SaveLayer(&save_layer_bounds, /*paint=*/nullptr, &blur); + canvas->SaveLayer(&save_layer_bounds, /*paint=*/nullptr, blur.get()); canvas->Restore(); }; @@ -226,5 +227,29 @@ TEST_P(DlGoldenTest, ShimmerTest) { EXPECT_TRUE(average_rmse >= 0.0) << "average_rmse: " << average_rmse; } +TEST_P(DlGoldenTest, StrokedRRectFastBlur) { + impeller::Point content_scale = GetContentScale(); + + DlRect rect = DlRect::MakeXYWH(50, 50, 100, 100); + DlRoundRect rrect = DlRoundRect::MakeRectRadius(rect, 10.0f); + DlPaint fill = DlPaint().setColor(DlColor::kBlue()); + DlPaint stroke = + DlPaint(fill).setDrawStyle(DlDrawStyle::kStroke).setStrokeWidth(10.0f); + DlPaint blur = DlPaint(fill).setMaskFilter( + DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 5.0, true)); + DlPaint blur_stroke = + DlPaint(blur).setDrawStyle(DlDrawStyle::kStroke).setStrokeWidth(10.0f); + + DisplayListBuilder builder; + builder.DrawColor(DlColor(0xff111111), DlBlendMode::kSrc); + builder.Scale(content_scale.x, content_scale.y); + builder.DrawRoundRect(rrect, fill); + builder.DrawRoundRect(rrect.Shift(150, 0), stroke); + builder.DrawRoundRect(rrect.Shift(0, 150), blur); + builder.DrawRoundRect(rrect.Shift(150, 150), blur_stroke); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + } // namespace testing } // namespace flutter diff --git a/impeller/display_list/dl_golden_unittests.cc b/impeller/display_list/dl_golden_unittests.cc index 10edeafb5f53f..377f77f12c0dd 100644 --- a/impeller/display_list/dl_golden_unittests.cc +++ b/impeller/display_list/dl_golden_unittests.cc @@ -78,11 +78,13 @@ TEST_P(DlGoldenTest, Bug147807) { SkRRect::MakeOval(SkRect::MakeLTRB(201.25, 10, 361.25, 170)), DlCanvas::ClipOp::kIntersect, true); SkRect save_layer_bounds = SkRect::MakeLTRB(201.25, 10, 361.25, 170); - DlMatrixImageFilter backdrop(SkMatrix::MakeAll(3, 0, -280, // - 0, 3, -920, // - 0, 0, 1), - DlImageSampling::kLinear); - canvas->SaveLayer(&save_layer_bounds, /*paint=*/nullptr, &backdrop); + auto backdrop = + DlImageFilter::MakeMatrix(DlMatrix::MakeRow(3, 0, 0.0, -280, // + 0, 3, 0.0, -920, // + 0, 0, 1.0, 0.0, // + 0, 0, 0.0, 1.0), + DlImageSampling::kLinear); + canvas->SaveLayer(&save_layer_bounds, /*paint=*/nullptr, backdrop.get()); { canvas->Translate(201.25, 10); auto paint = DlPaint() diff --git a/impeller/display_list/dl_unittests.cc b/impeller/display_list/dl_unittests.cc index 0cb493c71cefe..832515a720582 100644 --- a/impeller/display_list/dl_unittests.cc +++ b/impeller/display_list/dl_unittests.cc @@ -14,7 +14,7 @@ #include "flutter/display_list/dl_tile_mode.h" #include "flutter/display_list/effects/dl_color_filter.h" #include "flutter/display_list/effects/dl_color_source.h" -#include "flutter/display_list/effects/dl_image_filter.h" +#include "flutter/display_list/effects/dl_image_filters.h" #include "flutter/display_list/effects/dl_mask_filter.h" #include "flutter/testing/testing.h" #include "gtest/gtest.h" @@ -522,18 +522,18 @@ TEST_P(DisplayListTest, CanDrawWithBlendColorFilter) { // Pipeline blended image. { - auto filter = flutter::DlBlendColorFilter(flutter::DlColor::kYellow(), - flutter::DlBlendMode::kModulate); - paint.setColorFilter(&filter); + auto filter = flutter::DlColorFilter::MakeBlend( + flutter::DlColor::kYellow(), flutter::DlBlendMode::kModulate); + paint.setColorFilter(filter); builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100), flutter::DlImageSampling::kNearestNeighbor, &paint); } // Advanced blended image. { - auto filter = flutter::DlBlendColorFilter(flutter::DlColor::kRed(), - flutter::DlBlendMode::kScreen); - paint.setColorFilter(&filter); + auto filter = flutter::DlColorFilter::MakeBlend( + flutter::DlColor::kRed(), flutter::DlBlendMode::kScreen); + paint.setColorFilter(filter); builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(250, 250), flutter::DlImageSampling::kNearestNeighbor, &paint); } @@ -552,17 +552,15 @@ TEST_P(DisplayListTest, CanDrawWithColorFilterImageFilter) { flutter::DisplayListBuilder builder; flutter::DlPaint paint; - auto color_filter = - std::make_shared(invert_color_matrix); - auto image_filter = - std::make_shared(color_filter); + auto color_filter = flutter::DlColorFilter::MakeMatrix(invert_color_matrix); + auto image_filter = flutter::DlImageFilter::MakeColorFilter(color_filter); - paint.setImageFilter(image_filter.get()); + paint.setImageFilter(image_filter); builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100), flutter::DlImageSampling::kNearestNeighbor, &paint); builder.Translate(0, 700); - paint.setColorFilter(color_filter.get()); + paint.setColorFilter(color_filter); builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100), flutter::DlImageSampling::kNearestNeighbor, &paint); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); @@ -628,13 +626,11 @@ TEST_P(DisplayListTest, CanClampTheResultingColorOfColorMatrixFilter) { 0, 0, 0, 0.5, 0, // }; auto inner_color_filter = - std::make_shared(inner_color_matrix); + flutter::DlColorFilter::MakeMatrix(inner_color_matrix); auto outer_color_filter = - std::make_shared(outer_color_matrix); - auto inner = - std::make_shared(inner_color_filter); - auto outer = - std::make_shared(outer_color_filter); + flutter::DlColorFilter::MakeMatrix(outer_color_matrix); + auto inner = flutter::DlImageFilter::MakeColorFilter(inner_color_filter); + auto outer = flutter::DlImageFilter::MakeColorFilter(outer_color_filter); auto compose = std::make_shared(outer, inner); flutter::DisplayListBuilder builder; @@ -1010,9 +1006,10 @@ TEST_P(DisplayListTest, CanDrawWithMatrixFilter) { // Set the matrix filter auto filter_matrix = - SkMatrix::MakeAll(scale[0], skew[0], translation[0], // - skew[1], scale[1], translation[1], // - 0, 0, 1); + Matrix::MakeRow(scale[0], skew[0], 0.0f, translation[0], // + skew[1], scale[1], 0.0f, translation[1], // + 0.0f, 0.0f, 1.0f, 0.0f, // + 0.0f, 0.0f, 0.0f, 1.0f); if (enable) { switch (selected_matrix_type) { @@ -1071,8 +1068,8 @@ TEST_P(DisplayListTest, CanDrawWithMatrixFilterWhenSavingLayer) { flutter::DlPaint save_paint; SkRect bounds = SkRect::MakeXYWH(100, 100, 100, 100); - SkMatrix translate_matrix = - SkMatrix::Translate(translation[0], translation[1]); + Matrix translate_matrix = + Matrix::MakeTranslation({translation[0], translation[1]}); if (enable_save_layer) { auto filter = flutter::DlMatrixImageFilter( translate_matrix, flutter::DlImageSampling::kNearestNeighbor); @@ -1083,10 +1080,10 @@ TEST_P(DisplayListTest, CanDrawWithMatrixFilterWhenSavingLayer) { builder.Transform(translate_matrix); } - SkMatrix filter_matrix = SkMatrix::I(); - filter_matrix.postTranslate(-150, -150); - filter_matrix.postScale(0.2f, 0.2f); - filter_matrix.postTranslate(150, 150); + Matrix filter_matrix; + filter_matrix.Translate({150, 150}); + filter_matrix.Scale({0.2f, 0.2f}); + filter_matrix.Translate({-150, -150}); auto filter = flutter::DlMatrixImageFilter( filter_matrix, flutter::DlImageSampling::kNearestNeighbor); @@ -1108,13 +1105,11 @@ TEST_P(DisplayListTest, CanDrawRectWithLinearToSrgbColorFilter) { flutter::DlPaint paint; paint.setColor(flutter::DlColor(0xFF2196F3).withAlpha(128)); flutter::DisplayListBuilder builder; - paint.setColorFilter( - flutter::DlLinearToSrgbGammaColorFilter::kInstance.get()); + paint.setColorFilter(flutter::DlColorFilter::MakeLinearToSrgbGamma()); builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint); builder.Translate(0, 200); - paint.setColorFilter( - flutter::DlSrgbToLinearGammaColorFilter::kInstance.get()); + paint.setColorFilter(flutter::DlColorFilter::MakeSrgbToLinearGamma()); builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); @@ -1162,9 +1157,9 @@ TEST_P(DisplayListTest, CanDrawPaintWithColorSource) { builder.Translate(500, 500); builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false); auto texture = CreateTextureForFixture("table_mountain_nx.png"); - auto image = std::make_shared( - DlImageImpeller::Make(texture), flutter::DlTileMode::kRepeat, - flutter::DlTileMode::kRepeat); + auto image = flutter::DlColorSource::MakeImage(DlImageImpeller::Make(texture), + flutter::DlTileMode::kRepeat, + flutter::DlTileMode::kRepeat); paint.setColorSource(image); builder.DrawPaint(paint); builder.Restore(); @@ -1238,11 +1233,11 @@ TEST_P(DisplayListTest, CanDrawCorrectlyWithColorFilterAndImageFilter) { 0, 0, 0, 1, 0, // }; auto green_color_filter = - std::make_shared(green_color_matrix); + flutter::DlColorFilter::MakeMatrix(green_color_matrix); auto blue_color_filter = - std::make_shared(blue_color_matrix); + flutter::DlColorFilter::MakeMatrix(blue_color_matrix); auto blue_image_filter = - std::make_shared(blue_color_filter); + flutter::DlImageFilter::MakeColorFilter(blue_color_filter); flutter::DlPaint paint; paint.setColor(flutter::DlColor::kRed()); @@ -1261,30 +1256,41 @@ TEST_P(DisplayListTest, MaskBlursApplyCorrectlyToColorSources) { std::array colors = {flutter::DlColor::kBlue(), flutter::DlColor::kGreen()}; std::array stops = {0, 1}; + auto texture = CreateTextureForFixture("airplane.jpg"); + auto matrix = flutter::DlMatrix::MakeTranslation({-300, -110}); std::array, 2> color_sources = { - std::make_shared(flutter::DlColor::kWhite()), + flutter::DlColorSource::MakeImage( + DlImageImpeller::Make(texture), flutter::DlTileMode::kRepeat, + flutter::DlTileMode::kRepeat, flutter::DlImageSampling::kLinear, + &matrix), flutter::DlColorSource::MakeLinear( - SkPoint::Make(0, 0), SkPoint::Make(100, 50), 2, colors.data(), - stops.data(), flutter::DlTileMode::kClamp)}; + flutter::DlPoint(0, 0), flutter::DlPoint(100, 50), 2, colors.data(), + stops.data(), flutter::DlTileMode::kClamp), + }; - int offset = 100; + builder.Save(); + builder.Translate(0, 100); for (const auto& color_source : color_sources) { flutter::DlPaint paint; paint.setColorSource(color_source); paint.setMaskFilter(blur_filter); + builder.Save(); + builder.Translate(100, 0); paint.setDrawStyle(flutter::DlDrawStyle::kFill); - builder.DrawRRect( - SkRRect::MakeRectXY(SkRect::MakeXYWH(100, offset, 100, 50), 30, 30), - paint); + builder.DrawRRect(SkRRect::MakeRectXY(SkRect::MakeWH(100, 50), 30, 30), + paint); + paint.setDrawStyle(flutter::DlDrawStyle::kStroke); paint.setStrokeWidth(10); - builder.DrawRRect( - SkRRect::MakeRectXY(SkRect::MakeXYWH(300, offset, 100, 50), 30, 30), - paint); + builder.Translate(200, 0); + builder.DrawRRect(SkRRect::MakeRectXY(SkRect::MakeWH(100, 50), 30, 30), + paint); - offset += 100; + builder.Restore(); + builder.Translate(0, 100); } + builder.Restore(); ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } @@ -1532,7 +1538,7 @@ TEST_P(DisplayListTest, DrawMaskBlursThatMightUseSaveLayers) { auto normal_filter = flutter::DlBlurMaskFilter::Make(flutter::DlBlurStyle::kNormal, 5.0f); auto rotate_if = flutter::DlMatrixImageFilter::Make( - SkMatrix::RotateDeg(10), flutter::DlImageSampling::kLinear); + Matrix::MakeRotationZ(Degrees(10)), flutter::DlImageSampling::kLinear); flutter::DlPaint normal_if_paint = flutter::DlPaint() // .setMaskFilter(solid_filter) // diff --git a/impeller/display_list/image_filter.cc b/impeller/display_list/image_filter.cc index 2f0af79c66ea9..1546cbf31f294 100644 --- a/impeller/display_list/image_filter.cc +++ b/impeller/display_list/image_filter.cc @@ -3,6 +3,8 @@ // found in the LICENSE file. #include "impeller/display_list/image_filter.h" + +#include "flutter/display_list/effects/dl_image_filters.h" #include "fml/logging.h" #include "impeller/display_list/color_filter.h" #include "impeller/display_list/skia_conversions.h" @@ -55,7 +57,7 @@ std::shared_ptr WrapInput(const flutter::DlImageFilter* filter, auto matrix_filter = filter->asMatrix(); FML_DCHECK(matrix_filter); - auto matrix = skia_conversions::ToMatrix(matrix_filter->matrix()); + auto matrix = matrix_filter->matrix(); auto desc = skia_conversions::ToSamplerDescriptor(matrix_filter->sampling()); return FilterContents::MakeMatrixFilter(input, matrix, desc); @@ -65,7 +67,7 @@ std::shared_ptr WrapInput(const flutter::DlImageFilter* filter, FML_DCHECK(matrix_filter); FML_DCHECK(matrix_filter->image_filter()); - auto matrix = skia_conversions::ToMatrix(matrix_filter->matrix()); + auto matrix = matrix_filter->matrix(); return FilterContents::MakeLocalMatrixFilter( FilterInput::Make( WrapInput(matrix_filter->image_filter().get(), input)), @@ -102,6 +104,43 @@ std::shared_ptr WrapInput(const flutter::DlImageFilter* filter, outer_dl_filter.get(), FilterInput::Make(WrapInput(inner_dl_filter.get(), input))); } + case flutter::DlImageFilterType::kRuntimeEffect: { + const flutter::DlRuntimeEffectImageFilter* runtime_filter = + filter->asRuntimeEffectFilter(); + FML_DCHECK(runtime_filter); + std::shared_ptr runtime_stage = + runtime_filter->runtime_effect()->runtime_stage(); + + std::vector texture_inputs; + size_t index = 0; + for (const std::shared_ptr& sampler : + runtime_filter->samplers()) { + if (index == 0 && sampler == nullptr) { + // Insert placeholder for filter. + texture_inputs.push_back( + {.sampler_descriptor = skia_conversions::ToSamplerDescriptor({}), + .texture = nullptr}); + continue; + } + if (sampler == nullptr) { + return nullptr; + } + auto* image = sampler->asImage(); + if (!image) { + return nullptr; + } + FML_DCHECK(image->image()->impeller_texture()); + index++; + texture_inputs.push_back({ + .sampler_descriptor = + skia_conversions::ToSamplerDescriptor(image->sampling()), + .texture = image->image()->impeller_texture(), + }); + } + return FilterContents::MakeRuntimeEffect(input, std::move(runtime_stage), + runtime_filter->uniform_data(), + std::move(texture_inputs)); + } } FML_UNREACHABLE(); } diff --git a/impeller/display_list/paint.cc b/impeller/display_list/paint.cc index 13bd681fe30fb..0305a4a88e853 100644 --- a/impeller/display_list/paint.cc +++ b/impeller/display_list/paint.cc @@ -6,9 +6,9 @@ #include -#include "display_list/effects/dl_color_filter.h" -#include "display_list/effects/dl_color_source.h" -#include "display_list/geometry/dl_path.h" +#include "flutter/display_list/effects/dl_color_filter.h" +#include "flutter/display_list/effects/dl_color_sources.h" +#include "flutter/display_list/geometry/dl_path.h" #include "fml/logging.h" #include "impeller/display_list/color_filter.h" #include "impeller/display_list/skia_conversions.h" @@ -46,14 +46,14 @@ std::shared_ptr Paint::CreateContents() const { const flutter::DlLinearGradientColorSource* linear = color_source->asLinearGradient(); FML_DCHECK(linear); - auto start_point = skia_conversions::ToPoint(linear->start_point()); - auto end_point = skia_conversions::ToPoint(linear->end_point()); + auto start_point = linear->start_point(); + auto end_point = linear->end_point(); std::vector colors; std::vector stops; skia_conversions::ConvertStops(linear, colors, stops); auto tile_mode = static_cast(linear->tile_mode()); - auto effect_transform = skia_conversions::ToMatrix(linear->matrix()); + auto effect_transform = linear->matrix(); auto contents = std::make_shared(); contents->SetOpacityFactor(color.alpha); @@ -74,7 +74,7 @@ std::shared_ptr Paint::CreateContents() const { const flutter::DlRadialGradientColorSource* radialGradient = color_source->asRadialGradient(); FML_DCHECK(radialGradient); - auto center = skia_conversions::ToPoint(radialGradient->center()); + auto center = radialGradient->center(); auto radius = radialGradient->radius(); std::vector colors; std::vector stops; @@ -82,8 +82,7 @@ std::shared_ptr Paint::CreateContents() const { auto tile_mode = static_cast(radialGradient->tile_mode()); - auto effect_transform = - skia_conversions::ToMatrix(radialGradient->matrix()); + auto effect_transform = radialGradient->matrix(); auto contents = std::make_shared(); contents->SetOpacityFactor(color.alpha); @@ -105,10 +104,9 @@ std::shared_ptr Paint::CreateContents() const { const flutter::DlConicalGradientColorSource* conical_gradient = color_source->asConicalGradient(); FML_DCHECK(conical_gradient); - Point center = skia_conversions::ToPoint(conical_gradient->end_center()); + Point center = conical_gradient->end_center(); DlScalar radius = conical_gradient->end_radius(); - Point focus_center = - skia_conversions::ToPoint(conical_gradient->start_center()); + Point focus_center = conical_gradient->start_center(); DlScalar focus_radius = conical_gradient->start_radius(); std::vector colors; std::vector stops; @@ -116,8 +114,7 @@ std::shared_ptr Paint::CreateContents() const { auto tile_mode = static_cast(conical_gradient->tile_mode()); - auto effect_transform = - skia_conversions::ToMatrix(conical_gradient->matrix()); + auto effect_transform = conical_gradient->matrix(); std::shared_ptr contents = std::make_shared(); @@ -142,7 +139,7 @@ std::shared_ptr Paint::CreateContents() const { color_source->asSweepGradient(); FML_DCHECK(sweepGradient); - auto center = skia_conversions::ToPoint(sweepGradient->center()); + auto center = sweepGradient->center(); auto start_angle = Degrees(sweepGradient->start()); auto end_angle = Degrees(sweepGradient->end()); std::vector colors; @@ -151,8 +148,7 @@ std::shared_ptr Paint::CreateContents() const { auto tile_mode = static_cast(sweepGradient->tile_mode()); - auto effect_transform = - skia_conversions::ToMatrix(sweepGradient->matrix()); + auto effect_transform = sweepGradient->matrix(); auto contents = std::make_shared(); contents->SetOpacityFactor(color.alpha); @@ -176,8 +172,7 @@ std::shared_ptr Paint::CreateContents() const { image_color_source->vertical_tile_mode()); auto sampler_descriptor = skia_conversions::ToSamplerDescriptor(image_color_source->sampling()); - auto effect_transform = - skia_conversions::ToMatrix(image_color_source->matrix()); + auto effect_transform = image_color_source->matrix(); auto contents = std::make_shared(); contents->SetOpacityFactor(color.alpha); @@ -244,11 +239,6 @@ std::shared_ptr Paint::CreateContents() const { contents->SetTextureInputs(std::move(texture_inputs)); return contents; } - case flutter::DlColorSourceType::kColor: { - auto contents = std::make_shared(); - contents->SetColor(color); - return contents; - } } FML_UNREACHABLE(); } diff --git a/impeller/display_list/skia_conversions.cc b/impeller/display_list/skia_conversions.cc index 9d15c3ffb123a..14047ab964352 100644 --- a/impeller/display_list/skia_conversions.cc +++ b/impeller/display_list/skia_conversions.cc @@ -3,7 +3,8 @@ // found in the LICENSE file. #include "impeller/display_list/skia_conversions.h" -#include "display_list/dl_color.h" +#include "flutter/display_list/dl_blend_mode.h" +#include "flutter/display_list/dl_color.h" #include "third_party/skia/modules/skparagraph/include/Paragraph.h" namespace impeller { diff --git a/impeller/display_list/skia_conversions.h b/impeller/display_list/skia_conversions.h index dce1a98f4ebfd..7180da35adc10 100644 --- a/impeller/display_list/skia_conversions.h +++ b/impeller/display_list/skia_conversions.h @@ -5,8 +5,9 @@ #ifndef FLUTTER_IMPELLER_DISPLAY_LIST_SKIA_CONVERSIONS_H_ #define FLUTTER_IMPELLER_DISPLAY_LIST_SKIA_CONVERSIONS_H_ -#include "display_list/dl_color.h" -#include "display_list/effects/dl_color_source.h" +#include "flutter/display_list/dl_blend_mode.h" +#include "flutter/display_list/dl_color.h" +#include "flutter/display_list/effects/dl_color_sources.h" #include "impeller/core/formats.h" #include "impeller/core/sampler_descriptor.h" #include "impeller/geometry/color.h" diff --git a/impeller/display_list/skia_conversions_unittests.cc b/impeller/display_list/skia_conversions_unittests.cc index 9e9d9e4876a7c..a06dd7adc80b3 100644 --- a/impeller/display_list/skia_conversions_unittests.cc +++ b/impeller/display_list/skia_conversions_unittests.cc @@ -134,8 +134,8 @@ TEST(SkiaConversionsTest, GradientStopConversion) { flutter::DlColor::kGreen()}; std::vector stops = {0.0, 0.5, 1.0}; const auto gradient = - flutter::DlColorSource::MakeLinear(SkPoint::Make(0, 0), // - SkPoint::Make(1.0, 1.0), // + flutter::DlColorSource::MakeLinear(flutter::DlPoint(0, 0), // + flutter::DlPoint(1.0, 1.0), // 3, // colors.data(), // stops.data(), // @@ -145,7 +145,7 @@ TEST(SkiaConversionsTest, GradientStopConversion) { std::vector converted_colors; std::vector converted_stops; - skia_conversions::ConvertStops(gradient.get(), converted_colors, + skia_conversions::ConvertStops(gradient->asLinearGradient(), converted_colors, converted_stops); ASSERT_TRUE(ScalarNearlyEqual(converted_stops[0], 0.0f)); @@ -158,8 +158,8 @@ TEST(SkiaConversionsTest, GradientMissing0) { flutter::DlColor::kRed()}; std::vector stops = {0.5, 1.0}; const auto gradient = - flutter::DlColorSource::MakeLinear(SkPoint::Make(0, 0), // - SkPoint::Make(1.0, 1.0), // + flutter::DlColorSource::MakeLinear(flutter::DlPoint(0, 0), // + flutter::DlPoint(1.0, 1.0), // 2, // colors.data(), // stops.data(), // @@ -169,7 +169,7 @@ TEST(SkiaConversionsTest, GradientMissing0) { std::vector converted_colors; std::vector converted_stops; - skia_conversions::ConvertStops(gradient.get(), converted_colors, + skia_conversions::ConvertStops(gradient->asLinearGradient(), converted_colors, converted_stops); // First color is inserted as blue. @@ -184,8 +184,8 @@ TEST(SkiaConversionsTest, GradientMissingLastValue) { flutter::DlColor::kRed()}; std::vector stops = {0.0, .5}; const auto gradient = - flutter::DlColorSource::MakeLinear(SkPoint::Make(0, 0), // - SkPoint::Make(1.0, 1.0), // + flutter::DlColorSource::MakeLinear(flutter::DlPoint(0, 0), // + flutter::DlPoint(1.0, 1.0), // 2, // colors.data(), // stops.data(), // @@ -195,7 +195,7 @@ TEST(SkiaConversionsTest, GradientMissingLastValue) { std::vector converted_colors; std::vector converted_stops; - skia_conversions::ConvertStops(gradient.get(), converted_colors, + skia_conversions::ConvertStops(gradient->asLinearGradient(), converted_colors, converted_stops); // Last color is inserted as red. @@ -211,8 +211,8 @@ TEST(SkiaConversionsTest, GradientStopGreaterThan1) { flutter::DlColor::kRed()}; std::vector stops = {0.0, 100, 1.0}; const auto gradient = - flutter::DlColorSource::MakeLinear(SkPoint::Make(0, 0), // - SkPoint::Make(1.0, 1.0), // + flutter::DlColorSource::MakeLinear(flutter::DlPoint(0, 0), // + flutter::DlPoint(1.0, 1.0), // 3, // colors.data(), // stops.data(), // @@ -222,7 +222,7 @@ TEST(SkiaConversionsTest, GradientStopGreaterThan1) { std::vector converted_colors; std::vector converted_stops; - skia_conversions::ConvertStops(gradient.get(), converted_colors, + skia_conversions::ConvertStops(gradient->asLinearGradient(), converted_colors, converted_stops); // Value is clamped to 1.0 @@ -237,8 +237,8 @@ TEST(SkiaConversionsTest, GradientConversionNonMonotonic) { flutter::DlColor::kGreen(), flutter::DlColor::kRed()}; std::vector stops = {0.0, 0.5, 0.4, 1.0}; const auto gradient = - flutter::DlColorSource::MakeLinear(SkPoint::Make(0, 0), // - SkPoint::Make(1.0, 1.0), // + flutter::DlColorSource::MakeLinear(flutter::DlPoint(0, 0), // + flutter::DlPoint(1.0, 1.0), // 4, // colors.data(), // stops.data(), // @@ -248,7 +248,7 @@ TEST(SkiaConversionsTest, GradientConversionNonMonotonic) { std::vector converted_colors; std::vector converted_stops; - skia_conversions::ConvertStops(gradient.get(), converted_colors, + skia_conversions::ConvertStops(gradient->asLinearGradient(), converted_colors, converted_stops); // Value is clamped to 0.5 diff --git a/impeller/docs/benchmarks.md b/impeller/docs/benchmarks.md index f2a5b5f700678..96dc8b22c53d2 100644 --- a/impeller/docs/benchmarks.md +++ b/impeller/docs/benchmarks.md @@ -4,8 +4,8 @@ Here are some noteworthy benchmarks related to Impeller performance: - **New Gallery** - Runs through the Flutter Gallery with a driver test. - Pixel 7 Pro - - Vulkan: [dashboard](https://flutter-flutter-perf.skia.org/e/?keys=X3f83005df6350b72d23479764c787b2d) - - OpenGLES: [dashboard](https://flutter-flutter-perf.skia.org/e/?keys=X080f13e1d6607d5ad3f4fe5c67e61538) + - Vulkan: [dashboard](https://flutter-flutter-perf.skia.org/e/?queries=device_type%3DPixel_7_Pro%26test%3Dnew_gallery_impeller__transition_perf) + - OpenGLES: [dashboard](https://flutter-flutter-perf.skia.org/e/?queries=device_type%3DPixel_7_Pro%26test%3Dnew_gallery_opengles_impeller__transition_perf) - Skia vs Vulkan Impeller - frame raster time stats: [dashboard](https://flutter-flutter-perf.skia.org/e/?queries=device_type%3DPixel_7_Pro%26sub_result%3D90th_percentile_frame_rasterizer_time_millis%26sub_result%3D99th_percentile_frame_rasterizer_time_millis%26sub_result%3Daverage_frame_rasterizer_time_millis%26sub_result%3Dworst_frame_rasterizer_time_millis%26test%3Dnew_gallery__transition_perf%26test%3Dnew_gallery_impeller__transition_perf) - Vulkan vs OpenGLES - average rasterizer time: [dashboard](https://flutter-flutter-perf.skia.org/e/?queries=device_type%3DPixel_7_Pro%26sub_result%3Daverage_frame_rasterizer_time_millis%26test%3Dnew_gallery_impeller__transition_perf%26test%3Dnew_gallery_opengles_impeller__transition_perf) - Samsung S10 @@ -20,18 +20,17 @@ Here are some noteworthy benchmarks related to Impeller performance: animates a Blur Backdrop filter to get progressively blurrier. This covers a gap in the "New Gallery" tests we've seen in places like Wonderous. - Pixel 7 Pro - - Vulkan: [dashboard](https://flutter-flutter-perf.skia.org/e/?keys=X6d3dd43039c95ec80a8b3914cf386f48) - - OpenGLES: [dashboard](https://flutter-flutter-perf.skia.org/e/?keys=X48625544c02c75d050c4440405025d80) + - Vulkan: [dashboard](https://flutter-flutter-perf.skia.org/e/?queries=device_type%3DPixel_7_Pro%26test%3Danimated_blur_backdrop_filter_perf__timeline_summary) + - OpenGLES: [dashboard](https://flutter-flutter-perf.skia.org/e/?queries=device_type%3DPixel_7_Pro%26test%3Danimated_blur_backdrop_filter_perf_opengles__timeline_summary) - Vulkan vs OpenGLES - Average rasterizer time: [dashboard](https://flutter-flutter-perf.skia.org/e/?queries=device_type%3DPixel_7_Pro%26sub_result%3Daverage_frame_rasterizer_time_millis%26test%3Danimated_blur_backdrop_filter_perf__timeline_summary%26test%3Danimated_blur_backdrop_filter_perf_opengles__timeline_summary) - - Moto G4 (OpenGLES): [dashboard](https://flutter-flutter-perf.skia.org/e/?keys=X78023772ea9e94c81f37456a7fa7bf46) - - iPhone 11 (Metal): [dashboard](https://flutter-flutter-perf.skia.org/e/?keys=X2f7504aba3db6aeff08cc896081ace55) + - iPhone 11 (Metal): [dashboard](https://flutter-flutter-perf.skia.org/e/?queries=test%3Danimated_blur_backdrop_filter_perf_ios__timeline_summary) - **Animated Advanced Blend** - A driver test like the Animated Blur test, but is displaying a handful of advanced blurs since it represents a specific case exercised in Wonderous. - Pixel 7 Pro - - Vulkan: [dashboard](https://flutter-flutter-perf.skia.org/e/?keys=Xe742e40d9f7510cf6c8ddbf9eee9d51b) - - OpenGLES: [dashboard](https://flutter-flutter-perf.skia.org/e/?keys=X78baa100c6cadec3171d42063cc857bf) + - Vulkan: [dashboard](https://flutter-flutter-perf.skia.org/e/?queries=test%3Danimated_advanced_blend_perf__timeline_summary) + - OpenGLES: [dashboard](https://flutter-flutter-perf.skia.org/e/?queries=test%3Danimated_advanced_blend_perf_opengles__timeline_summary) - Vulkan vs OpenGLES - Average rasterizer time: [dashboard](https://flutter-flutter-perf.skia.org/e/?queries=device_type%3DPixel_7_Pro%26sub_result%3Daverage_frame_rasterizer_time_millis%26test%3Danimated_advanced_blend_perf__timeline_summary%26test%3Danimated_advanced_blend_perf_opengles__timeline_summary) - iPhone 11 (Metal): [dashboard](https://flutter-flutter-perf.skia.org/e/?keys=X65477f5b5026c0d5ee8fee79122427ab) diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 9f966ec4c6a91..735081d4f8db5 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -18,18 +18,22 @@ impeller_shaders("entity_shaders") { "shaders/blending/advanced_blend.frag", "shaders/clip.frag", "shaders/clip.vert", - "shaders/gradients/conical_gradient_fill.frag", "shaders/glyph_atlas.frag", "shaders/glyph_atlas.vert", "shaders/gradients/gradient_fill.vert", + "shaders/gradients/conical_gradient_fill.frag", + "shaders/gradients/conical_gradient_uniform_fill.frag", "shaders/gradients/linear_gradient_fill.frag", + "shaders/gradients/linear_gradient_uniform_fill.frag", "shaders/gradients/radial_gradient_fill.frag", + "shaders/gradients/radial_gradient_uniform_fill.frag", + "shaders/gradients/sweep_gradient_fill.frag", + "shaders/gradients/sweep_gradient_uniform_fill.frag", "shaders/rrect_blur.vert", "shaders/rrect_blur.frag", "shaders/runtime_effect.vert", "shaders/solid_fill.frag", "shaders/solid_fill.vert", - "shaders/gradients/sweep_gradient_fill.frag", "shaders/texture_fill.frag", "shaders/texture_fill.vert", "shaders/texture_uv_fill.vert", @@ -51,6 +55,7 @@ impeller_shaders("entity_shaders") { "shaders/gradients/fast_gradient.vert", "shaders/gradients/fast_gradient.frag", "shaders/texture_downsample.frag", + "shaders/texture_downsample_gles.frag", ] } @@ -138,6 +143,8 @@ impeller_component("entity") { "contents/filters/matrix_filter_contents.h", "contents/filters/morphology_filter_contents.cc", "contents/filters/morphology_filter_contents.h", + "contents/filters/runtime_effect_filter_contents.cc", + "contents/filters/runtime_effect_filter_contents.h", "contents/filters/srgb_to_linear_filter_contents.cc", "contents/filters/srgb_to_linear_filter_contents.h", "contents/filters/yuv_to_rgb_filter_contents.cc", @@ -192,6 +199,8 @@ impeller_component("entity") { "geometry/rect_geometry.h", "geometry/round_rect_geometry.cc", "geometry/round_rect_geometry.h", + "geometry/round_superellipse_geometry.cc", + "geometry/round_superellipse_geometry.h", "geometry/stroke_path_geometry.cc", "geometry/stroke_path_geometry.h", "geometry/superellipse_geometry.cc", @@ -222,8 +231,6 @@ impeller_component("entity_test_helpers") { testonly = true sources = [ - "contents/test/contents_test_helpers.cc", - "contents/test/contents_test_helpers.h", "contents/test/recording_render_pass.cc", "contents/test/recording_render_pass.h", ] @@ -235,7 +242,7 @@ impeller_component("entity_unittests") { testonly = true sources = [ - "contents/clip_contents_unittests.cc", + "clip_stack_unittests.cc", "contents/filters/blend_filter_contents_unittests.cc", "contents/filters/gaussian_blur_filter_contents_unittests.cc", "contents/filters/inputs/filter_input_unittests.cc", @@ -244,7 +251,6 @@ impeller_component("entity_unittests") { "contents/tiled_texture_contents_unittests.cc", "draw_order_resolver_unittests.cc", "entity_pass_target_unittests.cc", - "entity_pass_unittests.cc", "entity_playground.cc", "entity_playground.h", "entity_unittests.cc", diff --git a/impeller/entity/entity_pass_unittests.cc b/impeller/entity/clip_stack_unittests.cc similarity index 54% rename from impeller/entity/entity_pass_unittests.cc rename to impeller/entity/clip_stack_unittests.cc index 2c24eab20c3fb..7ec6c047b0f2c 100644 --- a/impeller/entity/entity_pass_unittests.cc +++ b/impeller/entity/clip_stack_unittests.cc @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include - #include "flutter/testing/testing.h" #include "gtest/gtest.h" #include "impeller/entity/contents/clip_contents.h" @@ -19,27 +17,31 @@ TEST(EntityPassClipStackTest, CanPushAndPopEntities) { EXPECT_TRUE(recorder.GetReplayEntities().empty()); - Entity entity; - recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kAppend, - Rect::MakeLTRB(0, 0, 100, 100)); + recorder.RecordClip(ClipContents(Rect::MakeLTRB(0, 0, 100, 100), + /*is_axis_aligned_rect=*/false), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/true); + EXPECT_EQ(recorder.GetReplayEntities().size(), 1u); - recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kAppend, - Rect::MakeLTRB(0, 0, 50, 50)); + recorder.RecordClip(ClipContents(Rect::MakeLTRB(0, 0, 50.5, 50.5), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 2, 100, /*is_aa=*/true); + EXPECT_EQ(recorder.GetReplayEntities().size(), 2u); ASSERT_TRUE(recorder.GetReplayEntities()[0].clip_coverage.has_value()); ASSERT_TRUE(recorder.GetReplayEntities()[1].clip_coverage.has_value()); + // NOLINTBEGIN(bugprone-unchecked-optional-access) EXPECT_EQ(recorder.GetReplayEntities()[0].clip_coverage.value(), Rect::MakeLTRB(0, 0, 100, 100)); EXPECT_EQ(recorder.GetReplayEntities()[1].clip_coverage.value(), - Rect::MakeLTRB(0, 0, 50, 50)); + Rect::MakeLTRB(0, 0, 50.5, 50.5)); // NOLINTEND(bugprone-unchecked-optional-access) - recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kRestore, Rect()); + recorder.RecordRestore({0, 0}, 1); EXPECT_EQ(recorder.GetReplayEntities().size(), 1u); - recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kRestore, Rect()); + recorder.RecordRestore({0, 0}, 0); EXPECT_TRUE(recorder.GetReplayEntities().empty()); } @@ -49,61 +51,52 @@ TEST(EntityPassClipStackTest, CanPopEntitiesSafely) { EXPECT_TRUE(recorder.GetReplayEntities().empty()); - Entity entity; - recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kRestore, Rect()); + recorder.RecordRestore({0, 0}, 0); EXPECT_TRUE(recorder.GetReplayEntities().empty()); } -TEST(EntityPassClipStackTest, CanAppendNoChange) { +TEST(EntityPassClipStackTest, AppendAndRestoreClipCoverage) { EntityPassClipStack recorder = EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100)); - EXPECT_TRUE(recorder.GetReplayEntities().empty()); - - Entity entity; - recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kNoChange, - Rect()); - EXPECT_TRUE(recorder.GetReplayEntities().empty()); -} + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); -TEST(EntityPassClipStackTest, AppendCoverageNoChange) { - EntityPassClipStack recorder = - EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100)); + // Push a clip. + EntityPassClipStack::ClipStateResult result = + recorder.RecordClip(ClipContents(Rect::MakeLTRB(50, 50, 55.5, 55.5), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/true); + EXPECT_TRUE(result.should_render); + EXPECT_TRUE(result.clip_did_change); - EXPECT_EQ(recorder.GetClipCoverageLayers()[0].coverage, - Rect::MakeSize(Size::MakeWH(100, 100))); - EXPECT_EQ(recorder.GetClipCoverageLayers()[0].clip_height, 0u); + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 2u); + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage, + Rect::MakeLTRB(50, 50, 55.5, 55.5)); + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].clip_height, 1u); + EXPECT_EQ(recorder.GetReplayEntities().size(), 1u); - Entity entity; - EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kNoChange, - .coverage = std::nullopt, - }, - entity, 0, Point(0, 0)); - EXPECT_TRUE(result.should_render); - EXPECT_FALSE(result.clip_did_change); + // Restore the clip. + recorder.RecordRestore({0, 0}, 0); + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); EXPECT_EQ(recorder.GetClipCoverageLayers()[0].coverage, Rect::MakeSize(Size::MakeWH(100, 100))); EXPECT_EQ(recorder.GetClipCoverageLayers()[0].clip_height, 0u); + EXPECT_EQ(recorder.GetReplayEntities().size(), 0u); } -TEST(EntityPassClipStackTest, AppendAndRestoreClipCoverage) { +TEST(EntityPassClipStackTest, AppendAndRestoreClipCoverageNonAA) { EntityPassClipStack recorder = EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100)); ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); // Push a clip. - Entity entity; - EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kAppend, - .coverage = Rect::MakeLTRB(50, 50, 55, 55), - }, - entity, 0, Point(0, 0)); - EXPECT_TRUE(result.should_render); + EntityPassClipStack::ClipStateResult result = + recorder.RecordClip(ClipContents(Rect::MakeLTRB(50, 50, 55.4, 55.4), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/false); + EXPECT_FALSE(result.should_render); EXPECT_TRUE(result.clip_did_change); ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 2u); @@ -113,15 +106,7 @@ TEST(EntityPassClipStackTest, AppendAndRestoreClipCoverage) { EXPECT_EQ(recorder.GetReplayEntities().size(), 1u); // Restore the clip. - auto restore_clip = std::make_shared(); - restore_clip->SetRestoreHeight(0); - entity.SetContents(std::move(restore_clip)); - recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kRestore, - .coverage = Rect::MakeLTRB(50, 50, 55, 55), - }, - entity, 0, Point(0, 0)); + recorder.RecordRestore({0, 0}, 0); ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); EXPECT_EQ(recorder.GetClipCoverageLayers()[0].coverage, @@ -139,23 +124,18 @@ TEST(EntityPassClipStackTest, AppendLargerClipCoverage) { ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); // Push a clip. - Entity entity; - EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kAppend, - .coverage = Rect::MakeLTRB(50, 50, 55, 55), - }, - entity, 0, Point(0, 0)); + EntityPassClipStack::ClipStateResult result = + recorder.RecordClip(ClipContents(Rect::MakeLTRB(50, 50, 55.5, 55.5), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/true); + EXPECT_TRUE(result.should_render); EXPECT_TRUE(result.clip_did_change); // Push a clip with larger coverage than the previous state. - result = recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kAppend, - .coverage = Rect::MakeLTRB(0, 0, 100, 100), - }, - entity, 0, Point(0, 0)); + result = recorder.RecordClip(ClipContents(Rect::MakeLTRB(0, 0, 100.5, 100.5), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 1, 100, /*is_aa=*/true); EXPECT_FALSE(result.should_render); EXPECT_FALSE(result.clip_did_change); @@ -171,24 +151,18 @@ TEST(EntityPassClipStackTest, ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); // Push a clip. - Entity entity; - EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kAppend, - .coverage = Rect::MakeLTRB(50, 50, 55, 55), - }, - entity, 0, Point(0, 0)); - EXPECT_TRUE(result.should_render); + EntityPassClipStack::ClipStateResult result = + recorder.RecordClip(ClipContents(Rect::MakeLTRB(50, 50, 55, 55), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/true); + + EXPECT_FALSE(result.should_render); EXPECT_TRUE(result.clip_did_change); // Push a clip with larger coverage than the previous state. - result = recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kAppend, - .is_difference_or_non_square = true, - .coverage = Rect::MakeLTRB(0, 0, 100, 100), - }, - entity, 0, Point(0, 0)); + result = recorder.RecordClip(ClipContents(Rect::MakeLTRB(0, 0, 100, 100), + /*is_axis_aligned_rect=*/false), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/true); EXPECT_TRUE(result.should_render); EXPECT_TRUE(result.clip_did_change); @@ -204,16 +178,15 @@ TEST(EntityPassClipStackTest, AppendDecreasingSizeClipCoverage) { Entity entity; for (auto i = 1; i < 20; i++) { - EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kAppend, - .coverage = Rect::MakeLTRB(i, i, 100 - i, 100 - i), - }, - entity, 0, Point(0, 0)); + EntityPassClipStack::ClipStateResult result = recorder.RecordClip( + ClipContents(Rect::MakeLTRB(i, i, 99.6 - i, 99.6 - i), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/true); + EXPECT_TRUE(result.should_render); EXPECT_TRUE(result.clip_did_change); EXPECT_EQ(recorder.CurrentClipCoverage(), - Rect::MakeLTRB(i, i, 100 - i, 100 - i)); + Rect::MakeLTRB(i, i, 99.6 - i, 99.6 - i)); } } @@ -224,15 +197,13 @@ TEST(EntityPassClipStackTest, AppendIncreasingSizeClipCoverage) { ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); // Push Clips that grow in size. All should be skipped. - Entity entity; for (auto i = 1; i < 20; i++) { - EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kAppend, - .coverage = Rect::MakeLTRB(0 - i, 0 - i, 100 + i, 100 + i), - }, - entity, 0, Point(0, 0)); + EntityPassClipStack::ClipStateResult result = recorder.RecordClip( + ClipContents(Rect::MakeLTRB(0 - i, 0 - i, 100 + i, 100 + i), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/true); + EXPECT_FALSE(result.should_render); EXPECT_FALSE(result.clip_did_change); EXPECT_EQ(recorder.CurrentClipCoverage(), Rect::MakeLTRB(0, 0, 100, 100)); @@ -246,16 +217,8 @@ TEST(EntityPassClipStackTest, UnbalancedRestore) { ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); // Restore the clip. - Entity entity; - auto restore_clip = std::make_shared(); - restore_clip->SetRestoreHeight(0); - entity.SetContents(std::move(restore_clip)); - EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kRestore, - .coverage = Rect::MakeLTRB(50, 50, 55, 55), - }, - entity, 0, Point(0, 0)); + EntityPassClipStack::ClipStateResult result = + recorder.RecordRestore(Point(0, 0), 0); EXPECT_FALSE(result.should_render); EXPECT_FALSE(result.clip_did_change); @@ -273,21 +236,19 @@ TEST(EntityPassClipStackTest, ClipAndRestoreWithSubpasses) { ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); // Push a clip. - Entity entity; { - EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kAppend, - .coverage = Rect::MakeLTRB(50, 50, 55, 55), - }, - entity, 0, Point(0, 0)); + EntityPassClipStack::ClipStateResult result = + recorder.RecordClip(ClipContents(Rect::MakeLTRB(50, 50, 55.5, 55.5), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/true); + EXPECT_TRUE(result.should_render); EXPECT_TRUE(result.clip_did_change); } ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 2u); EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage, - Rect::MakeLTRB(50, 50, 55, 55)); + Rect::MakeLTRB(50, 50, 55.5, 55.5)); EXPECT_EQ(recorder.GetClipCoverageLayers()[1].clip_height, 1u); EXPECT_EQ(recorder.GetReplayEntities().size(), 1u); @@ -298,18 +259,66 @@ TEST(EntityPassClipStackTest, ClipAndRestoreWithSubpasses) { Rect::MakeLTRB(50, 50, 55, 55)); { - EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState( - Contents::ClipCoverage{ - .type = Contents::ClipCoverage::Type::kAppend, - .coverage = Rect::MakeLTRB(54, 54, 55, 55), - }, - entity, 0, Point(0, 0)); + EntityPassClipStack::ClipStateResult result = + recorder.RecordClip(ClipContents(Rect::MakeLTRB(54, 54, 54.5, 54.5), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/true); + EXPECT_TRUE(result.should_render); EXPECT_TRUE(result.clip_did_change); } EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage, - Rect::MakeLTRB(54, 54, 55, 55)); + Rect::MakeLTRB(54, 54, 54.5, 54.5)); + + // End subpass. + recorder.PopSubpass(); + + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage, + Rect::MakeLTRB(50, 50, 55.5, 55.5)); +} + +TEST(EntityPassClipStackTest, ClipAndRestoreWithSubpassesNonAA) { + EntityPassClipStack recorder = + EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100)); + + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); + + // Push a clip. + { + EntityPassClipStack::ClipStateResult result = + recorder.RecordClip(ClipContents(Rect::MakeLTRB(50, 50, 55.4, 55.4), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/false); + + EXPECT_FALSE(result.should_render); + EXPECT_TRUE(result.clip_did_change); + } + + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 2u); + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage, + Rect::MakeLTRB(50, 50, 55.0, 55.0)); + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].clip_height, 1u); + EXPECT_EQ(recorder.GetReplayEntities().size(), 1u); + + // Begin a subpass. + recorder.PushSubpass(Rect::MakeLTRB(50, 50, 55, 55), 1); + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); + EXPECT_EQ(recorder.GetClipCoverageLayers()[0].coverage, + Rect::MakeLTRB(50, 50, 55, 55)); + + { + EntityPassClipStack::ClipStateResult result = + recorder.RecordClip(ClipContents(Rect::MakeLTRB(54, 54, 55.4, 55.4), + /*is_axis_aligned_rect=*/true), + Matrix(), {0, 0}, 0, 100, /*is_aa=*/false); + + EXPECT_FALSE(result.should_render); + EXPECT_TRUE(result.clip_did_change); + } + + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage, + Rect::MakeLTRB(54, 54, 55.0, 55.0)); // End subpass. recorder.PopSubpass(); diff --git a/impeller/entity/contents/clip_contents.cc b/impeller/entity/contents/clip_contents.cc index ee4535ed7084e..16e7268750253 100644 --- a/impeller/entity/contents/clip_contents.cc +++ b/impeller/entity/contents/clip_contents.cc @@ -16,81 +16,68 @@ namespace impeller { -static Scalar GetShaderClipDepth(const Entity& entity) { +static Scalar GetShaderClipDepth(uint32_t clip_depth) { // Draw the clip at the max of the clip entity's depth slice, so that other // draw calls with this same depth value will be culled even if they have a // perspective transform. - return std::nextafterf(Entity::GetShaderClipDepth(entity.GetClipDepth() + 1), - 0.0f); + return std::nextafterf(Entity::GetShaderClipDepth(clip_depth + 1), 0.0f); } /******************************************************************************* ******* ClipContents ******************************************************************************/ -ClipContents::ClipContents() = default; +ClipContents::ClipContents(Rect coverage_rect, bool is_axis_aligned_rect) + : coverage_rect_(coverage_rect), + is_axis_aligned_rect_(is_axis_aligned_rect) {} ClipContents::~ClipContents() = default; -void ClipContents::SetGeometry(const Geometry* geometry) { - geometry_ = geometry; +void ClipContents::SetGeometry(GeometryResult clip_geometry) { + clip_geometry_ = std::move(clip_geometry); } void ClipContents::SetClipOperation(Entity::ClipOperation clip_op) { clip_op_ = clip_op; } -std::optional ClipContents::GetCoverage(const Entity& entity) const { - return std::nullopt; -}; - -Contents::ClipCoverage ClipContents::GetClipCoverage( - const Entity& entity, +ClipCoverage ClipContents::GetClipCoverage( const std::optional& current_clip_coverage) const { if (!current_clip_coverage.has_value()) { - return {.type = ClipCoverage::Type::kAppend, .coverage = std::nullopt}; + return {.coverage = std::nullopt}; } switch (clip_op_) { case Entity::ClipOperation::kDifference: // This can be optimized further by considering cases when the bounds of // the current stencil will shrink. return { - .type = ClipCoverage::Type::kAppend, // .is_difference_or_non_square = true, // .coverage = current_clip_coverage // }; case Entity::ClipOperation::kIntersect: - if (!geometry_) { - return {.type = ClipCoverage::Type::kAppend, .coverage = std::nullopt}; - } - auto coverage = geometry_->GetCoverage(entity.GetTransform()); - if (!coverage.has_value() || !current_clip_coverage.has_value()) { - return {.type = ClipCoverage::Type::kAppend, .coverage = std::nullopt}; + if (coverage_rect_.IsEmpty() || !current_clip_coverage.has_value()) { + return {.coverage = std::nullopt}; } return { - .type = ClipCoverage::Type::kAppend, // - .is_difference_or_non_square = !geometry_->IsAxisAlignedRect(), // - .coverage = current_clip_coverage->Intersection(coverage.value()), // + .is_difference_or_non_square = !is_axis_aligned_rect_, // + .coverage = current_clip_coverage->Intersection(coverage_rect_), // }; } FML_UNREACHABLE(); } -void ClipContents::SetInheritedOpacity(Scalar opacity) {} - bool ClipContents::Render(const ContentContext& renderer, - const Entity& entity, - RenderPass& pass) const { - if (!geometry_) { + RenderPass& pass, + uint32_t clip_depth) const { + if (!clip_geometry_.vertex_buffer) { return true; } using VS = ClipPipeline::VertexShader; VS::FrameInfo info; - info.depth = GetShaderClipDepth(entity); + info.depth = GetShaderClipDepth(clip_depth); - auto geometry_result = geometry_->GetPositionBuffer(renderer, entity, pass); auto options = OptionsFromPass(pass); options.blend_mode = BlendMode::kDestination; @@ -99,9 +86,9 @@ bool ClipContents::Render(const ContentContext& renderer, /// Stencil preparation draw. options.depth_write_enabled = false; - options.primitive_type = geometry_result.type; - pass.SetVertexBuffer(std::move(geometry_result.vertex_buffer)); - switch (geometry_result.mode) { + options.primitive_type = clip_geometry_.type; + pass.SetVertexBuffer(clip_geometry_.vertex_buffer); + switch (clip_geometry_.mode) { case GeometryResult::Mode::kNonZero: pass.SetCommandLabel("Clip stencil preparation (NonZero)"); options.stencil_mode = @@ -121,7 +108,7 @@ bool ClipContents::Render(const ContentContext& renderer, } pass.SetPipeline(renderer.GetClipPipeline(options)); - info.mvp = geometry_result.transform; + info.mvp = clip_geometry_.transform; VS::BindFrameInfo(pass, renderer.GetTransientsBuffer().EmplaceUniform(info)); if (!pass.Draw().ok()) { @@ -143,12 +130,7 @@ bool ClipContents::Render(const ContentContext& renderer, case Entity::ClipOperation::kDifference: pass.SetCommandLabel("Difference Clip"); options.stencil_mode = ContentContextOptions::StencilMode::kCoverCompare; - std::optional maybe_cover_area = - geometry_->GetCoverage(entity.GetTransform()); - if (!maybe_cover_area.has_value()) { - return true; - } - cover_area = maybe_cover_area.value(); + cover_area = coverage_rect_; break; } auto points = cover_area.GetPoints(); @@ -167,39 +149,10 @@ bool ClipContents::Render(const ContentContext& renderer, ******* ClipRestoreContents ******************************************************************************/ -ClipRestoreContents::ClipRestoreContents() = default; - -ClipRestoreContents::~ClipRestoreContents() = default; - -void ClipRestoreContents::SetRestoreHeight(size_t clip_height) { - restore_height_ = clip_height; -} - -size_t ClipRestoreContents::GetRestoreHeight() const { - return restore_height_; -} - -void ClipRestoreContents::SetRestoreCoverage( - std::optional restore_coverage) { - restore_coverage_ = restore_coverage; -} - -std::optional ClipRestoreContents::GetCoverage( - const Entity& entity) const { - return std::nullopt; -}; - -Contents::ClipCoverage ClipRestoreContents::GetClipCoverage( - const Entity& entity, - const std::optional& current_clip_coverage) const { - return {.type = ClipCoverage::Type::kRestore, .coverage = std::nullopt}; -} - -void ClipRestoreContents::SetInheritedOpacity(Scalar opacity) {} - -bool ClipRestoreContents::Render(const ContentContext& renderer, - const Entity& entity, - RenderPass& pass) const { +bool RenderClipRestore(const ContentContext& renderer, + RenderPass& pass, + uint32_t clip_depth, + std::optional restore_coverage) { using VS = ClipPipeline::VertexShader; pass.SetCommandLabel("Restore Clip"); @@ -214,7 +167,7 @@ bool ClipRestoreContents::Render(const ContentContext& renderer, // Create a rect that covers either the given restore area, or the whole // render target texture. auto ltrb = - restore_coverage_.value_or(Rect::MakeSize(pass.GetRenderTargetSize())) + restore_coverage.value_or(Rect::MakeSize(pass.GetRenderTargetSize())) .GetLTRB(); std::array vertices = { @@ -227,7 +180,7 @@ bool ClipRestoreContents::Render(const ContentContext& renderer, CreateVertexBuffer(vertices, renderer.GetTransientsBuffer())); VS::FrameInfo info; - info.depth = GetShaderClipDepth(entity); + info.depth = GetShaderClipDepth(clip_depth); info.mvp = pass.GetOrthographicTransform(); VS::BindFrameInfo(pass, renderer.GetTransientsBuffer().EmplaceUniform(info)); diff --git a/impeller/entity/contents/clip_contents.h b/impeller/entity/contents/clip_contents.h index 51e76644f4ec9..8690bcb657153 100644 --- a/impeller/entity/contents/clip_contents.h +++ b/impeller/entity/contents/clip_contents.h @@ -11,82 +11,68 @@ namespace impeller { -class ClipContents final : public Contents { - public: - ClipContents(); - - ~ClipContents(); - - void SetGeometry(const Geometry* geometry); - - void SetClipOperation(Entity::ClipOperation clip_op); - - // |Contents| - std::optional GetCoverage(const Entity& entity) const override; +struct ClipCoverage { + // TODO(jonahwilliams): this should probably use the Entity::ClipOperation + // enum, but that has transitive import errors. + bool is_difference_or_non_square = false; - // |Contents| - ClipCoverage GetClipCoverage( - const Entity& entity, - const std::optional& current_clip_coverage) const override; - - // |Contents| - bool Render(const ContentContext& renderer, - const Entity& entity, - RenderPass& pass) const override; - - // |Contents| - void SetInheritedOpacity(Scalar opacity) override; - - private: - const Geometry* geometry_ = nullptr; - Entity::ClipOperation clip_op_ = Entity::ClipOperation::kIntersect; - - ClipContents(const ClipContents&) = delete; - - ClipContents& operator=(const ClipContents&) = delete; + /// @brief This coverage is the outer coverage of the clip. + /// + /// For example, if the clip is a circular clip, this is the rectangle that + /// contains the circle and not the rectangle that is contained within the + /// circle. This means that we cannot use the coverage alone to determine if + /// a clip can be culled, and instead also use the somewhat hacky + /// "is_difference_or_non_square" field. + std::optional coverage = std::nullopt; }; -class ClipRestoreContents final : public Contents { +class ClipContents { public: - ClipRestoreContents(); + ClipContents(Rect coverage_rect, bool is_axis_aligned_rect); - ~ClipRestoreContents(); + ~ClipContents(); - void SetRestoreHeight(size_t clip_height); + /// @brief Set the pre-tessellated clip geometry. + void SetGeometry(GeometryResult geometry); - size_t GetRestoreHeight() const; + void SetClipOperation(Entity::ClipOperation clip_op); - /// @brief The area on the pass texture where this clip restore will be - /// applied. If unset, the entire pass texture will be restored. + //---------------------------------------------------------------------------- + /// @brief Given the current pass space bounding rectangle of the clip + /// buffer, return the expected clip coverage after this draw call. + /// This should only be implemented for contents that may write to the + /// clip buffer. + /// + /// During rendering, coverage coordinates count pixels from the top + /// left corner of the framebuffer. /// - /// @note This rectangle is not transformed by the entity's transform. - void SetRestoreCoverage(std::optional coverage); - - // |Contents| - std::optional GetCoverage(const Entity& entity) const override; - - // |Contents| ClipCoverage GetClipCoverage( - const Entity& entity, - const std::optional& current_clip_coverage) const override; + const std::optional& current_clip_coverage) const; - // |Contents| bool Render(const ContentContext& renderer, - const Entity& entity, - RenderPass& pass) const override; - - // |Contents| - void SetInheritedOpacity(Scalar opacity) override; + RenderPass& pass, + uint32_t clip_depth) const; private: - std::optional restore_coverage_; - size_t restore_height_ = 0; - - ClipRestoreContents(const ClipRestoreContents&) = delete; - - ClipRestoreContents& operator=(const ClipRestoreContents&) = delete; + // Pre-tessellated clip geometry. + GeometryResult clip_geometry_; + // Coverage rect of the tessellated geometry. + Rect coverage_rect_; + bool is_axis_aligned_rect_ = false; + Entity::ClipOperation clip_op_ = Entity::ClipOperation::kIntersect; }; +/// @brief Render a restore clip. +/// +/// This is is intended to be used for prevent overdraw mechanism. The clip +/// depth should be the depth of the entity that is currently being drawn, and +/// restore_coverage should be its coverage. If restore_coverage is +/// std::nullopt, the render pass coverage is used instead. +bool RenderClipRestore(const ContentContext& renderer, + RenderPass& pass, + uint32_t clip_depth, + std::optional restore_coverage); + } // namespace impeller #endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_CLIP_CONTENTS_H_ diff --git a/impeller/entity/contents/clip_contents_unittests.cc b/impeller/entity/contents/clip_contents_unittests.cc deleted file mode 100644 index 21070a0bc5bb5..0000000000000 --- a/impeller/entity/contents/clip_contents_unittests.cc +++ /dev/null @@ -1,54 +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. - -#include -#include - -#include "gtest/gtest.h" - -#include "impeller/entity/contents/clip_contents.h" -#include "impeller/entity/contents/content_context.h" -#include "impeller/entity/contents/contents.h" -#include "impeller/entity/contents/test/recording_render_pass.h" -#include "impeller/entity/entity.h" -#include "impeller/entity/entity_playground.h" -#include "impeller/renderer/render_target.h" - -namespace impeller { -namespace testing { - -using EntityTest = EntityPlayground; - -TEST_P(EntityTest, ClipContentsOptimizesFullScreenIntersectClips) { - // Set up mock environment. - - auto content_context = GetContentContext(); - auto buffer = content_context->GetContext()->CreateCommandBuffer(); - auto render_target = - GetContentContext()->GetRenderTargetCache()->CreateOffscreenMSAA( - *content_context->GetContext(), {100, 100}, - /*mip_count=*/1); - auto render_pass = buffer->CreateRenderPass(render_target); - auto recording_pass = std::make_shared( - render_pass, GetContext(), render_target); - - // Set up clip contents. - - auto contents = std::make_shared(); - contents->SetClipOperation(Entity::ClipOperation::kIntersect); - auto geom = Geometry::MakeCover(); - contents->SetGeometry(geom.get()); - - Entity entity; - entity.SetContents(std::move(contents)); - - // Render the clip contents. - - ASSERT_TRUE(recording_pass->GetCommands().empty()); - ASSERT_TRUE(entity.Render(*content_context, *recording_pass)); - ASSERT_FALSE(recording_pass->GetCommands().empty()); -} - -} // namespace testing -} // namespace impeller diff --git a/impeller/entity/contents/color_source_contents.h b/impeller/entity/contents/color_source_contents.h index 52985493f78aa..76a49ffe4a1db 100644 --- a/impeller/entity/contents/color_source_contents.h +++ b/impeller/entity/contents/color_source_contents.h @@ -232,8 +232,10 @@ class ColorSourceContents : public Contents { // If overdraw prevention is enabled (like when drawing stroke paths), we // increment the stencil buffer as we draw, preventing overlapping fragments // from drawing. Afterwards, we need to append another draw call to clean up - // the stencil buffer (happens below in this method). - if (geometry_result.mode == GeometryResult::Mode::kPreventOverdraw) { + // the stencil buffer (happens below in this method). This can be skipped + // for draws that are fully opaque or use src blend mode. + if (geometry_result.mode == GeometryResult::Mode::kPreventOverdraw && + options.blend_mode != BlendMode::kSource) { options.stencil_mode = ContentContextOptions::StencilMode::kOverdrawPreventionIncrement; } @@ -259,11 +261,10 @@ class ColorSourceContents : public Contents { // If we performed overdraw prevention, a subsection of the clip heightmap // was incremented by 1 in order to self-clip. So simply append a clip // restore to clean it up. - if (geometry_result.mode == GeometryResult::Mode::kPreventOverdraw) { - auto restore = ClipRestoreContents(); - restore.SetRestoreCoverage(GetCoverage(entity)); - Entity restore_entity = entity.Clone(); - return restore.Render(renderer, restore_entity, pass); + if (geometry_result.mode == GeometryResult::Mode::kPreventOverdraw && + options.blend_mode != BlendMode::kSource) { + return RenderClipRestore(renderer, pass, entity.GetClipDepth(), + GetCoverage(entity)); } return true; } diff --git a/impeller/entity/contents/conical_gradient_contents.cc b/impeller/entity/contents/conical_gradient_contents.cc index 82a9bbff5ea4b..85c2b62403df3 100644 --- a/impeller/entity/contents/conical_gradient_contents.cc +++ b/impeller/entity/contents/conical_gradient_contents.cc @@ -49,12 +49,24 @@ void ConicalGradientContents::SetFocus(std::optional focus, focus_radius_ = radius; } +#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) +#define UNIFORM_FRAG_INFO(t) \ + t##GradientUniformFillPipeline::FragmentShader::FragInfo +#define UNIFORM_COLOR_SIZE ARRAY_LEN(UNIFORM_FRAG_INFO(Conical)::colors) +#define UNIFORM_STOP_SIZE ARRAY_LEN(UNIFORM_FRAG_INFO(Conical)::stop_pairs) +static_assert(UNIFORM_COLOR_SIZE == kMaxUniformGradientStops); +static_assert(UNIFORM_STOP_SIZE == kMaxUniformGradientStops / 2); + bool ConicalGradientContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { if (renderer.GetDeviceCapabilities().SupportsSSBO()) { return RenderSSBO(renderer, entity, pass); } + if (colors_.size() <= kMaxUniformGradientStops && + stops_.size() <= kMaxUniformGradientStops) { + return RenderUniform(renderer, entity, pass); + } return RenderTexture(renderer, entity, pass); } @@ -107,6 +119,49 @@ bool ConicalGradientContents::RenderSSBO(const ContentContext& renderer, }); } +bool ConicalGradientContents::RenderUniform(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + using VS = ConicalGradientUniformFillPipeline::VertexShader; + using FS = ConicalGradientUniformFillPipeline::FragmentShader; + + VS::FrameInfo frame_info; + frame_info.matrix = GetInverseEffectTransform(); + + PipelineBuilderCallback pipeline_callback = + [&renderer](ContentContextOptions options) { + return renderer.GetConicalGradientUniformFillPipeline(options); + }; + return ColorSourceContents::DrawGeometry( + renderer, entity, pass, pipeline_callback, frame_info, + [this, &renderer, &entity](RenderPass& pass) { + FS::FragInfo frag_info; + frag_info.center = center_; + if (focus_) { + frag_info.focus = focus_.value(); + frag_info.focus_radius = focus_radius_; + } else { + frag_info.focus = center_; + frag_info.focus_radius = 0.0; + } + frag_info.radius = radius_; + frag_info.tile_mode = static_cast(tile_mode_); + frag_info.alpha = + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); + frag_info.colors_length = PopulateUniformGradientColors( + colors_, stops_, frag_info.colors, frag_info.stop_pairs); + frag_info.decal_border_color = decal_border_color_; + + pass.SetCommandLabel("ConicalGradientUniformFill"); + + FS::BindFragInfo( + pass, renderer.GetTransientsBuffer().EmplaceUniform(frag_info)); + + return true; + }); +} + bool ConicalGradientContents::RenderTexture(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { diff --git a/impeller/entity/contents/conical_gradient_contents.h b/impeller/entity/contents/conical_gradient_contents.h index 16d43c74afb64..1174722385c04 100644 --- a/impeller/entity/contents/conical_gradient_contents.h +++ b/impeller/entity/contents/conical_gradient_contents.h @@ -51,6 +51,11 @@ class ConicalGradientContents final : public ColorSourceContents { bool RenderSSBO(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const; + + bool RenderUniform(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const; + Point center_; Scalar radius_ = 0.0f; std::vector colors_; diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index 70d1df77c38c1..b3cc2d9932585 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -8,7 +8,6 @@ #include #include "fml/trace_event.h" -#include "impeller/base/strings.h" #include "impeller/base/validation.h" #include "impeller/core/formats.h" #include "impeller/core/texture_descriptor.h" @@ -250,14 +249,15 @@ ContentContext::ContentContext( ? std::make_shared( context_->GetResourceAllocator()) : std::move(render_target_allocator)), - host_buffer_(HostBuffer::Create(context_->GetResourceAllocator())) { + host_buffer_(HostBuffer::Create(context_->GetResourceAllocator(), + context_->GetIdleWaiter())) { if (!context_ || !context_->IsValid()) { return; } { TextureDescriptor desc; - desc.storage_mode = StorageMode::kHostVisible; + desc.storage_mode = StorageMode::kDevicePrivate; desc.format = PixelFormat::kR8G8B8A8UNormInt; desc.size = ISize{1, 1}; empty_texture_ = GetContext()->GetResourceAllocator()->CreateTexture(desc); @@ -308,6 +308,12 @@ ContentContext::ContentContext( conical_gradient_ssbo_fill_pipelines_.CreateDefault(*context_, options); sweep_gradient_ssbo_fill_pipelines_.CreateDefault(*context_, options); } else { + linear_gradient_uniform_fill_pipelines_.CreateDefault(*context_, options); + radial_gradient_uniform_fill_pipelines_.CreateDefault(*context_, options); + conical_gradient_uniform_fill_pipelines_.CreateDefault(*context_, + options); + sweep_gradient_uniform_fill_pipelines_.CreateDefault(*context_, options); + linear_gradient_fill_pipelines_.CreateDefault(*context_, options); radial_gradient_fill_pipelines_.CreateDefault(*context_, options); conical_gradient_fill_pipelines_.CreateDefault(*context_, options); @@ -454,10 +460,14 @@ ContentContext::ContentContext( options_trianglestrip); yuv_to_rgb_filter_pipelines_.CreateDefault(*context_, options_trianglestrip); - // GLES only shader that is unsupported on macOS. -#if defined(IMPELLER_ENABLE_OPENGLES) && !defined(FML_OS_MACOSX) +#if defined(IMPELLER_ENABLE_OPENGLES) if (GetContext()->GetBackendType() == Context::BackendType::kOpenGLES) { +#if !defined(FML_OS_MACOSX) + // GLES only shader that is unsupported on macOS. tiled_texture_external_pipelines_.CreateDefault(*context_, options); +#endif // !defined(FML_OS_MACOSX) + texture_downsample_gles_pipelines_.CreateDefault(*context_, + options_trianglestrip); } #endif // IMPELLER_ENABLE_OPENGLES @@ -543,8 +553,8 @@ fml::StatusOr ContentContext::MakeSubpass( return subpass_target; } -std::shared_ptr ContentContext::GetTessellator() const { - return tessellator_; +Tessellator& ContentContext::GetTessellator() const { + return *tessellator_; } std::shared_ptr ContentContext::GetContext() const { diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index 9abdc51ba91ca..0cfb4a2623e3e 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -56,6 +56,11 @@ #include "impeller/entity/tiled_texture_fill.frag.h" #include "impeller/entity/yuv_to_rgb_filter.frag.h" +#include "impeller/entity/conical_gradient_uniform_fill.frag.h" +#include "impeller/entity/linear_gradient_uniform_fill.frag.h" +#include "impeller/entity/radial_gradient_uniform_fill.frag.h" +#include "impeller/entity/sweep_gradient_uniform_fill.frag.h" + #include "impeller/entity/conical_gradient_ssbo_fill.frag.h" #include "impeller/entity/linear_gradient_ssbo_fill.frag.h" #include "impeller/entity/radial_gradient_ssbo_fill.frag.h" @@ -70,6 +75,7 @@ #include "impeller/entity/vertices_uber.frag.h" #ifdef IMPELLER_ENABLE_OPENGLES +#include "impeller/entity/texture_downsample_gles.frag.h" #include "impeller/entity/tiled_texture_fill_external.frag.h" #endif // IMPELLER_ENABLE_OPENGLES @@ -91,6 +97,18 @@ using ConicalGradientFillPipeline = using SweepGradientFillPipeline = RenderPipelineHandle; +using LinearGradientUniformFillPipeline = + RenderPipelineHandle; +using ConicalGradientUniformFillPipeline = + RenderPipelineHandle; +using RadialGradientUniformFillPipeline = + RenderPipelineHandle; +using SweepGradientUniformFillPipeline = + RenderPipelineHandle; using LinearGradientSSBOFillPipeline = RenderPipelineHandle; @@ -239,6 +257,9 @@ using VerticesUberShader = RenderPipelineHandle; +using TextureDownsampleGlesPipeline = + RenderPipelineHandle; #endif // IMPELLER_ENABLE_OPENGLES /// Pipeline state configuration. @@ -315,47 +336,27 @@ struct ContentContextOptions { bool wireframe = false; bool is_for_rrect_blur_clear = false; - struct Hash { - constexpr uint64_t operator()(const ContentContextOptions& o) const { - static_assert(sizeof(o.sample_count) == 1); - static_assert(sizeof(o.blend_mode) == 1); - static_assert(sizeof(o.sample_count) == 1); - static_assert(sizeof(o.depth_compare) == 1); - static_assert(sizeof(o.stencil_mode) == 1); - static_assert(sizeof(o.primitive_type) == 1); - static_assert(sizeof(o.color_attachment_pixel_format) == 1); - - return (o.is_for_rrect_blur_clear ? 1llu : 0llu) << 0 | - (o.wireframe ? 1llu : 0llu) << 1 | - (o.has_depth_stencil_attachments ? 1llu : 0llu) << 2 | - (o.depth_write_enabled ? 1llu : 0llu) << 3 | - // enums - static_cast(o.color_attachment_pixel_format) << 8 | - static_cast(o.primitive_type) << 16 | - static_cast(o.stencil_mode) << 24 | - static_cast(o.depth_compare) << 32 | - static_cast(o.blend_mode) << 40 | - static_cast(o.sample_count) << 48; - } - }; - - struct Equal { - constexpr bool operator()(const ContentContextOptions& lhs, - const ContentContextOptions& rhs) const { - return lhs.sample_count == rhs.sample_count && - lhs.blend_mode == rhs.blend_mode && - lhs.depth_write_enabled == rhs.depth_write_enabled && - lhs.depth_compare == rhs.depth_compare && - lhs.stencil_mode == rhs.stencil_mode && - lhs.primitive_type == rhs.primitive_type && - lhs.color_attachment_pixel_format == - rhs.color_attachment_pixel_format && - lhs.has_depth_stencil_attachments == - rhs.has_depth_stencil_attachments && - lhs.wireframe == rhs.wireframe && - lhs.is_for_rrect_blur_clear == rhs.is_for_rrect_blur_clear; - } - }; + constexpr uint64_t ToKey() const { + static_assert(sizeof(sample_count) == 1); + static_assert(sizeof(blend_mode) == 1); + static_assert(sizeof(sample_count) == 1); + static_assert(sizeof(depth_compare) == 1); + static_assert(sizeof(stencil_mode) == 1); + static_assert(sizeof(primitive_type) == 1); + static_assert(sizeof(color_attachment_pixel_format) == 1); + + return (is_for_rrect_blur_clear ? 1llu : 0llu) << 0 | + (wireframe ? 1llu : 0llu) << 1 | + (has_depth_stencil_attachments ? 1llu : 0llu) << 2 | + (depth_write_enabled ? 1llu : 0llu) << 3 | + // enums + static_cast(color_attachment_pixel_format) << 8 | + static_cast(primitive_type) << 16 | + static_cast(stencil_mode) << 24 | + static_cast(depth_compare) << 32 | + static_cast(blend_mode) << 40 | + static_cast(sample_count) << 48; + } void ApplyToPipelineDescriptor(PipelineDescriptor& desc) const; }; @@ -374,7 +375,7 @@ class ContentContext { bool IsValid() const; - std::shared_ptr GetTessellator() const; + Tessellator& GetTessellator() const; std::shared_ptr> GetFastGradientPipeline( ContentContextOptions opts) const { @@ -386,6 +387,26 @@ class ContentContext { return GetPipeline(linear_gradient_fill_pipelines_, opts); } + std::shared_ptr> + GetLinearGradientUniformFillPipeline(ContentContextOptions opts) const { + return GetPipeline(linear_gradient_uniform_fill_pipelines_, opts); + } + + std::shared_ptr> + GetRadialGradientUniformFillPipeline(ContentContextOptions opts) const { + return GetPipeline(radial_gradient_uniform_fill_pipelines_, opts); + } + + std::shared_ptr> + GetConicalGradientUniformFillPipeline(ContentContextOptions opts) const { + return GetPipeline(conical_gradient_uniform_fill_pipelines_, opts); + } + + std::shared_ptr> + GetSweepGradientUniformFillPipeline(ContentContextOptions opts) const { + return GetPipeline(sweep_gradient_uniform_fill_pipelines_, opts); + } + std::shared_ptr> GetLinearGradientSSBOFillPipeline(ContentContextOptions opts) const { FML_DCHECK(GetDeviceCapabilities().SupportsSSBO()); @@ -446,6 +467,11 @@ class ContentContext { } #ifdef IMPELLER_ENABLE_OPENGLES + std::shared_ptr> + GetDownsampleTextureGlesPipeline(ContentContextOptions opts) const { + return GetPipeline(texture_downsample_gles_pipelines_, opts); + } + std::shared_ptr> GetTiledTextureExternalPipeline( ContentContextOptions opts) const { FML_DCHECK(GetContext()->GetBackendType() == @@ -771,7 +797,7 @@ class ContentContext { struct Hash { std::size_t operator()(const RuntimeEffectPipelineKey& key) const { return fml::HashCombine(key.unique_entrypoint_name, - ContentContextOptions::Hash{}(key.options)); + key.options.ToKey()); } }; @@ -779,7 +805,7 @@ class ContentContext { constexpr bool operator()(const RuntimeEffectPipelineKey& lhs, const RuntimeEffectPipelineKey& rhs) const { return lhs.unique_entrypoint_name == rhs.unique_entrypoint_name && - ContentContextOptions::Equal{}(lhs.options, rhs.options); + lhs.options.ToKey() == rhs.options.ToKey(); } }; }; @@ -810,7 +836,13 @@ class ContentContext { void Set(const ContentContextOptions& options, std::unique_ptr pipeline) { - pipelines_[options] = std::move(pipeline); + uint64_t p_key = options.ToKey(); + for (const auto& [key, pipeline] : pipelines_) { + if (key == p_key) { + return; + } + } + pipelines_.push_back(std::make_pair(p_key, std::move(pipeline))); } void SetDefault(const ContentContextOptions& options, @@ -833,8 +865,11 @@ class ContentContext { } PipelineHandleT* Get(const ContentContextOptions& options) const { - if (auto found = pipelines_.find(options); found != pipelines_.end()) { - return found->second.get(); + uint64_t p_key = options.ToKey(); + for (const auto& [key, pipeline] : pipelines_) { + if (key == p_key) { + return pipeline.get(); + } } return nullptr; } @@ -850,10 +885,7 @@ class ContentContext { private: std::optional default_options_; - std::unordered_map, - ContentContextOptions::Hash, - ContentContextOptions::Equal> + std::vector>> pipelines_; Variants(const Variants&) = delete; @@ -872,6 +904,14 @@ class ContentContext { mutable Variants conical_gradient_fill_pipelines_; mutable Variants sweep_gradient_fill_pipelines_; + mutable Variants + linear_gradient_uniform_fill_pipelines_; + mutable Variants + radial_gradient_uniform_fill_pipelines_; + mutable Variants + conical_gradient_uniform_fill_pipelines_; + mutable Variants + sweep_gradient_uniform_fill_pipelines_; mutable Variants linear_gradient_ssbo_fill_pipelines_; mutable Variants @@ -887,6 +927,8 @@ class ContentContext { #ifdef IMPELLER_ENABLE_OPENGLES mutable Variants tiled_texture_external_pipelines_; + mutable Variants + texture_downsample_gles_pipelines_; #endif // IMPELLER_ENABLE_OPENGLES mutable Variants tiled_texture_pipelines_; mutable Variants gaussian_blur_pipelines_; diff --git a/impeller/entity/contents/contents.cc b/impeller/entity/contents/contents.cc index abebbfc68b4e7..60998616fefcf 100644 --- a/impeller/entity/contents/contents.cc +++ b/impeller/entity/contents/contents.cc @@ -26,7 +26,7 @@ ContentContextOptions OptionsFromPass(const RenderPass& pass) { FML_DCHECK(pass.HasDepthAttachment() == pass.HasStencilAttachment()); opts.has_depth_stencil_attachments = has_depth_stencil_attachments; - opts.depth_compare = CompareFunction::kGreater; + opts.depth_compare = CompareFunction::kGreaterEqual; opts.stencil_mode = ContentContextOptions::StencilMode::kIgnore; return opts; } @@ -53,13 +53,6 @@ bool Contents::IsOpaque(const Matrix& transform) const { return false; } -Contents::ClipCoverage Contents::GetClipCoverage( - const Entity& entity, - const std::optional& current_clip_coverage) const { - return {.type = ClipCoverage::Type::kNoChange, - .coverage = current_clip_coverage}; -} - std::optional Contents::RenderToSnapshot( const ContentContext& renderer, const Entity& entity, diff --git a/impeller/entity/contents/contents.h b/impeller/entity/contents/contents.h index 1577f3054f345..31a8aed38faab 100644 --- a/impeller/entity/contents/contents.h +++ b/impeller/entity/contents/contents.h @@ -34,24 +34,6 @@ class Contents { /// unpremultiplied color. using ColorFilterProc = std::function; - struct ClipCoverage { - enum class Type { kNoChange, kAppend, kRestore }; - - Type type = Type::kNoChange; - // TODO(jonahwilliams): this should probably use the Entity::ClipOperation - // enum, but that has transitive import errors. - bool is_difference_or_non_square = false; - - /// @brief This coverage is the outer coverage of the clip. - /// - /// For example, if the clip is a circular clip, this is the rectangle that - /// contains the circle and not the rectangle that is contained within the - /// circle. This means that we cannot use the coverage alone to determine if - /// a clip can be culled, and instead also use the somewhat hacky - /// "is_difference_or_non_square" field. - std::optional coverage = std::nullopt; - }; - using RenderProc = std::function; @@ -100,19 +82,6 @@ class Contents { /// render this contents. virtual bool IsOpaque(const Matrix& transform) const; - //---------------------------------------------------------------------------- - /// @brief Given the current pass space bounding rectangle of the clip - /// buffer, return the expected clip coverage after this draw call. - /// This should only be implemented for contents that may write to the - /// clip buffer. - /// - /// During rendering, coverage coordinates count pixels from the top - /// left corner of the framebuffer. - /// - virtual ClipCoverage GetClipCoverage( - const Entity& entity, - const std::optional& current_clip_coverage) const; - //---------------------------------------------------------------------------- /// @brief Render this contents to a snapshot, respecting the entity's /// transform, path, clip depth, and blend mode. diff --git a/impeller/entity/contents/filters/blend_filter_contents.cc b/impeller/entity/contents/filters/blend_filter_contents.cc index ce35b710af751..b427de9ab76e6 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.cc +++ b/impeller/entity/contents/filters/blend_filter_contents.cc @@ -850,6 +850,10 @@ std::optional BlendFilterContents::CreateFramebufferAdvancedBlend( VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info)); frag_info.src_input_alpha = 1.0; + frag_info.dst_input_alpha = + absorb_opacity == ColorFilterContents::AbsorbOpacity::kYes + ? dst_snapshot->opacity + : 1.0; FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); return pass.Draw().ok(); diff --git a/impeller/entity/contents/filters/blend_filter_contents_unittests.cc b/impeller/entity/contents/filters/blend_filter_contents_unittests.cc index 977eae47a26e4..cc8176679e404 100644 --- a/impeller/entity/contents/filters/blend_filter_contents_unittests.cc +++ b/impeller/entity/contents/filters/blend_filter_contents_unittests.cc @@ -54,8 +54,8 @@ TEST_P(BlendFilterContentsTest, AdvancedBlendColorAlignsColorTo4) { uint8_t byte = 0xff; BufferView buffer_view = renderer->GetTransientsBuffer().Emplace(&byte, /*length=*/1, /*align=*/1); - EXPECT_EQ(buffer_view.range.offset, 4u); - EXPECT_EQ(buffer_view.range.length, 1u); + EXPECT_EQ(buffer_view.GetRange().offset, 4u); + EXPECT_EQ(buffer_view.GetRange().length, 1u); Entity entity; std::optional result = filter_contents.GetEntity( diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc index 988d6638a05af..ddfaa2c6388c5 100644 --- a/impeller/entity/contents/filters/filter_contents.cc +++ b/impeller/entity/contents/filters/filter_contents.cc @@ -21,12 +21,14 @@ #include "impeller/entity/contents/filters/local_matrix_filter_contents.h" #include "impeller/entity/contents/filters/matrix_filter_contents.h" #include "impeller/entity/contents/filters/morphology_filter_contents.h" +#include "impeller/entity/contents/filters/runtime_effect_filter_contents.h" #include "impeller/entity/contents/filters/yuv_to_rgb_filter_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/entity/entity.h" #include "impeller/geometry/path_builder.h" #include "impeller/renderer/command_buffer.h" #include "impeller/renderer/render_pass.h" +#include "impeller/runtime_stage/runtime_stage.h" namespace impeller { @@ -114,6 +116,19 @@ std::shared_ptr FilterContents::MakeYUVToRGBFilter( return filter; } +std::shared_ptr FilterContents::MakeRuntimeEffect( + FilterInput::Ref input, + std::shared_ptr runtime_stage, + std::shared_ptr> uniforms, + std::vector texture_inputs) { + auto filter = std::make_shared(); + filter->SetInputs({std::move(input)}); + filter->SetRuntimeStage(std::move(runtime_stage)); + filter->SetUniforms(std::move(uniforms)); + filter->SetTextureInputs(std::move(texture_inputs)); + return filter; +} + FilterContents::FilterContents() = default; FilterContents::~FilterContents() = default; diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h index 9b5c019541e78..3177cf7518ee3 100644 --- a/impeller/entity/contents/filters/filter_contents.h +++ b/impeller/entity/contents/filters/filter_contents.h @@ -12,10 +12,12 @@ #include "impeller/core/formats.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" +#include "impeller/entity/contents/runtime_effect_contents.h" #include "impeller/entity/entity.h" #include "impeller/entity/geometry/geometry.h" #include "impeller/geometry/matrix.h" #include "impeller/geometry/sigma.h" +#include "impeller/runtime_stage/runtime_stage.h" namespace impeller { @@ -77,6 +79,12 @@ class FilterContents : public Contents { std::shared_ptr uv_texture, YUVColorSpace yuv_color_space); + static std::shared_ptr MakeRuntimeEffect( + FilterInput::Ref input, + std::shared_ptr runtime_stage, + std::shared_ptr> uniforms, + std::vector texture_inputs); + FilterContents(); ~FilterContents() override; diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index 3e592636b8887..b2e81ce1dc342 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -9,6 +9,7 @@ #include "flutter/fml/make_copyable.h" #include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" #include "impeller/entity/texture_downsample.frag.h" #include "impeller/entity/texture_fill.frag.h" #include "impeller/entity/texture_fill.vert.h" @@ -395,7 +396,20 @@ fml::StatusOr MakeDownsampleSubpass( pass.SetCommandLabel("Gaussian blur downsample"); auto pipeline_options = OptionsFromPass(pass); pipeline_options.primitive_type = PrimitiveType::kTriangleStrip; +#ifdef IMPELLER_ENABLE_OPENGLES + // The GLES backend conditionally supports decal tile mode, while + // decal is always supported for Vulkan and Metal. + if (renderer.GetDeviceCapabilities() + .SupportsDecalSamplerAddressMode() || + tile_mode != Entity::TileMode::kDecal) { + pass.SetPipeline(renderer.GetDownsamplePipeline(pipeline_options)); + } else { + pass.SetPipeline( + renderer.GetDownsampleTextureGlesPipeline(pipeline_options)); + } +#else pass.SetPipeline(renderer.GetDownsamplePipeline(pipeline_options)); +#endif // IMPELLER_ENABLE_OPENGLES TextureFillVertexShader::FrameInfo frame_info; frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); @@ -512,27 +526,33 @@ Entity ApplyClippedBlurStyle(Entity::ClipOperation clip_operation, const Snapshot& input_snapshot, Entity blur_entity, const Geometry* geometry) { - auto clip_contents = std::make_shared(); - clip_contents->SetClipOperation(clip_operation); - clip_contents->SetGeometry(geometry); - Entity clipper; - clipper.SetContents(clip_contents); - auto restore = std::make_unique(); Matrix entity_transform = entity.GetTransform(); Matrix blur_transform = blur_entity.GetTransform(); - auto renderer = fml::MakeCopyable( - [blur_entity = blur_entity.Clone(), clipper = std::move(clipper), - restore = std::move(restore), entity_transform, - blur_transform](const ContentContext& renderer, const Entity& entity, - RenderPass& pass) mutable { - bool result = true; + + auto renderer = + fml::MakeCopyable([blur_entity = blur_entity.Clone(), clip_operation, + entity_transform, blur_transform, geometry]( + const ContentContext& renderer, + const Entity& entity, RenderPass& pass) mutable { + Entity clipper; clipper.SetClipDepth(entity.GetClipDepth()); clipper.SetTransform(entity.GetTransform() * entity_transform); - result = clipper.Render(renderer, pass) && result; + + auto geom_result = geometry->GetPositionBuffer(renderer, clipper, pass); + + ClipContents clip_contents(geometry->GetCoverage(clipper.GetTransform()) + .value_or(Rect::MakeLTRB(0, 0, 0, 0)), + /*is_axis_aligned_rect=*/false); + clip_contents.SetClipOperation(clip_operation); + clip_contents.SetGeometry(std::move(geom_result)); + + if (!clip_contents.Render(renderer, pass, entity.GetClipDepth())) { + return false; + } blur_entity.SetClipDepth(entity.GetClipDepth()); blur_entity.SetTransform(entity.GetTransform() * blur_transform); - result = blur_entity.Render(renderer, pass) && result; - return result; + + return blur_entity.Render(renderer, pass); }); auto coverage = fml::MakeCopyable([blur_entity = std::move(blur_entity), @@ -582,15 +602,15 @@ Entity ApplyBlurStyle(FilterContents::BlurStyle blur_style, const ContentContext& renderer, const Entity& entity, RenderPass& pass) mutable { - bool result = true; snapshot_entity.SetTransform(entity.GetTransform() * snapshot_transform); snapshot_entity.SetClipDepth(entity.GetClipDepth()); - result = result && snapshot_entity.Render(renderer, pass); + if (!snapshot_entity.Render(renderer, pass)) { + return false; + } blur_entity.SetClipDepth(entity.GetClipDepth()); blur_entity.SetTransform(entity.GetTransform() * blurred_transform); - result = result && blur_entity.Render(renderer, pass); - return result; + return blur_entity.Render(renderer, pass); }), fml::MakeCopyable([blur_entity = blur_entity.Clone(), blurred_transform](const Entity& entity) mutable { diff --git a/impeller/entity/contents/filters/local_matrix_filter_contents.cc b/impeller/entity/contents/filters/local_matrix_filter_contents.cc index 568346b9a3694..de06e4f953a24 100644 --- a/impeller/entity/contents/filters/local_matrix_filter_contents.cc +++ b/impeller/entity/contents/filters/local_matrix_filter_contents.cc @@ -23,7 +23,7 @@ std::optional LocalMatrixFilterContents::GetFilterSourceCoverage( const Matrix& effect_transform, const Rect& output_limit) const { auto matrix = matrix_.Basis(); - if (matrix.GetDeterminant() == 0.0) { + if (!matrix.IsInvertible()) { return std::nullopt; } auto inverse = matrix.Invert(); diff --git a/impeller/entity/contents/filters/matrix_filter_contents.cc b/impeller/entity/contents/filters/matrix_filter_contents.cc index 289e67fc922cb..444a62ee298aa 100644 --- a/impeller/entity/contents/filters/matrix_filter_contents.cc +++ b/impeller/entity/contents/filters/matrix_filter_contents.cc @@ -110,7 +110,7 @@ std::optional MatrixFilterContents::GetFilterSourceCoverage( auto transform = effect_transform * // matrix_ * // effect_transform.Invert(); // - if (transform.GetDeterminant() == 0.0) { + if (!transform.IsInvertible()) { return std::nullopt; } auto inverse = transform.Invert(); diff --git a/impeller/entity/contents/filters/runtime_effect_filter_contents.cc b/impeller/entity/contents/filters/runtime_effect_filter_contents.cc new file mode 100644 index 0000000000000..4bc87c443efd9 --- /dev/null +++ b/impeller/entity/contents/filters/runtime_effect_filter_contents.cc @@ -0,0 +1,111 @@ +// 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. + +#include "impeller/entity/contents/filters/runtime_effect_filter_contents.h" + +#include +#include + +#include "impeller/base/validation.h" +#include "impeller/entity/contents/anonymous_contents.h" +#include "impeller/entity/contents/runtime_effect_contents.h" +#include "impeller/geometry/size.h" + +namespace impeller { + +void RuntimeEffectFilterContents::SetRuntimeStage( + std::shared_ptr runtime_stage) { + runtime_stage_ = std::move(runtime_stage); +} + +void RuntimeEffectFilterContents::SetUniforms( + std::shared_ptr> uniforms) { + uniforms_ = std::move(uniforms); +} + +void RuntimeEffectFilterContents::SetTextureInputs( + std::vector texture_inputs) { + texture_inputs_ = std::move(texture_inputs); +} + +// |FilterContents| +std::optional RuntimeEffectFilterContents::RenderFilter( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage, + const std::optional& coverage_hint) const { + if (inputs.empty()) { + return std::nullopt; + } + + auto input_snapshot = + inputs[0]->GetSnapshot("RuntimeEffectContents", renderer, entity); + if (!input_snapshot.has_value()) { + return std::nullopt; + } + std::optional maybe_input_coverage = input_snapshot->GetCoverage(); + if (!maybe_input_coverage.has_value()) { + return std::nullopt; + } + Rect input_coverage = maybe_input_coverage.value(); + // The shader is required to have at least one sampler, the first of + // which is treated as the input and a vec2 size uniform to compute the + // offsets. These are validated at the dart:ui layer, but to avoid crashes we + // check here too. + if (texture_inputs_.size() < 1 || uniforms_->size() < 8) { + VALIDATION_LOG + << "Invalid fragment shader in RuntimeEffectFilterContents. " + << "Shader must have at least one sampler and a vec2 size uniform."; + return std::nullopt; + } + + // Update uniform values. + std::vector texture_input_copy = + texture_inputs_; + texture_input_copy[0].texture = input_snapshot->texture; + + Size size = Size(input_snapshot->texture->GetSize()); + memcpy(uniforms_->data(), &size, sizeof(Size)); + + //---------------------------------------------------------------------------- + /// Create AnonymousContents for rendering. + /// + RenderProc render_proc = + [input_snapshot, runtime_stage = runtime_stage_, uniforms = uniforms_, + texture_inputs = texture_input_copy, + input_coverage](const ContentContext& renderer, const Entity& entity, + RenderPass& pass) -> bool { + RuntimeEffectContents contents; + RectGeometry geom(Rect::MakeSize(input_coverage.GetSize())); + contents.SetRuntimeStage(runtime_stage); + contents.SetUniformData(uniforms); + contents.SetTextureInputs(texture_inputs); + contents.SetGeometry(&geom); + return contents.Render(renderer, entity, pass); + }; + + CoverageProc coverage_proc = + [coverage](const Entity& entity) -> std::optional { + return coverage.TransformBounds(entity.GetTransform()); + }; + + auto contents = AnonymousContents::Make(render_proc, coverage_proc); + + Entity sub_entity; + sub_entity.SetContents(std::move(contents)); + sub_entity.SetBlendMode(entity.GetBlendMode()); + sub_entity.SetTransform(input_snapshot->transform); + return sub_entity; +} + +// |FilterContents| +std::optional RuntimeEffectFilterContents::GetFilterSourceCoverage( + const Matrix& effect_transform, + const Rect& output_limit) const { + return output_limit; +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/runtime_effect_filter_contents.h b/impeller/entity/contents/filters/runtime_effect_filter_contents.h new file mode 100644 index 0000000000000..3a68ecd67ab15 --- /dev/null +++ b/impeller/entity/contents/filters/runtime_effect_filter_contents.h @@ -0,0 +1,53 @@ +// 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. + +#ifndef FLUTTER_IMPELLER_ENTITY_CONTENTS_FILTERS_RUNTIME_EFFECT_FILTER_CONTENTS_H_ +#define FLUTTER_IMPELLER_ENTITY_CONTENTS_FILTERS_RUNTIME_EFFECT_FILTER_CONTENTS_H_ + +#include "impeller/entity/contents/filters/filter_contents.h" + +namespace impeller { + +/// A filter that applies a runtime effect shader +class RuntimeEffectFilterContents final : public FilterContents { + public: + RuntimeEffectFilterContents() {} + + ~RuntimeEffectFilterContents() = default; + + void SetRuntimeStage(std::shared_ptr runtime_stage); + + void SetUniforms(std::shared_ptr> uniforms); + + void SetTextureInputs( + std::vector texture_inputs); + + private: + std::shared_ptr runtime_stage_; + std::shared_ptr> uniforms_; + std::vector texture_inputs_; + + // |FilterContents| + std::optional RenderFilter( + const FilterInput::Vector& input_textures, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage, + const std::optional& coverage_hint) const override; + + // |FilterContents| + std::optional GetFilterSourceCoverage( + const Matrix& effect_transform, + const Rect& output_limit) const override; + + RuntimeEffectFilterContents(const RuntimeEffectFilterContents&) = delete; + + RuntimeEffectFilterContents& operator=(const RuntimeEffectFilterContents&) = + delete; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_FILTERS_RUNTIME_EFFECT_FILTER_CONTENTS_H_ diff --git a/impeller/entity/contents/framebuffer_blend_contents.cc b/impeller/entity/contents/framebuffer_blend_contents.cc index 13f7bd61a0d9d..6597b3ff9ca44 100644 --- a/impeller/entity/contents/framebuffer_blend_contents.cc +++ b/impeller/entity/contents/framebuffer_blend_contents.cc @@ -139,6 +139,7 @@ bool FramebufferBlendContents::Render(const ContentContext& renderer, VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info)); frag_info.src_input_alpha = src_snapshot->opacity; + frag_info.dst_input_alpha = 1.0; FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); return pass.Draw().ok(); diff --git a/impeller/entity/contents/gradient_generator.cc b/impeller/entity/contents/gradient_generator.cc index 90f489dcfa45f..8aa1984c522c3 100644 --- a/impeller/entity/contents/gradient_generator.cc +++ b/impeller/entity/contents/gradient_generator.cc @@ -67,4 +67,31 @@ std::vector CreateGradientColors(const std::vector& colors, return result; } +int PopulateUniformGradientColors( + const std::vector& colors, + const std::vector& stops, + Vector4 frag_info_colors[kMaxUniformGradientStops], + Vector4 frag_info_stop_pairs[kMaxUniformGradientStops / 2]) { + FML_DCHECK(stops.size() == colors.size()); + + Scalar last_stop = 0; + int index = 0; + for (auto i = 0u; i < stops.size() && i < kMaxUniformGradientStops; i++) { + Scalar cur_stop = stops[i]; + Scalar delta = cur_stop - last_stop; + Scalar inverse_delta = delta == 0.0f ? 0.0 : 1.0 / delta; + frag_info_colors[index] = colors[i]; + if ((i & 1) == 0) { + frag_info_stop_pairs[index / 2].x = cur_stop; + frag_info_stop_pairs[index / 2].y = inverse_delta; + } else { + frag_info_stop_pairs[index / 2].z = cur_stop; + frag_info_stop_pairs[index / 2].w = inverse_delta; + } + last_stop = cur_stop; + index++; + } + return index; +} + } // namespace impeller diff --git a/impeller/entity/contents/gradient_generator.h b/impeller/entity/contents/gradient_generator.h index db48829126e8b..fa6dab716f94a 100644 --- a/impeller/entity/contents/gradient_generator.h +++ b/impeller/entity/contents/gradient_generator.h @@ -44,6 +44,28 @@ static_assert(sizeof(StopData) == 32); std::vector CreateGradientColors(const std::vector& colors, const std::vector& stops); +static constexpr uint32_t kMaxUniformGradientStops = 256u; + +/** + * @brief Populate 2 arrays with the colors and stop data for a gradient + * + * The color data is simply converted to a vec4 format, but the stop data + * is both turned into pairs of {t, inverse_delta} information and also + * stops are themselves paired up into a vec4 format for efficient packing + * in the uniform data. + * + * @param colors colors from gradient + * @param stops stops from gradient + * @param frag_info_colors colors for fragment shader in vec4 format + * @param frag_info_stop_pairs pairs of stop data for shader in vec4 format + * @return count of colors stored + */ +int PopulateUniformGradientColors( + const std::vector& colors, + const std::vector& stops, + Vector4 frag_info_colors[kMaxUniformGradientStops], + Vector4 frag_info_stop_pairs[kMaxUniformGradientStops / 2]); + } // namespace impeller #endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_GRADIENT_GENERATOR_H_ diff --git a/impeller/entity/contents/host_buffer_unittests.cc b/impeller/entity/contents/host_buffer_unittests.cc index 77f425bf7ef81..ffac9c806adaa 100644 --- a/impeller/entity/contents/host_buffer_unittests.cc +++ b/impeller/entity/contents/host_buffer_unittests.cc @@ -4,30 +4,48 @@ #include #include + #include "flutter/testing/testing.h" +#include "gmock/gmock.h" #include "impeller/base/validation.h" #include "impeller/core/allocator.h" #include "impeller/core/host_buffer.h" +#include "impeller/core/idle_waiter.h" #include "impeller/entity/entity_playground.h" namespace impeller { namespace testing { +class MockIdleWaiter : public IdleWaiter { + public: + MOCK_METHOD(void, WaitIdle, (), (const, override)); +}; + using HostBufferTest = EntityPlayground; INSTANTIATE_PLAYGROUND_SUITE(HostBufferTest); +TEST_P(HostBufferTest, IdleWaiter) { + auto mock_idle_waiter = std::make_shared(); + { + auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + mock_idle_waiter); + EXPECT_CALL(*mock_idle_waiter, WaitIdle()); + } +} + TEST_P(HostBufferTest, CanEmplace) { struct Length2 { uint8_t pad[2]; }; static_assert(sizeof(Length2) == 2u); - auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); for (size_t i = 0; i < 12500; i++) { auto view = buffer->Emplace(Length2{}); ASSERT_TRUE(view); - ASSERT_EQ(view.range, Range(i * sizeof(Length2), 2u)); + ASSERT_EQ(view.GetRange(), Range(i * sizeof(Length2), 2u)); } } @@ -42,37 +60,39 @@ TEST_P(HostBufferTest, CanEmplaceWithAlignment) { static_assert(alignof(Align16) == 16); static_assert(sizeof(Align16) == 16); - auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); ASSERT_TRUE(buffer); { auto view = buffer->Emplace(Length2{}); ASSERT_TRUE(view); - ASSERT_EQ(view.range, Range(0u, 2u)); + ASSERT_EQ(view.GetRange(), Range(0u, 2u)); } { auto view = buffer->Emplace(Align16{}); ASSERT_TRUE(view); - ASSERT_EQ(view.range.offset, 16u); - ASSERT_EQ(view.range.length, 16u); + ASSERT_EQ(view.GetRange().offset, 16u); + ASSERT_EQ(view.GetRange().length, 16u); } { auto view = buffer->Emplace(Length2{}); ASSERT_TRUE(view); - ASSERT_EQ(view.range, Range(32u, 2u)); + ASSERT_EQ(view.GetRange(), Range(32u, 2u)); } { auto view = buffer->Emplace(Align16{}); ASSERT_TRUE(view); - ASSERT_EQ(view.range.offset, 48u); - ASSERT_EQ(view.range.length, 16u); + ASSERT_EQ(view.GetRange().offset, 48u); + ASSERT_EQ(view.GetRange().length, 16u); } } TEST_P(HostBufferTest, HostBufferInitialState) { - auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); EXPECT_EQ(buffer->GetStateForTest().current_buffer, 0u); EXPECT_EQ(buffer->GetStateForTest().current_frame, 0u); @@ -80,7 +100,8 @@ TEST_P(HostBufferTest, HostBufferInitialState) { } TEST_P(HostBufferTest, ResetIncrementsFrameCounter) { - auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); EXPECT_EQ(buffer->GetStateForTest().current_frame, 0u); @@ -99,7 +120,8 @@ TEST_P(HostBufferTest, ResetIncrementsFrameCounter) { TEST_P(HostBufferTest, EmplacingLargerThanBlockSizeCreatesOneOffBufferCallback) { - auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); // Emplace an amount larger than the block size, to verify that the host // buffer does not create a buffer. @@ -111,7 +133,8 @@ TEST_P(HostBufferTest, } TEST_P(HostBufferTest, EmplacingLargerThanBlockSizeCreatesOneOffBuffer) { - auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); // Emplace an amount larger than the block size, to verify that the host // buffer does not create a buffer. @@ -123,7 +146,8 @@ TEST_P(HostBufferTest, EmplacingLargerThanBlockSizeCreatesOneOffBuffer) { } TEST_P(HostBufferTest, UnusedBuffersAreDiscardedWhenResetting) { - auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); // Emplace two large allocations to force the allocation of a second buffer. auto buffer_view_a = buffer->Emplace(1020000, 0, [](uint8_t* data) {}); @@ -154,13 +178,14 @@ TEST_P(HostBufferTest, UnusedBuffersAreDiscardedWhenResetting) { } TEST_P(HostBufferTest, EmplaceWithProcIsAligned) { - auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); BufferView view = buffer->Emplace(std::array()); - EXPECT_EQ(view.range, Range(0, 21)); + EXPECT_EQ(view.GetRange(), Range(0, 21)); view = buffer->Emplace(64, 16, [](uint8_t*) {}); - EXPECT_EQ(view.range, Range(32, 64)); + EXPECT_EQ(view.GetRange(), Range(32, 64)); } static constexpr const size_t kMagicFailingAllocation = 1024000 * 2; @@ -197,13 +222,13 @@ TEST_P(HostBufferTest, EmplaceWithFailingAllocationDoesntCrash) { ScopedValidationDisable disable; std::shared_ptr allocator = std::make_shared(GetContext()->GetResourceAllocator()); - auto buffer = HostBuffer::Create(allocator); + auto buffer = HostBuffer::Create(allocator, GetContext()->GetIdleWaiter()); auto view = buffer->Emplace(nullptr, kMagicFailingAllocation, 0); - EXPECT_EQ(view.buffer, nullptr); - EXPECT_EQ(view.range.offset, 0u); - EXPECT_EQ(view.range.length, 0u); + EXPECT_EQ(view.GetBuffer(), nullptr); + EXPECT_EQ(view.GetRange().offset, 0u); + EXPECT_EQ(view.GetRange().length, 0u); } } // namespace testing diff --git a/impeller/entity/contents/linear_gradient_contents.cc b/impeller/entity/contents/linear_gradient_contents.cc index 73d533f2d7dbf..7eef8b78fbbbb 100644 --- a/impeller/entity/contents/linear_gradient_contents.cc +++ b/impeller/entity/contents/linear_gradient_contents.cc @@ -186,6 +186,14 @@ bool LinearGradientContents::FastLinearGradient(const ContentContext& renderer, /*force_stencil=*/force_stencil, geom_callback); } +#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) +#define UNIFORM_FRAG_INFO(t) \ + t##GradientUniformFillPipeline::FragmentShader::FragInfo +#define UNIFORM_COLOR_SIZE ARRAY_LEN(UNIFORM_FRAG_INFO(Linear)::colors) +#define UNIFORM_STOP_SIZE ARRAY_LEN(UNIFORM_FRAG_INFO(Linear)::stop_pairs) +static_assert(UNIFORM_COLOR_SIZE == kMaxUniformGradientStops); +static_assert(UNIFORM_STOP_SIZE == kMaxUniformGradientStops / 2); + bool LinearGradientContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { @@ -198,6 +206,10 @@ bool LinearGradientContents::Render(const ContentContext& renderer, if (renderer.GetDeviceCapabilities().SupportsSSBO()) { return RenderSSBO(renderer, entity, pass); } + if (colors_.size() <= kMaxUniformGradientStops && + stops_.size() <= kMaxUniformGradientStops) { + return RenderUniform(renderer, entity, pass); + } return RenderTexture(renderer, entity, pass); } @@ -310,6 +322,44 @@ bool LinearGradientContents::RenderSSBO(const ContentContext& renderer, }); } +bool LinearGradientContents::RenderUniform(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + using VS = LinearGradientUniformFillPipeline::VertexShader; + using FS = LinearGradientUniformFillPipeline::FragmentShader; + + VS::FrameInfo frame_info; + frame_info.matrix = GetInverseEffectTransform(); + + PipelineBuilderCallback pipeline_callback = + [&renderer](ContentContextOptions options) { + return renderer.GetLinearGradientUniformFillPipeline(options); + }; + return ColorSourceContents::DrawGeometry( + renderer, entity, pass, pipeline_callback, frame_info, + [this, &renderer, &entity](RenderPass& pass) { + FS::FragInfo frag_info; + frag_info.start_point = start_point_; + frag_info.start_to_end = end_point_ - start_point_; + frag_info.alpha = + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); + frag_info.tile_mode = static_cast(tile_mode_); + frag_info.colors_length = PopulateUniformGradientColors( + colors_, stops_, frag_info.colors, frag_info.stop_pairs); + frag_info.inverse_dot_start_to_end = + CalculateInverseDotStartToEnd(start_point_, end_point_); + frag_info.decal_border_color = decal_border_color_; + + pass.SetCommandLabel("LinearGradientUniformFill"); + + FS::BindFragInfo( + pass, renderer.GetTransientsBuffer().EmplaceUniform(frag_info)); + + return true; + }); +} + bool LinearGradientContents::ApplyColorFilter( const ColorFilterProc& color_filter_proc) { for (Color& color : colors_) { diff --git a/impeller/entity/contents/linear_gradient_contents.h b/impeller/entity/contents/linear_gradient_contents.h index ae2e12f2bfdaa..1aaccb1e43921 100644 --- a/impeller/entity/contents/linear_gradient_contents.h +++ b/impeller/entity/contents/linear_gradient_contents.h @@ -53,6 +53,10 @@ class LinearGradientContents final : public ColorSourceContents { const Entity& entity, RenderPass& pass) const; + bool RenderUniform(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const; + bool FastLinearGradient(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const; diff --git a/impeller/entity/contents/radial_gradient_contents.cc b/impeller/entity/contents/radial_gradient_contents.cc index 31fc52eee3cd3..8fad640b1b607 100644 --- a/impeller/entity/contents/radial_gradient_contents.cc +++ b/impeller/entity/contents/radial_gradient_contents.cc @@ -55,12 +55,24 @@ bool RadialGradientContents::IsOpaque(const Matrix& transform) const { return !AppliesAlphaForStrokeCoverage(transform); } +#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) +#define UNIFORM_FRAG_INFO(t) \ + t##GradientUniformFillPipeline::FragmentShader::FragInfo +#define UNIFORM_COLOR_SIZE ARRAY_LEN(UNIFORM_FRAG_INFO(Radial)::colors) +#define UNIFORM_STOP_SIZE ARRAY_LEN(UNIFORM_FRAG_INFO(Radial)::stop_pairs) +static_assert(UNIFORM_COLOR_SIZE == kMaxUniformGradientStops); +static_assert(UNIFORM_STOP_SIZE == kMaxUniformGradientStops / 2); + bool RadialGradientContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { if (renderer.GetDeviceCapabilities().SupportsSSBO()) { return RenderSSBO(renderer, entity, pass); } + if (colors_.size() <= kMaxUniformGradientStops && + stops_.size() <= kMaxUniformGradientStops) { + return RenderUniform(renderer, entity, pass); + } return RenderTexture(renderer, entity, pass); } @@ -106,6 +118,42 @@ bool RadialGradientContents::RenderSSBO(const ContentContext& renderer, }); } +bool RadialGradientContents::RenderUniform(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + using VS = RadialGradientUniformFillPipeline::VertexShader; + using FS = RadialGradientUniformFillPipeline::FragmentShader; + + VS::FrameInfo frame_info; + frame_info.matrix = GetInverseEffectTransform(); + + PipelineBuilderCallback pipeline_callback = + [&renderer](ContentContextOptions options) { + return renderer.GetRadialGradientUniformFillPipeline(options); + }; + return ColorSourceContents::DrawGeometry( + renderer, entity, pass, pipeline_callback, frame_info, + [this, &renderer, &entity](RenderPass& pass) { + FS::FragInfo frag_info; + frag_info.center = center_; + frag_info.radius = radius_; + frag_info.tile_mode = static_cast(tile_mode_); + frag_info.alpha = + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); + frag_info.colors_length = PopulateUniformGradientColors( + colors_, stops_, frag_info.colors, frag_info.stop_pairs); + frag_info.decal_border_color = decal_border_color_; + + pass.SetCommandLabel("RadialGradientUniformFill"); + + FS::BindFragInfo( + pass, renderer.GetTransientsBuffer().EmplaceUniform(frag_info)); + + return true; + }); +} + bool RadialGradientContents::RenderTexture(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { diff --git a/impeller/entity/contents/radial_gradient_contents.h b/impeller/entity/contents/radial_gradient_contents.h index 21872d801fe07..f1cff5813c3f8 100644 --- a/impeller/entity/contents/radial_gradient_contents.h +++ b/impeller/entity/contents/radial_gradient_contents.h @@ -52,6 +52,11 @@ class RadialGradientContents final : public ColorSourceContents { bool RenderSSBO(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const; + + bool RenderUniform(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const; + Point center_; Scalar radius_; std::vector colors_; diff --git a/impeller/entity/contents/runtime_effect_contents.cc b/impeller/entity/contents/runtime_effect_contents.cc index d690b66924f78..d568567277044 100644 --- a/impeller/entity/contents/runtime_effect_contents.cc +++ b/impeller/entity/contents/runtime_effect_contents.cc @@ -24,6 +24,38 @@ namespace impeller { +namespace { +constexpr char kPaddingType = 0; +constexpr char kFloatType = 1; +} // namespace + +// static +BufferView RuntimeEffectContents::EmplaceVulkanUniform( + const std::shared_ptr>& input_data, + HostBuffer& host_buffer, + const RuntimeUniformDescription& uniform) { + // TODO(jonahwilliams): rewrite this to emplace directly into + // HostBuffer. + std::vector uniform_buffer; + uniform_buffer.reserve(uniform.struct_layout.size()); + size_t uniform_byte_index = 0u; + for (char byte_type : uniform.struct_layout) { + if (byte_type == kPaddingType) { + uniform_buffer.push_back(0.f); + } else { + FML_DCHECK(byte_type == kFloatType); + uniform_buffer.push_back(reinterpret_cast( + input_data->data())[uniform_byte_index++]); + } + } + size_t alignment = std::max(sizeof(float) * uniform_buffer.size(), + DefaultUniformAlignment()); + + return host_buffer.Emplace( + reinterpret_cast(uniform_buffer.data()), + sizeof(float) * uniform_buffer.size(), alignment); +} + void RuntimeEffectContents::SetRuntimeStage( std::shared_ptr runtime_stage) { runtime_stage_ = std::move(runtime_stage); @@ -50,9 +82,9 @@ static ShaderType GetShaderType(RuntimeUniformType type) { } } -static std::shared_ptr MakeShaderMetadata( +static std::unique_ptr MakeShaderMetadata( const RuntimeUniformDescription& uniform) { - auto metadata = std::make_shared(); + std::unique_ptr metadata = std::make_unique(); metadata->name = uniform.name; metadata->members.emplace_back(ShaderStructMemberMetadata{ .type = GetShaderType(uniform.type), @@ -206,8 +238,7 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, size_t buffer_offset = 0; for (const auto& uniform : runtime_stage_->GetUniforms()) { - std::shared_ptr metadata = MakeShaderMetadata(uniform); - + std::unique_ptr metadata = MakeShaderMetadata(uniform); switch (uniform.type) { case kSampledImage: { // Sampler uniforms are ordered in the IPLR according to their @@ -238,9 +269,9 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, ShaderUniformSlot uniform_slot; uniform_slot.name = uniform.name.c_str(); uniform_slot.ext_res_0 = uniform.location; - pass.BindResource(ShaderStage::kFragment, - DescriptorType::kUniformBuffer, uniform_slot, - metadata, std::move(buffer_view)); + pass.BindDynamicResource(ShaderStage::kFragment, + DescriptorType::kUniformBuffer, uniform_slot, + std::move(metadata), std::move(buffer_view)); buffer_index++; buffer_offset += uniform.GetSize(); break; @@ -249,40 +280,21 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, FML_DCHECK(renderer.GetContext()->GetBackendType() == Context::BackendType::kVulkan); ShaderUniformSlot uniform_slot; - uniform_slot.name = uniform.name.c_str(); uniform_slot.binding = uniform.location; + uniform_slot.name = uniform.name.c_str(); - // TODO(jonahwilliams): rewrite this to emplace directly into - // HostBuffer. - std::vector uniform_buffer; - uniform_buffer.reserve(uniform.struct_layout.size()); - size_t uniform_byte_index = 0u; - for (const auto& byte_type : uniform.struct_layout) { - if (byte_type == 0) { - uniform_buffer.push_back(0.f); - } else if (byte_type == 1) { - uniform_buffer.push_back(reinterpret_cast( - uniform_data_->data())[uniform_byte_index++]); - } else { - FML_UNREACHABLE(); - } - } - size_t alignment = std::max(sizeof(float) * uniform_buffer.size(), - DefaultUniformAlignment()); - - BufferView buffer_view = renderer.GetTransientsBuffer().Emplace( - reinterpret_cast(uniform_buffer.data()), - sizeof(float) * uniform_buffer.size(), alignment); - pass.BindResource(ShaderStage::kFragment, - DescriptorType::kUniformBuffer, uniform_slot, - ShaderMetadata{}, std::move(buffer_view)); + pass.BindResource( + ShaderStage::kFragment, DescriptorType::kUniformBuffer, + uniform_slot, nullptr, + EmplaceVulkanUniform(uniform_data_, + renderer.GetTransientsBuffer(), uniform)); } } } size_t sampler_index = 0; for (const auto& uniform : runtime_stage_->GetUniforms()) { - std::shared_ptr metadata = MakeShaderMetadata(uniform); + std::unique_ptr metadata = MakeShaderMetadata(uniform); switch (uniform.type) { case kSampledImage: { @@ -297,9 +309,9 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, image_slot.name = uniform.name.c_str(); image_slot.binding = uniform.binding; image_slot.texture_index = uniform.location - minimum_sampler_index; - pass.BindResource(ShaderStage::kFragment, - DescriptorType::kSampledImage, image_slot, - *metadata, input.texture, sampler); + pass.BindDynamicResource(ShaderStage::kFragment, + DescriptorType::kSampledImage, image_slot, + std::move(metadata), input.texture, sampler); sampler_index++; break; diff --git a/impeller/entity/contents/runtime_effect_contents.h b/impeller/entity/contents/runtime_effect_contents.h index 151bb315f9ef2..dd62059f221fc 100644 --- a/impeller/entity/contents/runtime_effect_contents.h +++ b/impeller/entity/contents/runtime_effect_contents.h @@ -8,6 +8,7 @@ #include #include +#include "impeller/core/host_buffer.h" #include "impeller/core/sampler_descriptor.h" #include "impeller/entity/contents/color_source_contents.h" #include "impeller/runtime_stage/runtime_stage.h" @@ -35,6 +36,12 @@ class RuntimeEffectContents final : public ColorSourceContents { /// Load the runtime effect and ensure a default PSO is initialized. bool BootstrapShader(const ContentContext& renderer) const; + // Visible for testing + static BufferView EmplaceVulkanUniform( + const std::shared_ptr>& input_data, + HostBuffer& host_buffer, + const RuntimeUniformDescription& uniform); + private: bool RegisterShader(const ContentContext& renderer) const; diff --git a/impeller/entity/contents/solid_color_contents.cc b/impeller/entity/contents/solid_color_contents.cc index 6fdd23fa69a88..9284f01cd604d 100644 --- a/impeller/entity/contents/solid_color_contents.cc +++ b/impeller/entity/contents/solid_color_contents.cc @@ -7,7 +7,6 @@ #include "impeller/entity/contents/content_context.h" #include "impeller/entity/entity.h" #include "impeller/entity/geometry/geometry.h" -#include "impeller/geometry/path.h" #include "impeller/renderer/render_pass.h" namespace impeller { diff --git a/impeller/entity/contents/sweep_gradient_contents.cc b/impeller/entity/contents/sweep_gradient_contents.cc index a26d5ac43335a..1dfa1292203a7 100644 --- a/impeller/entity/contents/sweep_gradient_contents.cc +++ b/impeller/entity/contents/sweep_gradient_contents.cc @@ -61,12 +61,24 @@ bool SweepGradientContents::IsOpaque(const Matrix& transform) const { return !AppliesAlphaForStrokeCoverage(transform); } +#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) +#define UNIFORM_FRAG_INFO(t) \ + t##GradientUniformFillPipeline::FragmentShader::FragInfo +#define UNIFORM_COLOR_SIZE ARRAY_LEN(UNIFORM_FRAG_INFO(Sweep)::colors) +#define UNIFORM_STOP_SIZE ARRAY_LEN(UNIFORM_FRAG_INFO(Sweep)::stop_pairs) +static_assert(UNIFORM_COLOR_SIZE == kMaxUniformGradientStops); +static_assert(UNIFORM_STOP_SIZE == kMaxUniformGradientStops / 2); + bool SweepGradientContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { if (renderer.GetDeviceCapabilities().SupportsSSBO()) { return RenderSSBO(renderer, entity, pass); } + if (colors_.size() <= kMaxUniformGradientStops && + stops_.size() <= kMaxUniformGradientStops) { + return RenderUniform(renderer, entity, pass); + } return RenderTexture(renderer, entity, pass); } @@ -116,6 +128,46 @@ bool SweepGradientContents::RenderSSBO(const ContentContext& renderer, }); } +bool SweepGradientContents::RenderUniform(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + using VS = SweepGradientUniformFillPipeline::VertexShader; + using FS = SweepGradientUniformFillPipeline::FragmentShader; + + VS::FrameInfo frame_info; + frame_info.matrix = GetInverseEffectTransform(); + VS::BindFrameInfo(pass, + renderer.GetTransientsBuffer().EmplaceUniform(frame_info)); + + PipelineBuilderCallback pipeline_callback = + [&renderer](ContentContextOptions options) { + return renderer.GetSweepGradientUniformFillPipeline(options); + }; + return ColorSourceContents::DrawGeometry( + renderer, entity, pass, pipeline_callback, frame_info, + [this, &renderer, &entity](RenderPass& pass) { + FS::FragInfo frag_info; + frag_info.center = center_; + frag_info.bias = bias_; + frag_info.scale = scale_; + frag_info.tile_mode = static_cast(tile_mode_); + frag_info.alpha = + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); + frag_info.colors_length = PopulateUniformGradientColors( + colors_, stops_, frag_info.colors, frag_info.stop_pairs); + + frag_info.decal_border_color = decal_border_color_; + + pass.SetCommandLabel("SweepGradientUniformFill"); + + FS::BindFragInfo( + pass, renderer.GetTransientsBuffer().EmplaceUniform(frag_info)); + + return true; + }); +} + bool SweepGradientContents::RenderTexture(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { diff --git a/impeller/entity/contents/sweep_gradient_contents.h b/impeller/entity/contents/sweep_gradient_contents.h index 81e643d660516..d145987e19f13 100644 --- a/impeller/entity/contents/sweep_gradient_contents.h +++ b/impeller/entity/contents/sweep_gradient_contents.h @@ -54,6 +54,10 @@ class SweepGradientContents final : public ColorSourceContents { const Entity& entity, RenderPass& pass) const; + bool RenderUniform(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const; + Point center_; Scalar bias_; Scalar scale_; diff --git a/impeller/entity/contents/test/contents_test_helpers.h b/impeller/entity/contents/test/contents_test_helpers.h deleted file mode 100644 index e5f33b19bdeef..0000000000000 --- a/impeller/entity/contents/test/contents_test_helpers.h +++ /dev/null @@ -1,49 +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. - -#ifndef FLUTTER_IMPELLER_ENTITY_CONTENTS_TEST_CONTENTS_TEST_HELPERS_H_ -#define FLUTTER_IMPELLER_ENTITY_CONTENTS_TEST_CONTENTS_TEST_HELPERS_H_ - -#include "impeller/renderer/command.h" - -namespace impeller { - -/// @brief Retrieve the [VertInfo] struct data from the provided [command]. -template -typename T::VertInfo* GetVertInfo(const Command& command) { - auto resource = std::find_if(command.vertex_bindings.buffers.begin(), - command.vertex_bindings.buffers.end(), - [](const BufferAndUniformSlot& data) { - return data.slot.ext_res_0 == 0u; - }); - if (resource == command.vertex_bindings.buffers.end()) { - return nullptr; - } - - auto data = (resource->view.resource.buffer->OnGetContents() + - resource->view.resource.range.offset); - return reinterpret_cast(data); -} - -/// @brief Retrieve the [FragInfo] struct data from the provided [command]. -template -typename T::FragInfo* GetFragInfo(const Command& command) { - auto resource = std::find_if(command.fragment_bindings.buffers.begin(), - command.fragment_bindings.buffers.end(), - [](const BufferAndUniformSlot& data) { - return data.slot.ext_res_0 == 0u || - data.slot.binding == 64; - }); - if (resource == command.fragment_bindings.buffers.end()) { - return nullptr; - } - - auto data = (resource->view.resource.buffer->OnGetContents() + - resource->view.resource.range.offset); - return reinterpret_cast(data); -} - -} // namespace impeller - -#endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_TEST_CONTENTS_TEST_HELPERS_H_ diff --git a/impeller/entity/contents/test/recording_render_pass.cc b/impeller/entity/contents/test/recording_render_pass.cc index da36a5fa1ae27..39a8f69b50c58 100644 --- a/impeller/entity/contents/test/recording_render_pass.cc +++ b/impeller/entity/contents/test/recording_render_pass.cc @@ -6,7 +6,7 @@ #include -namespace impeller { +namespace impeller::testing { RecordingRenderPass::RecordingRenderPass( std::shared_ptr delegate, @@ -74,7 +74,6 @@ void RecordingRenderPass::SetInstanceCount(size_t count) { // |RenderPass| bool RecordingRenderPass::SetVertexBuffer(VertexBuffer buffer) { - pending_.BindVertices(buffer); if (delegate_) { return delegate_->SetVertexBuffer(buffer); } @@ -108,9 +107,8 @@ bool RecordingRenderPass::OnEncodeCommands(const Context& context) const { bool RecordingRenderPass::BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, BufferView view) { - pending_.BindResource(stage, type, slot, metadata, view); if (delegate_) { return delegate_->BindResource(stage, type, slot, metadata, view); } @@ -118,28 +116,41 @@ bool RecordingRenderPass::BindResource(ShaderStage stage, } // |RenderPass| -bool RecordingRenderPass::BindResource( +bool RecordingRenderPass::BindDynamicResource( ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const std::shared_ptr& metadata, + std::unique_ptr metadata, BufferView view) { - pending_.BindResource(stage, type, slot, metadata, view); if (delegate_) { - return delegate_->BindResource(stage, type, slot, metadata, view); + return delegate_->BindDynamicResource(stage, type, slot, + std::move(metadata), view); } return true; } // |RenderPass| +bool RecordingRenderPass::BindDynamicResource( + ShaderStage stage, + DescriptorType type, + const SampledImageSlot& slot, + std::unique_ptr metadata, + std::shared_ptr texture, + const std::unique_ptr& sampler) { + if (delegate_) { + return delegate_->BindDynamicResource( + stage, type, slot, std::move(metadata), texture, sampler); + } + return true; +} + bool RecordingRenderPass::BindResource( ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) { - pending_.BindResource(stage, type, slot, metadata, texture, sampler); if (delegate_) { return delegate_->BindResource(stage, type, slot, metadata, texture, sampler); @@ -147,4 +158,4 @@ bool RecordingRenderPass::BindResource( return true; } -} // namespace impeller +} // namespace impeller::testing diff --git a/impeller/entity/contents/test/recording_render_pass.h b/impeller/entity/contents/test/recording_render_pass.h index ffbef6fd7d521..3fb9b34d344e1 100644 --- a/impeller/entity/contents/test/recording_render_pass.h +++ b/impeller/entity/contents/test/recording_render_pass.h @@ -7,7 +7,7 @@ #include "impeller/renderer/render_pass.h" -namespace impeller { +namespace impeller::testing { class RecordingRenderPass : public RenderPass { public: @@ -46,28 +46,35 @@ class RecordingRenderPass : public RenderPass { // |RenderPass| fml::Status Draw() override; - // |RenderPass| - bool BindResource(ShaderStage stage, - DescriptorType type, - const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, - BufferView view) override; - - // |RenderPass| bool BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const std::shared_ptr& metadata, + const ShaderMetadata* metadata, BufferView view) override; - // |RenderPass| bool BindResource(ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) override; + // |RenderPass| + bool BindDynamicResource(ShaderStage stage, + DescriptorType type, + const ShaderUniformSlot& slot, + std::unique_ptr metadata, + BufferView view) override; + + // |RenderPass| + bool BindDynamicResource( + ShaderStage stage, + DescriptorType type, + const SampledImageSlot& slot, + std::unique_ptr metadata, + std::shared_ptr texture, + const std::unique_ptr& sampler) override; + // |RenderPass| void OnSetLabel(std::string_view label) override; @@ -82,6 +89,6 @@ class RecordingRenderPass : public RenderPass { std::vector commands_; }; -} // namespace impeller +} // namespace impeller::testing #endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_TEST_RECORDING_RENDER_PASS_H_ diff --git a/impeller/entity/contents/text_contents.cc b/impeller/entity/contents/text_contents.cc index 12c1e6ac488f7..4d4a130480157 100644 --- a/impeller/entity/contents/text_contents.cc +++ b/impeller/entity/contents/text_contents.cc @@ -17,7 +17,6 @@ #include "impeller/geometry/point.h" #include "impeller/renderer/render_pass.h" #include "impeller/typographer/glyph_atlas.h" -#include "impeller/typographer/lazy_glyph_atlas.h" namespace impeller { @@ -90,6 +89,10 @@ bool TextContents::Render(const ContentContext& renderer, VALIDATION_LOG << "Cannot render glyphs without prepared atlas."; return false; } + if (!frame_->IsFrameComplete()) { + VALIDATION_LOG << "Failed to find font glyph bounds."; + return false; + } // Information shared by all glyph draw calls. pass.SetCommandLabel("TextFrame"); @@ -169,16 +172,11 @@ bool TextContents::Render(const ContentContext& renderer, VS::PerVertexData* vtx_contents = reinterpret_cast(contents); size_t i = 0u; + size_t bounds_offset = 0u; for (const TextRun& run : frame_->GetRuns()) { const Font& font = run.GetFont(); - Scalar rounded_scale = TextFrame::RoundScaledFontSize( - scale_, font.GetMetrics().point_size); - const FontGlyphAtlas* font_atlas = - atlas->GetFontGlyphAtlas(font, rounded_scale); - if (!font_atlas) { - VALIDATION_LOG << "Could not find font in the atlas."; - continue; - } + Scalar rounded_scale = TextFrame::RoundScaledFontSize(scale_); + FontGlyphAtlas* font_atlas = nullptr; // Adjust glyph position based on the subpixel rounding // used by the font. @@ -201,22 +199,44 @@ bool TextContents::Render(const ContentContext& renderer, Point screen_offset = (entity_transform * Point(0, 0)); for (const TextRun::GlyphPosition& glyph_position : run.GetGlyphPositions()) { - // Note: uses unrounded scale for more accurate subpixel position. - Point subpixel = TextFrame::ComputeSubpixelPosition( - glyph_position, font.GetAxisAlignment(), offset_, scale_); - std::optional> maybe_atlas_glyph_bounds = - font_atlas->FindGlyphBounds(SubpixelGlyph{ - glyph_position.glyph, subpixel, - (properties_.stroke || frame_->HasColor()) - ? std::optional(properties_) - : std::nullopt}); - if (!maybe_atlas_glyph_bounds.has_value()) { - VALIDATION_LOG << "Could not find glyph position in the atlas."; - continue; + const FrameBounds& frame_bounds = + frame_->GetFrameBounds(bounds_offset); + bounds_offset++; + auto atlas_glyph_bounds = frame_bounds.atlas_bounds; + auto glyph_bounds = frame_bounds.glyph_bounds; + + // If frame_bounds.is_placeholder is true, this is the first frame + // the glyph has been rendered and so its atlas position was not + // known when the glyph was recorded. Perform a slow lookup into the + // glyph atlas hash table. + if (frame_bounds.is_placeholder) { + if (!font_atlas) { + font_atlas = atlas->GetOrCreateFontGlyphAtlas( + ScaledFont{font, rounded_scale}); + } + + if (!font_atlas) { + VALIDATION_LOG << "Could not find font in the atlas."; + continue; + } + Point subpixel = TextFrame::ComputeSubpixelPosition( + glyph_position, font.GetAxisAlignment(), offset_, + rounded_scale); + + std::optional maybe_atlas_glyph_bounds = + font_atlas->FindGlyphBounds(SubpixelGlyph{ + glyph_position.glyph, // + subpixel, // + GetGlyphProperties() // + }); + if (!maybe_atlas_glyph_bounds.has_value()) { + VALIDATION_LOG << "Could not find glyph position in the atlas."; + continue; + } + atlas_glyph_bounds = + maybe_atlas_glyph_bounds.value().atlas_bounds; } - const Rect& atlas_glyph_bounds = - maybe_atlas_glyph_bounds.value().first; - Rect glyph_bounds = maybe_atlas_glyph_bounds.value().second; + Rect scaled_bounds = glyph_bounds.Scale(1.0 / rounded_scale); // For each glyph, we compute two rectangles. One for the vertex // positions and one for the texture coordinates (UVs). The atlas @@ -263,4 +283,10 @@ bool TextContents::Render(const ContentContext& renderer, return pass.Draw().ok(); } +std::optional TextContents::GetGlyphProperties() const { + return (properties_.stroke || frame_->HasColor()) + ? std::optional(properties_) + : std::nullopt; +} + } // namespace impeller diff --git a/impeller/entity/contents/text_contents.h b/impeller/entity/contents/text_contents.h index f379f1836e371..091da3d74888f 100644 --- a/impeller/entity/contents/text_contents.h +++ b/impeller/entity/contents/text_contents.h @@ -62,6 +62,8 @@ class TextContents final : public Contents { RenderPass& pass) const override; private: + std::optional GetGlyphProperties() const; + std::shared_ptr frame_; Scalar scale_ = 1.0; Scalar inherited_opacity_ = 1.0; diff --git a/impeller/entity/entity.cc b/impeller/entity/entity.cc index 5f06a309a7e8f..92d6a10201ca1 100644 --- a/impeller/entity/entity.cc +++ b/impeller/entity/entity.cc @@ -5,12 +5,9 @@ #include "impeller/entity/entity.h" #include -#include #include -#include "impeller/base/validation.h" #include "impeller/entity/contents/content_context.h" -#include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/geometry/color.h" #include "impeller/geometry/vector.h" @@ -55,8 +52,8 @@ Matrix Entity::GetShaderTransform(const RenderPass& pass) const { Matrix Entity::GetShaderTransform(Scalar shader_clip_depth, const RenderPass& pass, const Matrix& transform) { - return Matrix::MakeTranslation({0, 0, shader_clip_depth}) * - Matrix::MakeScale({1, 1, Entity::kDepthEpsilon}) * + return Matrix::MakeTranslateScale({1, 1, Entity::kDepthEpsilon}, + {0, 0, shader_clip_depth}) * pass.GetOrthographicTransform() * transform; } @@ -72,14 +69,6 @@ std::optional Entity::GetCoverage() const { return contents_->GetCoverage(*this); } -Contents::ClipCoverage Entity::GetClipCoverage( - const std::optional& current_clip_coverage) const { - if (!contents_) { - return {}; - } - return contents_->GetClipCoverage(*this, current_clip_coverage); -} - void Entity::SetContents(std::shared_ptr contents) { contents_ = std::move(contents); } diff --git a/impeller/entity/entity.h b/impeller/entity/entity.h index 86d2e0b3296d9..dd3cc6ad4472b 100644 --- a/impeller/entity/entity.h +++ b/impeller/entity/entity.h @@ -92,9 +92,6 @@ class Entity { std::optional GetCoverage() const; - Contents::ClipCoverage GetClipCoverage( - const std::optional& current_clip_coverage) const; - void SetContents(std::shared_ptr contents); const std::shared_ptr& GetContents() const; diff --git a/impeller/entity/entity_pass_clip_stack.cc b/impeller/entity/entity_pass_clip_stack.cc index 1f3749ce3b296..3a00fd8595f6b 100644 --- a/impeller/entity/entity_pass_clip_stack.cc +++ b/impeller/entity/entity_pass_clip_stack.cc @@ -6,7 +6,6 @@ #include "flutter/fml/logging.h" #include "impeller/entity/contents/clip_contents.h" -#include "impeller/entity/entity.h" namespace impeller { @@ -52,128 +51,150 @@ EntityPassClipStack::GetClipCoverageLayers() const { return subpass_state_.back().clip_coverage; } -EntityPassClipStack::ClipStateResult EntityPassClipStack::ApplyClipState( - Contents::ClipCoverage global_clip_coverage, - Entity& entity, - size_t clip_height_floor, - Point global_pass_position) { +EntityPassClipStack::ClipStateResult EntityPassClipStack::RecordRestore( + Point global_pass_position, + size_t restore_height) { ClipStateResult result = {.should_render = false, .clip_did_change = false}; - auto& subpass_state = GetCurrentSubpassState(); - switch (global_clip_coverage.type) { - case Contents::ClipCoverage::Type::kNoChange: - break; - case Contents::ClipCoverage::Type::kAppend: { - auto maybe_coverage = CurrentClipCoverage(); - - // Compute the previous clip height. - size_t previous_clip_height = 0; - if (!subpass_state.clip_coverage.empty()) { - previous_clip_height = subpass_state.clip_coverage.back().clip_height; - } else { - // If there is no clip coverage, then the previous clip height is the - // clip height floor. - previous_clip_height = clip_height_floor; - } - if (!maybe_coverage.has_value()) { - // Running this append op won't impact the clip buffer because the - // whole screen is already being clipped, so skip it. - return result; - } - auto op = maybe_coverage.value(); + if (subpass_state.clip_coverage.back().clip_height <= restore_height) { + // Drop clip restores that will do nothing. + return result; + } - // If the new clip coverage is bigger than the existing coverage for - // intersect clips, we do not need to change the clip region. - if (!global_clip_coverage.is_difference_or_non_square && - global_clip_coverage.coverage.has_value() && - global_clip_coverage.coverage.value().Contains(op)) { - subpass_state.clip_coverage.push_back(ClipCoverageLayer{ - .coverage = op, .clip_height = previous_clip_height + 1}); + auto restoration_index = + restore_height - subpass_state.clip_coverage.front().clip_height; + FML_DCHECK(restoration_index < subpass_state.clip_coverage.size()); + + // We only need to restore the area that covers the coverage of the + // clip rect at target height + 1. + std::optional restore_coverage = + (restoration_index + 1 < subpass_state.clip_coverage.size()) + ? subpass_state.clip_coverage[restoration_index + 1].coverage + : std::nullopt; + if (restore_coverage.has_value()) { + // Make the coverage rectangle relative to the current pass. + restore_coverage = restore_coverage->Shift(-global_pass_position); + } - return result; - } + subpass_state.clip_coverage.resize(restoration_index + 1); + result.clip_did_change = true; - subpass_state.clip_coverage.push_back( - ClipCoverageLayer{.coverage = global_clip_coverage.coverage, - .clip_height = previous_clip_height + 1}); - result.clip_did_change = true; + if (subpass_state.clip_coverage.back().coverage.has_value()) { + FML_DCHECK(next_replay_index_ <= + subpass_state.rendered_clip_entities.size()); + if (!subpass_state.rendered_clip_entities.empty()) { + subpass_state.rendered_clip_entities.pop_back(); - FML_DCHECK(subpass_state.clip_coverage.back().clip_height == - subpass_state.clip_coverage.front().clip_height + - subpass_state.clip_coverage.size() - 1); + if (next_replay_index_ > subpass_state.rendered_clip_entities.size()) { + next_replay_index_ = subpass_state.rendered_clip_entities.size(); + } + } + } + return result; +} - } break; - case Contents::ClipCoverage::Type::kRestore: { - ClipRestoreContents* restore_contents = - reinterpret_cast(entity.GetContents().get()); - size_t restore_height = restore_contents->GetRestoreHeight(); +EntityPassClipStack::ClipStateResult EntityPassClipStack::RecordClip( + const ClipContents& clip_contents, + Matrix transform, + Point global_pass_position, + uint32_t clip_depth, + size_t clip_height_floor, + bool is_aa) { + ClipStateResult result = {.should_render = false, .clip_did_change = false}; - if (subpass_state.clip_coverage.back().clip_height <= restore_height) { - // Drop clip restores that will do nothing. - return result; - } + std::optional maybe_clip_coverage = CurrentClipCoverage(); + // Running this append op won't impact the clip buffer because the + // whole screen is already being clipped, so skip it. + if (!maybe_clip_coverage.has_value()) { + return result; + } + auto current_clip_coverage = maybe_clip_coverage.value(); + // Entity transforms are relative to the current pass position, so we need + // to check clip coverage in the same space. + current_clip_coverage = current_clip_coverage.Shift(-global_pass_position); + + ClipCoverage clip_coverage = + clip_contents.GetClipCoverage(current_clip_coverage); + if (clip_coverage.coverage.has_value()) { + clip_coverage.coverage = + clip_coverage.coverage->Shift(global_pass_position); + } - auto restoration_index = - restore_height - subpass_state.clip_coverage.front().clip_height; - FML_DCHECK(restoration_index < subpass_state.clip_coverage.size()); - - // We only need to restore the area that covers the coverage of the - // clip rect at target height + 1. - std::optional restore_coverage = - (restoration_index + 1 < subpass_state.clip_coverage.size()) - ? subpass_state.clip_coverage[restoration_index + 1].coverage - : std::nullopt; - if (restore_coverage.has_value()) { - // Make the coverage rectangle relative to the current pass. - restore_coverage = restore_coverage->Shift(-global_pass_position); - } - subpass_state.clip_coverage.resize(restoration_index + 1); - result.clip_did_change = true; + SubpassState& subpass_state = GetCurrentSubpassState(); - // Skip all clip restores when stencil-then-cover is enabled. - if (subpass_state.clip_coverage.back().coverage.has_value()) { - RecordEntity(entity, global_clip_coverage.type, Rect()); - } - return result; + // Compute the previous clip height. + size_t previous_clip_height = 0; + if (!subpass_state.clip_coverage.empty()) { + previous_clip_height = subpass_state.clip_coverage.back().clip_height; + } else { + // If there is no clip coverage, then the previous clip height is the + // clip height floor. + previous_clip_height = clip_height_floor; + } - } break; + // If the new clip coverage is bigger than the existing coverage for + // intersect clips, we do not need to change the clip region. + if (!clip_coverage.is_difference_or_non_square && + clip_coverage.coverage.has_value() && + clip_coverage.coverage.value().Contains(current_clip_coverage)) { + subpass_state.clip_coverage.push_back(ClipCoverageLayer{ + .coverage = current_clip_coverage, // + .clip_height = previous_clip_height + 1 // + }); + + return result; } - RecordEntity(entity, global_clip_coverage.type, - subpass_state.clip_coverage.back().coverage); + // If the clip is an axis aligned rect and either is_aa is false or + // the clip is very nearly integral, then the depth write can be + // skipped for intersect clips. Since we use 4x MSAA, anything within + // < ~0.125 of an integral value in either axis can be treated as + // approximately the same as an integral value. + bool should_render = true; + std::optional coverage_value = clip_coverage.coverage; + if (!clip_coverage.is_difference_or_non_square && + coverage_value.has_value()) { + const Rect& coverage = coverage_value.value(); + constexpr Scalar threshold = 0.124; + if (!is_aa || + (std::abs(std::round(coverage.GetLeft()) - coverage.GetLeft()) <= + threshold && + std::abs(std::round(coverage.GetTop()) - coverage.GetTop()) <= + threshold && + std::abs(std::round(coverage.GetRight()) - coverage.GetRight()) <= + threshold && + std::abs(std::round(coverage.GetBottom()) - coverage.GetBottom()) <= + threshold)) { + coverage_value = Rect::Round(clip_coverage.coverage.value()); + should_render = false; + } + } - result.should_render = true; - return result; -} + subpass_state.clip_coverage.push_back(ClipCoverageLayer{ + .coverage = coverage_value, // + .clip_height = previous_clip_height + 1 // -void EntityPassClipStack::RecordEntity(const Entity& entity, - Contents::ClipCoverage::Type type, - std::optional clip_coverage) { - auto& subpass_state = GetCurrentSubpassState(); - switch (type) { - case Contents::ClipCoverage::Type::kNoChange: - return; - case Contents::ClipCoverage::Type::kAppend: - FML_DCHECK(next_replay_index_ == - subpass_state.rendered_clip_entities.size()) - << "Not all clips have been replayed before appending new clip."; - subpass_state.rendered_clip_entities.push_back( - {.entity = entity.Clone(), .clip_coverage = clip_coverage}); - next_replay_index_++; - break; - case Contents::ClipCoverage::Type::kRestore: - FML_DCHECK(next_replay_index_ <= - subpass_state.rendered_clip_entities.size()); - if (!subpass_state.rendered_clip_entities.empty()) { - subpass_state.rendered_clip_entities.pop_back(); - - if (next_replay_index_ > subpass_state.rendered_clip_entities.size()) { - next_replay_index_ = subpass_state.rendered_clip_entities.size(); - } - } - break; - } + }); + result.clip_did_change = true; + result.should_render = should_render; + + FML_DCHECK(subpass_state.clip_coverage.back().clip_height == + subpass_state.clip_coverage.front().clip_height + + subpass_state.clip_coverage.size() - 1); + + FML_DCHECK(next_replay_index_ == subpass_state.rendered_clip_entities.size()) + << "Not all clips have been replayed before appending new clip."; + + subpass_state.rendered_clip_entities.push_back(ReplayResult{ + .clip_contents = clip_contents, // + .transform = transform, // + .clip_coverage = coverage_value, // + .clip_depth = clip_depth // + }); + next_replay_index_++; + + return result; } EntityPassClipStack::SubpassState& @@ -199,7 +220,7 @@ EntityPassClipStack::GetNextReplayResult(size_t current_clip_depth) { } ReplayResult* next_replay = &subpass_state_.back().rendered_clip_entities[next_replay_index_]; - if (next_replay->entity.GetClipDepth() < current_clip_depth) { + if (next_replay->clip_depth < current_clip_depth) { // The next replay clip doesn't affect the current entity, so don't replay // it yet. return nullptr; diff --git a/impeller/entity/entity_pass_clip_stack.h b/impeller/entity/entity_pass_clip_stack.h index 9ec26b1d11f13..b0ca57b410d5d 100644 --- a/impeller/entity/entity_pass_clip_stack.h +++ b/impeller/entity/entity_pass_clip_stack.h @@ -5,8 +5,7 @@ #ifndef FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_CLIP_STACK_H_ #define FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_CLIP_STACK_H_ -#include "impeller/entity/contents/contents.h" -#include "impeller/entity/entity.h" +#include "impeller/entity/contents/clip_contents.h" #include "impeller/geometry/rect.h" namespace impeller { @@ -24,8 +23,10 @@ struct ClipCoverageLayer { class EntityPassClipStack { public: struct ReplayResult { - Entity entity; + ClipContents clip_contents; + Matrix transform; std::optional clip_coverage; + uint32_t clip_depth = 0; }; struct ClipStateResult { @@ -50,17 +51,19 @@ class EntityPassClipStack { bool HasCoverage() const; - /// @brief Applies the current clip state to an Entity. If the given Entity - /// is a clip operation, then the clip state is updated accordingly. - ClipStateResult ApplyClipState(Contents::ClipCoverage global_clip_coverage, - Entity& entity, - size_t clip_height_floor, - Point global_pass_position); + ClipStateResult RecordClip(const ClipContents& clip_contents, + Matrix transform, + Point global_pass_position, + uint32_t clip_depth, + size_t clip_height_floor, + bool is_aa); - // Visible for testing. - void RecordEntity(const Entity& entity, - Contents::ClipCoverage::Type type, - std::optional clip_coverage); + ReplayResult& GetLastReplayResult() { + return GetCurrentSubpassState().rendered_clip_entities.back(); + } + + ClipStateResult RecordRestore(Point global_pass_position, + size_t restore_height); const std::vector& GetReplayEntities() const; diff --git a/impeller/entity/entity_pass_target.cc b/impeller/entity/entity_pass_target.cc index e9284de522675..db9c917b27add 100644 --- a/impeller/entity/entity_pass_target.cc +++ b/impeller/entity/entity_pass_target.cc @@ -19,7 +19,7 @@ EntityPassTarget::EntityPassTarget(const RenderTarget& render_target, std::shared_ptr EntityPassTarget::Flip( const ContentContext& renderer) { - auto color0 = target_.GetColorAttachments().find(0)->second; + ColorAttachment color0 = target_.GetColorAttachment(0); if (!color0.resolve_texture) { VALIDATION_LOG << "EntityPassTarget Flip should never be called for a " "non-MSAA target."; diff --git a/impeller/entity/entity_pass_target_unittests.cc b/impeller/entity/entity_pass_target_unittests.cc index cd22acbffdb5a..4a72138a70e19 100644 --- a/impeller/entity/entity_pass_target_unittests.cc +++ b/impeller/entity/entity_pass_target_unittests.cc @@ -31,20 +31,14 @@ TEST_P(EntityPassTargetTest, SwapWithMSAATexture) { auto entity_pass_target = EntityPassTarget(render_target, false, false); - auto color0 = entity_pass_target.GetRenderTarget() - .GetColorAttachments() - .find(0u) - ->second; + auto color0 = entity_pass_target.GetRenderTarget().GetColorAttachment(0); auto msaa_tex = color0.texture; auto resolve_tex = color0.resolve_texture; FML_DCHECK(content_context); entity_pass_target.Flip(*content_context); - color0 = entity_pass_target.GetRenderTarget() - .GetColorAttachments() - .find(0u) - ->second; + color0 = entity_pass_target.GetRenderTarget().GetColorAttachment(0); ASSERT_EQ(msaa_tex, color0.texture); ASSERT_NE(resolve_tex, color0.resolve_texture); @@ -89,10 +83,7 @@ TEST_P(EntityPassTargetTest, SwapWithMSAAImplicitResolve) { auto entity_pass_target = EntityPassTarget(render_target, false, true); - auto color0 = entity_pass_target.GetRenderTarget() - .GetColorAttachments() - .find(0u) - ->second; + auto color0 = entity_pass_target.GetRenderTarget().GetColorAttachment(0); auto msaa_tex = color0.texture; auto resolve_tex = color0.resolve_texture; @@ -101,10 +92,7 @@ TEST_P(EntityPassTargetTest, SwapWithMSAAImplicitResolve) { FML_DCHECK(content_context); entity_pass_target.Flip(*content_context); - color0 = entity_pass_target.GetRenderTarget() - .GetColorAttachments() - .find(0u) - ->second; + color0 = entity_pass_target.GetRenderTarget().GetColorAttachment(0); ASSERT_NE(msaa_tex, color0.texture); ASSERT_NE(resolve_tex, color0.resolve_texture); diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 6804b1ed4804b..ed4af453c0c32 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -14,6 +14,7 @@ #include "gtest/gtest.h" #include "impeller/core/device_buffer.h" #include "impeller/core/formats.h" +#include "impeller/core/host_buffer.h" #include "impeller/core/texture_descriptor.h" #include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/contents/conical_gradient_contents.h" @@ -35,6 +36,8 @@ #include "impeller/entity/entity.h" #include "impeller/entity/entity_playground.h" #include "impeller/entity/geometry/geometry.h" +#include "impeller/entity/geometry/point_field_geometry.h" +#include "impeller/entity/geometry/round_superellipse_geometry.h" #include "impeller/entity/geometry/stroke_path_geometry.h" #include "impeller/entity/geometry/superellipse_geometry.h" #include "impeller/geometry/color.h" @@ -117,7 +120,7 @@ TEST_P(EntityTest, ThreeStrokesInOnePath) { entity.SetTransform(Matrix::MakeScale(GetContentScale())); auto contents = std::make_unique(); - static std::unique_ptr geom = Geometry::MakeStrokePath(path, 5.0); + std::unique_ptr geom = Geometry::MakeStrokePath(path, 5.0); contents->SetGeometry(geom.get()); contents->SetColor(Color::Red()); entity.SetContents(std::move(contents)); @@ -138,7 +141,7 @@ TEST_P(EntityTest, StrokeWithTextureContents) { Entity entity; entity.SetTransform(Matrix::MakeScale(GetContentScale())); auto contents = std::make_unique(); - static std::unique_ptr geom = Geometry::MakeStrokePath(path, 100.0); + std::unique_ptr geom = Geometry::MakeStrokePath(path, 100.0); contents->SetGeometry(geom.get()); contents->SetTexture(bridge); contents->SetTileModes(Entity::TileMode::kClamp, Entity::TileMode::kClamp); @@ -150,20 +153,19 @@ TEST_P(EntityTest, TriangleInsideASquare) { auto callback = [&](ContentContext& context, RenderPass& pass) { Point offset(100, 100); - static PlaygroundPoint point_a(Point(10, 10) + offset, 20, Color::White()); + PlaygroundPoint point_a(Point(10, 10) + offset, 20, Color::White()); Point a = DrawPlaygroundPoint(point_a); - static PlaygroundPoint point_b(Point(210, 10) + offset, 20, Color::White()); + PlaygroundPoint point_b(Point(210, 10) + offset, 20, Color::White()); Point b = DrawPlaygroundPoint(point_b); - static PlaygroundPoint point_c(Point(210, 210) + offset, 20, - Color::White()); + PlaygroundPoint point_c(Point(210, 210) + offset, 20, Color::White()); Point c = DrawPlaygroundPoint(point_c); - static PlaygroundPoint point_d(Point(10, 210) + offset, 20, Color::White()); + PlaygroundPoint point_d(Point(10, 210) + offset, 20, Color::White()); Point d = DrawPlaygroundPoint(point_d); - static PlaygroundPoint point_e(Point(50, 50) + offset, 20, Color::White()); + PlaygroundPoint point_e(Point(50, 50) + offset, 20, Color::White()); Point e = DrawPlaygroundPoint(point_e); - static PlaygroundPoint point_f(Point(100, 50) + offset, 20, Color::White()); + PlaygroundPoint point_f(Point(100, 50) + offset, 20, Color::White()); Point f = DrawPlaygroundPoint(point_f); - static PlaygroundPoint point_g(Point(50, 150) + offset, 20, Color::White()); + PlaygroundPoint point_g(Point(50, 150) + offset, 20, Color::White()); Point g = DrawPlaygroundPoint(point_g); Path path = PathBuilder{} .MoveTo(a) @@ -180,8 +182,7 @@ TEST_P(EntityTest, TriangleInsideASquare) { Entity entity; entity.SetTransform(Matrix::MakeScale(GetContentScale())); auto contents = std::make_unique(); - static std::unique_ptr geom = - Geometry::MakeStrokePath(path, 20.0); + std::unique_ptr geom = Geometry::MakeStrokePath(path, 20.0); contents->SetGeometry(geom.get()); contents->SetColor(Color::Red()); entity.SetContents(std::move(contents)); @@ -217,7 +218,7 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { auto render_path = [width = width, &context, &pass, &world_matrix]( const Path& path, Cap cap, Join join) { auto contents = std::make_unique(); - static std::unique_ptr geom = + std::unique_ptr geom = Geometry::MakeStrokePath(path, width, miter_limit, cap, join); contents->SetGeometry(geom.get()); contents->SetColor(Color::Red()); @@ -230,7 +231,7 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { if (coverage.has_value()) { auto bounds_contents = std::make_unique(); - static std::unique_ptr geom = Geometry::MakeFillPath( + std::unique_ptr geom = Geometry::MakeFillPath( PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath()); bounds_contents->SetGeometry(geom.get()); @@ -249,11 +250,11 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { // Cap::kButt demo. { Point off = Point(0, 0) * padding + margin; - static PlaygroundPoint point_a(off + a_def, r, Color::Black()); - static PlaygroundPoint point_b(off + b_def, r, Color::White()); + PlaygroundPoint point_a(off + a_def, r, Color::Black()); + PlaygroundPoint point_b(off + b_def, r, Color::White()); auto [a, b] = DrawPlaygroundLine(point_a, point_b); - static PlaygroundPoint point_c(off + c_def, r, Color::Black()); - static PlaygroundPoint point_d(off + d_def, r, Color::White()); + PlaygroundPoint point_c(off + c_def, r, Color::Black()); + PlaygroundPoint point_d(off + d_def, r, Color::White()); auto [c, d] = DrawPlaygroundLine(point_c, point_d); render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), Cap::kButt, Join::kBevel); @@ -262,11 +263,11 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { // Cap::kSquare demo. { Point off = Point(1, 0) * padding + margin; - static PlaygroundPoint point_a(off + a_def, r, Color::Black()); - static PlaygroundPoint point_b(off + b_def, r, Color::White()); + PlaygroundPoint point_a(off + a_def, r, Color::Black()); + PlaygroundPoint point_b(off + b_def, r, Color::White()); auto [a, b] = DrawPlaygroundLine(point_a, point_b); - static PlaygroundPoint point_c(off + c_def, r, Color::Black()); - static PlaygroundPoint point_d(off + d_def, r, Color::White()); + PlaygroundPoint point_c(off + c_def, r, Color::Black()); + PlaygroundPoint point_d(off + d_def, r, Color::White()); auto [c, d] = DrawPlaygroundLine(point_c, point_d); render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), Cap::kSquare, Join::kBevel); @@ -275,11 +276,11 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { // Cap::kRound demo. { Point off = Point(2, 0) * padding + margin; - static PlaygroundPoint point_a(off + a_def, r, Color::Black()); - static PlaygroundPoint point_b(off + b_def, r, Color::White()); + PlaygroundPoint point_a(off + a_def, r, Color::Black()); + PlaygroundPoint point_b(off + b_def, r, Color::White()); auto [a, b] = DrawPlaygroundLine(point_a, point_b); - static PlaygroundPoint point_c(off + c_def, r, Color::Black()); - static PlaygroundPoint point_d(off + d_def, r, Color::White()); + PlaygroundPoint point_c(off + c_def, r, Color::Black()); + PlaygroundPoint point_d(off + d_def, r, Color::White()); auto [c, d] = DrawPlaygroundLine(point_c, point_d); render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), Cap::kRound, Join::kBevel); @@ -288,12 +289,9 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { // Join::kBevel demo. { Point off = Point(0, 1) * padding + margin; - static PlaygroundPoint point_a = - PlaygroundPoint(off + a_def, r, Color::White()); - static PlaygroundPoint point_b = - PlaygroundPoint(off + e_def, r, Color::White()); - static PlaygroundPoint point_c = - PlaygroundPoint(off + c_def, r, Color::White()); + PlaygroundPoint point_a = PlaygroundPoint(off + a_def, r, Color::White()); + PlaygroundPoint point_b = PlaygroundPoint(off + e_def, r, Color::White()); + PlaygroundPoint point_c = PlaygroundPoint(off + c_def, r, Color::White()); Point a = DrawPlaygroundPoint(point_a); Point b = DrawPlaygroundPoint(point_b); Point c = DrawPlaygroundPoint(point_c); @@ -305,9 +303,9 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { // Join::kMiter demo. { Point off = Point(1, 1) * padding + margin; - static PlaygroundPoint point_a(off + a_def, r, Color::White()); - static PlaygroundPoint point_b(off + e_def, r, Color::White()); - static PlaygroundPoint point_c(off + c_def, r, Color::White()); + PlaygroundPoint point_a(off + a_def, r, Color::White()); + PlaygroundPoint point_b(off + e_def, r, Color::White()); + PlaygroundPoint point_c(off + c_def, r, Color::White()); Point a = DrawPlaygroundPoint(point_a); Point b = DrawPlaygroundPoint(point_b); Point c = DrawPlaygroundPoint(point_c); @@ -319,9 +317,9 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { // Join::kRound demo. { Point off = Point(2, 1) * padding + margin; - static PlaygroundPoint point_a(off + a_def, r, Color::White()); - static PlaygroundPoint point_b(off + e_def, r, Color::White()); - static PlaygroundPoint point_c(off + c_def, r, Color::White()); + PlaygroundPoint point_a(off + a_def, r, Color::White()); + PlaygroundPoint point_b(off + e_def, r, Color::White()); + PlaygroundPoint point_c(off + c_def, r, Color::White()); Point a = DrawPlaygroundPoint(point_a); Point b = DrawPlaygroundPoint(point_b); Point c = DrawPlaygroundPoint(point_c); @@ -354,7 +352,7 @@ TEST_P(EntityTest, CubicCurveTest) { Entity entity; entity.SetTransform(Matrix::MakeScale(GetContentScale())); - static std::unique_ptr geom = Geometry::MakeFillPath(path); + std::unique_ptr geom = Geometry::MakeFillPath(path); auto contents = std::make_shared(); contents->SetColor(Color::Red()); @@ -405,7 +403,7 @@ TEST_P(EntityTest, CanDrawCorrectlyWithRotatedTransform) { Entity entity; entity.SetTransform(result_transform); - static std::unique_ptr geom = Geometry::MakeFillPath(path); + std::unique_ptr geom = Geometry::MakeFillPath(path); auto contents = std::make_shared(); contents->SetColor(Color::Red()); @@ -642,7 +640,7 @@ TEST_P(EntityTest, CubicCurveAndOverlapTest) { Entity entity; entity.SetTransform(Matrix::MakeScale(GetContentScale())); - static std::unique_ptr geom = Geometry::MakeFillPath(path); + std::unique_ptr geom = Geometry::MakeFillPath(path); auto contents = std::make_shared(); contents->SetColor(Color::Red()); @@ -806,11 +804,11 @@ TEST_P(EntityTest, BlendingModeOptions) { BlendMode selected_mode = blend_mode_values[current_blend_index]; Point a, b, c, d; - static PlaygroundPoint point_a(Point(400, 100), 20, Color::White()); - static PlaygroundPoint point_b(Point(200, 300), 20, Color::White()); + PlaygroundPoint point_a(Point(400, 100), 20, Color::White()); + PlaygroundPoint point_b(Point(200, 300), 20, Color::White()); std::tie(a, b) = DrawPlaygroundLine(point_a, point_b); - static PlaygroundPoint point_c(Point(470, 190), 20, Color::White()); - static PlaygroundPoint point_d(Point(270, 390), 20, Color::White()); + PlaygroundPoint point_c(Point(470, 190), 20, Color::White()); + PlaygroundPoint point_d(Point(270, 390), 20, Color::White()); std::tie(c, d) = DrawPlaygroundLine(point_c, point_d); bool result = true; @@ -856,7 +854,7 @@ TEST_P(EntityTest, BezierCircleScaled) { entity.SetTransform( Matrix::MakeScale({scale, scale, 1.0}).Translate({-90, -20, 0})); - static std::unique_ptr geom = Geometry::MakeFillPath(path); + std::unique_ptr geom = Geometry::MakeFillPath(path); auto contents = std::make_shared(); contents->SetColor(Color::Red()); @@ -985,6 +983,8 @@ TEST_P(EntityTest, GaussianBlurFilter) { auto input_rect = Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], path_rect[3]); + + std::unique_ptr solid_color_input; if (selected_input_type == 0) { auto texture = std::make_shared(); texture->SetSourceRect(Rect::MakeSize(boston->GetSize())); @@ -997,10 +997,10 @@ TEST_P(EntityTest, GaussianBlurFilter) { } else { auto fill = std::make_shared(); fill->SetColor(input_color); - static std::unique_ptr geom = + solid_color_input = Geometry::MakeFillPath(PathBuilder{}.AddRect(input_rect).TakePath()); - fill->SetGeometry(geom.get()); + fill->SetGeometry(solid_color_input.get()); input = fill; input_size = input_rect.GetSize(); @@ -1045,7 +1045,7 @@ TEST_P(EntityTest, GaussianBlurFilter) { // Renders a red "cover" rectangle that shows the original position of the // unfiltered input. Entity cover_entity; - static std::unique_ptr geom = + std::unique_ptr geom = Geometry::MakeFillPath(PathBuilder{}.AddRect(input_rect).TakePath()); auto contents = std::make_shared(); contents->SetColor(cover_color); @@ -1059,7 +1059,7 @@ TEST_P(EntityTest, GaussianBlurFilter) { std::optional target_contents_coverage = target_contents->GetCoverage(entity); if (target_contents_coverage.has_value()) { - static std::unique_ptr geom = Geometry::MakeFillPath( + std::unique_ptr geom = Geometry::MakeFillPath( PathBuilder{} .AddRect(target_contents->GetCoverage(entity).value()) .TakePath()); @@ -1157,7 +1157,7 @@ TEST_P(EntityTest, MorphologyFilter) { // Renders a red "cover" rectangle that shows the original position of the // unfiltered input. Entity cover_entity; - static std::unique_ptr geom = + std::unique_ptr geom = Geometry::MakeFillPath(PathBuilder{}.AddRect(input_rect).TakePath()); auto cover_contents = std::make_shared(); cover_contents->SetColor(cover_color); @@ -1168,7 +1168,7 @@ TEST_P(EntityTest, MorphologyFilter) { // Renders a green bounding rect of the target filter. Entity bounds_entity; - static std::unique_ptr bounds_geom = Geometry::MakeFillPath( + std::unique_ptr bounds_geom = Geometry::MakeFillPath( PathBuilder{} .AddRect(contents->GetCoverage(entity).value()) .TakePath()); @@ -1328,69 +1328,6 @@ TEST_P(EntityTest, SolidFillCoverageIsCorrect) { } } -TEST_P(EntityTest, ClipContentsGetClipCoverageIsCorrect) { - // Intersection: No stencil coverage, no geometry. - { - auto clip = std::make_shared(); - clip->SetClipOperation(Entity::ClipOperation::kIntersect); - auto result = clip->GetClipCoverage(Entity{}, Rect{}); - - ASSERT_FALSE(result.coverage.has_value()); - } - - // Intersection: No stencil coverage, with geometry. - { - auto clip = std::make_shared(); - auto geom = Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath()); - clip->SetClipOperation(Entity::ClipOperation::kIntersect); - clip->SetGeometry(geom.get()); - auto result = clip->GetClipCoverage(Entity{}, Rect{}); - - ASSERT_FALSE(result.coverage.has_value()); - } - - // Intersection: With stencil coverage, no geometry. - { - auto clip = std::make_shared(); - clip->SetClipOperation(Entity::ClipOperation::kIntersect); - auto result = - clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); - - ASSERT_FALSE(result.coverage.has_value()); - } - - // Intersection: With stencil coverage, with geometry. - { - auto clip = std::make_shared(); - auto geom = Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath()); - clip->SetClipOperation(Entity::ClipOperation::kIntersect); - clip->SetGeometry(geom.get()); - auto result = - clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); - - ASSERT_TRUE(result.coverage.has_value()); - ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 50, 50)); - ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend); - } - - // Difference: With stencil coverage, with geometry. - { - auto clip = std::make_shared(); - auto geom = Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath()); - clip->SetClipOperation(Entity::ClipOperation::kDifference); - clip->SetGeometry(geom.get()); - auto result = - clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); - - ASSERT_TRUE(result.coverage.has_value()); - ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 100, 100)); - ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend); - } -} - TEST_P(EntityTest, RRectShadowTest) { auto callback = [&](ContentContext& context, RenderPass& pass) { static Color color = Color::Red(); @@ -1410,9 +1347,8 @@ TEST_P(EntityTest, RRectShadowTest) { } ImGui::End(); - static PlaygroundPoint top_left_point(Point(200, 200), 30, Color::White()); - static PlaygroundPoint bottom_right_point(Point(600, 400), 30, - Color::White()); + PlaygroundPoint top_left_point(Point(200, 200), 30, Color::White()); + PlaygroundPoint bottom_right_point(Point(600, 400), 30, Color::White()); auto [top_left, bottom_right] = DrawPlaygroundLine(top_left_point, bottom_right_point); auto rect = @@ -1761,7 +1697,7 @@ TEST_P(EntityTest, RuntimeEffect) { bool expect_dirty = true; Pipeline* first_pipeline; - static std::unique_ptr geom = Geometry::MakeCover(); + std::unique_ptr geom = Geometry::MakeCover(); auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { EXPECT_EQ(runtime_stage->IsDirty(), expect_dirty); @@ -1908,26 +1844,16 @@ TEST_P(EntityTest, RuntimeEffectSetsRightSizeWhenUniformIsStruct) { auto uniform_data = std::make_shared>(); uniform_data->resize(sizeof(FragUniforms)); memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); - contents->SetUniformData(uniform_data); - - Entity entity; - entity.SetContents(contents); - auto context = GetContentContext(); - RenderTarget target = context->GetRenderTargetCache()->CreateOffscreen( - *context->GetContext(), {1, 1}, 1u); + auto buffer_view = RuntimeEffectContents::EmplaceVulkanUniform( + uniform_data, GetContentContext()->GetTransientsBuffer(), + runtime_stage->GetUniforms()[0]); - testing::MockRenderPass pass(GetContext(), target); - ASSERT_TRUE(contents->Render(*context, entity, pass)); - ASSERT_EQ(pass.GetCommands().size(), 1u); - const auto& command = pass.GetCommands()[0]; - ASSERT_EQ(command.fragment_bindings.buffers.size(), 1u); // 16 bytes: // 8 bytes for iResolution // 4 bytes for iTime // 4 bytes padding - EXPECT_EQ(command.fragment_bindings.buffers[0].view.resource.range.length, - 16u); + EXPECT_EQ(buffer_view.GetRange().length, 16u); } TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) { @@ -2157,9 +2083,9 @@ TEST_P(EntityTest, TiledTextureContentsIsOpaque) { TEST_P(EntityTest, PointFieldGeometryCoverage) { std::vector points = {{10, 20}, {100, 200}}; - auto geometry = Geometry::MakePointField(points, 5.0, false); - ASSERT_EQ(*geometry->GetCoverage(Matrix()), Rect::MakeLTRB(5, 15, 105, 205)); - ASSERT_EQ(*geometry->GetCoverage(Matrix::MakeTranslation({30, 0, 0})), + PointFieldGeometry geometry(points.data(), 2, 5.0, false); + ASSERT_EQ(geometry.GetCoverage(Matrix()), Rect::MakeLTRB(5, 15, 105, 205)); + ASSERT_EQ(geometry.GetCoverage(Matrix::MakeTranslation({30, 0, 0})), Rect::MakeLTRB(35, 15, 135, 205)); } @@ -2184,11 +2110,11 @@ TEST_P(EntityTest, ColorFilterContentsWithLargeGeometry) { } TEST_P(EntityTest, TextContentsCeilsGlyphScaleToDecimal) { - ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f, 12), 0.43f); - ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f, 12), 0.53f); - ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f, 12), 2.1f); - ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f, 12), 0.0f); - ASSERT_EQ(TextFrame::RoundScaledFontSize(100000000.0f, 12), 48.0f); + ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f), 0.43f); + ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f), 0.53f); + ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f), 2.1f); + ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f), 0.0f); + ASSERT_EQ(TextFrame::RoundScaledFontSize(100000000.0f), 48.0f); } TEST_P(EntityTest, SpecializationConstantsAreAppliedToVariants) { @@ -2232,16 +2158,16 @@ TEST_P(EntityTest, DecalSpecializationAppliedToMorphologyFilter) { // set of options, we can just compare benchmarks. TEST_P(EntityTest, ContentContextOptionsHasReasonableHashFunctions) { ContentContextOptions opts; - auto hash_a = ContentContextOptions::Hash{}(opts); + auto hash_a = opts.ToKey(); opts.blend_mode = BlendMode::kColorBurn; - auto hash_b = ContentContextOptions::Hash{}(opts); + auto hash_b = opts.ToKey(); opts.has_depth_stencil_attachments = false; - auto hash_c = ContentContextOptions::Hash{}(opts); + auto hash_c = opts.ToKey(); opts.primitive_type = PrimitiveType::kPoint; - auto hash_d = ContentContextOptions::Hash{}(opts); + auto hash_d = opts.ToKey(); EXPECT_NE(hash_a, hash_b); EXPECT_NE(hash_b, hash_c); @@ -2352,7 +2278,7 @@ TEST_P(EntityTest, CanRenderEmptyPathsWithoutCrashing) { EXPECT_TRUE(path.GetBoundingBox()->IsEmpty()); auto contents = std::make_shared(); - static std::unique_ptr geom = Geometry::MakeFillPath(path); + std::unique_ptr geom = Geometry::MakeFillPath(path); contents->SetGeometry(geom.get()); contents->SetColor(Color::Red()); @@ -2381,7 +2307,7 @@ TEST_P(EntityTest, DrawSuperEllipse) { ImGui::End(); auto contents = std::make_shared(); - static std::unique_ptr geom = + std::unique_ptr geom = std::make_unique(Point{400, 400}, radius, degree, alpha, beta); contents->SetColor(color); @@ -2396,6 +2322,41 @@ TEST_P(EntityTest, DrawSuperEllipse) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } +TEST_P(EntityTest, DrawRoundSuperEllipse) { + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + // UI state. + static float center_x = 100; + static float center_y = 100; + static float width = 900; + static float height = 900; + static float corner_radius = 300; + static Color color = Color::Red(); + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::SliderFloat("Center X", ¢er_x, 0, 1000); + ImGui::SliderFloat("Center Y", ¢er_y, 0, 1000); + ImGui::SliderFloat("Width", &width, 0, 1000); + ImGui::SliderFloat("Height", &height, 0, 1000); + ImGui::SliderFloat("Corner radius", &corner_radius, 0, 500); + ImGui::End(); + + auto contents = std::make_shared(); + std::unique_ptr geom = + std::make_unique( + Rect::MakeOriginSize({center_x, center_y}, {width, height}), + corner_radius); + contents->SetColor(color); + contents->SetGeometry(geom.get()); + + Entity entity; + entity.SetContents(contents); + + return entity.Render(context, pass); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + TEST_P(EntityTest, SolidColorApplyColorFilter) { auto contents = SolidColorContents(); contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75)); @@ -2426,6 +2387,119 @@ APPLY_COLOR_FILTER_GRADIENT_TEST(Radial); APPLY_COLOR_FILTER_GRADIENT_TEST(Conical); APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep); +TEST_P(EntityTest, GiantStrokePathAllocation) { + PathBuilder builder{}; + for (int i = 0; i < 10000; i++) { + builder.LineTo(Point(i, i)); + } + Path path = builder.TakePath(); + auto geom = Geometry::MakeStrokePath(path, /*stroke_width=*/10); + + ContentContext content_context(GetContext(), /*typographer_context=*/nullptr); + Entity entity; + + auto cmd_buffer = content_context.GetContext()->CreateCommandBuffer(); + + RenderTargetAllocator allocator( + content_context.GetContext()->GetResourceAllocator()); + + auto render_target = allocator.CreateOffscreen( + *content_context.GetContext(), /*size=*/{10, 10}, /*mip_count=*/1); + auto pass = cmd_buffer->CreateRenderPass(render_target); + + GeometryResult result = + geom->GetPositionBuffer(content_context, entity, *pass); + + // Validate the buffer data overflowed the small buffer + EXPECT_GT(result.vertex_buffer.vertex_count, kPointArenaSize); + + // Validate that there are no uninitialized points near the gap. + Point* written_data = reinterpret_cast( + (result.vertex_buffer.vertex_buffer.GetBuffer()->OnGetContents() + + result.vertex_buffer.vertex_buffer.GetRange().offset)); + + std::vector expected = { + Point(1019.46, 1026.54), // + Point(1026.54, 1019.46), // + Point(1020.45, 1027.54), // + Point(1027.54, 1020.46), // + Point(1020.46, 1027.53) // + }; + + Point point = written_data[kPointArenaSize - 2]; + EXPECT_NEAR(point.x, expected[0].x, 0.1); + EXPECT_NEAR(point.y, expected[0].y, 0.1); + + point = written_data[kPointArenaSize - 1]; + EXPECT_NEAR(point.x, expected[1].x, 0.1); + EXPECT_NEAR(point.y, expected[1].y, 0.1); + + point = written_data[kPointArenaSize]; + EXPECT_NEAR(point.x, expected[2].x, 0.1); + EXPECT_NEAR(point.y, expected[2].y, 0.1); + + point = written_data[kPointArenaSize + 1]; + EXPECT_NEAR(point.x, expected[3].x, 0.1); + EXPECT_NEAR(point.y, expected[3].y, 0.1); + + point = written_data[kPointArenaSize + 2]; + EXPECT_NEAR(point.x, expected[4].x, 0.1); + EXPECT_NEAR(point.y, expected[4].y, 0.1); +} + +TEST_P(EntityTest, GiantLineStripPathAllocation) { + PathBuilder builder{}; + for (int i = 0; i < 10000; i++) { + builder.LineTo(Point(i, i)); + } + Path path = builder.TakePath(); + + ContentContext content_context(GetContext(), /*typographer_context=*/nullptr); + Entity entity; + + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); + auto tessellator = Tessellator(); + + auto vertex_buffer = tessellator.GenerateLineStrip(path, *host_buffer, 1.0); + + // Validate the buffer data overflowed the small buffer + EXPECT_GT(vertex_buffer.vertex_count, kPointArenaSize); + + // Validate that there are no uninitialized points near the gap. + Point* written_data = reinterpret_cast( + (vertex_buffer.vertex_buffer.GetBuffer()->OnGetContents() + + vertex_buffer.vertex_buffer.GetRange().offset)); + + std::vector expected = { + Point(4093, 4093), // + Point(4094, 4094), // + Point(4095, 4095), // + Point(4096, 4096), // + Point(4097, 4097) // + }; + + Point point = written_data[kPointArenaSize - 2]; + EXPECT_NEAR(point.x, expected[0].x, 0.1); + EXPECT_NEAR(point.y, expected[0].y, 0.1); + + point = written_data[kPointArenaSize - 1]; + EXPECT_NEAR(point.x, expected[1].x, 0.1); + EXPECT_NEAR(point.y, expected[1].y, 0.1); + + point = written_data[kPointArenaSize]; + EXPECT_NEAR(point.x, expected[2].x, 0.1); + EXPECT_NEAR(point.y, expected[2].y, 0.1); + + point = written_data[kPointArenaSize + 1]; + EXPECT_NEAR(point.x, expected[3].x, 0.1); + EXPECT_NEAR(point.y, expected[3].y, 0.1); + + point = written_data[kPointArenaSize + 2]; + EXPECT_NEAR(point.x, expected[4].x, 0.1); + EXPECT_NEAR(point.y, expected[4].y, 0.1); +} + } // namespace testing } // namespace impeller diff --git a/impeller/entity/geometry/circle_geometry.cc b/impeller/entity/geometry/circle_geometry.cc index ab265a6c10462..8565f0f0d8c7f 100644 --- a/impeller/entity/geometry/circle_geometry.cc +++ b/impeller/entity/geometry/circle_geometry.cc @@ -42,18 +42,14 @@ GeometryResult CircleGeometry::GetPositionBuffer(const ContentContext& renderer, RenderPass& pass) const { auto& transform = entity.GetTransform(); - Scalar half_width = stroke_width_ < 0 - ? 0.0 - : LineGeometry::ComputePixelHalfWidth( - transform, stroke_width_, - pass.GetSampleCount() == SampleCount::kCount4); - - const std::shared_ptr& tessellator = renderer.GetTessellator(); + Scalar half_width = stroke_width_ < 0 ? 0.0 + : LineGeometry::ComputePixelHalfWidth( + transform, stroke_width_); // We call the StrokedCircle method which will simplify to a // FilledCircleGenerator if the inner_radius is <= 0. - auto generator = - tessellator->StrokedCircle(transform, center_, radius_, half_width); + auto generator = renderer.GetTessellator().StrokedCircle(transform, center_, + radius_, half_width); return ComputePositionGeometry(renderer, generator, entity, pass); } diff --git a/impeller/entity/geometry/ellipse_geometry.cc b/impeller/entity/geometry/ellipse_geometry.cc index 76e457f2013df..71f3acec1426d 100644 --- a/impeller/entity/geometry/ellipse_geometry.cc +++ b/impeller/entity/geometry/ellipse_geometry.cc @@ -18,7 +18,7 @@ GeometryResult EllipseGeometry::GetPositionBuffer( RenderPass& pass) const { return ComputePositionGeometry( renderer, - renderer.GetTessellator()->FilledEllipse(entity.GetTransform(), bounds_), + renderer.GetTessellator().FilledEllipse(entity.GetTransform(), bounds_), entity, pass); } diff --git a/impeller/entity/geometry/fill_path_geometry.cc b/impeller/entity/geometry/fill_path_geometry.cc index 347aa3ed42607..72937bf0538fd 100644 --- a/impeller/entity/geometry/fill_path_geometry.cc +++ b/impeller/entity/geometry/fill_path_geometry.cc @@ -38,12 +38,20 @@ GeometryResult FillPathGeometry::GetPositionBuffer( }; } - VertexBuffer vertex_buffer = renderer.GetTessellator()->TessellateConvex( - path_, host_buffer, entity.GetTransform().GetMaxBasisLengthXY()); + bool supports_primitive_restart = + renderer.GetDeviceCapabilities().SupportsPrimitiveRestart(); + bool supports_triangle_fan = + renderer.GetDeviceCapabilities().SupportsTriangleFan() && + supports_primitive_restart; + VertexBuffer vertex_buffer = renderer.GetTessellator().TessellateConvex( + path_, host_buffer, entity.GetTransform().GetMaxBasisLengthXY(), + /*supports_primitive_restart=*/supports_primitive_restart, + /*supports_triangle_fan=*/supports_triangle_fan); return GeometryResult{ - .type = PrimitiveType::kTriangleStrip, - .vertex_buffer = vertex_buffer, + .type = supports_triangle_fan ? PrimitiveType::kTriangleFan + : PrimitiveType::kTriangleStrip, + .vertex_buffer = std::move(vertex_buffer), .transform = entity.GetShaderTransform(pass), .mode = GetResultMode(), }; diff --git a/impeller/entity/geometry/geometry.cc b/impeller/entity/geometry/geometry.cc index 40540dd649a7f..0f3185fce940e 100644 --- a/impeller/entity/geometry/geometry.cc +++ b/impeller/entity/geometry/geometry.cc @@ -13,7 +13,6 @@ #include "impeller/entity/geometry/ellipse_geometry.h" #include "impeller/entity/geometry/fill_path_geometry.h" #include "impeller/entity/geometry/line_geometry.h" -#include "impeller/entity/geometry/point_field_geometry.h" #include "impeller/entity/geometry/rect_geometry.h" #include "impeller/entity/geometry/round_rect_geometry.h" #include "impeller/entity/geometry/stroke_path_geometry.h" @@ -63,12 +62,6 @@ std::unique_ptr Geometry::MakeFillPath( return std::make_unique(path, inner_rect); } -std::unique_ptr Geometry::MakePointField(std::vector points, - Scalar radius, - bool round) { - return std::make_unique(std::move(points), radius, round); -} - std::unique_ptr Geometry::MakeStrokePath(const Path& path, Scalar stroke_width, Scalar miter_limit, @@ -133,9 +126,7 @@ bool Geometry::CanApplyMaskFilter() const { Scalar Geometry::ComputeStrokeAlphaCoverage(const Matrix& transform, Scalar stroke_width) { Scalar scaled_stroke_width = transform.GetMaxBasisLengthXY() * stroke_width; - // If the stroke width is 0 or greater than kMinStrokeSizeMSAA, don't apply - // any additional alpha. This is intended to match Skia behavior. - if (scaled_stroke_width == 0.0 || scaled_stroke_width >= kMinStrokeSizeMSAA) { + if (scaled_stroke_width == 0.0 || scaled_stroke_width >= kMinStrokeSize) { return 1.0; } // This scalling is eyeballed from Skia. diff --git a/impeller/entity/geometry/geometry.h b/impeller/entity/geometry/geometry.h index 8a24936366377..d5c504d79e844 100644 --- a/impeller/entity/geometry/geometry.h +++ b/impeller/entity/geometry/geometry.h @@ -16,11 +16,6 @@ namespace impeller { class Tessellator; -/// @brief The minimum stroke size can be less than one physical pixel because -/// of MSAA, but no less that half a physical pixel otherwise we might -/// not hit one of the sample positions. -static constexpr Scalar kMinStrokeSizeMSAA = 0.5f; - static constexpr Scalar kMinStrokeSize = 1.0f; struct GeometryResult { @@ -88,10 +83,6 @@ class Geometry { static std::unique_ptr MakeRoundRect(const Rect& rect, const Size& radii); - static std::unique_ptr MakePointField(std::vector points, - Scalar radius, - bool round); - virtual GeometryResult GetPositionBuffer(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const = 0; diff --git a/impeller/entity/geometry/geometry_unittests.cc b/impeller/entity/geometry/geometry_unittests.cc index 4b29450bf595d..8889ab141f14b 100644 --- a/impeller/entity/geometry/geometry_unittests.cc +++ b/impeller/entity/geometry/geometry_unittests.cc @@ -14,13 +14,13 @@ #include "impeller/renderer/testing/mocks.h" inline ::testing::AssertionResult SolidVerticesNear( - std::vector a, - std::vector b) { + std::vector a, + std::vector b) { if (a.size() != b.size()) { return ::testing::AssertionFailure() << "Colors length does not match"; } for (auto i = 0u; i < b.size(); i++) { - if (!PointNear(a[i].position, b[i].position)) { + if (!PointNear(a[i], b[i])) { return ::testing::AssertionFailure() << "Positions are not equal."; } } @@ -53,13 +53,13 @@ namespace impeller { class ImpellerEntityUnitTestAccessor { public: - static std::vector - GenerateSolidStrokeVertices(const Path::Polyline& polyline, - Scalar stroke_width, - Scalar miter_limit, - Join stroke_join, - Cap stroke_cap, - Scalar scale) { + static std::vector GenerateSolidStrokeVertices( + const Path::Polyline& polyline, + Scalar stroke_width, + Scalar miter_limit, + Join stroke_join, + Cap stroke_cap, + Scalar scale) { return StrokePathGeometry::GenerateSolidStrokeVertices( polyline, stroke_width, miter_limit, stroke_join, stroke_cap, scale); } diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index d5f5b640630b1..56508b03777bb 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -15,21 +15,19 @@ LineGeometry::LineGeometry(Point p0, Point p1, Scalar width, Cap cap) LineGeometry::~LineGeometry() = default; Scalar LineGeometry::ComputePixelHalfWidth(const Matrix& transform, - Scalar width, - bool msaa) { + Scalar width) { Scalar max_basis = transform.GetMaxBasisLengthXY(); if (max_basis == 0) { return {}; } - Scalar min_size = (msaa ? kMinStrokeSize : kMinStrokeSizeMSAA) / max_basis; + Scalar min_size = kMinStrokeSize / max_basis; return std::max(width, min_size) * 0.5f; } Vector2 LineGeometry::ComputeAlongVector(const Matrix& transform, - bool allow_zero_length, - bool msaa) const { - Scalar stroke_half_width = ComputePixelHalfWidth(transform, width_, msaa); + bool allow_zero_length) const { + Scalar stroke_half_width = ComputePixelHalfWidth(transform, width_); if (stroke_half_width < kEhCloseEnough) { return {}; } @@ -49,9 +47,8 @@ Vector2 LineGeometry::ComputeAlongVector(const Matrix& transform, bool LineGeometry::ComputeCorners(Point corners[4], const Matrix& transform, - bool extend_endpoints, - bool msaa) const { - auto along = ComputeAlongVector(transform, extend_endpoints, msaa); + bool extend_endpoints) const { + auto along = ComputeAlongVector(transform, extend_endpoints); if (along.IsZero()) { return false; } @@ -80,18 +77,16 @@ GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, using VT = SolidFillVertexShader::PerVertexData; auto& transform = entity.GetTransform(); - auto radius = ComputePixelHalfWidth( - transform, width_, pass.GetSampleCount() == SampleCount::kCount4); + auto radius = ComputePixelHalfWidth(transform, width_); if (cap_ == Cap::kRound) { - std::shared_ptr tessellator = renderer.GetTessellator(); - auto generator = tessellator->RoundCapLine(transform, p0_, p1_, radius); + auto generator = + renderer.GetTessellator().RoundCapLine(transform, p0_, p1_, radius); return ComputePositionGeometry(renderer, generator, entity, pass); } Point corners[4]; - if (!ComputeCorners(corners, transform, cap_ == Cap::kSquare, - pass.GetSampleCount() == SampleCount::kCount4)) { + if (!ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { return kEmptyResult; } @@ -123,7 +118,7 @@ GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, std::optional LineGeometry::GetCoverage(const Matrix& transform) const { Point corners[4]; // Note: MSAA boolean doesn't matter for coverage computation. - if (!ComputeCorners(corners, transform, cap_ != Cap::kButt, /*msaa=*/false)) { + if (!ComputeCorners(corners, transform, cap_ != Cap::kButt)) { return {}; } diff --git a/impeller/entity/geometry/line_geometry.h b/impeller/entity/geometry/line_geometry.h index 5f9a0306a1f22..340d32424316c 100644 --- a/impeller/entity/geometry/line_geometry.h +++ b/impeller/entity/geometry/line_geometry.h @@ -15,9 +15,7 @@ class LineGeometry final : public Geometry { ~LineGeometry() override; - static Scalar ComputePixelHalfWidth(const Matrix& transform, - Scalar width, - bool msaa); + static Scalar ComputePixelHalfWidth(const Matrix& transform, Scalar width); // |Geometry| bool CoversArea(const Matrix& transform, const Rect& rect) const override; @@ -44,12 +42,10 @@ class LineGeometry final : public Geometry { // @return true if the transform and width were not degenerate bool ComputeCorners(Point corners[4], const Matrix& transform, - bool extend_endpoints, - bool msaa) const; + bool extend_endpoints) const; Vector2 ComputeAlongVector(const Matrix& transform, - bool allow_zero_length, - bool msaa) const; + bool allow_zero_length) const; // |Geometry| GeometryResult GetPositionBuffer(const ContentContext& renderer, diff --git a/impeller/entity/geometry/point_field_geometry.cc b/impeller/entity/geometry/point_field_geometry.cc index e2ee046750e3f..2a8429a2d7ac5 100644 --- a/impeller/entity/geometry/point_field_geometry.cc +++ b/impeller/entity/geometry/point_field_geometry.cc @@ -12,10 +12,14 @@ namespace impeller { -PointFieldGeometry::PointFieldGeometry(std::vector points, +PointFieldGeometry::PointFieldGeometry(const Point* points, + size_t point_count, Scalar radius, bool round) - : points_(std::move(points)), radius_(radius), round_(round) {} + : point_count_(point_count), + radius_(radius), + round_(round), + points_(points) {} PointFieldGeometry::~PointFieldGeometry() = default; @@ -23,7 +27,7 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - if (radius_ < 0.0 || points_.empty()) { + if (radius_ < 0.0 || point_count_ == 0) { return {}; } const Matrix& transform = entity.GetTransform(); @@ -42,7 +46,7 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( // Get triangulation relative to {0, 0} so we can translate it to each // point in turn. Tessellator::EllipticalVertexGenerator generator = - renderer.GetTessellator()->FilledCircle(transform, {}, radius); + renderer.GetTessellator().FilledCircle(transform, {}, radius); FML_DCHECK(generator.GetTriangleType() == PrimitiveType::kTriangleStrip); std::vector circle_vertices; @@ -52,7 +56,7 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( }); FML_DCHECK(circle_vertices.size() == generator.GetVertexCount()); - vertex_count = (circle_vertices.size() + 2) * points_.size() - 2; + vertex_count = (circle_vertices.size() + 2) * point_count_ - 2; buffer_view = host_buffer.Emplace( vertex_count * sizeof(Point), alignof(Point), [&](uint8_t* data) { Point* output = reinterpret_cast(data); @@ -66,7 +70,7 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( // the strip. This could be optimized out if we switched to using // primitive restart. Point last_point = circle_vertices.back() + center; - for (size_t i = 1; i < points_.size(); i++) { + for (size_t i = 1; i < point_count_; i++) { Point center = points_[i]; output[offset++] = last_point; output[offset++] = Point(center + circle_vertices[0]); @@ -77,7 +81,7 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( } }); } else { - vertex_count = 6 * points_.size() - 2; + vertex_count = 6 * point_count_ - 2; buffer_view = host_buffer.Emplace( vertex_count * sizeof(Point), alignof(Point), [&](uint8_t* data) { Point* output = reinterpret_cast(data); @@ -97,7 +101,7 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( // For all subequent points, insert a degenerate triangle to break // the strip. This could be optimized out if we switched to using // primitive restart. - for (size_t i = 1; i < points_.size(); i++) { + for (size_t i = 1; i < point_count_; i++) { Point point = points_[i]; Point first = Point(point.x - radius, point.y - radius); @@ -129,22 +133,21 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( // |Geometry| std::optional PointFieldGeometry::GetCoverage( const Matrix& transform) const { - if (points_.size() > 0) { + if (point_count_ > 0) { // Doesn't use MakePointBounds as this isn't resilient to points that // all lie along the same axis. - auto first = points_.begin(); - auto last = points_.end(); - auto left = first->x; - auto top = first->y; - auto right = first->x; - auto bottom = first->y; - for (auto it = first + 1; it < last; ++it) { - left = std::min(left, it->x); - top = std::min(top, it->y); - right = std::max(right, it->x); - bottom = std::max(bottom, it->y); + Scalar left = points_[0].x; + Scalar top = points_[0].y; + Scalar right = points_[0].x; + Scalar bottom = points_[0].y; + for (auto i = 1u; i < point_count_; i++) { + const Point point = points_[i]; + left = std::min(left, point.x); + top = std::min(top, point.y); + right = std::max(right, point.x); + bottom = std::max(bottom, point.y); } - auto coverage = Rect::MakeLTRB(left - radius_, top - radius_, + Rect coverage = Rect::MakeLTRB(left - radius_, top - radius_, right + radius_, bottom + radius_); return coverage.TransformBounds(transform); } diff --git a/impeller/entity/geometry/point_field_geometry.h b/impeller/entity/geometry/point_field_geometry.h index cdbb19288671d..8ab8e8b4ae0bb 100644 --- a/impeller/entity/geometry/point_field_geometry.h +++ b/impeller/entity/geometry/point_field_geometry.h @@ -10,24 +10,31 @@ namespace impeller { /// @brief A geometry class specialized for Canvas::DrawPoints. +/// +/// Does not hold ownership of the allocated point data, which is expected to be +/// maintained via the display list structure. class PointFieldGeometry final : public Geometry { public: - PointFieldGeometry(std::vector points, Scalar radius, bool round); + PointFieldGeometry(const Point* points, + size_t point_count, + Scalar radius, + bool round); ~PointFieldGeometry() override; + // |Geometry| + std::optional GetCoverage(const Matrix& transform) const override; + private: // |Geometry| GeometryResult GetPositionBuffer(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const override; - // |Geometry| - std::optional GetCoverage(const Matrix& transform) const override; - - std::vector points_; + size_t point_count_; Scalar radius_; bool round_; + const Point* points_; PointFieldGeometry(const PointFieldGeometry&) = delete; diff --git a/impeller/entity/geometry/round_rect_geometry.cc b/impeller/entity/geometry/round_rect_geometry.cc index 1d673f53add31..060303c605eaa 100644 --- a/impeller/entity/geometry/round_rect_geometry.cc +++ b/impeller/entity/geometry/round_rect_geometry.cc @@ -16,7 +16,7 @@ GeometryResult RoundRectGeometry::GetPositionBuffer( const Entity& entity, RenderPass& pass) const { return ComputePositionGeometry(renderer, - renderer.GetTessellator()->FilledRoundRect( + renderer.GetTessellator().FilledRoundRect( entity.GetTransform(), bounds_, radii_), entity, pass); } diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc new file mode 100644 index 0000000000000..b595b169f1738 --- /dev/null +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -0,0 +1,429 @@ +// 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. + +#include + +#include "flutter/impeller/entity/geometry/round_superellipse_geometry.h" + +#include "impeller/geometry/constants.h" + +namespace impeller { + +namespace { +// A look up table with precomputed variables. +// +// The columns represent the following variabls respectively: +// +// * ratio = size / a +// * n +// * d / a +// * thetaJ +// +// For definition of the variables, see DrawOctantSquareLikeSquircle. +constexpr Scalar kPrecomputedVariables[][4] = { + {2.000, 2.00000, 0.00000, 0.24040}, // + {2.020, 2.03340, 0.01447, 0.24040}, // + {2.040, 2.06540, 0.02575, 0.21167}, // + {2.060, 2.09800, 0.03668, 0.20118}, // + {2.080, 2.13160, 0.04719, 0.19367}, // + {2.100, 2.17840, 0.05603, 0.16233}, // + {2.120, 2.19310, 0.06816, 0.20020}, // + {2.140, 2.22990, 0.07746, 0.19131}, // + {2.160, 2.26360, 0.08693, 0.19008}, // + {2.180, 2.30540, 0.09536, 0.17935}, // + {2.200, 2.32900, 0.10541, 0.19136}, // + {2.220, 2.38330, 0.11237, 0.17130}, // + {2.240, 2.39770, 0.12271, 0.18956}, // + {2.260, 2.41770, 0.13251, 0.20254}, // + {2.280, 2.47180, 0.13879, 0.18454}, // + {2.300, 2.50910, 0.14658, 0.18261} // +}; + +constexpr size_t kNumRecords = + sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]); +constexpr Scalar kMinRatio = kPrecomputedVariables[0][0]; +constexpr Scalar kMaxRatio = kPrecomputedVariables[kNumRecords - 1][0]; +constexpr Scalar kRatioStep = + kPrecomputedVariables[1][0] - kPrecomputedVariables[0][0]; + +// Linear interpolation for `kPrecomputedVariables`. +// +// The `column` is a 0-based index that decides the target variable, where 1 +// corresponds to the 2nd element of each row, etc. +// +// The `ratio` corresponds to column 0, on which the lerp is calculated. +Scalar LerpPrecomputedVariable(size_t column, Scalar ratio) { + Scalar steps = + std::clamp((ratio - kMinRatio) / kRatioStep, 0, kNumRecords - 1); + size_t left = + std::clamp((size_t)std::floor(steps), 0, kNumRecords - 2); + Scalar frac = steps - left; + + return (1 - frac) * kPrecomputedVariables[left][column] + + frac * kPrecomputedVariables[left + 1][column]; +} + +// Return the shortest of `corner_radius`, height/2, and width/2. +// +// Corner radii longer than 1/2 of the side length does not make sense, and will +// be limited to the longest possible. +Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { + return std::min(corner_radius, + std::min(bounds.GetWidth() / 2, bounds.GetHeight() / 2)); +} + +// The max angular step that the algorithm will traverse a quadrant of the +// curve. +// +// This limits the max number of points of the curve. +constexpr Scalar kMaxQuadrantSteps = 40; + +// Calculates the angular step size for a smooth curve. +// +// Returns the angular step needed to ensure a curve appears smooth +// based on the smallest dimension of a shape. Smaller dimensions require +// larger steps as less detail is needed for smoothness. +// +// The `minDimension` is the smallest dimension (e.g., width or height) of the +// shape. +// +// The `fullAngle` is the total angular range to traverse. +Scalar CalculateStep(Scalar minDimension, Scalar fullAngle) { + constexpr Scalar kMinAngleStep = kPiOver2 / kMaxQuadrantSteps; + + // Assumes at least 1 point is needed per pixel to achieve sufficient + // smoothness. + constexpr Scalar pointsPerPixel = 1.0; + size_t pointsByDimension = (size_t)std::ceil(minDimension * pointsPerPixel); + Scalar angleByDimension = fullAngle / pointsByDimension; + + return std::min(kMinAngleStep, angleByDimension); +} + +// The distance from point M (the 45deg point) to either side of the closer +// bounding box is defined as `CalculateGap`. +constexpr Scalar CalculateGap(Scalar corner_radius) { + // Heuristic formula derived from experimentation. + return 0.2924066406 * corner_radius; +} + +// Draw a circular arc from `start` to `end` with a radius of `r`. +// +// It is assumed that `start` is north-west to `end`, and the center +// of the circle is south-west to both points. +// +// The resulting points are appended to `output` and include the starting point +// but exclude the ending point. +// +// Returns the number of the +size_t DrawCircularArc(Point* output, Point start, Point end, Scalar r) { + /* Denote the middle point of S and E as M. The key is to find the center of + * the circle. + * S --__ + * / ⟍ `、 + * / M ⟍\ + * / ⟋ E + * / ⟋ ↗ + * / ⟋ + * / ⟋ r + * C ᜱ ↙ + */ + + Point s_to_e = end - start; + Point m = (start + end) / 2; + Point c_to_m = Point(-s_to_e.y, s_to_e.x); + Scalar distance_sm = s_to_e.GetLength() / 2; + Scalar distance_cm = sqrt(r * r - distance_sm * distance_sm); + Point c = m - distance_cm * c_to_m.Normalize(); + Scalar angle_sce = asinf(distance_sm / r) * 2; + Point c_to_s = start - c; + + Scalar step = CalculateStep(std::abs(s_to_e.y), angle_sce); + + Point* next = output; + Scalar angle = 0; + while (angle < angle_sce) { + *(next++) = c_to_s.Rotate(Radians(-angle)) + c; + angle += step; + } + return next - output; +} + +// Draws an arc representing the top 1/8 segment of a square-like rounded +// superellipse. +// +// The resulting arc centers at the origin, spanning from 0 to pi/4, moving +// clockwise starting from the positive Y-axis, and includes the starting point +// (the middle of the top flat side) while excluding the ending point (the x=y +// point). +// +// The full square-like rounded superellipse has a width and height specified by +// `size` and features rounded corners determined by `corner_radius`. The +// `corner_radius` corresponds to the `cornerRadius` parameter in SwiftUI, +// rather than the literal radius of corner circles. +// +// Returns the number of points generated. +size_t DrawOctantSquareLikeSquircle(Point* output, + Scalar size, + Scalar corner_radius) { + /* The following figure shows the first quadrant of a square-like rounded + * superellipse. The target arc consists of the "stretch" (AB), a + * superellipsoid arc (BJ), and a circular arc (JM). + * + * straight superelipse + * ↓ ↓ + * A B J circular arc + * ---------...._ ↙ + * | | / `⟍ M + * | | / ⟋ ⟍ + * | | / ⟋ \ + * | | / ⟋ | + * | | ᜱD | + * | | / | + * ↑ +----+ | + * s | | | + * ↓ +----+---------------| A' + * O S + * ← s → + * ←------ size/2 ------→ + * + * Define gap (g) as the distance between point M and the bounding box, + * therefore point M is at (size/2 - g, size/2 - g). + * + * The superellipsoid curve can be drawn with an implicit parameter θ: + * x = a * sinθ ^ (2/n) + * y = a * cosθ ^ (2/n) + * https://math.stackexchange.com/questions/2573746/superellipse-parametric-equation + * + * Define thetaJ as the θ at point J. + */ + + Scalar ratio = {std::min(size / corner_radius, kMaxRatio)}; + Scalar a = ratio * corner_radius / 2; + Scalar s = size / 2 - a; + Scalar g = CalculateGap(corner_radius); + + Scalar n = LerpPrecomputedVariable(1, ratio); + Scalar d = LerpPrecomputedVariable(2, ratio) * a; + Scalar thetaJ = LerpPrecomputedVariable(3, ratio); + + Scalar R = (a - d - g) * sqrt(2); + + Point pointM(size / 2 - g, size / 2 - g); + + Scalar xJ = a * pow(abs(sinf(thetaJ)), 2 / n); + Scalar yJ = a * pow(abs(cosf(thetaJ)), 2 / n); + + Point* next = output; + // A + *(next++) = Point(0, size / 2); + // Superellipsoid arc BJ (B inclusive, J exclusive) + { + Scalar step = CalculateStep(a - yJ, thetaJ); + Scalar angle = 0; + while (angle < thetaJ) { + Scalar x = a * pow(abs(sinf(angle)), 2 / n); + Scalar y = a * pow(abs(cosf(angle)), 2 / n); + *(next++) = Point(x + s, y + s); + angle += step; + } + } + + // Circular arc JM (B inclusive, M exclusive) + next += DrawCircularArc(next, {xJ + s, yJ + s}, pointM, R); + return next - output; +} + +// Optionally `flip` the input points before offsetting it by `center`, and +// append the result to `output`. +// +// If `flip` is true, then the entire input list is reversed, and the x and y +// coordinate of each point is swapped as well. This effectively mirrors the +// input point list by the y=x line. +size_t FlipAndOffset(Point* output, + const Point* input, + size_t input_length, + bool flip, + const Point& center) { + if (!flip) { + for (size_t i = 0; i < input_length; i++) { + output[i] = input[i] + center; + } + } else { + for (size_t i = 0; i < input_length; i++) { + const Point& point = input[input_length - i - 1]; + output[i] = Point(point.y + center.x, point.x + center.y); + } + } + return input_length; +} + +constexpr Point kReflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; + +// Mirror the point list `quad` into other quadrants and output as a triangle +// strip. +// +// The input arc `quad` should reside in the first quadrant, starting at +// positive Y axis and ending at positive X axis (both ends inclusive), for a +// total of `quad_length` points. This function mirrors the arc into 4 +// quadrants, offset the result by `center`, and rearrange it as a triangle +// strip, which is appended to `output`. +// +// A total of (quad_length - 1) * 4 points will be appended, and `output` must +// have sufficient memory allocated before this call. +void MirrorIntoTriangleStrip(const Point* quad, + size_t quad_length, + const Point& center, + Point* output) { + // The length of 1/4 arc including the starting point but excluding the + // ending point. + const size_t arc_length = quad_length - 1; + auto GetPoint = [quad, arc_length](size_t i) -> Point { + if (i < arc_length) { + return quad[i]; + } + i = i - arc_length; + if (i < arc_length) { + return quad[arc_length - i] * kReflection[1]; + } + i = i - arc_length; + if (i < arc_length) { + return quad[i] * kReflection[2]; + } + i = i - arc_length; + if (i < arc_length) { + return quad[arc_length - i] * kReflection[3]; + } else { + // Unreachable + return Point(); + } + }; + + size_t index_count = 0; + + output[index_count++] = GetPoint(0) + center; + + size_t a = 1; + size_t b = arc_length * 4 - 1; + while (a < b) { + output[index_count++] = GetPoint(a) + center; + output[index_count++] = GetPoint(b) + center; + a++; + b--; + } + if (a == b) { + output[index_count++] = GetPoint(b) + center; + } +} + +} // namespace + +RoundSuperellipseGeometry::RoundSuperellipseGeometry(const Rect& bounds, + Scalar corner_radius) + : bounds_(bounds), corner_radius_(LimitRadius(corner_radius, bounds)) {} + +RoundSuperellipseGeometry::~RoundSuperellipseGeometry() {} + +GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + const Size size = bounds_.GetSize(); + const Point center = bounds_.GetCenter(); + + // The full shape is divided into 4 segments: the top and bottom edges come + // from two square-like rounded superellipses (called "width-aligned"), while + // the left and right squircles come from another two ("height-aligned"). + // + // Denote the distance from the center of the square-like squircles to the + // origin as `c`. The width-aligned square-like squircle and the + // height-aligned one have the same offset in different directions. + const Scalar c = (size.width - size.height) / 2; + + // The cache is allocated as follows: + // + // * The first chunk stores the quadrant arc. + // * The second chunk stores an octant arc before flipping and translation. + Point* cache = renderer.GetTessellator().GetStrokePointCache().data(); + + // The memory size (in units of Points) allocated to store the first chunk. + constexpr size_t kMaxQuadrantLength = kPointArenaSize / 4; + // Since the curve is traversed in steps bounded by kMaxQuadrantSteps, the + // curving part will have fewer points than kMaxQuadrantSteps. Multiply it by + // 2 for storing other sporatic points (an extremely conservative estimate). + static_assert(kMaxQuadrantLength > 2 * kMaxQuadrantSteps); + + // Draw the first quadrant of the shape and store in `quadrant`, including + // both ends. It will be mirrored to other quadrants later. + Point* quadrant = cache; + size_t quadrant_length; + { + Point* next = quadrant; + + Point* octant_cache = cache + kMaxQuadrantLength; + size_t octant_length; + + octant_length = + DrawOctantSquareLikeSquircle(octant_cache, size.width, corner_radius_); + next += FlipAndOffset(next, octant_cache, octant_length, /*flip=*/false, + Point(0, -c)); + + *(next++) = Point(size / 2) - CalculateGap(corner_radius_); // Point M + + octant_length = + DrawOctantSquareLikeSquircle(octant_cache, size.height, corner_radius_); + next += FlipAndOffset(next, octant_cache, octant_length, /*flip=*/true, + Point(c, 0)); + + quadrant_length = next - quadrant; + } + + // The `contour_point_count` include all points on the border. The "-1" comes + // from duplicate ends from the mirrored arcs. + size_t contour_length = 4 * (quadrant_length - 1); + BufferView vertex_buffer = renderer.GetTransientsBuffer().Emplace( + nullptr, sizeof(Point) * contour_length, alignof(Point)); + Point* vertex_data = + reinterpret_cast(vertex_buffer.GetBuffer()->OnGetContents() + + vertex_buffer.GetRange().offset); + + MirrorIntoTriangleStrip(quadrant, quadrant_length, center, vertex_data); + + return GeometryResult{ + .type = PrimitiveType::kTriangleStrip, + .vertex_buffer = + { + .vertex_buffer = vertex_buffer, + .vertex_count = contour_length, + .index_type = IndexType::kNone, + }, + .transform = entity.GetShaderTransform(pass), + }; +} + +std::optional RoundSuperellipseGeometry::GetCoverage( + const Matrix& transform) const { + return bounds_.TransformBounds(transform); +} + +bool RoundSuperellipseGeometry::CoversArea(const Matrix& transform, + const Rect& rect) const { + if (!transform.IsTranslationScaleOnly()) { + return false; + } + // Use the rectangle formed by the four 45deg points (point M) as a + // conservative estimate of the inner rectangle. + Scalar g = CalculateGap(corner_radius_); + Rect coverage = + Rect::MakeLTRB(bounds_.GetLeft() + g, bounds_.GetTop() + g, + bounds_.GetRight() - g, bounds_.GetBottom() - g) + .TransformBounds(transform); + return coverage.Contains(rect); +} + +bool RoundSuperellipseGeometry::IsAxisAlignedRect() const { + return false; +} + +} // namespace impeller diff --git a/impeller/entity/geometry/round_superellipse_geometry.h b/impeller/entity/geometry/round_superellipse_geometry.h new file mode 100644 index 0000000000000..c5d5c7bfc9965 --- /dev/null +++ b/impeller/entity/geometry/round_superellipse_geometry.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef FLUTTER_IMPELLER_ENTITY_GEOMETRY_ROUND_SUPERELLIPSE_GEOMETRY_H_ +#define FLUTTER_IMPELLER_ENTITY_GEOMETRY_ROUND_SUPERELLIPSE_GEOMETRY_H_ + +#include "impeller/entity/geometry/geometry.h" + +namespace impeller { + +/// Geometry class that can generate vertices for a rounded superellipse. +/// +/// A superellipse is an ellipse-like shape that is defined by the parameters N, +/// alpha, and beta: +/// +/// 1 = |x / b| ^n + |y / a| ^n +/// +/// A rounded superellipse is a square-like superellipse (a=b) with its four +/// corners replaced by circular arcs. It replicates the `RoundedRectangle` +/// shape in SwiftUI with corner style `.continuous`. +/// +/// The `bounds` defines the position and size of the shape. The `corner_radius` +/// corresponds to SwiftUI's `cornerRadius` parameter, which is close to, but +/// not exactly equals to, the radius of the corner circles. +class RoundSuperellipseGeometry final : public Geometry { + public: + explicit RoundSuperellipseGeometry(const Rect& bounds, Scalar corner_radius); + + ~RoundSuperellipseGeometry() override; + + // |Geometry| + bool CoversArea(const Matrix& transform, const Rect& rect) const override; + + // |Geometry| + bool IsAxisAlignedRect() const override; + + private: + // |Geometry| + GeometryResult GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + // |Geometry| + std::optional GetCoverage(const Matrix& transform) const override; + + const Rect bounds_; + double corner_radius_; + + RoundSuperellipseGeometry(const RoundSuperellipseGeometry&) = delete; + + RoundSuperellipseGeometry& operator=(const RoundSuperellipseGeometry&) = + delete; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_ENTITY_GEOMETRY_ROUND_SUPERELLIPSE_GEOMETRY_H_ diff --git a/impeller/entity/geometry/stroke_path_geometry.cc b/impeller/entity/geometry/stroke_path_geometry.cc index aa06c3ad98ee0..a4fc27edbaf73 100644 --- a/impeller/entity/geometry/stroke_path_geometry.cc +++ b/impeller/entity/geometry/stroke_path_geometry.cc @@ -6,54 +6,69 @@ #include "impeller/core/buffer_view.h" #include "impeller/core/formats.h" +#include "impeller/core/host_buffer.h" #include "impeller/entity/geometry/geometry.h" #include "impeller/geometry/constants.h" #include "impeller/geometry/path_builder.h" #include "impeller/geometry/path_component.h" #include "impeller/geometry/separated_vector.h" +#include "impeller/geometry/wangs_formula.h" namespace impeller { -using VS = SolidFillVertexShader; namespace { -template -using CapProc = std::function& points) + : points_(points), oversized_() { + FML_DCHECK(points_.size() == kPointArenaSize); + } + + void AppendVertex(const Point& point) { + if (offset_ >= kPointArenaSize) { + oversized_.push_back(point); + } else { + points_[offset_++] = point; + } + } + + /// @brief Return the number of points used in the arena, followed by + /// the number of points allocated in the overized buffer. + std::pair GetUsedSize() const { + return std::make_pair(offset_, oversized_.size()); + } + + bool HasOversizedBuffer() const { return !oversized_.empty(); } + + const std::vector& GetOversizedBuffer() const { return oversized_; } + + private: + std::vector& points_; + std::vector oversized_; + size_t offset_ = 0u; +}; + +using CapProc = std::function; -template -using JoinProc = std::function; -class PositionWriter { - public: - void AppendVertex(const Point& point) { - data_.emplace_back(SolidFillVertexShader::PerVertexData{.position = point}); - } - - const std::vector& GetData() const { - return data_; - } - - private: - std::vector data_ = {}; -}; - -template class StrokeGenerator { public: StrokeGenerator(const Path::Polyline& p_polyline, const Scalar p_stroke_width, const Scalar p_scaled_miter_limit, - const JoinProc& p_join_proc, - const CapProc& p_cap_proc, + const JoinProc& p_join_proc, + const CapProc& p_cap_proc, const Scalar p_scale) : polyline(p_polyline), stroke_width(p_stroke_width), @@ -62,7 +77,7 @@ class StrokeGenerator { cap_proc(p_cap_proc), scale(p_scale) {} - void Generate(VertexWriter& vtx_builder) { + void Generate(PositionWriter& vtx_builder) { for (size_t contour_i = 0; contour_i < polyline.contours.size(); contour_i++) { const Path::PolylineContour& contour = polyline.contours[contour_i]; @@ -70,7 +85,7 @@ class StrokeGenerator { std::tie(contour_start_point_i, contour_end_point_i) = polyline.GetContourPointBounds(contour_i); - auto contour_delta = contour_end_point_i - contour_start_point_i; + size_t contour_delta = contour_end_point_i - contour_start_point_i; if (contour_delta == 1) { Point p = polyline.GetPoint(contour_start_point_i); cap_proc(vtx_builder, p, {-stroke_width * 0.5f, 0}, scale, @@ -177,7 +192,7 @@ class StrokeGenerator { stroke_width * 0.5f); } - void AddVerticesForLinearComponent(VertexWriter& vtx_builder, + void AddVerticesForLinearComponent(PositionWriter& vtx_builder, const size_t component_start_index, const size_t component_end_index, const size_t contour_start_point_i, @@ -216,7 +231,7 @@ class StrokeGenerator { } } - void AddVerticesForCurveComponent(VertexWriter& vtx_builder, + void AddVerticesForCurveComponent(PositionWriter& vtx_builder, const size_t component_start_index, const size_t component_end_index, const size_t contour_start_point_i, @@ -296,8 +311,8 @@ class StrokeGenerator { const Path::Polyline& polyline; const Scalar stroke_width; const Scalar scaled_miter_limit; - const JoinProc& join_proc; - const CapProc& cap_proc; + const JoinProc& join_proc; + const CapProc& cap_proc; const Scalar scale; SeparatedVector2 previous_offset; @@ -305,22 +320,17 @@ class StrokeGenerator { SolidFillVertexShader::PerVertexData vtx; }; -template -void CreateButtCap(VertexWriter& vtx_builder, +void CreateButtCap(PositionWriter& vtx_builder, const Point& position, const Point& offset, Scalar scale, bool reverse) { Point orientation = offset * (reverse ? -1 : 1); - VS::PerVertexData vtx; - vtx.position = position + orientation; - vtx_builder.AppendVertex(vtx.position); - vtx.position = position - orientation; - vtx_builder.AppendVertex(vtx.position); + vtx_builder.AppendVertex(position + orientation); + vtx_builder.AppendVertex(position - orientation); } -template -void CreateRoundCap(VertexWriter& vtx_builder, +void CreateRoundCap(PositionWriter& vtx_builder, const Point& position, const Point& offset, Scalar scale, @@ -347,17 +357,23 @@ void CreateRoundCap(VertexWriter& vtx_builder, vtx = position - orientation; vtx_builder.AppendVertex(vtx); - arc.ToLinearPathComponents(scale, [&vtx_builder, &vtx, forward_normal, - position](const Point& point) { + Scalar line_count = std::ceilf(ComputeCubicSubdivisions(scale, arc)); + for (size_t i = 1; i < line_count; i++) { + Point point = arc.Solve(i / line_count); vtx = position + point; vtx_builder.AppendVertex(vtx); vtx = position + (-point).Reflect(forward_normal); vtx_builder.AppendVertex(vtx); - }); + } + + Point point = arc.p2; + vtx = position + point; + vtx_builder.AppendVertex(position + point); + vtx = position + (-point).Reflect(forward_normal); + vtx_builder.AppendVertex(vtx); } -template -void CreateSquareCap(VertexWriter& vtx_builder, +void CreateSquareCap(PositionWriter& vtx_builder, const Point& position, const Point& offset, Scalar scale, @@ -375,8 +391,7 @@ void CreateSquareCap(VertexWriter& vtx_builder, vtx_builder.AppendVertex(vtx); } -template -Scalar CreateBevelAndGetDirection(VertexWriter& vtx_builder, +Scalar CreateBevelAndGetDirection(PositionWriter& vtx_builder, const Point& position, const Point& start_offset, const Point& end_offset) { @@ -392,8 +407,7 @@ Scalar CreateBevelAndGetDirection(VertexWriter& vtx_builder, return dir; } -template -void CreateMiterJoin(VertexWriter& vtx_builder, +void CreateMiterJoin(PositionWriter& vtx_builder, const Point& position, const Point& start_offset, const Point& end_offset, @@ -417,13 +431,10 @@ void CreateMiterJoin(VertexWriter& vtx_builder, } // Outer miter point. - VS::PerVertexData vtx; - vtx.position = position + miter_point * direction; - vtx_builder.AppendVertex(vtx.position); + vtx_builder.AppendVertex(position + miter_point * direction); } -template -void CreateRoundJoin(VertexWriter& vtx_builder, +void CreateRoundJoin(PositionWriter& vtx_builder, const Point& position, const Point& start_offset, const Point& end_offset, @@ -452,19 +463,20 @@ void CreateRoundJoin(VertexWriter& vtx_builder, PathBuilder::kArcApproximationMagic * alignment * direction; - VS::PerVertexData vtx; - CubicPathComponent(start_offset, start_handle, middle_handle, middle) - .ToLinearPathComponents(scale, [&vtx_builder, direction, &vtx, position, - middle_normal](const Point& point) { - vtx.position = position + point * direction; - vtx_builder.AppendVertex(vtx.position); - vtx.position = position + (-point * direction).Reflect(middle_normal); - vtx_builder.AppendVertex(vtx.position); - }); + CubicPathComponent arc(start_offset, start_handle, middle_handle, middle); + Scalar line_count = std::ceilf(ComputeCubicSubdivisions(scale, arc)); + for (size_t i = 1; i < line_count; i++) { + Point point = arc.Solve(i / line_count); + vtx_builder.AppendVertex(position + point * direction); + vtx_builder.AppendVertex(position + + (-point * direction).Reflect(middle_normal)); + } + vtx_builder.AppendVertex(position + arc.p2 * direction); + vtx_builder.AppendVertex(position + + (-arc.p2 * direction).Reflect(middle_normal)); } -template -void CreateBevelJoin(VertexWriter& vtx_builder, +void CreateBevelJoin(PositionWriter& vtx_builder, const Point& position, const Point& start_offset, const Point& end_offset, @@ -473,13 +485,12 @@ void CreateBevelJoin(VertexWriter& vtx_builder, CreateBevelAndGetDirection(vtx_builder, position, start_offset, end_offset); } -template -void CreateSolidStrokeVertices(VertexWriter& vtx_builder, +void CreateSolidStrokeVertices(PositionWriter& vtx_builder, const Path::Polyline& polyline, Scalar stroke_width, Scalar scaled_miter_limit, - const JoinProc& join_proc, - const CapProc& cap_proc, + const JoinProc& join_proc, + const CapProc& cap_proc, Scalar scale) { StrokeGenerator stroke_generator(polyline, stroke_width, scaled_miter_limit, join_proc, cap_proc, scale); @@ -487,46 +498,46 @@ void CreateSolidStrokeVertices(VertexWriter& vtx_builder, } // static -template -JoinProc GetJoinProc(Join stroke_join) { + +JoinProc GetJoinProc(Join stroke_join) { switch (stroke_join) { case Join::kBevel: - return &CreateBevelJoin; + return &CreateBevelJoin; case Join::kMiter: - return &CreateMiterJoin; + return &CreateMiterJoin; case Join::kRound: - return &CreateRoundJoin; + return &CreateRoundJoin; } } -template -CapProc GetCapProc(Cap stroke_cap) { +CapProc GetCapProc(Cap stroke_cap) { switch (stroke_cap) { case Cap::kButt: - return &CreateButtCap; + return &CreateButtCap; case Cap::kRound: - return &CreateRoundCap; + return &CreateRoundCap; case Cap::kSquare: - return &CreateSquareCap; + return &CreateSquareCap; } } } // namespace -std::vector -StrokePathGeometry::GenerateSolidStrokeVertices(const Path::Polyline& polyline, - Scalar stroke_width, - Scalar miter_limit, - Join stroke_join, - Cap stroke_cap, - Scalar scale) { +std::vector StrokePathGeometry::GenerateSolidStrokeVertices( + const Path::Polyline& polyline, + Scalar stroke_width, + Scalar miter_limit, + Join stroke_join, + Cap stroke_cap, + Scalar scale) { auto scaled_miter_limit = stroke_width * miter_limit * 0.5f; - auto join_proc = GetJoinProc(stroke_join); - auto cap_proc = GetCapProc(stroke_cap); + JoinProc join_proc = GetJoinProc(stroke_join); + CapProc cap_proc = GetCapProc(stroke_cap); StrokeGenerator stroke_generator(polyline, stroke_width, scaled_miter_limit, join_proc, cap_proc, scale); - PositionWriter vtx_builder; + std::vector points(4096); + PositionWriter vtx_builder(points); stroke_generator.Generate(vtx_builder); - return vtx_builder.GetData(); + return points; } StrokePathGeometry::StrokePathGeometry(const Path& path, @@ -574,38 +585,66 @@ GeometryResult StrokePathGeometry::GetPositionBuffer( return {}; } - Scalar min_size = - (pass.GetSampleCount() == SampleCount::kCount4 ? kMinStrokeSizeMSAA - : kMinStrokeSize) / - max_basis; + Scalar min_size = kMinStrokeSize / max_basis; Scalar stroke_width = std::max(stroke_width_, min_size); auto& host_buffer = renderer.GetTransientsBuffer(); auto scale = entity.GetTransform().GetMaxBasisLengthXY(); - PositionWriter position_writer; - auto polyline = renderer.GetTessellator()->CreateTempPolyline(path_, scale); + PositionWriter position_writer( + renderer.GetTessellator().GetStrokePointCache()); + Path::Polyline polyline = + renderer.GetTessellator().CreateTempPolyline(path_, scale); + CreateSolidStrokeVertices(position_writer, polyline, stroke_width, miter_limit_ * stroke_width_ * 0.5f, - GetJoinProc(stroke_join_), - GetCapProc(stroke_cap_), scale); - - BufferView buffer_view = - host_buffer.Emplace(position_writer.GetData().data(), - position_writer.GetData().size() * - sizeof(SolidFillVertexShader::PerVertexData), - alignof(SolidFillVertexShader::PerVertexData)); - - return GeometryResult{ - .type = PrimitiveType::kTriangleStrip, - .vertex_buffer = - { - .vertex_buffer = buffer_view, - .vertex_count = position_writer.GetData().size(), - .index_type = IndexType::kNone, - }, - .transform = entity.GetShaderTransform(pass), - .mode = GeometryResult::Mode::kPreventOverdraw}; + GetJoinProc(stroke_join_), GetCapProc(stroke_cap_), + scale); + + const auto [arena_length, oversized_length] = position_writer.GetUsedSize(); + if (!position_writer.HasOversizedBuffer()) { + BufferView buffer_view = host_buffer.Emplace( + renderer.GetTessellator().GetStrokePointCache().data(), + arena_length * sizeof(Point), alignof(Point)); + + return GeometryResult{.type = PrimitiveType::kTriangleStrip, + .vertex_buffer = + { + .vertex_buffer = buffer_view, + .vertex_count = arena_length, + .index_type = IndexType::kNone, + }, + .transform = entity.GetShaderTransform(pass), + .mode = GeometryResult::Mode::kPreventOverdraw}; + } + const std::vector& oversized_data = + position_writer.GetOversizedBuffer(); + BufferView buffer_view = host_buffer.Emplace( + /*buffer=*/nullptr, // + (arena_length + oversized_length) * sizeof(Point), // + alignof(Point) // + ); + memcpy(buffer_view.GetBuffer()->OnGetContents() + + buffer_view.GetRange().offset, // + renderer.GetTessellator().GetStrokePointCache().data(), // + arena_length * sizeof(Point) // + ); + memcpy(buffer_view.GetBuffer()->OnGetContents() + + buffer_view.GetRange().offset + arena_length * sizeof(Point), // + oversized_data.data(), // + oversized_data.size() * sizeof(Point) // + ); + buffer_view.GetBuffer()->Flush(buffer_view.GetRange()); + + return GeometryResult{.type = PrimitiveType::kTriangleStrip, + .vertex_buffer = + { + .vertex_buffer = buffer_view, + .vertex_count = arena_length + oversized_length, + .index_type = IndexType::kNone, + }, + .transform = entity.GetShaderTransform(pass), + .mode = GeometryResult::Mode::kPreventOverdraw}; } GeometryResult::Mode StrokePathGeometry::GetResultMode() const { diff --git a/impeller/entity/geometry/stroke_path_geometry.h b/impeller/entity/geometry/stroke_path_geometry.h index 4265710e6941a..95970d9dec3ba 100644 --- a/impeller/entity/geometry/stroke_path_geometry.h +++ b/impeller/entity/geometry/stroke_path_geometry.h @@ -44,13 +44,13 @@ class StrokePathGeometry final : public Geometry { std::optional GetCoverage(const Matrix& transform) const override; // Private for benchmarking and debugging - static std::vector - GenerateSolidStrokeVertices(const Path::Polyline& polyline, - Scalar stroke_width, - Scalar miter_limit, - Join stroke_join, - Cap stroke_cap, - Scalar scale); + static std::vector GenerateSolidStrokeVertices( + const Path::Polyline& polyline, + Scalar stroke_width, + Scalar miter_limit, + Join stroke_join, + Cap stroke_cap, + Scalar scale); friend class ImpellerBenchmarkAccessor; friend class ImpellerEntityUnitTestAccessor; diff --git a/impeller/entity/inline_pass_context.cc b/impeller/entity/inline_pass_context.cc index ac86af2139143..82d94a614ec5d 100644 --- a/impeller/entity/inline_pass_context.cc +++ b/impeller/entity/inline_pass_context.cc @@ -86,20 +86,13 @@ const std::shared_ptr& InlinePassContext::GetRenderPass() { return pass_; } - if (pass_target_.GetRenderTarget().GetColorAttachments().empty()) { - VALIDATION_LOG << "Color attachment unexpectedly missing from the " - "EntityPass render target."; - return pass_; - } - command_buffer_->SetLabel("EntityPass Command Buffer"); { // If the pass target has a resolve texture, then we're using MSAA. - bool is_msaa = pass_target_.GetRenderTarget() - .GetColorAttachments() - .find(0) - ->second.resolve_texture != nullptr; + bool is_msaa = + pass_target_.GetRenderTarget().GetColorAttachment(0).resolve_texture != + nullptr; if (pass_count_ > 0 && is_msaa) { pass_target_.Flip(renderer_); } @@ -107,8 +100,7 @@ const std::shared_ptr& InlinePassContext::GetRenderPass() { // Find the color attachment a second time, since the target may have just // flipped. - auto color0 = - pass_target_.GetRenderTarget().GetColorAttachments().find(0)->second; + ColorAttachment color0 = pass_target_.GetRenderTarget().GetColorAttachment(0); bool is_msaa = color0.resolve_texture != nullptr; if (pass_count_ > 0) { diff --git a/impeller/entity/render_target_cache.cc b/impeller/entity/render_target_cache.cc index dbfe0a9b50c63..3bc6e20d5dfce 100644 --- a/impeller/entity/render_target_cache.cc +++ b/impeller/entity/render_target_cache.cc @@ -3,12 +3,15 @@ // found in the LICENSE file. #include "impeller/entity/render_target_cache.h" +#include "impeller/core/formats.h" #include "impeller/renderer/render_target.h" namespace impeller { -RenderTargetCache::RenderTargetCache(std::shared_ptr allocator) - : RenderTargetAllocator(std::move(allocator)) {} +RenderTargetCache::RenderTargetCache(std::shared_ptr allocator, + uint32_t keep_alive_frame_count) + : RenderTargetAllocator(std::move(allocator)), + keep_alive_frame_count_(keep_alive_frame_count) {} void RenderTargetCache::Start() { for (auto& td : render_target_data_) { @@ -19,9 +22,12 @@ void RenderTargetCache::Start() { void RenderTargetCache::End() { std::vector retain; - for (const auto& td : render_target_data_) { + for (RenderTargetData& td : render_target_data_) { if (td.used_this_frame) { retain.push_back(td); + } else if (td.keep_alive_frame_count > 0) { + td.keep_alive_frame_count--; + retain.push_back(td); } } render_target_data_.swap(retain); @@ -48,14 +54,15 @@ RenderTarget RenderTargetCache::CreateOffscreen( .has_msaa = false, .has_depth_stencil = stencil_attachment_config.has_value(), }; - for (auto& render_target_data : render_target_data_) { - const auto other_config = render_target_data.config; + for (RenderTargetData& render_target_data : render_target_data_) { + const RenderTargetConfig other_config = render_target_data.config; if (!render_target_data.used_this_frame && other_config == config) { render_target_data.used_this_frame = true; - auto color0 = render_target_data.render_target.GetColorAttachments() - .find(0u) - ->second; - auto depth = render_target_data.render_target.GetDepthAttachment(); + render_target_data.keep_alive_frame_count = keep_alive_frame_count_; + ColorAttachment color0 = + render_target_data.render_target.GetColorAttachment(0); + std::optional depth = + render_target_data.render_target.GetDepthAttachment(); std::shared_ptr depth_tex = depth ? depth->texture : nullptr; return RenderTargetAllocator::CreateOffscreen( context, size, mip_count, label, color_attachment_config, @@ -68,10 +75,12 @@ RenderTarget RenderTargetCache::CreateOffscreen( if (!created_target.IsValid()) { return created_target; } - render_target_data_.push_back( - RenderTargetData{.used_this_frame = true, - .config = config, - .render_target = created_target}); + render_target_data_.push_back(RenderTargetData{ + .used_this_frame = true, // + .keep_alive_frame_count = keep_alive_frame_count_, // + .config = config, // + .render_target = created_target // + }); return created_target; } @@ -98,14 +107,15 @@ RenderTarget RenderTargetCache::CreateOffscreenMSAA( .has_msaa = true, .has_depth_stencil = stencil_attachment_config.has_value(), }; - for (auto& render_target_data : render_target_data_) { - const auto other_config = render_target_data.config; + for (RenderTargetData& render_target_data : render_target_data_) { + const RenderTargetConfig other_config = render_target_data.config; if (!render_target_data.used_this_frame && other_config == config) { render_target_data.used_this_frame = true; - auto color0 = render_target_data.render_target.GetColorAttachments() - .find(0u) - ->second; - auto depth = render_target_data.render_target.GetDepthAttachment(); + render_target_data.keep_alive_frame_count = keep_alive_frame_count_; + ColorAttachment color0 = + render_target_data.render_target.GetColorAttachment(0); + std::optional depth = + render_target_data.render_target.GetDepthAttachment(); std::shared_ptr depth_tex = depth ? depth->texture : nullptr; return RenderTargetAllocator::CreateOffscreenMSAA( context, size, mip_count, label, color_attachment_config, @@ -119,10 +129,12 @@ RenderTarget RenderTargetCache::CreateOffscreenMSAA( if (!created_target.IsValid()) { return created_target; } - render_target_data_.push_back( - RenderTargetData{.used_this_frame = true, - .config = config, - .render_target = created_target}); + render_target_data_.push_back(RenderTargetData{ + .used_this_frame = true, // + .keep_alive_frame_count = keep_alive_frame_count_, // + .config = config, // + .render_target = created_target // + }); return created_target; } diff --git a/impeller/entity/render_target_cache.h b/impeller/entity/render_target_cache.h index 7c7f91451b7cc..c877dc422e872 100644 --- a/impeller/entity/render_target_cache.h +++ b/impeller/entity/render_target_cache.h @@ -16,7 +16,8 @@ namespace impeller { /// Any textures unused after a frame are immediately discarded. class RenderTargetCache : public RenderTargetAllocator { public: - explicit RenderTargetCache(std::shared_ptr allocator); + explicit RenderTargetCache(std::shared_ptr allocator, + uint32_t keep_alive_frame_count = 4); ~RenderTargetCache() = default; @@ -59,11 +60,13 @@ class RenderTargetCache : public RenderTargetAllocator { private: struct RenderTargetData { bool used_this_frame; + uint32_t keep_alive_frame_count; RenderTargetConfig config; RenderTarget render_target; }; std::vector render_target_data_; + uint32_t keep_alive_frame_count_; RenderTargetCache(const RenderTargetCache&) = delete; diff --git a/impeller/entity/render_target_cache_unittests.cc b/impeller/entity/render_target_cache_unittests.cc index 73493e7641c85..4fe3ffb0c9c4d 100644 --- a/impeller/entity/render_target_cache_unittests.cc +++ b/impeller/entity/render_target_cache_unittests.cc @@ -7,6 +7,7 @@ #include "flutter/testing/testing.h" #include "impeller/base/validation.h" #include "impeller/core/allocator.h" +#include "impeller/core/formats.h" #include "impeller/core/texture_descriptor.h" #include "impeller/entity/entity_playground.h" #include "impeller/entity/render_target_cache.h" @@ -49,8 +50,8 @@ class TestAllocator : public Allocator { }; TEST_P(RenderTargetCacheTest, CachesUsedTexturesAcrossFrames) { - auto render_target_cache = - RenderTargetCache(GetContext()->GetResourceAllocator()); + auto render_target_cache = RenderTargetCache( + GetContext()->GetResourceAllocator(), /*keep_alive_frame_count=*/0); render_target_cache.Start(); // Create two render targets of the same exact size/shape. Both should be @@ -72,10 +73,41 @@ TEST_P(RenderTargetCacheTest, CachesUsedTexturesAcrossFrames) { EXPECT_EQ(render_target_cache.CachedTextureCount(), 1u); } +TEST_P(RenderTargetCacheTest, CachesUsedTexturesAcrossFramesWithKeepAlive) { + auto render_target_cache = RenderTargetCache( + GetContext()->GetResourceAllocator(), /*keep_alive_frame_count=*/3); + + render_target_cache.Start(); + // Create two render targets of the same exact size/shape. Both should be + // marked as used this frame, so the cached data set will contain two. + render_target_cache.CreateOffscreen(*GetContext(), {100, 100}, 1); + render_target_cache.CreateOffscreen(*GetContext(), {100, 100}, 1); + + EXPECT_EQ(render_target_cache.CachedTextureCount(), 2u); + + render_target_cache.End(); + render_target_cache.Start(); + + // The unused texture is kept alive until the keep alive countdown + // reaches 0. + EXPECT_EQ(render_target_cache.CachedTextureCount(), 2u); + + for (auto i = 0; i < 3; i++) { + render_target_cache.Start(); + render_target_cache.End(); + EXPECT_EQ(render_target_cache.CachedTextureCount(), 2u); + } + // After the countdown has elapsed the texture is removed. + render_target_cache.Start(); + render_target_cache.End(); + EXPECT_EQ(render_target_cache.CachedTextureCount(), 0u); +} + TEST_P(RenderTargetCacheTest, DoesNotPersistFailedAllocations) { ScopedValidationDisable disable; auto allocator = std::make_shared(); - auto render_target_cache = RenderTargetCache(allocator); + auto render_target_cache = + RenderTargetCache(allocator, /*keep_alive_frame_count=*/0); render_target_cache.Start(); allocator->should_fail = true; @@ -88,8 +120,8 @@ TEST_P(RenderTargetCacheTest, DoesNotPersistFailedAllocations) { } TEST_P(RenderTargetCacheTest, CachedTextureGetsNewAttachmentConfig) { - auto render_target_cache = - RenderTargetCache(GetContext()->GetResourceAllocator()); + auto render_target_cache = RenderTargetCache( + GetContext()->GetResourceAllocator(), /*keep_alive_frame_count=*/0); render_target_cache.Start(); RenderTarget::AttachmentConfig color_attachment_config = @@ -104,8 +136,8 @@ TEST_P(RenderTargetCacheTest, CachedTextureGetsNewAttachmentConfig) { *GetContext(), {100, 100}, 1, "Offscreen2", color_attachment_config); render_target_cache.End(); - auto color1 = target1.GetColorAttachments().find(0)->second; - auto color2 = target2.GetColorAttachments().find(0)->second; + ColorAttachment color1 = target1.GetColorAttachment(0); + ColorAttachment color2 = target2.GetColorAttachment(0); // The second color attachment should reuse the first attachment's texture // but with attributes from the second AttachmentConfig. EXPECT_EQ(color2.texture, color1.texture); @@ -113,8 +145,8 @@ TEST_P(RenderTargetCacheTest, CachedTextureGetsNewAttachmentConfig) { } TEST_P(RenderTargetCacheTest, CreateWithEmptySize) { - auto render_target_cache = - RenderTargetCache(GetContext()->GetResourceAllocator()); + auto render_target_cache = RenderTargetCache( + GetContext()->GetResourceAllocator(), /*keep_alive_frame_count=*/0); render_target_cache.Start(); RenderTarget empty_target = diff --git a/impeller/entity/save_layer_utils.cc b/impeller/entity/save_layer_utils.cc index 304990240ec84..f6ad180f8cfba 100644 --- a/impeller/entity/save_layer_utils.cc +++ b/impeller/entity/save_layer_utils.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "impeller/entity/save_layer_utils.h" +#include "impeller/geometry/scalar.h" namespace impeller { @@ -11,6 +12,8 @@ bool SizeDifferenceUnderThreshold(Size a, Size b, Scalar threshold) { return (std::abs(a.width - b.width) / b.width) < threshold && (std::abs(a.height - b.height) / b.height) < threshold; } + +static constexpr Scalar kDefaultSizeThreshold = 0.3; } // namespace std::optional ComputeSaveLayerCoverage( @@ -83,7 +86,8 @@ std::optional ComputeSaveLayerCoverage( transformed_coverage.Intersection(source_coverage_limit.value()); if (intersected_coverage.has_value() && SizeDifferenceUnderThreshold(transformed_coverage.GetSize(), - intersected_coverage->GetSize(), 0.2)) { + intersected_coverage->GetSize(), + kDefaultSizeThreshold)) { // Returning the transformed coverage is always correct, it just may // be larger than the clip area or onscreen texture. return transformed_coverage; @@ -106,8 +110,22 @@ std::optional ComputeSaveLayerCoverage( // Transform the input coverage into the global coordinate space before // computing the bounds limit intersection. - return coverage.TransformBounds(effect_transform) - .Intersection(coverage_limit); + Rect transformed_coverage = coverage.TransformBounds(effect_transform); + std::optional intersection = + transformed_coverage.Intersection(coverage_limit); + if (!intersection.has_value()) { + return std::nullopt; + } + // The the resulting coverage rect is nearly the same as the coverage_limit, + // round up to the coverage_limit. + Rect intersect_rect = intersection.value(); + if (SizeDifferenceUnderThreshold(intersect_rect.GetSize(), + coverage_limit.GetSize(), + kDefaultSizeThreshold)) { + return coverage_limit; + } + + return intersect_rect; } } // namespace impeller diff --git a/impeller/entity/save_layer_utils_unittests.cc b/impeller/entity/save_layer_utils_unittests.cc index b72ac33ae768b..5139204d7c2af 100644 --- a/impeller/entity/save_layer_utils_unittests.cc +++ b/impeller/entity/save_layer_utils_unittests.cc @@ -280,6 +280,63 @@ TEST(SaveLayerUtilsTest, EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 50, 50)); } +TEST(SaveLayerUtilsTest, RoundUpCoverageWhenCloseToCoverageLimit) { + // X varies, translation is performed on coverage. + auto coverage = ComputeSaveLayerCoverage( + /*content_coverage=*/Rect::MakeLTRB(0, 0, 90, 90), // + /*effect_transform=*/{}, // + /*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), // + /*image_filter=*/nullptr // + ); + + ASSERT_TRUE(coverage.has_value()); + // Size that matches coverage limit + EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 100, 100)); +} + +TEST(SaveLayerUtilsTest, DontRoundUpCoverageWhenNotCloseToCoverageLimitWidth) { + // X varies, translation is performed on coverage. + auto coverage = ComputeSaveLayerCoverage( + /*content_coverage=*/Rect::MakeLTRB(0, 0, 50, 90), // + /*effect_transform=*/{}, // + /*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), // + /*image_filter=*/nullptr // + ); + + ASSERT_TRUE(coverage.has_value()); + // Size that matches coverage limit + EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 50, 90)); +} + +TEST(SaveLayerUtilsTest, DontRoundUpCoverageWhenNotCloseToCoverageLimitHeight) { + // X varies, translation is performed on coverage. + auto coverage = ComputeSaveLayerCoverage( + /*content_coverage=*/Rect::MakeLTRB(0, 0, 90, 50), // + /*effect_transform=*/{}, // + /*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), // + /*image_filter=*/nullptr // + ); + + ASSERT_TRUE(coverage.has_value()); + // Size that matches coverage limit + EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 90, 50)); +} + +TEST(SaveLayerUtilsTest, + DontRoundUpCoverageWhenNotCloseToCoverageLimitWidthHeight) { + // X varies, translation is performed on coverage. + auto coverage = ComputeSaveLayerCoverage( + /*content_coverage=*/Rect::MakeLTRB(0, 0, 50, 50), // + /*effect_transform=*/{}, // + /*coverage_limit=*/Rect::MakeLTRB(0, 0, 100, 100), // + /*image_filter=*/nullptr // + ); + + ASSERT_TRUE(coverage.has_value()); + // Size that matches coverage limit + EXPECT_EQ(coverage.value(), Rect::MakeLTRB(0, 0, 50, 50)); +} + } // namespace testing } // namespace impeller diff --git a/impeller/entity/shaders/blending/advanced_blend.frag b/impeller/entity/shaders/blending/advanced_blend.frag index db444f522a092..ff553b7c6b25e 100644 --- a/impeller/entity/shaders/blending/advanced_blend.frag +++ b/impeller/entity/shaders/blending/advanced_blend.frag @@ -40,7 +40,7 @@ void main() { IPHalfUnpremultiply(Sample(texture_sampler_dst, // sampler v_dst_texture_coords // texture coordinates )); - dst *= blend_info.dst_input_alpha; + dst.a *= blend_info.dst_input_alpha; f16vec4 src = blend_info.color_factor > 0.0hf ? blend_info.color : IPHalfUnpremultiply(Sample( diff --git a/impeller/entity/shaders/blending/framebuffer_blend.frag b/impeller/entity/shaders/blending/framebuffer_blend.frag index 6d03856b4a824..624bb7dc17d71 100644 --- a/impeller/entity/shaders/blending/framebuffer_blend.frag +++ b/impeller/entity/shaders/blending/framebuffer_blend.frag @@ -28,6 +28,7 @@ uniform sampler2D texture_sampler_src; uniform FragInfo { float16_t src_input_alpha; + float16_t dst_input_alpha; } frag_info; @@ -44,6 +45,7 @@ vec4 Sample(sampler2D texture_sampler, vec2 texture_coords) { void main() { f16vec4 dst = IPHalfUnpremultiply(f16vec4(ReadDestination())); + dst.a *= frag_info.dst_input_alpha; f16vec4 src = IPHalfUnpremultiply( f16vec4(Sample(texture_sampler_src, // sampler v_src_texture_coords // texture coordinates diff --git a/impeller/entity/shaders/downsample.glsl b/impeller/entity/shaders/downsample.glsl new file mode 100644 index 0000000000000..58973b3043aaa --- /dev/null +++ b/impeller/entity/shaders/downsample.glsl @@ -0,0 +1,44 @@ +// 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. + +precision mediump float; + +#include +#include + +uniform f16sampler2D texture_sampler; + +uniform FragInfo { + float edge; + float ratio; + vec2 pixel_size; +} +frag_info; + +in highp vec2 v_texture_coords; + +out vec4 frag_color; + +vec4 Sample(vec2 uv) { +#ifdef SUPPORTS_DECAL + return texture(texture_sampler, uv, float16_t(kDefaultMipBias)); +#else + if ((uv.x < 0 || uv.y < 0 || uv.x > 1 || uv.y > 1)) { + return vec4(0); + } else { + return texture(texture_sampler, uv, float16_t(kDefaultMipBias)); + } +#endif +} + +void main() { + vec4 total = vec4(0.0); + for (float i = -frag_info.edge; i <= frag_info.edge; i += 2) { + for (float j = -frag_info.edge; j <= frag_info.edge; j += 2) { + total += (Sample(v_texture_coords + frag_info.pixel_size * vec2(i, j)) * + frag_info.ratio); + } + } + frag_color = total; +} diff --git a/impeller/entity/shaders/gradients/conical_gradient_uniform_fill.frag b/impeller/entity/shaders/gradients/conical_gradient_uniform_fill.frag new file mode 100644 index 0000000000000..b99f753e98d48 --- /dev/null +++ b/impeller/entity/shaders/gradients/conical_gradient_uniform_fill.frag @@ -0,0 +1,68 @@ +// 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. + +precision mediump float; + +#include +#include +#include +#include +#include + +uniform FragInfo { + highp vec2 center; + vec2 focus; + float focus_radius; + float radius; + float tile_mode; + float alpha; + float colors_length; + vec4 decal_border_color; + vec4 colors[256]; + vec4 stop_pairs[128]; +} +frag_info; + +highp in vec2 v_position; + +out vec4 frag_color; + +void main() { + vec2 res = IPComputeConicalT(frag_info.focus, frag_info.focus_radius, + frag_info.center, frag_info.radius, v_position); + + float t = res.x; + vec4 result_color = vec4(0); + if (res.y < 0.0 || + ((t < 0.0 || t > 1.0) && frag_info.tile_mode == kTileModeDecal)) { + result_color = frag_info.decal_border_color; + } else { + t = IPFloatTile(t, frag_info.tile_mode); + + vec2 prev_stop = frag_info.stop_pairs[0].xy; + bool even = false; + for (int i = 1; i < frag_info.colors_length; i++) { + // stop_pairs[i/2].xy = values for stop i + // stop_pairs[i/2].zw = values for stop i+1 + vec2 cur_stop = even ? frag_info.stop_pairs[i / 2].xy + : frag_info.stop_pairs[i / 2].zw; + even = !even; + // stop.x == t value + // stop.y == inverse_delta to next stop + if (t >= prev_stop.x && t <= cur_stop.x) { + if (cur_stop.y > 1000.0) { + result_color = frag_info.colors[i]; + } else { + float ratio = (t - prev_stop.x) * cur_stop.y; + result_color = + mix(frag_info.colors[i - 1], frag_info.colors[i], ratio); + } + break; + } + prev_stop = cur_stop; + } + } + + frag_color = IPPremultiply(result_color) * frag_info.alpha; +} diff --git a/impeller/entity/shaders/gradients/linear_gradient_uniform_fill.frag b/impeller/entity/shaders/gradients/linear_gradient_uniform_fill.frag new file mode 100644 index 0000000000000..ace605876d9be --- /dev/null +++ b/impeller/entity/shaders/gradients/linear_gradient_uniform_fill.frag @@ -0,0 +1,64 @@ +// 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. + +precision mediump float; + +#include +#include +#include +#include +#include + +uniform FragInfo { + highp vec2 start_point; + highp vec2 start_to_end; + float alpha; + float tile_mode; + float colors_length; + float inverse_dot_start_to_end; + vec4 decal_border_color; + vec4 colors[256]; + vec4 stop_pairs[128]; +} +frag_info; + +highp in vec2 v_position; + +out vec4 frag_color; + +void main() { + highp vec2 start_to_position = v_position - frag_info.start_point; + highp float t = dot(start_to_position, frag_info.start_to_end) * + frag_info.inverse_dot_start_to_end; + + if ((t < 0.0 || t > 1.0) && frag_info.tile_mode == kTileModeDecal) { + frag_color = frag_info.decal_border_color; + } else { + t = IPFloatTile(t, frag_info.tile_mode); + + vec2 prev_stop = frag_info.stop_pairs[0].xy; + bool even = false; + for (int i = 1; i < frag_info.colors_length; i++) { + // stop_pairs[i/2].xy = values for stop i + // stop_pairs[i/2].zw = values for stop i+1 + vec2 cur_stop = even ? frag_info.stop_pairs[i / 2].xy + : frag_info.stop_pairs[i / 2].zw; + even = !even; + // stop.x == t value + // stop.y == inverse_delta to next stop + if (t >= prev_stop.x && t <= cur_stop.x) { + if (cur_stop.y > 1000.0) { + frag_color = frag_info.colors[i]; + } else { + float ratio = (t - prev_stop.x) * cur_stop.y; + frag_color = mix(frag_info.colors[i - 1], frag_info.colors[i], ratio); + } + break; + } + prev_stop = cur_stop; + } + } + + frag_color = IPPremultiply(frag_color) * frag_info.alpha; +} diff --git a/impeller/entity/shaders/gradients/radial_gradient_uniform_fill.frag b/impeller/entity/shaders/gradients/radial_gradient_uniform_fill.frag new file mode 100644 index 0000000000000..f20163e68652d --- /dev/null +++ b/impeller/entity/shaders/gradients/radial_gradient_uniform_fill.frag @@ -0,0 +1,64 @@ +// 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. + +precision mediump float; + +#include +#include +#include +#include +#include + +uniform FragInfo { + highp vec2 center; + float radius; + float tile_mode; + float alpha; + float colors_length; + vec4 decal_border_color; + vec4 colors[256]; + vec4 stop_pairs[128]; +} +frag_info; + +highp in vec2 v_position; + +out vec4 frag_color; + +void main() { + float len = length(v_position - frag_info.center); + float t = len / frag_info.radius; + + vec4 result_color = vec4(0); + if ((t < 0.0 || t > 1.0) && frag_info.tile_mode == kTileModeDecal) { + result_color = frag_info.decal_border_color; + } else { + t = IPFloatTile(t, frag_info.tile_mode); + + vec2 prev_stop = frag_info.stop_pairs[0].xy; + bool even = false; + for (int i = 1; i < frag_info.colors_length; i++) { + // stop_pairs[i/2].xy = values for stop i + // stop_pairs[i/2].zw = values for stop i+1 + vec2 cur_stop = even ? frag_info.stop_pairs[i / 2].xy + : frag_info.stop_pairs[i / 2].zw; + even = !even; + // stop.x == t value + // stop.y == inverse_delta to next stop + if (t >= prev_stop.x && t <= cur_stop.x) { + if (cur_stop.y > 1000.0) { + result_color = frag_info.colors[i]; + } else { + float ratio = (t - prev_stop.x) * cur_stop.y; + result_color = + mix(frag_info.colors[i - 1], frag_info.colors[i], ratio); + } + break; + } + prev_stop = cur_stop; + } + } + + frag_color = IPPremultiply(result_color) * frag_info.alpha; +} diff --git a/impeller/entity/shaders/gradients/sweep_gradient_uniform_fill.frag b/impeller/entity/shaders/gradients/sweep_gradient_uniform_fill.frag new file mode 100644 index 0000000000000..d25b62c5ba857 --- /dev/null +++ b/impeller/entity/shaders/gradients/sweep_gradient_uniform_fill.frag @@ -0,0 +1,65 @@ +// 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. + +#include +#include +#include +#include +#include +#include + +uniform FragInfo { + highp vec2 center; + float bias; + float scale; + float tile_mode; + float alpha; + float colors_length; + vec4 decal_border_color; + vec4 colors[256]; + vec4 stop_pairs[128]; +} +frag_info; + +highp in vec2 v_position; + +out vec4 frag_color; + +void main() { + vec2 coord = v_position - frag_info.center; + float angle = atan(-coord.y, -coord.x); + float t = (angle * k1Over2Pi + 0.5 + frag_info.bias) * frag_info.scale; + + vec4 result_color = vec4(0); + if ((t < 0.0 || t > 1.0) && frag_info.tile_mode == kTileModeDecal) { + result_color = frag_info.decal_border_color; + } else { + t = IPFloatTile(t, frag_info.tile_mode); + + vec2 prev_stop = frag_info.stop_pairs[0].xy; + bool even = false; + for (int i = 1; i < frag_info.colors_length; i++) { + // stop_pairs[i/2].xy = values for stop i + // stop_pairs[i/2].zw = values for stop i+1 + vec2 cur_stop = even ? frag_info.stop_pairs[i / 2].xy + : frag_info.stop_pairs[i / 2].zw; + even = !even; + // stop.x == t value + // stop.y == inverse_delta to next stop + if (t >= prev_stop.x && t <= cur_stop.x) { + if (cur_stop.y > 1000.0) { + result_color = frag_info.colors[i]; + } else { + float ratio = (t - prev_stop.x) * cur_stop.y; + result_color = + mix(frag_info.colors[i - 1], frag_info.colors[i], ratio); + } + break; + } + prev_stop = cur_stop; + } + } + + frag_color = IPPremultiply(result_color) * frag_info.alpha; +} diff --git a/impeller/entity/shaders/texture_downsample.frag b/impeller/entity/shaders/texture_downsample.frag index 8b512fe29abf5..5b7b14d107002 100644 --- a/impeller/entity/shaders/texture_downsample.frag +++ b/impeller/entity/shaders/texture_downsample.frag @@ -2,33 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -precision mediump float; +#define SUPPORTS_DECAL 1 -#include -#include - -uniform f16sampler2D texture_sampler; - -uniform FragInfo { - float edge; - float ratio; - vec2 pixel_size; -} -frag_info; - -in highp vec2 v_texture_coords; - -out vec4 frag_color; - -void main() { - vec4 total = vec4(0.0); - for (float i = -frag_info.edge; i <= frag_info.edge; i += 2) { - for (float j = -frag_info.edge; j <= frag_info.edge; j += 2) { - total += (texture(texture_sampler, - v_texture_coords + frag_info.pixel_size * vec2(i, j), - float16_t(kDefaultMipBias)) * - frag_info.ratio); - } - } - frag_color = total; -} +#include diff --git a/impeller/entity/contents/test/contents_test_helpers.cc b/impeller/entity/shaders/texture_downsample_gles.frag similarity index 63% rename from impeller/entity/contents/test/contents_test_helpers.cc rename to impeller/entity/shaders/texture_downsample_gles.frag index 4ec4301d19346..a16583437c2d7 100644 --- a/impeller/entity/contents/test/contents_test_helpers.cc +++ b/impeller/entity/shaders/texture_downsample_gles.frag @@ -2,10 +2,4 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "impeller/entity/contents/test/contents_test_helpers.h" - -namespace impeller { - -// - -} \ No newline at end of file +#include diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn index dfeaae94b1720..0e97078395dd9 100644 --- a/impeller/fixtures/BUILD.gn +++ b/impeller/fixtures/BUILD.gn @@ -82,6 +82,7 @@ impellerc("runtime_stages") { shaders = [ "ink_sparkle.frag", "runtime_stage_example.frag", + "runtime_stage_filter_example.frag", "runtime_stage_simple.frag", "runtime_stage_position.frag", "gradient.frag", diff --git a/impeller/fixtures/dart_tests.dart b/impeller/fixtures/dart_tests.dart index f1e9f231473fc..c585b256f8d80 100644 --- a/impeller/fixtures/dart_tests.dart +++ b/impeller/fixtures/dart_tests.dart @@ -61,48 +61,6 @@ gpu.RenderPipeline createUnlitRenderPipeline() { return gpu.gpuContext.createRenderPipeline(vertex!, fragment!); } -gpu.RenderPass createRenderPass() { - final gpu.Texture? renderTexture = - gpu.gpuContext.createTexture(gpu.StorageMode.devicePrivate, 100, 100); - assert(renderTexture != null); - - final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer(); - - final gpu.RenderTarget renderTarget = gpu.RenderTarget.singleColor( - gpu.ColorAttachment(texture: renderTexture!), - ); - return commandBuffer.createRenderPass(renderTarget); -} - -@pragma('vm:entry-point') -void uniformBindFailsForInvalidHostBufferOffset() { - final gpu.RenderPass encoder = createRenderPass(); - - final gpu.RenderPipeline pipeline = createUnlitRenderPipeline(); - encoder.bindPipeline(pipeline); - - final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer(); - final gpu.BufferView vertInfoData = transients.emplace(float32([ - 1, 0, 0, 0, // mvp - 0, 1, 0, 0, // mvp - 0, 0, 1, 0, // mvp - 0, 0, 0, 1, // mvp - 0, 1, 0, 1, // color - ])); - final gpu.BufferView viewWithBadOffset = gpu.BufferView(vertInfoData.buffer, - offsetInBytes: 1, lengthInBytes: vertInfoData.lengthInBytes); - - final gpu.UniformSlot vertInfo = - pipeline.vertexShader.getUniformSlot('VertInfo'); - String? exception; - try { - encoder.bindUniform(vertInfo, viewWithBadOffset); - } catch (e) { - exception = e.toString(); - } - assert(exception!.contains('Failed to bind uniform')); -} - ByteData float32(List values) { return Float32List.fromList(values).buffer.asByteData(); } diff --git a/impeller/fixtures/runtime_stage_filter_example.frag b/impeller/fixtures/runtime_stage_filter_example.frag new file mode 100644 index 0000000000000..83b8a4f2145bc --- /dev/null +++ b/impeller/fixtures/runtime_stage_filter_example.frag @@ -0,0 +1,16 @@ +// 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. + +#include + +uniform vec2 u_size; +uniform sampler2D u_texture; + +out vec4 frag_color; + +void main() { + frag_color = texture(u_texture, FlutterFragCoord().xy / u_size) * + (sin(FlutterFragCoord().y / u_size.y * 3.14) * + cos(FlutterFragCoord().x / u_size.x * 3.14)); +} diff --git a/impeller/geometry/geometry_benchmarks.cc b/impeller/geometry/geometry_benchmarks.cc index 1df17cffd69ba..d59650be5c731 100644 --- a/impeller/geometry/geometry_benchmarks.cc +++ b/impeller/geometry/geometry_benchmarks.cc @@ -4,8 +4,6 @@ #include "flutter/benchmarking/benchmarking.h" -#include "flutter/impeller/entity/solid_fill.vert.h" - #include "impeller/entity/geometry/stroke_path_geometry.h" #include "impeller/geometry/path.h" #include "impeller/geometry/path_builder.h" @@ -15,13 +13,13 @@ namespace impeller { class ImpellerBenchmarkAccessor { public: - static std::vector - GenerateSolidStrokeVertices(const Path::Polyline& polyline, - Scalar stroke_width, - Scalar miter_limit, - Join stroke_join, - Cap stroke_cap, - Scalar scale) { + static std::vector GenerateSolidStrokeVertices( + const Path::Polyline& polyline, + Scalar stroke_width, + Scalar miter_limit, + Join stroke_join, + Cap stroke_cap, + Scalar scale) { return StrokePathGeometry::GenerateSolidStrokeVertices( polyline, stroke_width, miter_limit, stroke_join, stroke_cap, scale); } diff --git a/impeller/geometry/geometry_unittests.cc b/impeller/geometry/geometry_unittests.cc index cdc4078c49a0a..3a88ff85a826b 100644 --- a/impeller/geometry/geometry_unittests.cc +++ b/impeller/geometry/geometry_unittests.cc @@ -1008,6 +1008,52 @@ TEST(GeometryTest, PointMin) { ASSERT_POINT_NEAR(result, expected); } +TEST(GeometryTest, Vector4IsFinite) { + { + Vector4 v; + ASSERT_TRUE(v.IsFinite()); + v.x = std::numeric_limits::infinity(); + ASSERT_FALSE(v.IsFinite()); + v.x = -std::numeric_limits::infinity(); + ASSERT_FALSE(v.IsFinite()); + v.x = -std::numeric_limits::quiet_NaN(); + ASSERT_FALSE(v.IsFinite()); + } + + { + Vector4 v; + ASSERT_TRUE(v.IsFinite()); + v.y = std::numeric_limits::infinity(); + ASSERT_FALSE(v.IsFinite()); + v.y = -std::numeric_limits::infinity(); + ASSERT_FALSE(v.IsFinite()); + v.y = -std::numeric_limits::quiet_NaN(); + ASSERT_FALSE(v.IsFinite()); + } + + { + Vector4 v; + ASSERT_TRUE(v.IsFinite()); + v.z = std::numeric_limits::infinity(); + ASSERT_FALSE(v.IsFinite()); + v.z = -std::numeric_limits::infinity(); + ASSERT_FALSE(v.IsFinite()); + v.z = -std::numeric_limits::quiet_NaN(); + ASSERT_FALSE(v.IsFinite()); + } + + { + Vector4 v; + ASSERT_TRUE(v.IsFinite()); + v.w = std::numeric_limits::infinity(); + ASSERT_FALSE(v.IsFinite()); + v.w = -std::numeric_limits::infinity(); + ASSERT_FALSE(v.IsFinite()); + v.w = -std::numeric_limits::quiet_NaN(); + ASSERT_FALSE(v.IsFinite()); + } +} + TEST(GeometryTest, Vector3Min) { Vector3 p(1, 2, 3); Vector3 result = p.Min({0, 10, 2}); diff --git a/impeller/geometry/matrix.cc b/impeller/geometry/matrix.cc index e47dd867bd718..ba49965498aa5 100644 --- a/impeller/geometry/matrix.cc +++ b/impeller/geometry/matrix.cc @@ -224,7 +224,7 @@ std::optional Matrix::Decompose() const { perpectiveMatrix.e[3][3] = 1; - if (perpectiveMatrix.GetDeterminant() == 0.0) { + if (!perpectiveMatrix.IsInvertible()) { return std::nullopt; } diff --git a/impeller/geometry/matrix.h b/impeller/geometry/matrix.h index 166fc11f9609f..484bf40ddcc73 100644 --- a/impeller/geometry/matrix.h +++ b/impeller/geometry/matrix.h @@ -110,6 +110,16 @@ struct Matrix { // clang-format on } + static constexpr Matrix MakeTranslateScale(const Vector3& s, + const Vector3& t) { + // clang-format off + return Matrix(s.x, 0.0f, 0.0f, 0.0f, + 0.0f, s.y, 0.0f, 0.0f, + 0.0f, 0.0f, s.z, 0.0f, + t.x , t.y, t.z, 1.0f); + // clang-format on + } + static constexpr Matrix MakeScale(const Vector2& s) { return MakeScale(Vector3(s.x, s.y, 1.0f)); } @@ -295,6 +305,8 @@ struct Matrix { Scalar GetDeterminant() const; + bool IsInvertible() const { return GetDeterminant() != 0; } + constexpr Scalar GetMaxBasisLengthXY() const { // The full basis computation requires computing the squared scaling factor // for translate/scale only matrices. This substantially limits the range of @@ -323,6 +335,11 @@ struct Matrix { direction.GetLength(); } + constexpr bool IsFinite() const { + return vec[0].IsFinite() && vec[1].IsFinite() && vec[2].IsFinite() && + vec[3].IsFinite(); + } + constexpr bool IsAffine() const { return (m[2] == 0 && m[3] == 0 && m[6] == 0 && m[7] == 0 && m[8] == 0 && m[9] == 0 && m[10] == 1 && m[11] == 0 && m[14] == 0 && m[15] == 1); @@ -392,6 +409,19 @@ struct Matrix { ); } + /// @brief Returns true if the matrix has no entries other than translation + /// components. Note that an identity matrix meets this criteria. + constexpr bool IsTranslationOnly() const { + return ( + // clang-format off + m[0] == 1.0 && m[1] == 0.0 && m[2] == 0.0 && m[3] == 0.0 && + m[4] == 0.0 && m[5] == 1.0 && m[6] == 0.0 && m[7] == 0.0 && + m[8] == 0.0 && m[9] == 0.0 && m[10] == 1.0 && m[11] == 0.0 && + m[15] == 1.0 + // clang-format on + ); + } + /// @brief Returns true if the matrix has a scale-only basis and is /// non-projective. Note that an identity matrix meets this criteria. constexpr bool IsTranslationScaleOnly() const { diff --git a/impeller/geometry/matrix_unittests.cc b/impeller/geometry/matrix_unittests.cc index 91a2a50f2a7d4..6c2ef35228a2a 100644 --- a/impeller/geometry/matrix_unittests.cc +++ b/impeller/geometry/matrix_unittests.cc @@ -80,6 +80,93 @@ TEST(MatrixTest, HasTranslation) { EXPECT_FALSE(Matrix().HasTranslation()); } +TEST(MatrixTest, IsTranslationOnly) { + EXPECT_TRUE(Matrix::MakeTranslation({100, 100, 0}).IsTranslationOnly()); + EXPECT_TRUE(Matrix::MakeTranslation({100, 100, 0}).IsTranslationScaleOnly()); + EXPECT_TRUE(Matrix::MakeTranslation({0, 100, 0}).IsTranslationOnly()); + EXPECT_TRUE(Matrix::MakeTranslation({0, 100, 0}).IsTranslationScaleOnly()); + EXPECT_TRUE(Matrix::MakeTranslation({100, 0, 0}).IsTranslationOnly()); + EXPECT_TRUE(Matrix::MakeTranslation({100, 0, 0}).IsTranslationScaleOnly()); + EXPECT_TRUE(Matrix().IsTranslationOnly()); + EXPECT_TRUE(Matrix().IsTranslationScaleOnly()); +} + +TEST(MatrixTest, IsTranslationScaleOnly) { + EXPECT_FALSE(Matrix::MakeScale({100, 100, 1}).IsTranslationOnly()); + EXPECT_TRUE(Matrix::MakeScale({100, 100, 1}).IsTranslationScaleOnly()); + EXPECT_FALSE(Matrix::MakeScale({1, 100, 1}).IsTranslationOnly()); + EXPECT_TRUE(Matrix::MakeScale({1, 100, 1}).IsTranslationScaleOnly()); + EXPECT_FALSE(Matrix::MakeScale({100, 1, 1}).IsTranslationOnly()); + EXPECT_TRUE(Matrix::MakeScale({100, 1, 1}).IsTranslationScaleOnly()); + EXPECT_TRUE(Matrix().IsTranslationOnly()); + EXPECT_TRUE(Matrix().IsTranslationScaleOnly()); +} + +TEST(MatrixTest, IsInvertibleGetDeterminant) { + EXPECT_TRUE(Matrix().IsInvertible()); + EXPECT_NE(Matrix().GetDeterminant(), 0.0f); + + EXPECT_TRUE(Matrix::MakeTranslation({100, 100, 0}).IsInvertible()); + EXPECT_NE(Matrix::MakeTranslation({100, 100, 0}).GetDeterminant(), 0.0f); + + EXPECT_TRUE(Matrix::MakeScale({100, 100, 1}).IsInvertible()); + EXPECT_NE(Matrix::MakeScale({100, 100, 1}).GetDeterminant(), 0.0f); + + EXPECT_TRUE(Matrix::MakeRotationX(Degrees(30)).IsInvertible()); + EXPECT_NE(Matrix::MakeRotationX(Degrees(30)).GetDeterminant(), 0.0f); + + EXPECT_TRUE(Matrix::MakeRotationY(Degrees(30)).IsInvertible()); + EXPECT_NE(Matrix::MakeRotationY(Degrees(30)).GetDeterminant(), 0.0f); + + EXPECT_TRUE(Matrix::MakeRotationZ(Degrees(30)).IsInvertible()); + EXPECT_NE(Matrix::MakeRotationZ(Degrees(30)).GetDeterminant(), 0.0f); + + EXPECT_FALSE(Matrix::MakeScale({0, 1, 1}).IsInvertible()); + EXPECT_EQ(Matrix::MakeScale({0, 1, 1}).GetDeterminant(), 0.0f); + EXPECT_FALSE(Matrix::MakeScale({1, 0, 1}).IsInvertible()); + EXPECT_EQ(Matrix::MakeScale({1, 0, 1}).GetDeterminant(), 0.0f); + EXPECT_FALSE(Matrix::MakeScale({1, 1, 0}).IsInvertible()); + EXPECT_EQ(Matrix::MakeScale({1, 1, 0}).GetDeterminant(), 0.0f); +} + +TEST(MatrixTest, IsFinite) { + EXPECT_TRUE(Matrix().IsFinite()); + + EXPECT_TRUE(Matrix::MakeTranslation({100, 100, 0}).IsFinite()); + EXPECT_TRUE(Matrix::MakeScale({100, 100, 1}).IsFinite()); + + EXPECT_TRUE(Matrix::MakeRotationX(Degrees(30)).IsFinite()); + EXPECT_TRUE(Matrix::MakeRotationY(Degrees(30)).IsFinite()); + EXPECT_TRUE(Matrix::MakeRotationZ(Degrees(30)).IsFinite()); + + EXPECT_TRUE(Matrix::MakeScale({0, 1, 1}).IsFinite()); + EXPECT_TRUE(Matrix::MakeScale({1, 0, 1}).IsFinite()); + EXPECT_TRUE(Matrix::MakeScale({1, 1, 0}).IsFinite()); + + for (int i = 0; i < 16; i++) { + { + Matrix matrix; + ASSERT_TRUE(matrix.IsFinite()); + matrix.m[i] = std::numeric_limits::infinity(); + ASSERT_FALSE(matrix.IsFinite()); + } + + { + Matrix matrix; + ASSERT_TRUE(matrix.IsFinite()); + matrix.m[i] = -std::numeric_limits::infinity(); + ASSERT_FALSE(matrix.IsFinite()); + } + + { + Matrix matrix; + ASSERT_TRUE(matrix.IsFinite()); + matrix.m[i] = -std::numeric_limits::quiet_NaN(); + ASSERT_FALSE(matrix.IsFinite()); + } + } +} + TEST(MatrixTest, IsAligned2D) { EXPECT_TRUE(Matrix().IsAligned2D()); EXPECT_TRUE(Matrix::MakeScale({1.0f, 1.0f, 2.0f}).IsAligned2D()); @@ -211,5 +298,20 @@ TEST(MatrixTest, TranslateWithPerspective) { 0.0, 2.0, 0.0, 430.0))); } +TEST(MatrixTest, MakeScaleTranslate) { + EXPECT_TRUE(MatrixNear( + Matrix::MakeTranslateScale({1, 1, 1.0 / 1024}, {10, 10, 1.0 / 1024}), + Matrix::MakeTranslation({10, 10, 1.0 / 1024}) * + Matrix::MakeScale({1, 1, 1.0 / 1024}))); + + EXPECT_TRUE(MatrixNear( + Matrix::MakeTranslateScale({2, 2, 2}, {10, 10, 0}), + Matrix::MakeTranslation({10, 10, 0}) * Matrix::MakeScale({2, 2, 2}))); + + EXPECT_TRUE(MatrixNear( + Matrix::MakeTranslateScale({0, 0, 0}, {0, 0, 0}), + Matrix::MakeTranslation({0, 0, 0}) * Matrix::MakeScale({0, 0, 0}))); +} + } // namespace testing } // namespace impeller diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc index 7979f8399414e..f5ea8fb3b94b0 100644 --- a/impeller/geometry/path.cc +++ b/impeller/geometry/path.cc @@ -5,6 +5,7 @@ #include "impeller/geometry/path.h" #include +#include #include "flutter/fml/logging.h" #include "impeller/geometry/path_component.h" @@ -58,6 +59,49 @@ bool Path::IsEmpty() const { data_->components[0] == ComponentType::kContour); } +bool Path::IsSingleContour() const { + return data_->single_countour; +} + +/// Determine required storage for points and indices. +std::pair Path::CountStorage(Scalar scale) const { + size_t points = 0; + size_t contours = 0; + + auto& path_components = data_->components; + auto& path_points = data_->points; + + size_t storage_offset = 0u; + for (size_t component_i = 0; component_i < path_components.size(); + component_i++) { + const auto& path_component = path_components[component_i]; + switch (path_component) { + case ComponentType::kLinear: { + points += 2; + break; + } + case ComponentType::kQuadratic: { + const QuadraticPathComponent* quad = + reinterpret_cast( + &path_points[storage_offset]); + points += quad->CountLinearPathComponents(scale); + break; + } + case ComponentType::kCubic: { + const CubicPathComponent* cubic = + reinterpret_cast( + &path_points[storage_offset]); + points += cubic->CountLinearPathComponents(scale); + break; + } + case Path::ComponentType::kContour: + contours++; + } + storage_offset += VerbToOffset(path_component); + } + return std::make_pair(points, contours); +} + void Path::WritePolyline(Scalar scale, VertexWriter& writer) const { auto& path_components = data_->components; auto& path_points = data_->points; diff --git a/impeller/geometry/path.h b/impeller/geometry/path.h index 27a345ec082ab..86263aa9071f9 100644 --- a/impeller/geometry/path.h +++ b/impeller/geometry/path.h @@ -6,6 +6,7 @@ #define FLUTTER_IMPELLER_GEOMETRY_PATH_H_ #include +#include #include #include #include @@ -153,6 +154,9 @@ class Path { bool IsEmpty() const; + /// @brief Whether the line contains a single contour. + bool IsSingleContour() const; + bool GetLinearComponentAtIndex(size_t index, LinearPathComponent& linear) const; @@ -193,6 +197,9 @@ class Path { /// lines. void WritePolyline(Scalar scale, VertexWriter& writer) const; + /// Determine required storage for points and number of contours. + std::pair CountStorage(Scalar scale) const; + private: friend class PathBuilder; @@ -216,6 +223,7 @@ class Path { FillType fill = FillType::kNonZero; Convexity convexity = Convexity::kUnknown; + bool single_countour = true; std::optional bounds; std::vector points; std::vector components; diff --git a/impeller/geometry/path_builder.cc b/impeller/geometry/path_builder.cc index 6d49fca61d4b0..fb787f145dac8 100644 --- a/impeller/geometry/path_builder.cc +++ b/impeller/geometry/path_builder.cc @@ -18,13 +18,22 @@ PathBuilder::~PathBuilder() = default; Path PathBuilder::CopyPath(FillType fill) { prototype_.fill = fill; + prototype_.single_countour = + current_contour_location_ == 0u || + (contour_count_ == 2 && + prototype_.components.back() == Path::ComponentType::kContour); return Path(prototype_); } Path PathBuilder::TakePath(FillType fill) { prototype_.fill = fill; UpdateBounds(); + prototype_.single_countour = + current_contour_location_ == 0u || + (contour_count_ == 2 && + prototype_.components.back() == Path::ComponentType::kContour); current_contour_location_ = 0u; + contour_count_ = 1; return Path(std::move(prototype_)); } @@ -276,6 +285,7 @@ void PathBuilder::AddContourComponent(const Point& destination, points.push_back(destination); points.push_back(closed); components.push_back(Path::ComponentType::kContour); + contour_count_ += 1; } prototype_.bounds.reset(); } @@ -450,6 +460,7 @@ PathBuilder& PathBuilder::AddPath(const Path& path) { for (auto component : path.data_->components) { if (component == Path::ComponentType::kContour) { current_contour_location_ = source_offset; + contour_count_ += 1; } source_offset += Path::VerbToOffset(component); } diff --git a/impeller/geometry/path_builder.h b/impeller/geometry/path_builder.h index abd899d2368c4..0184687cb611e 100644 --- a/impeller/geometry/path_builder.h +++ b/impeller/geometry/path_builder.h @@ -111,6 +111,7 @@ class PathBuilder { Point subpath_start_; Point current_; size_t current_contour_location_ = 0u; + size_t contour_count_ = 0u; Path::Data prototype_; PathBuilder& AddRoundedRectTopLeft(Rect rect, RoundingRadii radii); diff --git a/impeller/geometry/path_component.cc b/impeller/geometry/path_component.cc index d990f37ed7f73..1637bc9eb4a06 100644 --- a/impeller/geometry/path_component.cc +++ b/impeller/geometry/path_component.cc @@ -5,16 +5,109 @@ #include "path_component.h" #include +#include +#include "impeller/geometry/scalar.h" #include "impeller/geometry/wangs_formula.h" namespace impeller { -VertexWriter::VertexWriter(std::vector& points, - std::vector& indices) +/////////// FanVertexWriter /////////// + +FanVertexWriter::FanVertexWriter(Point* point_buffer, uint16_t* index_buffer) + : point_buffer_(point_buffer), index_buffer_(index_buffer) {} + +FanVertexWriter::~FanVertexWriter() = default; + +size_t FanVertexWriter::GetIndexCount() const { + return index_count_; +} + +void FanVertexWriter::EndContour() { + if (count_ == 0) { + return; + } + index_buffer_[index_count_++] = 0xFFFF; +} + +void FanVertexWriter::Write(Point point) { + index_buffer_[index_count_++] = count_; + point_buffer_[count_++] = point; +} + +/////////// StripVertexWriter /////////// + +StripVertexWriter::StripVertexWriter(Point* point_buffer, + uint16_t* index_buffer) + : point_buffer_(point_buffer), index_buffer_(index_buffer) {} + +StripVertexWriter::~StripVertexWriter() = default; + +size_t StripVertexWriter::GetIndexCount() const { + return index_count_; +} + +void StripVertexWriter::EndContour() { + if (count_ == 0u || contour_start_ == count_ - 1) { + // Empty or first contour. + return; + } + + size_t start = contour_start_; + size_t end = count_ - 1; + + index_buffer_[index_count_++] = start; + + size_t a = start + 1; + size_t b = end; + while (a < b) { + index_buffer_[index_count_++] = a; + index_buffer_[index_count_++] = b; + a++; + b--; + } + if (a == b) { + index_buffer_[index_count_++] = a; + } + + contour_start_ = count_; + index_buffer_[index_count_++] = 0xFFFF; +} + +void StripVertexWriter::Write(Point point) { + point_buffer_[count_++] = point; +} + +/////////// LineStripVertexWriter //////// + +LineStripVertexWriter::LineStripVertexWriter(std::vector& points) + : points_(points) {} + +void LineStripVertexWriter::EndContour() {} + +void LineStripVertexWriter::Write(Point point) { + if (offset_ >= points_.size()) { + overflow_.push_back(point); + } else { + points_[offset_++] = point; + } +} + +const std::vector& LineStripVertexWriter::GetOversizedBuffer() const { + return overflow_; +} + +std::pair LineStripVertexWriter::GetVertexCount() const { + return std::make_pair(offset_, overflow_.size()); +} + +/////////// GLESVertexWriter /////////// + +GLESVertexWriter::GLESVertexWriter(std::vector& points, + std::vector& indices) : points_(points), indices_(indices) {} -void VertexWriter::EndContour() { +void GLESVertexWriter::EndContour() { if (points_.size() == 0u || contour_start_ == points_.size() - 1) { // Empty or first contour. return; @@ -64,7 +157,7 @@ void VertexWriter::EndContour() { contour_start_ = points_.size(); } -void VertexWriter::Write(Point point) { +void GLESVertexWriter::Write(Point point) { points_.push_back(point); } @@ -187,6 +280,10 @@ void QuadraticPathComponent::ToLinearPathComponents( proc(p2); } +size_t QuadraticPathComponent::CountLinearPathComponents(Scalar scale) const { + return std::ceilf(ComputeQuadradicSubdivisions(scale, *this)) + 2; +} + std::vector QuadraticPathComponent::Extrema() const { CubicPathComponent elevated(*this); return elevated.Extrema(); @@ -242,6 +339,10 @@ void CubicPathComponent::ToLinearPathComponents(Scalar scale, writer.Write(p2); } +size_t CubicPathComponent::CountLinearPathComponents(Scalar scale) const { + return std::ceilf(ComputeCubicSubdivisions(scale, *this)) + 2; +} + inline QuadraticPathComponent CubicPathComponent::Lower() const { return QuadraticPathComponent(3.0 * (cp1 - p1), 3.0 * (cp2 - cp1), 3.0 * (p2 - cp2)); diff --git a/impeller/geometry/path_component.h b/impeller/geometry/path_component.h index b54735dba61f2..b558c006a36e6 100644 --- a/impeller/geometry/path_component.h +++ b/impeller/geometry/path_component.h @@ -19,14 +19,86 @@ namespace impeller { /// strip. class VertexWriter { public: - explicit VertexWriter(std::vector& points, - std::vector& indices); + virtual void EndContour() = 0; - ~VertexWriter() = default; + virtual void Write(Point point) = 0; +}; + +/// @brief A vertex writer that generates a triangle fan and requires primitive +/// restart. +class FanVertexWriter : public VertexWriter { + public: + explicit FanVertexWriter(Point* point_buffer, uint16_t* index_buffer); + + ~FanVertexWriter(); + + size_t GetIndexCount() const; + + void EndContour() override; + + void Write(Point point) override; + + private: + size_t count_ = 0; + size_t index_count_ = 0; + Point* point_buffer_ = nullptr; + uint16_t* index_buffer_ = nullptr; +}; + +/// @brief A vertex writer that generates a triangle strip and requires +/// primitive restart. +class StripVertexWriter : public VertexWriter { + public: + explicit StripVertexWriter(Point* point_buffer, uint16_t* index_buffer); + + ~StripVertexWriter(); + + size_t GetIndexCount() const; - void EndContour(); + void EndContour() override; - void Write(Point point); + void Write(Point point) override; + + private: + size_t count_ = 0; + size_t index_count_ = 0; + size_t contour_start_ = 0; + Point* point_buffer_ = nullptr; + uint16_t* index_buffer_ = nullptr; +}; + +/// @brief A vertex writer that generates a line strip topology. +class LineStripVertexWriter : public VertexWriter { + public: + explicit LineStripVertexWriter(std::vector& points); + + ~LineStripVertexWriter() = default; + + void EndContour() override; + + void Write(Point point) override; + + std::pair GetVertexCount() const; + + const std::vector& GetOversizedBuffer() const; + + private: + size_t offset_ = 0u; + std::vector& points_; + std::vector overflow_; +}; + +/// @brief A vertex writer that has no hardware requirements. +class GLESVertexWriter : public VertexWriter { + public: + explicit GLESVertexWriter(std::vector& points, + std::vector& indices); + + ~GLESVertexWriter() = default; + + void EndContour() override; + + void Write(Point point) override; private: bool previous_contour_odd_points_ = false; @@ -85,6 +157,8 @@ struct QuadraticPathComponent { void ToLinearPathComponents(Scalar scale, VertexWriter& writer) const; + size_t CountLinearPathComponents(Scalar scale) const; + std::vector Extrema() const; bool operator==(const QuadraticPathComponent& other) const { @@ -132,6 +206,8 @@ struct CubicPathComponent { void ToLinearPathComponents(Scalar scale, VertexWriter& writer) const; + size_t CountLinearPathComponents(Scalar scale) const; + CubicPathComponent Subsegment(Scalar t0, Scalar t1) const; bool operator==(const CubicPathComponent& other) const { diff --git a/impeller/geometry/path_unittests.cc b/impeller/geometry/path_unittests.cc index 2f31c4bdc14fb..ef802cbe7c0eb 100644 --- a/impeller/geometry/path_unittests.cc +++ b/impeller/geometry/path_unittests.cc @@ -9,6 +9,7 @@ #include "impeller/geometry/path.h" #include "impeller/geometry/path_builder.h" #include "impeller/geometry/path_component.h" +#include "impeller/geometry/round_rect.h" namespace impeller { namespace testing { @@ -49,6 +50,142 @@ TEST(PathTest, PathCreatePolyLineDoesNotDuplicatePoints) { ASSERT_EQ(polyline.GetPoint(4).x, 50); } +TEST(PathTest, PathSingleContour) { + // Closed shapes. + { + Path path = PathBuilder{}.AddCircle({100, 100}, 50).TakePath(); + EXPECT_TRUE(path.IsSingleContour()); + } + + { + Path path = + PathBuilder{}.AddOval(Rect::MakeXYWH(100, 100, 100, 100)).TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } + + { + Path path = + PathBuilder{}.AddRect(Rect::MakeXYWH(100, 100, 100, 100)).TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddRoundRect(RoundRect::MakeRectRadius( + Rect::MakeXYWH(100, 100, 100, 100), 10)) + .TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } + + // Open shapes. + { + Point p(100, 100); + Path path = PathBuilder{}.AddLine(p, {200, 100}).TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } + + { + Path path = + PathBuilder{} + .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100}) + .TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100}) + .TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } +} + +TEST(PathTest, PathSingleContourDoubleShapes) { + // Closed shapes. + { + Path path = PathBuilder{} + .AddCircle({100, 100}, 50) + .AddCircle({100, 100}, 50) + .TakePath(); + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddOval(Rect::MakeXYWH(100, 100, 100, 100)) + .AddOval(Rect::MakeXYWH(100, 100, 100, 100)) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddRect(Rect::MakeXYWH(100, 100, 100, 100)) + .AddRect(Rect::MakeXYWH(100, 100, 100, 100)) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddRoundRect(RoundRect::MakeRectRadius( + Rect::MakeXYWH(100, 100, 100, 100), 10)) + .AddRoundRect(RoundRect::MakeRectRadius( + Rect::MakeXYWH(100, 100, 100, 100), 10)) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddRoundRect(RoundRect::MakeRectXY( + Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20))) + .AddRoundRect(RoundRect::MakeRectXY( + Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20))) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + // Open shapes. + { + Point p(100, 100); + Path path = + PathBuilder{}.AddLine(p, {200, 100}).AddLine(p, {200, 100}).TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = + PathBuilder{} + .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100}) + .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100}) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100}) + .Close() + .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100}) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } +} + TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) { // Closed shapes. { @@ -558,6 +695,128 @@ TEST(PathTest, CanBeCloned) { } } +TEST(PathTest, FanTessellation) { + Path path = PathBuilder{} + .AddRoundRect(RoundRect::MakeRectRadius( + Rect::MakeLTRB(0, 0, 100, 100), 10)) + .TakePath(); + auto [points, contours] = path.CountStorage(1.0); + + std::vector point_storage(points); + std::vector index_storage(points + (contours - 1)); + + FanVertexWriter writer(point_storage.data(), index_storage.data()); + path.WritePolyline(1.0, writer); + + EXPECT_LE(writer.GetIndexCount(), index_storage.size()); + EXPECT_EQ(point_storage[0], Point(10, 0)); +} + +// Filled Paths without an explicit close should still be closed +TEST(PathTest, FanTessellationUnclosedPath) { + // Create a rectangle that lacks an explicit close. + Path path = PathBuilder{} + .LineTo({100, 0}) + .LineTo({100, 100}) + .LineTo({0, 100}) + .TakePath(); + + std::vector expected = {{0, 0}, {100, 0}, {100, 100}, + {0, 100}, {0, 0}, {0, 0}}; + std::vector expected_indices = {0, 1, 2, 3, 0xFFFF, 0}; + + auto [points, contours] = path.CountStorage(1.0); + + std::vector point_storage(points); + std::vector index_storage(points + (contours - 1)); + + FanVertexWriter writer(point_storage.data(), index_storage.data()); + path.WritePolyline(1.0, writer); + + EXPECT_LE(index_storage, expected_indices); + EXPECT_EQ(point_storage, expected); +} + +// Filled Paths without an explicit close should still be closed +TEST(PathTest, StripTessellationUnclosedPath) { + // Create a rectangle that lacks an explicit close. + Path path = PathBuilder{} + .LineTo({100, 0}) + .LineTo({100, 100}) + .LineTo({0, 100}) + .TakePath(); + + std::vector expected = {{0, 0}, {100, 0}, {100, 100}, + {0, 100}, {0, 0}, {0, 0}}; + std::vector expected_indices = {0, 1, 3, 2, 0xFFFF, 0}; + + auto [points, contours] = path.CountStorage(1.0); + + std::vector point_storage(points); + std::vector index_storage(points + (contours - 1)); + + StripVertexWriter writer(point_storage.data(), index_storage.data()); + path.WritePolyline(1.0, writer); + + EXPECT_LE(index_storage, expected_indices); + EXPECT_EQ(point_storage, expected); +} + +TEST(PathTest, FanTessellationMultiContour) { + PathBuilder builder{}; + for (auto i = 0; i < 10; i++) { + builder.AddRoundRect( + RoundRect::MakeRectRadius(Rect::MakeLTRB(0 + i, 0 + i, 100, 100), 10)); + } + auto path = builder.TakePath(); + auto [points, contours] = path.CountStorage(1.0); + + std::vector point_storage(points); + std::vector index_storage(points + (contours - 1)); + + FanVertexWriter writer(point_storage.data(), index_storage.data()); + path.WritePolyline(1.0, writer); + + EXPECT_LE(writer.GetIndexCount(), index_storage.size()); + EXPECT_EQ(point_storage[0], Point(10, 0)); +} + +TEST(PathTest, StripTessellation) { + Path path = PathBuilder{} + .AddRoundRect(RoundRect::MakeRectRadius( + Rect::MakeLTRB(0, 0, 100, 100), 10)) + .TakePath(); + auto [points, contours] = path.CountStorage(1.0); + + std::vector point_storage(points); + std::vector index_storage(points + (contours - 1)); + + StripVertexWriter writer(point_storage.data(), index_storage.data()); + path.WritePolyline(1.0, writer); + + EXPECT_LE(writer.GetIndexCount(), index_storage.size()); + EXPECT_EQ(point_storage[0], Point(10, 0)); +} + +TEST(PathTest, StripTessellationMultiContour) { + PathBuilder builder{}; + for (auto i = 0; i < 10; i++) { + builder.AddRoundRect( + RoundRect::MakeRectRadius(Rect::MakeLTRB(0 + i, 0 + i, 100, 100), 10)); + } + auto path = builder.TakePath(); + auto [points, contours] = path.CountStorage(1.0); + + std::vector point_storage(points); + std::vector index_storage(points + (contours - 1)); + + StripVertexWriter writer(point_storage.data(), index_storage.data()); + path.WritePolyline(1.0, writer); + + EXPECT_LE(writer.GetIndexCount(), index_storage.size()); + EXPECT_EQ(point_storage[0], Point(10, 0)); +} + TEST(PathTest, PathBuilderDoesNotMutateCopiedPaths) { auto test_isolation = [](const std::function& mutator, diff --git a/impeller/geometry/rect.h b/impeller/geometry/rect.h index 86344e2b6b4f5..860a8747ceb28 100644 --- a/impeller/geometry/rect.h +++ b/impeller/geometry/rect.h @@ -151,6 +151,16 @@ struct TRect { return TRect(0.0, 0.0, size.width, size.height); } + /// Construct a floating point rect |Rect| from another Rect of a + /// potentially different storage type (eg. |IRect|). + template + constexpr static std::enable_if_t, TRect> Make( + const TRect& rect) { + return MakeLTRB( + static_cast(rect.GetLeft()), static_cast(rect.GetTop()), + static_cast(rect.GetRight()), static_cast(rect.GetBottom())); + } + template constexpr static std::optional MakePointBounds(const U& value) { return MakePointBounds(value.begin(), value.end()); @@ -788,7 +798,7 @@ namespace std { template inline std::ostream& operator<<(std::ostream& out, const impeller::TRect& r) { - out << "(" << r.GetOrigin() << ", " << r.GetSize() << ")"; + out << "(" << r.GetLeftTop() << " => " << r.GetRightBottom() << ")"; return out; } diff --git a/impeller/geometry/rect_unittests.cc b/impeller/geometry/rect_unittests.cc index 401c64fc23f0d..17d6ff6489b3d 100644 --- a/impeller/geometry/rect_unittests.cc +++ b/impeller/geometry/rect_unittests.cc @@ -161,6 +161,20 @@ TEST(RectTest, IRectSimpleWH) { EXPECT_FALSE(rect.IsEmpty()); } +TEST(RectTest, RectFromIRect) { + IRect irect = IRect::MakeLTRB(10, 20, 30, 40); + Rect rect = Rect::Make(irect); + + EXPECT_EQ(rect.GetLeft(), 10); + EXPECT_EQ(rect.GetTop(), 20); + EXPECT_EQ(rect.GetRight(), 30); + EXPECT_EQ(rect.GetBottom(), 40); + + // The following do not compile + // IRect irect2 = IRect::Make(rect); + // IRect irect2 = IRect::Make(irect); +} + TEST(RectTest, RectOverflowXYWH) { auto min = std::numeric_limits::lowest(); auto max = std::numeric_limits::max(); diff --git a/impeller/geometry/size.h b/impeller/geometry/size.h index b31970f1ddc2a..2c609cddade0f 100644 --- a/impeller/geometry/size.h +++ b/impeller/geometry/size.h @@ -7,6 +7,7 @@ #include #include +#include #include #include #include diff --git a/impeller/geometry/vector.h b/impeller/geometry/vector.h index d1358bffef4de..0f1c4dc10681e 100644 --- a/impeller/geometry/vector.h +++ b/impeller/geometry/vector.h @@ -255,6 +255,11 @@ struct Vector4 { constexpr Vector4(std::array values) : x(values[0]), y(values[1]), z(values[2]), w(values[3]) {} + constexpr bool IsFinite() const { + return std::isfinite(x) && std::isfinite(y) && std::isfinite(z) && + std::isfinite(w); + } + Vector4 Normalize() const { const Scalar inverse = 1.0f / sqrt(x * x + y * y + z * z + w * w); return Vector4(x * inverse, y * inverse, z * inverse, w * inverse); diff --git a/impeller/golden_tests/BUILD.gn b/impeller/golden_tests/BUILD.gn index 2149a522e763b..a3156120b2a37 100644 --- a/impeller/golden_tests/BUILD.gn +++ b/impeller/golden_tests/BUILD.gn @@ -67,6 +67,7 @@ if (is_mac) { deps = [ ":screenshot", "//flutter/display_list", + "//flutter/fml", "//flutter/impeller/display_list", "//flutter/impeller/playground", "//flutter/impeller/renderer/backend/metal:metal", @@ -92,6 +93,7 @@ if (is_mac) { "//flutter/impeller/display_list:aiks_unittests_golden", "//flutter/impeller/display_list:display_list_unittests_golden", "//flutter/impeller/fixtures", + "//flutter/impeller/renderer:renderer_unittests_golden", "//flutter/third_party/angle:libEGL", "//flutter/third_party/angle:libGLESv2", "//flutter/third_party/googletest:gtest", diff --git a/impeller/golden_tests/golden_tests.cc b/impeller/golden_tests/golden_tests.cc index a55e1aba6f89d..c96c093d9a9e7 100644 --- a/impeller/golden_tests/golden_tests.cc +++ b/impeller/golden_tests/golden_tests.cc @@ -82,7 +82,7 @@ TEST_F(GoldenTests, ConicalGradient) { flutter::DlColor::RGBA(0, 0, 1, 1)}; Scalar stops[2] = {0, 1}; - paint.setColorSource(flutter::DlConicalGradientColorSource::MakeConical( + paint.setColorSource(flutter::DlColorSource::MakeConical( /*start_center=*/{125, 125}, // /*start_radius=*/125, {180, 180}, // /*end_radius=*/0, // diff --git a/impeller/golden_tests/metal_screenshot.h b/impeller/golden_tests/metal_screenshot.h index becd4b4230646..739c7691d238e 100644 --- a/impeller/golden_tests/metal_screenshot.h +++ b/impeller/golden_tests/metal_screenshot.h @@ -11,6 +11,20 @@ #include #include +#include "flutter/fml/platform/darwin/cf_utils.h" + +namespace fml { + +/// fml::CFRef retain and release implementations for CGImageRef. +template <> +struct CFRefTraits { + static constexpr CGImageRef kNullValue = nullptr; + static void Retain(CGImageRef instance) { CGImageRetain(instance); } + static void Release(CGImageRef instance) { CGImageRelease(instance); } +}; + +} // namespace fml + namespace impeller { namespace testing { @@ -35,8 +49,8 @@ class MetalScreenshot : public Screenshot { MetalScreenshot(const MetalScreenshot&) = delete; MetalScreenshot& operator=(const MetalScreenshot&) = delete; - CGImageRef cg_image_; - CFDataRef pixel_data_; + fml::CFRef cg_image_; + fml::CFRef pixel_data_; }; } // namespace testing } // namespace impeller diff --git a/impeller/golden_tests/metal_screenshot.mm b/impeller/golden_tests/metal_screenshot.mm index fd7ab0c083461..7062372e74eaf 100644 --- a/impeller/golden_tests/metal_screenshot.mm +++ b/impeller/golden_tests/metal_screenshot.mm @@ -9,13 +9,10 @@ MetalScreenshot::MetalScreenshot(CGImageRef cgImage) : cg_image_(cgImage) { CGDataProviderRef data_provider = CGImageGetDataProvider(cgImage); - pixel_data_ = CGDataProviderCopyData(data_provider); + pixel_data_.Reset(CGDataProviderCopyData(data_provider)); } -MetalScreenshot::~MetalScreenshot() { - CFRelease(pixel_data_); - CGImageRelease(cg_image_); -} +MetalScreenshot::~MetalScreenshot() = default; const uint8_t* MetalScreenshot::GetBytes() const { return CFDataGetBytePtr(pixel_data_); @@ -37,17 +34,15 @@ bool result = false; NSURL* output_url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path.c_str()]]; - CGImageDestinationRef destination = CGImageDestinationCreateWithURL( - (__bridge CFURLRef)output_url, kUTTypePNG, 1, nullptr); - if (destination != nullptr) { + fml::CFRef destination(CGImageDestinationCreateWithURL( + (__bridge CFURLRef)output_url, kUTTypePNG, 1, nullptr)); + if (destination) { CGImageDestinationAddImage(destination, cg_image_, (__bridge CFDictionaryRef) @{}); if (CGImageDestinationFinalize(destination)) { result = true; } - - CFRelease(destination); } return result; } diff --git a/impeller/playground/imgui/imgui_impl_impeller.cc b/impeller/playground/imgui/imgui_impl_impeller.cc index f6fd6c33f954e..caf346c8bcf38 100644 --- a/impeller/playground/imgui/imgui_impl_impeller.cc +++ b/impeller/playground/imgui/imgui_impl_impeller.cc @@ -9,6 +9,7 @@ #include #include +#include "fml/mapping.h" #include "impeller/core/buffer_view.h" #include "impeller/core/host_buffer.h" #include "impeller/core/platform.h" @@ -78,8 +79,8 @@ bool ImGui_ImplImpeller_Init( int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - auto texture_descriptor = impeller::TextureDescriptor{}; - texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; + impeller::TextureDescriptor texture_descriptor; + texture_descriptor.storage_mode = impeller::StorageMode::kDevicePrivate; texture_descriptor.format = impeller::PixelFormat::kR8G8B8A8UNormInt; texture_descriptor.size = {width, height}; texture_descriptor.mip_count = 1u; @@ -90,8 +91,20 @@ bool ImGui_ImplImpeller_Init( "Could not allocate ImGui font texture."); bd->font_texture->SetLabel("ImGui Font Texture"); - [[maybe_unused]] bool uploaded = bd->font_texture->SetContents( - pixels, texture_descriptor.GetByteSizeOfBaseMipLevel()); + auto command_buffer = context->CreateCommandBuffer(); + auto blit_pass = command_buffer->CreateBlitPass(); + auto mapping = std::make_shared( + reinterpret_cast(pixels), + texture_descriptor.GetByteSizeOfBaseMipLevel()); + auto device_buffer = + context->GetResourceAllocator()->CreateBufferWithCopy(*mapping); + + blit_pass->AddCopy(impeller::DeviceBuffer::AsBufferView(device_buffer), + bd->font_texture); + blit_pass->EncodeCommands(context->GetResourceAllocator()); + + [[maybe_unused]] bool uploaded = + context->GetCommandQueue()->Submit({command_buffer}).ok(); IM_ASSERT(uploaded && "Could not upload ImGui font texture to device memory."); } @@ -125,12 +138,11 @@ void ImGui_ImplImpeller_Shutdown() { } void ImGui_ImplImpeller_RenderDrawData(ImDrawData* draw_data, - impeller::RenderPass& render_pass) { + impeller::RenderPass& render_pass, + impeller::HostBuffer& host_buffer) { if (draw_data->CmdListsCount == 0) { return; // Nothing to render. } - auto host_buffer = impeller::HostBuffer::Create( - render_pass.GetContext()->GetResourceAllocator()); using VS = impeller::ImguiRasterVertexShader; using FS = impeller::ImguiRasterFragmentShader; @@ -164,7 +176,7 @@ void ImGui_ImplImpeller_RenderDrawData(ImDrawData* draw_data, VS::UniformBuffer uniforms; uniforms.mvp = impeller::Matrix::MakeOrthographic(display_rect.GetSize()) .Translate(-display_rect.GetOrigin()); - auto vtx_uniforms = host_buffer->EmplaceUniform(uniforms); + auto vtx_uniforms = host_buffer.EmplaceUniform(uniforms); size_t vertex_buffer_offset = 0; size_t index_buffer_offset = total_vtx_bytes; @@ -252,14 +264,12 @@ void ImGui_ImplImpeller_RenderDrawData(ImDrawData* draw_data, vertex_buffer_offset + pcmd->VtxOffset * sizeof(ImDrawVert); impeller::VertexBuffer vertex_buffer; - vertex_buffer.vertex_buffer = impeller::BufferView{ - .buffer = buffer, - .range = impeller::Range(vb_start, draw_list_vtx_bytes - vb_start)}; - vertex_buffer.index_buffer = { - .buffer = buffer, - .range = impeller::Range( - index_buffer_offset + pcmd->IdxOffset * sizeof(ImDrawIdx), - pcmd->ElemCount * sizeof(ImDrawIdx))}; + vertex_buffer.vertex_buffer = impeller::BufferView( + buffer, impeller::Range(vb_start, draw_list_vtx_bytes - vb_start)); + vertex_buffer.index_buffer = impeller::BufferView( + buffer, impeller::Range(index_buffer_offset + + pcmd->IdxOffset * sizeof(ImDrawIdx), + pcmd->ElemCount * sizeof(ImDrawIdx))); vertex_buffer.vertex_count = pcmd->ElemCount; vertex_buffer.index_type = impeller::IndexType::k16bit; render_pass.SetVertexBuffer(std::move(vertex_buffer)); @@ -272,5 +282,5 @@ void ImGui_ImplImpeller_RenderDrawData(ImDrawData* draw_data, vertex_buffer_offset += draw_list_vtx_bytes; index_buffer_offset += draw_list_idx_bytes; } - host_buffer->Reset(); + host_buffer.Reset(); } diff --git a/impeller/playground/imgui/imgui_impl_impeller.h b/impeller/playground/imgui/imgui_impl_impeller.h index cfb624a61c5a9..163dac8f9a01c 100644 --- a/impeller/playground/imgui/imgui_impl_impeller.h +++ b/impeller/playground/imgui/imgui_impl_impeller.h @@ -7,6 +7,7 @@ #include +#include "impeller/core/host_buffer.h" #include "third_party/imgui/imgui.h" namespace impeller { @@ -23,6 +24,7 @@ IMGUI_IMPL_API void ImGui_ImplImpeller_Shutdown(); IMGUI_IMPL_API void ImGui_ImplImpeller_RenderDrawData( ImDrawData* draw_data, - impeller::RenderPass& renderpass); + impeller::RenderPass& renderpass, + impeller::HostBuffer& host_buffer); #endif // FLUTTER_IMPELLER_PLAYGROUND_IMGUI_IMGUI_IMPL_IMPELLER_H_ diff --git a/impeller/playground/playground.cc b/impeller/playground/playground.cc index 880bed4feb14b..1ef9f17f134d8 100644 --- a/impeller/playground/playground.cc +++ b/impeller/playground/playground.cc @@ -9,6 +9,7 @@ #include "fml/closure.h" #include "fml/time/time_point.h" +#include "impeller/core/host_buffer.h" #include "impeller/playground/image/backends/skia/compressed_image_skia.h" #include "impeller/playground/image/decompressed_image.h" #include "impeller/renderer/command_buffer.h" @@ -148,6 +149,9 @@ bool Playground::IsPlaygroundEnabled() const { } void Playground::TeardownWindow() { + if (host_buffer_) { + host_buffer_.reset(); + } if (context_) { context_->Shutdown(); } @@ -280,12 +284,7 @@ bool Playground::OpenPlaygroundHere( } buffer->SetLabel("ImGui Command Buffer"); - if (render_target.GetColorAttachments().empty()) { - VALIDATION_LOG << "render target attachments are empty."; - return false; - } - - auto color0 = render_target.GetColorAttachments().find(0)->second; + auto color0 = render_target.GetColorAttachment(0); color0.load_action = LoadAction::kLoad; if (color0.resolve_texture) { color0.texture = color0.resolve_texture; @@ -293,7 +292,6 @@ bool Playground::OpenPlaygroundHere( color0.store_action = StoreAction::kStore; } render_target.SetColorAttachment(color0, 0); - render_target.SetStencilAttachment(std::nullopt); render_target.SetDepthAttachment(std::nullopt); @@ -303,8 +301,13 @@ bool Playground::OpenPlaygroundHere( return false; } pass->SetLabel("ImGui Render Pass"); + if (!host_buffer_) { + host_buffer_ = HostBuffer::Create(context_->GetResourceAllocator(), + context_->GetIdleWaiter()); + } - ImGui_ImplImpeller_RenderDrawData(ImGui::GetDrawData(), *pass); + ImGui_ImplImpeller_RenderDrawData(ImGui::GetDrawData(), *pass, + *host_buffer_); pass->EncodeCommands(); @@ -388,8 +391,8 @@ static std::shared_ptr CreateTextureForDecompressedImage( const std::shared_ptr& context, DecompressedImage& decompressed_image, bool enable_mipmapping) { - auto texture_descriptor = TextureDescriptor{}; - texture_descriptor.storage_mode = StorageMode::kHostVisible; + TextureDescriptor texture_descriptor; + texture_descriptor.storage_mode = StorageMode::kDevicePrivate; texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; texture_descriptor.size = decompressed_image.GetSize(); texture_descriptor.mip_count = @@ -463,8 +466,8 @@ std::shared_ptr Playground::CreateTextureCubeForFixture( images[i] = image.value(); } - auto texture_descriptor = TextureDescriptor{}; - texture_descriptor.storage_mode = StorageMode::kHostVisible; + TextureDescriptor texture_descriptor; + texture_descriptor.storage_mode = StorageMode::kDevicePrivate; texture_descriptor.type = TextureType::kTextureCube; texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; texture_descriptor.size = images[0].GetSize(); diff --git a/impeller/playground/playground.h b/impeller/playground/playground.h index 9bf16f2e957bf..0481211fd1d36 100644 --- a/impeller/playground/playground.h +++ b/impeller/playground/playground.h @@ -10,6 +10,7 @@ #include "flutter/fml/status.h" #include "flutter/fml/time/time_delta.h" +#include "impeller/core/host_buffer.h" #include "impeller/core/runtime_types.h" #include "impeller/core/texture.h" #include "impeller/geometry/point.h" @@ -130,6 +131,7 @@ class Playground { std::shared_ptr context_; Point cursor_position_; ISize window_size_ = ISize{1024, 768}; + std::shared_ptr host_buffer_; void SetCursorPosition(Point pos); diff --git a/impeller/renderer/BUILD.gn b/impeller/renderer/BUILD.gn index fe75154f28569..fdc2ae7d9899c 100644 --- a/impeller/renderer/BUILD.gn +++ b/impeller/renderer/BUILD.gn @@ -96,10 +96,9 @@ impeller_component("renderer") { deps = [ "//flutter/fml" ] } -impeller_component("renderer_unittests") { - testonly = true - - sources = [ +template("renderer_unittests_component") { + target_name = invoker.target_name + predefined_sources = [ "blit_pass_unittests.cc", "capabilities_unittests.cc", "device_buffer_unittests.cc", @@ -107,18 +106,43 @@ impeller_component("renderer_unittests") { "pool_unittests.cc", "renderer_unittests.cc", ] + impeller_component(target_name) { + testonly = true + if (defined(invoker.defines)) { + defines = invoker.defines + } else { + defines = [] + } + sources = predefined_sources + if (defined(invoker.deps)) { + deps = invoker.deps + } else { + deps = [] + } + deps += [ + ":renderer", + "../fixtures", + "../playground:playground_test", + "../tessellator:tessellator_libtess", + "//flutter/impeller/display_list:display_list", + "//flutter/testing:testing_lib", + ] + if (defined(invoker.public_configs)) { + public_configs = invoker.public_configs + } + } +} - deps = [ - ":renderer", - "../fixtures", - "../playground:playground_test", - "../tessellator:tessellator_libtess", - "//flutter/testing:testing_lib", - ] +renderer_unittests_component("renderer_unittests") { + deps = [ "//flutter/impeller/display_list:aiks_unittests" ] +} - if (impeller_enable_compute) { - sources += [ "compute_unittests.cc" ] - } +renderer_unittests_component("renderer_unittests_golden") { + deps = [ "//flutter/impeller/display_list:aiks_unittests_golden" ] + defines = [ + "IMPELLER_GOLDEN_TESTS", + "IMPELLER_ENABLE_VALIDATION=1", + ] } impeller_component("renderer_dart_unittests") { diff --git a/impeller/renderer/backend/gles/BUILD.gn b/impeller/renderer/backend/gles/BUILD.gn index 3a99fa9f99d32..a28309f5fa828 100644 --- a/impeller/renderer/backend/gles/BUILD.gn +++ b/impeller/renderer/backend/gles/BUILD.gn @@ -14,6 +14,8 @@ config("gles_config") { impeller_component("gles_unittests") { testonly = true sources = [ + "buffer_bindings_gles_unittests.cc", + "device_buffer_gles_unittests.cc", "test/capabilities_unittests.cc", "test/formats_gles_unittests.cc", "test/gpu_tracer_gles_unittests.cc", @@ -22,7 +24,11 @@ impeller_component("gles_unittests") { "test/mock_gles_unittests.cc", "test/pipeline_library_gles_unittests.cc", "test/proc_table_gles_unittests.cc", + "test/reactor_unittests.cc", "test/specialization_constants_unittests.cc", + "test/surface_gles_unittests.cc", + "test/texture_gles_unittests.cc", + "unique_handle_gles_unittests.cc", ] deps = [ ":gles", @@ -101,5 +107,6 @@ impeller_component("gles") { "../../:renderer", "../../../shader_archive", "//flutter/fml", + "//flutter/third_party/abseil-cpp/absl/container:flat_hash_map", ] } diff --git a/impeller/renderer/backend/gles/allocator_gles.cc b/impeller/renderer/backend/gles/allocator_gles.cc index cd062d8a445e3..820ec22d69163 100644 --- a/impeller/renderer/backend/gles/allocator_gles.cc +++ b/impeller/renderer/backend/gles/allocator_gles.cc @@ -13,7 +13,7 @@ namespace impeller { -AllocatorGLES::AllocatorGLES(ReactorGLES::Ref reactor) +AllocatorGLES::AllocatorGLES(std::shared_ptr reactor) : reactor_(std::move(reactor)), is_valid_(true) {} // |Allocator| diff --git a/impeller/renderer/backend/gles/allocator_gles.h b/impeller/renderer/backend/gles/allocator_gles.h index a01c39e9597a8..14728414f467e 100644 --- a/impeller/renderer/backend/gles/allocator_gles.h +++ b/impeller/renderer/backend/gles/allocator_gles.h @@ -18,10 +18,10 @@ class AllocatorGLES final : public Allocator { private: friend class ContextGLES; - ReactorGLES::Ref reactor_; + std::shared_ptr reactor_; bool is_valid_ = false; - explicit AllocatorGLES(ReactorGLES::Ref reactor); + explicit AllocatorGLES(std::shared_ptr reactor); // |Allocator| bool IsValid() const; diff --git a/impeller/renderer/backend/gles/blit_command_gles.cc b/impeller/renderer/backend/gles/blit_command_gles.cc index 04f6c4acceec5..d31c8212c0e57 100644 --- a/impeller/renderer/backend/gles/blit_command_gles.cc +++ b/impeller/renderer/backend/gles/blit_command_gles.cc @@ -220,7 +220,7 @@ bool BlitCopyBufferToTextureCommandGLES::Encode( } if (!tex_descriptor.IsValid() || - source.range.length != + source.GetRange().length != BytesPerPixelForPixelFormat(tex_descriptor.format) * destination_region.Area()) { return false; @@ -263,8 +263,8 @@ bool BlitCopyBufferToTextureCommandGLES::Encode( } const auto& gl = reactor.GetProcTable(); gl.BindTexture(texture_type, gl_handle.value()); - const GLvoid* tex_data = - data.buffer_view.buffer->OnGetContents() + data.buffer_view.range.offset; + const GLvoid* tex_data = data.buffer_view.GetBuffer()->OnGetContents() + + data.buffer_view.GetRange().offset; // GL_INVALID_OPERATION if the texture array has not been // defined by a previous glTexImage2D operation. @@ -372,6 +372,8 @@ bool BlitResizeTextureCommandGLES::Encode(const ReactorGLES& reactor) const { return false; } + destination->SetCoordinateSystem(source->GetCoordinateSystem()); + GLuint read_fbo = GL_NONE; GLuint draw_fbo = GL_NONE; fml::ScopedCleanupClosure delete_fbos([&gl, &read_fbo, &draw_fbo]() { diff --git a/impeller/renderer/backend/gles/blit_pass_gles.cc b/impeller/renderer/backend/gles/blit_pass_gles.cc index 94d830001eb98..7e8aaf9238d1f 100644 --- a/impeller/renderer/backend/gles/blit_pass_gles.cc +++ b/impeller/renderer/backend/gles/blit_pass_gles.cc @@ -14,7 +14,7 @@ namespace impeller { -BlitPassGLES::BlitPassGLES(ReactorGLES::Ref reactor) +BlitPassGLES::BlitPassGLES(std::shared_ptr reactor) : reactor_(std::move(reactor)), is_valid_(reactor_ && reactor_->IsValid()) {} diff --git a/impeller/renderer/backend/gles/blit_pass_gles.h b/impeller/renderer/backend/gles/blit_pass_gles.h index 321bd42dc9e8a..f3780f4bd41b2 100644 --- a/impeller/renderer/backend/gles/blit_pass_gles.h +++ b/impeller/renderer/backend/gles/blit_pass_gles.h @@ -25,11 +25,11 @@ class BlitPassGLES final : public BlitPass, friend class CommandBufferGLES; std::vector> commands_; - ReactorGLES::Ref reactor_; + std::shared_ptr reactor_; std::string label_; bool is_valid_ = false; - explicit BlitPassGLES(ReactorGLES::Ref reactor); + explicit BlitPassGLES(std::shared_ptr reactor); // |BlitPass| bool IsValid() const override; diff --git a/impeller/renderer/backend/gles/buffer_bindings_gles.cc b/impeller/renderer/backend/gles/buffer_bindings_gles.cc index c893833b460fd..3f10bc9b5d4ea 100644 --- a/impeller/renderer/backend/gles/buffer_bindings_gles.cc +++ b/impeller/renderer/backend/gles/buffer_bindings_gles.cc @@ -8,11 +8,14 @@ #include #include "impeller/base/validation.h" +#include "impeller/core/buffer_view.h" +#include "impeller/core/device_buffer.h" #include "impeller/core/shader_types.h" #include "impeller/renderer/backend/gles/device_buffer_gles.h" #include "impeller/renderer/backend/gles/formats_gles.h" #include "impeller/renderer/backend/gles/sampler_gles.h" #include "impeller/renderer/backend/gles/texture_gles.h" +#include "impeller/renderer/command.h" namespace impeller { @@ -100,6 +103,38 @@ bool BufferBindingsGLES::ReadUniformsBindings(const ProcTableGLES& gl, if (!gl.IsProgram(program)) { return false; } + program_handle_ = program; + if (gl.GetDescription()->GetGlVersion().IsAtLeast(Version{3, 0, 0})) { + return ReadUniformsBindingsV3(gl, program); + } + return ReadUniformsBindingsV2(gl, program); +} + +bool BufferBindingsGLES::ReadUniformsBindingsV3(const ProcTableGLES& gl, + GLuint program) { + program_handle_ = program; + GLint uniform_blocks = 0; + gl.GetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &uniform_blocks); + for (GLint i = 0; i < uniform_blocks; i++) { + GLint name_length = 0; + gl.GetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_NAME_LENGTH, + &name_length); + + std::vector name; + name.resize(name_length); + GLint length = 0; + gl.GetActiveUniformBlockName(program, i, name_length, &length, name.data()); + + GLuint block_index = gl.GetUniformBlockIndex(program, name.data()); + ubo_locations_[std::string{name.data(), static_cast(length)}] = + std::make_pair(block_index, i); + } + use_ubo_ = true; + return ReadUniformsBindingsV2(gl, program); +} + +bool BufferBindingsGLES::ReadUniformsBindingsV2(const ProcTableGLES& gl, + GLuint program) { GLint max_name_size = 0; gl.GetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_name_size); @@ -138,6 +173,9 @@ bool BufferBindingsGLES::ReadUniformsBindings(const ProcTableGLES& gl, auto location = gl.GetUniformLocation(program, name.data()); if (location == -1) { + if (use_ubo_) { + continue; + } VALIDATION_LOG << "Could not query the location of an active uniform."; return false; } @@ -153,11 +191,17 @@ bool BufferBindingsGLES::ReadUniformsBindings(const ProcTableGLES& gl, bool BufferBindingsGLES::BindVertexAttributes(const ProcTableGLES& gl, size_t binding, - size_t vertex_offset) const { + size_t vertex_offset) { if (binding >= vertex_attrib_arrays_.size()) { return false; } + if (!gl.GetCapabilities()->IsES()) { + FML_DCHECK(vertex_array_object_ == 0); + gl.GenVertexArrays(1, &vertex_array_object_); + gl.BindVertexArray(vertex_array_object_); + } + for (const auto& array : vertex_attrib_arrays_[binding]) { gl.EnableVertexAttribArray(array.index); gl.VertexAttribPointer(array.index, // index @@ -173,28 +217,23 @@ bool BufferBindingsGLES::BindVertexAttributes(const ProcTableGLES& gl, return true; } -bool BufferBindingsGLES::BindUniformData(const ProcTableGLES& gl, - Allocator& transients_allocator, - const Bindings& vertex_bindings, - const Bindings& fragment_bindings) { - for (const auto& buffer : vertex_bindings.buffers) { - if (!BindUniformBuffer(gl, transients_allocator, buffer.view)) { - return false; - } - } - for (const auto& buffer : fragment_bindings.buffers) { - if (!BindUniformBuffer(gl, transients_allocator, buffer.view)) { +bool BufferBindingsGLES::BindUniformData( + const ProcTableGLES& gl, + const std::vector& bound_textures, + const std::vector& bound_buffers, + Range texture_range, + Range buffer_range) { + for (auto i = 0u; i < buffer_range.length; i++) { + if (!BindUniformBuffer(gl, bound_buffers[buffer_range.offset + i])) { return false; } } - std::optional next_unit_index = - BindTextures(gl, vertex_bindings, ShaderStage::kVertex); + BindTextures(gl, bound_textures, texture_range, ShaderStage::kVertex); if (!next_unit_index.has_value()) { return false; } - - if (!BindTextures(gl, fragment_bindings, ShaderStage::kFragment, + if (!BindTextures(gl, bound_textures, texture_range, ShaderStage::kFragment, *next_unit_index) .has_value()) { return false; @@ -203,12 +242,16 @@ bool BufferBindingsGLES::BindUniformData(const ProcTableGLES& gl, return true; } -bool BufferBindingsGLES::UnbindVertexAttributes(const ProcTableGLES& gl) const { +bool BufferBindingsGLES::UnbindVertexAttributes(const ProcTableGLES& gl) { for (const auto& array : vertex_attrib_arrays_) { for (const auto& attribute : array) { gl.DisableVertexAttribArray(attribute.index); } } + if (!gl.GetCapabilities()->IsES()) { + gl.DeleteVertexArrays(1, &vertex_array_object_); + vertex_array_object_ = 0; + } return true; } @@ -232,15 +275,16 @@ GLint BufferBindingsGLES::ComputeTextureLocation( const std::vector& BufferBindingsGLES::ComputeUniformLocations( const ShaderMetadata* metadata) { - auto location = binding_map_.find(metadata->name); + BindingMap::iterator location = binding_map_.find(metadata->name); if (location != binding_map_.end()) { return location->second; } // For each metadata member, look up the binding location and record // it in the binding map. - auto& locations = binding_map_[metadata->name] = {}; - for (const auto& member : metadata->members) { + std::vector& locations = binding_map_[metadata->name] = {}; + locations.reserve(metadata->members.size()); + for (const ShaderStructMemberMetadata& member : metadata->members) { if (member.type == ShaderType::kVoid) { // Void types are used for padding. We are obviously not going to find // mappings for these. Keep going. @@ -249,9 +293,10 @@ const std::vector& BufferBindingsGLES::ComputeUniformLocations( } size_t element_count = member.array_elements.value_or(1); - const auto member_key = + const std::string member_key = CreateUniformMemberKey(metadata->name, member.name, element_count > 1); - const auto computed_location = uniform_locations_.find(member_key); + const absl::flat_hash_map::iterator computed_location = + uniform_locations_.find(member_key); if (computed_location == uniform_locations_.end()) { // Uniform was not active. locations.push_back(-1); @@ -263,17 +308,56 @@ const std::vector& BufferBindingsGLES::ComputeUniformLocations( } bool BufferBindingsGLES::BindUniformBuffer(const ProcTableGLES& gl, - Allocator& transients_allocator, const BufferResource& buffer) { - const auto* metadata = buffer.GetMetadata(); - auto device_buffer = buffer.resource.buffer; + const ShaderMetadata* metadata = buffer.GetMetadata(); + const DeviceBuffer* device_buffer = buffer.resource.GetBuffer(); if (!device_buffer) { VALIDATION_LOG << "Device buffer not found."; return false; } - const auto& device_buffer_gles = DeviceBufferGLES::Cast(*device_buffer); + const DeviceBufferGLES& device_buffer_gles = + DeviceBufferGLES::Cast(*device_buffer); + + if (use_ubo_) { + return BindUniformBufferV3(gl, buffer.resource, metadata, + device_buffer_gles); + } + return BindUniformBufferV2(gl, buffer.resource, metadata, device_buffer_gles); +} + +bool BufferBindingsGLES::BindUniformBufferV3( + const ProcTableGLES& gl, + const BufferView& buffer, + const ShaderMetadata* metadata, + const DeviceBufferGLES& device_buffer_gles) { + absl::flat_hash_map>::iterator it = + ubo_locations_.find(metadata->name); + if (it == ubo_locations_.end()) { + return BindUniformBufferV2(gl, buffer, metadata, device_buffer_gles); + } + const auto& [block_index, binding_point] = it->second; + gl.UniformBlockBinding(program_handle_, block_index, binding_point); + + if (!device_buffer_gles.BindAndUploadDataIfNecessary( + DeviceBufferGLES::BindingType::kUniformBuffer)) { + return false; + } + auto handle = device_buffer_gles.GetHandle(); + if (!handle.has_value()) { + return false; + } + gl.BindBufferRange(GL_UNIFORM_BUFFER, binding_point, handle.value(), + buffer.GetRange().offset, buffer.GetRange().length); + return true; +} + +bool BufferBindingsGLES::BindUniformBufferV2( + const ProcTableGLES& gl, + const BufferView& buffer, + const ShaderMetadata* metadata, + const DeviceBufferGLES& device_buffer_gles) { const uint8_t* buffer_ptr = - device_buffer_gles.GetBufferData() + buffer.resource.range.offset; + device_buffer_gles.GetBufferData() + buffer.GetRange().offset; if (metadata->members.empty()) { VALIDATION_LOG << "Uniform buffer had no members. This is currently " @@ -282,10 +366,10 @@ bool BufferBindingsGLES::BindUniformBuffer(const ProcTableGLES& gl, return false; } - const auto& locations = ComputeUniformLocations(metadata); - for (auto i = 0u; i < metadata->members.size(); i++) { - const auto& member = metadata->members[i]; - auto location = locations[i]; + const std::vector& locations = ComputeUniformLocations(metadata); + for (size_t i = 0u; i < metadata->members.size(); i++) { + const ShaderStructMemberMetadata& member = metadata->members[i]; + GLint location = locations[i]; // Void type or inactive uniform. if (location == -1 || member.type == ShaderType::kVoid) { continue; @@ -299,77 +383,59 @@ bool BufferBindingsGLES::BindUniformBuffer(const ProcTableGLES& gl, // When binding uniform arrays, the elements must be contiguous. Copy // the uniforms to a temp buffer to eliminate any padding needed by the // other backends if the array elements have padding. - std::vector array_element_buffer_; + std::vector array_element_buffer; if (element_count > 1 && element_stride != member.size) { - array_element_buffer_.resize(member.size * element_count); + array_element_buffer.resize(member.size * element_count); for (size_t element_i = 0; element_i < element_count; element_i++) { - std::memcpy(array_element_buffer_.data() + element_i * member.size, + std::memcpy(array_element_buffer.data() + element_i * member.size, reinterpret_cast(buffer_data) + element_i * element_stride, member.size); } buffer_data = - reinterpret_cast(array_element_buffer_.data()); + reinterpret_cast(array_element_buffer.data()); + } + + if (member.type != ShaderType::kFloat) { + VALIDATION_LOG << "Could not bind uniform buffer data for key: " + << member.name << " : " << static_cast(member.type); + return false; } - switch (member.type) { - case ShaderType::kFloat: - switch (member.size) { - case sizeof(Matrix): - gl.UniformMatrix4fv(location, // location - element_count, // count - GL_FALSE, // normalize - buffer_data // data - ); - continue; - case sizeof(Vector4): - gl.Uniform4fv(location, // location - element_count, // count - buffer_data // data - ); - continue; - case sizeof(Vector3): - gl.Uniform3fv(location, // location - element_count, // count - buffer_data // data - ); - continue; - case sizeof(Vector2): - gl.Uniform2fv(location, // location - element_count, // count - buffer_data // data - ); - continue; - case sizeof(Scalar): - gl.Uniform1fv(location, // location - element_count, // count - buffer_data // data - ); - continue; - } - VALIDATION_LOG << "Size " << member.size - << " could not be mapped ShaderType::kFloat for key: " - << member.name; - case ShaderType::kBoolean: - case ShaderType::kSignedByte: - case ShaderType::kUnsignedByte: - case ShaderType::kSignedShort: - case ShaderType::kUnsignedShort: - case ShaderType::kSignedInt: - case ShaderType::kUnsignedInt: - case ShaderType::kSignedInt64: - case ShaderType::kUnsignedInt64: - case ShaderType::kAtomicCounter: - case ShaderType::kUnknown: - case ShaderType::kVoid: - case ShaderType::kHalfFloat: - case ShaderType::kDouble: - case ShaderType::kStruct: - case ShaderType::kImage: - case ShaderType::kSampledImage: - case ShaderType::kSampler: - VALIDATION_LOG << "Could not bind uniform buffer data for key: " - << member.name << " : " << static_cast(member.type); + switch (member.size) { + case sizeof(Matrix): + gl.UniformMatrix4fv(location, // location + element_count, // count + GL_FALSE, // normalize + buffer_data // data + ); + continue; + case sizeof(Vector4): + gl.Uniform4fv(location, // location + element_count, // count + buffer_data // data + ); + continue; + case sizeof(Vector3): + gl.Uniform3fv(location, // location + element_count, // count + buffer_data // data + ); + continue; + case sizeof(Vector2): + gl.Uniform2fv(location, // location + element_count, // count + buffer_data // data + ); + continue; + case sizeof(Scalar): + gl.Uniform1fv(location, // location + element_count, // count + buffer_data // data + ); + continue; + default: + VALIDATION_LOG << "Invalid member size binding: " << member.size; return false; } } @@ -378,11 +444,16 @@ bool BufferBindingsGLES::BindUniformBuffer(const ProcTableGLES& gl, std::optional BufferBindingsGLES::BindTextures( const ProcTableGLES& gl, - const Bindings& bindings, + const std::vector& bound_textures, + Range texture_range, ShaderStage stage, size_t unit_start_index) { size_t active_index = unit_start_index; - for (const auto& data : bindings.sampled_images) { + for (auto i = 0u; i < texture_range.length; i++) { + const TextureAndSampler& data = bound_textures[texture_range.offset + i]; + if (data.stage != stage) { + continue; + } const auto& texture_gles = TextureGLES::Cast(*data.texture.resource); if (data.texture.GetMetadata() == nullptr) { VALIDATION_LOG << "No metadata found for texture binding."; diff --git a/impeller/renderer/backend/gles/buffer_bindings_gles.h b/impeller/renderer/backend/gles/buffer_bindings_gles.h index a8954b93bab58..29f032da76cf9 100644 --- a/impeller/renderer/backend/gles/buffer_bindings_gles.h +++ b/impeller/renderer/backend/gles/buffer_bindings_gles.h @@ -8,13 +8,19 @@ #include #include +#include "flutter/third_party/abseil-cpp/absl/container/flat_hash_map.h" #include "impeller/core/shader_types.h" +#include "impeller/renderer/backend/gles/device_buffer_gles.h" #include "impeller/renderer/backend/gles/gles.h" #include "impeller/renderer/backend/gles/proc_table_gles.h" #include "impeller/renderer/command.h" namespace impeller { +namespace testing { +FML_TEST_CLASS(BufferBindingsGLESTest, BindUniformData); +} // namespace testing + //------------------------------------------------------------------------------ /// @brief Sets up stage bindings for single draw call in the OpenGLES /// backend. @@ -34,16 +40,18 @@ class BufferBindingsGLES { bool BindVertexAttributes(const ProcTableGLES& gl, size_t binding, - size_t vertex_offset) const; + size_t vertex_offset); bool BindUniformData(const ProcTableGLES& gl, - Allocator& transients_allocator, - const Bindings& vertex_bindings, - const Bindings& fragment_bindings); + const std::vector& bound_textures, + const std::vector& bound_buffers, + Range texture_range, + Range buffer_range); - bool UnbindVertexAttributes(const ProcTableGLES& gl) const; + bool UnbindVertexAttributes(const ProcTableGLES& gl); private: + FML_FRIEND_TEST(testing::BufferBindingsGLESTest, BindUniformData); //---------------------------------------------------------------------------- /// @brief The arguments to glVertexAttribPointer. /// @@ -57,28 +65,52 @@ class BufferBindingsGLES { }; std::vector> vertex_attrib_arrays_; - std::unordered_map uniform_locations_; + absl::flat_hash_map uniform_locations_; + absl::flat_hash_map> ubo_locations_; - using BindingMap = std::unordered_map>; + using BindingMap = absl::flat_hash_map>; BindingMap binding_map_ = {}; + GLuint vertex_array_object_ = 0; + GLuint program_handle_ = GL_NONE; + bool use_ubo_ = false; const std::vector& ComputeUniformLocations( const ShaderMetadata* metadata); + bool ReadUniformsBindingsV2(const ProcTableGLES& gl, GLuint program); + + bool ReadUniformsBindingsV3(const ProcTableGLES& gl, GLuint program); + GLint ComputeTextureLocation(const ShaderMetadata* metadata); - bool BindUniformBuffer(const ProcTableGLES& gl, - Allocator& transients_allocator, - const BufferResource& buffer); + bool BindUniformBuffer(const ProcTableGLES& gl, const BufferResource& buffer); + + bool BindUniformBufferV2(const ProcTableGLES& gl, + const BufferView& buffer, + const ShaderMetadata* metadata, + const DeviceBufferGLES& device_buffer_gles); - std::optional BindTextures(const ProcTableGLES& gl, - const Bindings& bindings, - ShaderStage stage, - size_t unit_start_index = 0); + bool BindUniformBufferV3(const ProcTableGLES& gl, + const BufferView& buffer, + const ShaderMetadata* metadata, + const DeviceBufferGLES& device_buffer_gles); + + std::optional BindTextures( + const ProcTableGLES& gl, + const std::vector& bound_textures, + Range texture_range, + ShaderStage stage, + size_t unit_start_index = 0); BufferBindingsGLES(const BufferBindingsGLES&) = delete; BufferBindingsGLES& operator=(const BufferBindingsGLES&) = delete; + + // For testing. + void SetUniformBindings( + absl::flat_hash_map uniform_locations) { + uniform_locations_ = std::move(uniform_locations); + } }; } // namespace impeller diff --git a/impeller/renderer/backend/gles/buffer_bindings_gles_unittests.cc b/impeller/renderer/backend/gles/buffer_bindings_gles_unittests.cc new file mode 100644 index 0000000000000..45a342bfcbd06 --- /dev/null +++ b/impeller/renderer/backend/gles/buffer_bindings_gles_unittests.cc @@ -0,0 +1,51 @@ +// 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. + +#include "flutter/testing/testing.h" // IWYU pragma: keep +#include "gtest/gtest.h" +#include "impeller/renderer/backend/gles/buffer_bindings_gles.h" +#include "impeller/renderer/backend/gles/device_buffer_gles.h" +#include "impeller/renderer/backend/gles/test/mock_gles.h" +#include "impeller/renderer/command.h" + +namespace impeller { +namespace testing { + +using ::testing::_; + +TEST(BufferBindingsGLESTest, BindUniformData) { + BufferBindingsGLES bindings; + absl::flat_hash_map uniform_bindings; + uniform_bindings["SHADERMETADATA.FOOBAR"] = 1; + bindings.SetUniformBindings(std::move(uniform_bindings)); + auto mock_gles_impl = std::make_unique(); + + EXPECT_CALL(*mock_gles_impl, Uniform1fv(_, _, _)).Times(1); + + std::shared_ptr mock_gl = MockGLES::Init(std::move(mock_gles_impl)); + std::vector bound_buffers; + std::vector bound_textures; + + ShaderMetadata shader_metadata = { + .name = "shader_metadata", + .members = {ShaderStructMemberMetadata{.type = ShaderType::kFloat, + .name = "foobar", + .offset = 0, + .size = sizeof(float), + .byte_length = sizeof(float)}}}; + std::shared_ptr reactor; + std::shared_ptr backing_store = std::make_shared(); + ASSERT_TRUE(backing_store->Truncate(Bytes{sizeof(float)})); + DeviceBufferGLES device_buffer(DeviceBufferDescriptor{.size = sizeof(float)}, + reactor, backing_store); + BufferView buffer_view(&device_buffer, Range(0, sizeof(float))); + bound_buffers.push_back(BufferResource(&shader_metadata, buffer_view)); + + EXPECT_TRUE(bindings.BindUniformData(mock_gl->GetProcTable(), bound_textures, + bound_buffers, Range{0, 0}, + Range{0, 1})); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/renderer/backend/gles/capabilities_gles.cc b/impeller/renderer/backend/gles/capabilities_gles.cc index 5f078c2e3965a..67f73eff4e07f 100644 --- a/impeller/renderer/backend/gles/capabilities_gles.cc +++ b/impeller/renderer/backend/gles/capabilities_gles.cc @@ -22,6 +22,10 @@ static const constexpr char* kNvidiaTextureBorderClampExt = static const constexpr char* kMultisampledRenderToTextureExt = "GL_EXT_multisampled_render_to_texture"; +// https://registry.khronos.org/OpenGL/extensions/EXT/EXT_multisampled_render_to_texture2.txt +static const constexpr char* kMultisampledRenderToTexture2Ext = + "GL_EXT_multisampled_render_to_texture2"; + CapabilitiesGLES::CapabilitiesGLES(const ProcTableGLES& gl) { { GLint value = 0; @@ -109,6 +113,10 @@ CapabilitiesGLES::CapabilitiesGLES(const ProcTableGLES& gl) { default_glyph_atlas_format_ = PixelFormat::kR8UNormInt; } + if (desc->GetGlVersion().major_version >= 3) { + supports_texture_to_texture_blits_ = true; + } + supports_framebuffer_fetch_ = desc->HasExtension(kFramebufferFetchExt); if (desc->HasExtension(kTextureBorderClampExt) || @@ -119,15 +127,21 @@ CapabilitiesGLES::CapabilitiesGLES(const ProcTableGLES& gl) { if (desc->HasExtension(kMultisampledRenderToTextureExt)) { supports_implicit_msaa_ = true; - // We hard-code 4x MSAA, so let's make sure it's supported. - GLint value = 0; - gl.GetIntegerv(GL_MAX_SAMPLES_EXT, &value); - supports_offscreen_msaa_ = value >= 4; + if (desc->HasExtension(kMultisampledRenderToTexture2Ext)) { + // We hard-code 4x MSAA, so let's make sure it's supported. + GLint value = 0; + gl.GetIntegerv(GL_MAX_SAMPLES_EXT, &value); + supports_offscreen_msaa_ = value >= 4; + } } - + is_es_ = desc->IsES(); is_angle_ = desc->IsANGLE(); } +bool CapabilitiesGLES::IsES() const { + return is_es_; +} + size_t CapabilitiesGLES::GetMaxTextureUnits(ShaderStage stage) const { switch (stage) { case ShaderStage::kVertex: @@ -154,7 +168,7 @@ bool CapabilitiesGLES::SupportsSSBO() const { } bool CapabilitiesGLES::SupportsTextureToTextureBlits() const { - return false; + return supports_texture_to_texture_blits_; } bool CapabilitiesGLES::SupportsFramebufferFetch() const { @@ -201,6 +215,10 @@ bool CapabilitiesGLES::IsANGLE() const { return is_angle_; } +bool CapabilitiesGLES::SupportsPrimitiveRestart() const { + return false; +} + PixelFormat CapabilitiesGLES::GetDefaultGlyphAtlasFormat() const { return default_glyph_atlas_format_; } diff --git a/impeller/renderer/backend/gles/capabilities_gles.h b/impeller/renderer/backend/gles/capabilities_gles.h index bcc911cbe76d3..fa39cfecab637 100644 --- a/impeller/renderer/backend/gles/capabilities_gles.h +++ b/impeller/renderer/backend/gles/capabilities_gles.h @@ -77,6 +77,9 @@ class CapabilitiesGLES final bool IsANGLE() const; + /// @brief Whether this is an ES GL variant or (if false) desktop GL. + bool IsES() const; + // |Capabilities| bool SupportsOffscreenMSAA() const override; @@ -110,6 +113,9 @@ class CapabilitiesGLES final // |Capabilities| bool SupportsTriangleFan() const override; + // |Capabilities| + bool SupportsPrimitiveRestart() const override; + // |Capabilities| PixelFormat GetDefaultColorFormat() const override; @@ -125,11 +131,13 @@ class CapabilitiesGLES final ISize GetMaximumRenderPassAttachmentSize() const override; private: + bool supports_texture_to_texture_blits_ = false; bool supports_framebuffer_fetch_ = false; bool supports_decal_sampler_address_mode_ = false; bool supports_offscreen_msaa_ = false; bool supports_implicit_msaa_ = false; bool is_angle_ = false; + bool is_es_ = false; PixelFormat default_glyph_atlas_format_ = PixelFormat::kUnknown; }; diff --git a/impeller/renderer/backend/gles/command_buffer_gles.cc b/impeller/renderer/backend/gles/command_buffer_gles.cc index d570cbc6e972e..4cd6b279b9e18 100644 --- a/impeller/renderer/backend/gles/command_buffer_gles.cc +++ b/impeller/renderer/backend/gles/command_buffer_gles.cc @@ -11,7 +11,7 @@ namespace impeller { CommandBufferGLES::CommandBufferGLES(std::weak_ptr context, - ReactorGLES::Ref reactor) + std::shared_ptr reactor) : CommandBuffer(std::move(context)), reactor_(std::move(reactor)), is_valid_(reactor_ && reactor_->IsValid()) {} @@ -38,6 +38,11 @@ bool CommandBufferGLES::OnSubmitCommands(CompletionCallback callback) { return result; } +// |CommandBuffer| +void CommandBufferGLES::OnWaitUntilCompleted() { + reactor_->GetProcTable().Finish(); +} + // |CommandBuffer| void CommandBufferGLES::OnWaitUntilScheduled() { reactor_->GetProcTable().Flush(); diff --git a/impeller/renderer/backend/gles/command_buffer_gles.h b/impeller/renderer/backend/gles/command_buffer_gles.h index 2188ccdd3fa53..c7baea2d7bf15 100644 --- a/impeller/renderer/backend/gles/command_buffer_gles.h +++ b/impeller/renderer/backend/gles/command_buffer_gles.h @@ -19,11 +19,11 @@ class CommandBufferGLES final : public CommandBuffer { private: friend class ContextGLES; - ReactorGLES::Ref reactor_; + std::shared_ptr reactor_; bool is_valid_ = false; CommandBufferGLES(std::weak_ptr context, - ReactorGLES::Ref reactor); + std::shared_ptr reactor); // |CommandBuffer| void SetLabel(std::string_view label) const override; @@ -34,6 +34,9 @@ class CommandBufferGLES final : public CommandBuffer { // |CommandBuffer| bool OnSubmitCommands(CompletionCallback callback) override; + // |CommandBuffer| + void OnWaitUntilCompleted() override; + // |CommandBuffer| void OnWaitUntilScheduled() override; diff --git a/impeller/renderer/backend/gles/context_gles.cc b/impeller/renderer/backend/gles/context_gles.cc index 7629eff28e7cd..f8cf2841cd013 100644 --- a/impeller/renderer/backend/gles/context_gles.cc +++ b/impeller/renderer/backend/gles/context_gles.cc @@ -7,8 +7,13 @@ #include "impeller/base/config.h" #include "impeller/base/validation.h" +#include "impeller/base/version.h" +#include "impeller/core/runtime_types.h" #include "impeller/renderer/backend/gles/command_buffer_gles.h" #include "impeller/renderer/backend/gles/gpu_tracer_gles.h" +#include "impeller/renderer/backend/gles/handle_gles.h" +#include "impeller/renderer/backend/gles/render_pass_gles.h" +#include "impeller/renderer/backend/gles/texture_gles.h" #include "impeller/renderer/command_queue.h" namespace impeller { @@ -78,7 +83,7 @@ Context::BackendType ContextGLES::GetBackendType() const { return Context::BackendType::kOpenGLES; } -const ReactorGLES::Ref& ContextGLES::GetReactor() const { +const std::shared_ptr& ContextGLES::GetReactor() const { return reactor_; } @@ -145,4 +150,45 @@ std::shared_ptr ContextGLES::GetCommandQueue() const { return command_queue_; } +// |Context| +void ContextGLES::ResetThreadLocalState() const { + if (!IsValid()) { + return; + } + [[maybe_unused]] auto result = + reactor_->AddOperation([](const ReactorGLES& reactor) { + RenderPassGLES::ResetGLState(reactor.GetProcTable()); + }); +} + +bool ContextGLES::EnqueueCommandBuffer( + std::shared_ptr command_buffer) { + return true; +} + +// |Context| +[[nodiscard]] bool ContextGLES::FlushCommandBuffers() { + return reactor_->React(); +} + +// |Context| +bool ContextGLES::AddTrackingFence( + const std::shared_ptr& texture) const { + if (!reactor_->GetProcTable().FenceSync.IsAvailable()) { + return false; + } + HandleGLES fence = reactor_->CreateHandle(HandleType::kFence); + TextureGLES::Cast(*texture).SetFence(fence); + return true; +} + +// |Context| +RuntimeStageBackend ContextGLES::GetRuntimeStageBackend() const { + if (GetReactor()->GetProcTable().GetDescription()->GetGlVersion().IsAtLeast( + Version{3, 0, 0})) { + return RuntimeStageBackend::kOpenGLES3; + } + return RuntimeStageBackend::kOpenGLES; +} + } // namespace impeller diff --git a/impeller/renderer/backend/gles/context_gles.h b/impeller/renderer/backend/gles/context_gles.h index 2a0f09aca39b1..2e7e87a01c9fd 100644 --- a/impeller/renderer/backend/gles/context_gles.h +++ b/impeller/renderer/backend/gles/context_gles.h @@ -6,6 +6,7 @@ #define FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_CONTEXT_GLES_H_ #include "impeller/base/backend_cast.h" +#include "impeller/core/runtime_types.h" #include "impeller/renderer/backend/gles/allocator_gles.h" #include "impeller/renderer/backend/gles/capabilities_gles.h" #include "impeller/renderer/backend/gles/gpu_tracer_gles.h" @@ -34,7 +35,7 @@ class ContextGLES final : public Context, // |Context| BackendType GetBackendType() const override; - const ReactorGLES::Ref& GetReactor() const; + const std::shared_ptr& GetReactor() const; std::optional AddReactorWorker( const std::shared_ptr& worker); @@ -44,7 +45,7 @@ class ContextGLES final : public Context, std::shared_ptr GetGPUTracer() const { return gpu_tracer_; } private: - ReactorGLES::Ref reactor_; + std::shared_ptr reactor_; std::shared_ptr shader_library_; std::shared_ptr pipeline_library_; std::shared_ptr sampler_library_; @@ -93,6 +94,22 @@ class ContextGLES final : public Context, // |Context| void Shutdown() override; + // |Context| + bool AddTrackingFence(const std::shared_ptr& texture) const override; + + // |Context| + void ResetThreadLocalState() const override; + + // |Context| + [[nodiscard]] bool EnqueueCommandBuffer( + std::shared_ptr command_buffer) override; + + // |Context| + [[nodiscard]] bool FlushCommandBuffers() override; + + // |Context| + RuntimeStageBackend GetRuntimeStageBackend() const override; + ContextGLES(const ContextGLES&) = delete; ContextGLES& operator=(const ContextGLES&) = delete; diff --git a/impeller/renderer/backend/gles/description_gles.cc b/impeller/renderer/backend/gles/description_gles.cc index 855c192079a6b..fa01bbb8705a3 100644 --- a/impeller/renderer/backend/gles/description_gles.cc +++ b/impeller/renderer/backend/gles/description_gles.cc @@ -178,7 +178,10 @@ bool DescriptionGLES::HasExtension(const std::string& ext) const { } bool DescriptionGLES::HasDebugExtension() const { - return HasExtension("GL_KHR_debug"); + // Angle just logs calls instead of forwarding debug information to the + // backend. This just overwhelms the logs and is of limited use. Disable on + // Angle. + return HasExtension("GL_KHR_debug") && !IsANGLE(); } } // namespace impeller diff --git a/impeller/renderer/backend/gles/device_buffer_gles.cc b/impeller/renderer/backend/gles/device_buffer_gles.cc index f4caaf5af874d..0a7478d8f7e95 100644 --- a/impeller/renderer/backend/gles/device_buffer_gles.cc +++ b/impeller/renderer/backend/gles/device_buffer_gles.cc @@ -13,18 +13,16 @@ namespace impeller { DeviceBufferGLES::DeviceBufferGLES(DeviceBufferDescriptor desc, - ReactorGLES::Ref reactor, + std::shared_ptr reactor, std::shared_ptr backing_store) : DeviceBuffer(desc), reactor_(std::move(reactor)), - handle_(reactor_ ? reactor_->CreateHandle(HandleType::kBuffer) - : HandleGLES::DeadHandle()), backing_store_(std::move(backing_store)) {} // |DeviceBuffer| DeviceBufferGLES::~DeviceBufferGLES() { - if (!handle_.IsDead()) { - reactor_->CollectHandle(handle_); + if (handle_.has_value() && !handle_->IsDead()) { + reactor_->CollectHandle(*handle_); } } @@ -56,6 +54,14 @@ bool DeviceBufferGLES::OnCopyHostBuffer(const uint8_t* source, return true; } +std::optional DeviceBufferGLES::GetHandle() const { + if (handle_.has_value()) { + return reactor_->GetGLHandle(*handle_); + } else { + return std::nullopt; + } +} + void DeviceBufferGLES::Flush(std::optional range) const { if (!range.has_value()) { dirty_range_ = Range{ @@ -75,6 +81,8 @@ static GLenum ToTarget(DeviceBufferGLES::BindingType type) { return GL_ARRAY_BUFFER; case DeviceBufferGLES::BindingType::kElementArrayBuffer: return GL_ELEMENT_ARRAY_BUFFER; + case DeviceBufferGLES::BindingType::kUniformBuffer: + return GL_UNIFORM_BUFFER; } FML_UNREACHABLE(); } @@ -84,7 +92,16 @@ bool DeviceBufferGLES::BindAndUploadDataIfNecessary(BindingType type) const { return false; } - auto buffer = reactor_->GetGLHandle(handle_); + if (!handle_.has_value()) { + handle_ = reactor_->CreateUntrackedHandle(HandleType::kBuffer); +#ifdef IMPELLER_DEBUG + if (handle_.has_value() && label_.has_value()) { + reactor_->SetDebugLabel(*handle_, *label_); + } +#endif + } + + auto buffer = reactor_->GetGLHandle(*handle_); if (!buffer.has_value()) { return false; } @@ -112,7 +129,10 @@ bool DeviceBufferGLES::BindAndUploadDataIfNecessary(BindingType type) const { // |DeviceBuffer| bool DeviceBufferGLES::SetLabel(std::string_view label) { #ifdef IMPELLER_DEBUG - reactor_->SetDebugLabel(handle_, label); + label_ = label; + if (handle_.has_value()) { + reactor_->SetDebugLabel(*handle_, label); + } #endif // IMPELLER_DEBUG return true; } diff --git a/impeller/renderer/backend/gles/device_buffer_gles.h b/impeller/renderer/backend/gles/device_buffer_gles.h index a18e010dc2649..f080d4e3e0ffe 100644 --- a/impeller/renderer/backend/gles/device_buffer_gles.h +++ b/impeller/renderer/backend/gles/device_buffer_gles.h @@ -20,7 +20,7 @@ class DeviceBufferGLES final public BackendCast { public: DeviceBufferGLES(DeviceBufferDescriptor desc, - ReactorGLES::Ref reactor, + std::shared_ptr reactor, std::shared_ptr backing_store); // |DeviceBuffer| @@ -34,15 +34,20 @@ class DeviceBufferGLES final enum class BindingType { kArrayBuffer, kElementArrayBuffer, + kUniformBuffer, }; [[nodiscard]] bool BindAndUploadDataIfNecessary(BindingType type) const; void Flush(std::optional range = std::nullopt) const override; + std::optional GetHandle() const; + private: - ReactorGLES::Ref reactor_; - HandleGLES handle_; + std::shared_ptr reactor_; + std::optional label_; + // Mutable for lazy evaluation. + mutable std::optional handle_; mutable std::shared_ptr backing_store_; mutable std::optional dirty_range_ = std::nullopt; mutable bool initialized_ = false; diff --git a/impeller/renderer/backend/gles/device_buffer_gles_unittests.cc b/impeller/renderer/backend/gles/device_buffer_gles_unittests.cc new file mode 100644 index 0000000000000..1fe910faab0d9 --- /dev/null +++ b/impeller/renderer/backend/gles/device_buffer_gles_unittests.cc @@ -0,0 +1,49 @@ +// 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. + +#include "flutter/testing/testing.h" // IWYU pragma: keep +#include "gtest/gtest.h" +#include "impeller/renderer/backend/gles/device_buffer_gles.h" +#include "impeller/renderer/backend/gles/test/mock_gles.h" + +namespace impeller { +namespace testing { + +using ::testing::_; + +namespace { +class TestWorker : public ReactorGLES::Worker { + public: + bool CanReactorReactOnCurrentThreadNow( + const ReactorGLES& reactor) const override { + return true; + } +}; +} // namespace + +TEST(DeviceBufferGLESTest, BindUniformData) { + auto mock_gles_impl = std::make_unique(); + + EXPECT_CALL(*mock_gles_impl, GenBuffers(1, _)).Times(1); + + std::shared_ptr mock_gled = + MockGLES::Init(std::move(mock_gles_impl)); + ProcTableGLES::Resolver resolver = kMockResolverGLES; + auto proc_table = std::make_unique(resolver); + auto worker = std::make_shared(); + auto reactor = std::make_shared(std::move(proc_table)); + reactor->AddWorker(worker); + + std::shared_ptr backing_store = std::make_shared(); + ASSERT_TRUE(backing_store->Truncate(Bytes{sizeof(float)})); + DeviceBufferGLES device_buffer(DeviceBufferDescriptor{.size = sizeof(float)}, + reactor, backing_store); + EXPECT_FALSE(device_buffer.GetHandle().has_value()); + EXPECT_TRUE(device_buffer.BindAndUploadDataIfNecessary( + DeviceBufferGLES::BindingType::kUniformBuffer)); + EXPECT_TRUE(device_buffer.GetHandle().has_value()); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/renderer/backend/gles/handle_gles.cc b/impeller/renderer/backend/gles/handle_gles.cc index 5862d8cc63859..f1ee08ac9b491 100644 --- a/impeller/renderer/backend/gles/handle_gles.cc +++ b/impeller/renderer/backend/gles/handle_gles.cc @@ -22,6 +22,8 @@ std::string HandleTypeToString(HandleType type) { return "RenderBuffer"; case HandleType::kFrameBuffer: return "Framebuffer"; + case HandleType::kFence: + return "Fence"; } FML_UNREACHABLE(); } diff --git a/impeller/renderer/backend/gles/handle_gles.h b/impeller/renderer/backend/gles/handle_gles.h index cc52c8ceabf4a..808918fe8e3a7 100644 --- a/impeller/renderer/backend/gles/handle_gles.h +++ b/impeller/renderer/backend/gles/handle_gles.h @@ -22,6 +22,7 @@ enum class HandleType { kProgram, kRenderBuffer, kFrameBuffer, + kFence, }; std::string HandleTypeToString(HandleType type); @@ -33,10 +34,8 @@ class ReactorGLES; /// OpenGL object handles, these handles can be collected on any /// thread as long as their destruction is scheduled in a reactor. /// -struct HandleGLES { - HandleType type = HandleType::kUnknown; - std::optional name; - +class HandleGLES { + public: //---------------------------------------------------------------------------- /// @brief Creates a dead handle. /// @@ -51,7 +50,7 @@ struct HandleGLES { /// /// @return True if dead, False otherwise. /// - constexpr bool IsDead() const { return !name.has_value(); } + constexpr bool IsDead() const { return !name_.has_value(); } //---------------------------------------------------------------------------- /// @brief Get the hash value of this handle. Handles can be used as map @@ -59,9 +58,7 @@ struct HandleGLES { /// struct Hash { std::size_t operator()(const HandleGLES& handle) const { - return fml::HashCombine( - std::underlying_type_t(handle.type), - handle.name); + return handle.GetHash(); } }; @@ -70,17 +67,33 @@ struct HandleGLES { /// struct Equal { bool operator()(const HandleGLES& lhs, const HandleGLES& rhs) const { - return lhs.type == rhs.type && lhs.name == rhs.name; + return lhs.type_ == rhs.type_ && lhs.name_ == rhs.name_; } }; + HandleType GetType() const { return type_; } + const std::optional& GetName() const { return name_; } + std::size_t GetHash() const { return hash_; } + private: + HandleType type_ = HandleType::kUnknown; + std::optional name_; + std::size_t hash_; + std::optional untracked_id_; + friend class ReactorGLES; - HandleGLES(HandleType p_type, UniqueID p_name) : type(p_type), name(p_name) {} + HandleGLES(HandleType p_type, UniqueID p_name) + : type_(p_type), + name_(p_name), + hash_(fml::HashCombine(std::underlying_type_t(p_type), + p_name)) {} HandleGLES(HandleType p_type, std::optional p_name) - : type(p_type), name(p_name) {} + : type_(p_type), + name_(p_name), + hash_(fml::HashCombine(std::underlying_type_t(p_type), + p_name)) {} static HandleGLES Create(HandleType type) { return HandleGLES{type, UniqueID{}}; @@ -93,12 +106,13 @@ namespace std { inline std::ostream& operator<<(std::ostream& out, const impeller::HandleGLES& handle) { - out << HandleTypeToString(handle.type) << "("; + out << HandleTypeToString(handle.GetType()) << "("; if (handle.IsDead()) { out << "DEAD"; } else { - if (handle.name.has_value()) { - out << handle.name.value().id; + const std::optional& name = handle.GetName(); + if (name.has_value()) { + out << name.value().id; } else { out << "UNNAMED"; } diff --git a/impeller/renderer/backend/gles/pipeline_gles.cc b/impeller/renderer/backend/gles/pipeline_gles.cc index 557eca2b48443..e173ce167be85 100644 --- a/impeller/renderer/backend/gles/pipeline_gles.cc +++ b/impeller/renderer/backend/gles/pipeline_gles.cc @@ -6,7 +6,7 @@ namespace impeller { -PipelineGLES::PipelineGLES(ReactorGLES::Ref reactor, +PipelineGLES::PipelineGLES(std::shared_ptr reactor, std::weak_ptr library, const PipelineDescriptor& desc, std::shared_ptr handle) diff --git a/impeller/renderer/backend/gles/pipeline_gles.h b/impeller/renderer/backend/gles/pipeline_gles.h index e8a3ae8225bf4..0db8414f9a418 100644 --- a/impeller/renderer/backend/gles/pipeline_gles.h +++ b/impeller/renderer/backend/gles/pipeline_gles.h @@ -38,7 +38,7 @@ class PipelineGLES final private: friend PipelineLibraryGLES; - ReactorGLES::Ref reactor_; + std::shared_ptr reactor_; std::shared_ptr handle_; std::unique_ptr buffer_bindings_; bool is_valid_ = false; @@ -46,7 +46,7 @@ class PipelineGLES final // |Pipeline| bool IsValid() const override; - PipelineGLES(ReactorGLES::Ref reactor, + PipelineGLES(std::shared_ptr reactor, std::weak_ptr library, const PipelineDescriptor& desc, std::shared_ptr handle); diff --git a/impeller/renderer/backend/gles/pipeline_library_gles.cc b/impeller/renderer/backend/gles/pipeline_library_gles.cc index 493aabd926c83..199af7068ac26 100644 --- a/impeller/renderer/backend/gles/pipeline_library_gles.cc +++ b/impeller/renderer/backend/gles/pipeline_library_gles.cc @@ -16,7 +16,7 @@ namespace impeller { -PipelineLibraryGLES::PipelineLibraryGLES(ReactorGLES::Ref reactor) +PipelineLibraryGLES::PipelineLibraryGLES(std::shared_ptr reactor) : reactor_(std::move(reactor)) {} static std::string GetShaderInfoLog(const ProcTableGLES& gl, GLuint shader) { @@ -212,7 +212,8 @@ std::shared_ptr PipelineLibraryGLES::CreatePipeline( desc, // has_cached_program ? std::move(cached_program) - : std::make_shared(reactor, HandleType::kProgram))); + : std::make_shared(UniqueHandleGLES::MakeUntracked( + reactor, HandleType::kProgram)))); auto program = reactor->GetGLHandle(pipeline->GetProgramHandle()); @@ -323,7 +324,7 @@ void PipelineLibraryGLES::RemovePipelinesWithEntryPoint( // |PipelineLibrary| PipelineLibraryGLES::~PipelineLibraryGLES() = default; -const ReactorGLES::Ref& PipelineLibraryGLES::GetReactor() const { +const std::shared_ptr& PipelineLibraryGLES::GetReactor() const { return reactor_; } diff --git a/impeller/renderer/backend/gles/pipeline_library_gles.h b/impeller/renderer/backend/gles/pipeline_library_gles.h index e8b5c0433d88a..3b9f29f7d2274 100644 --- a/impeller/renderer/backend/gles/pipeline_library_gles.h +++ b/impeller/renderer/backend/gles/pipeline_library_gles.h @@ -87,12 +87,12 @@ class PipelineLibraryGLES final ProgramKey::Hash, ProgramKey::Equal>; - ReactorGLES::Ref reactor_; + std::shared_ptr reactor_; PipelineMap pipelines_; Mutex programs_mutex_; ProgramMap programs_ IPLR_GUARDED_BY(programs_mutex_); - explicit PipelineLibraryGLES(ReactorGLES::Ref reactor); + explicit PipelineLibraryGLES(std::shared_ptr reactor); // |PipelineLibrary| bool IsValid() const override; @@ -113,7 +113,7 @@ class PipelineLibraryGLES final void RemovePipelinesWithEntryPoint( std::shared_ptr function) override; - const ReactorGLES::Ref& GetReactor() const; + const std::shared_ptr& GetReactor() const; static std::shared_ptr CreatePipeline( const std::weak_ptr& weak_library, diff --git a/impeller/renderer/backend/gles/proc_table_gles.cc b/impeller/renderer/backend/gles/proc_table_gles.cc index 0ae89339425ef..c4450e7b3b673 100644 --- a/impeller/renderer/backend/gles/proc_table_gles.cc +++ b/impeller/renderer/backend/gles/proc_table_gles.cc @@ -6,9 +6,9 @@ #include -#include "fml/closure.h" #include "impeller/base/allocation.h" #include "impeller/base/comparable.h" +#include "impeller/base/strings.h" #include "impeller/base/validation.h" #include "impeller/renderer/backend/gles/capabilities_gles.h" #include "impeller/renderer/capabilities.h" @@ -120,7 +120,11 @@ ProcTableGLES::ProcTableGLES( // NOLINT(google-readability-function-size) reinterpret_cast(fn_ptr); \ proc_ivar.error_fn = error_fn; \ } - FOR_EACH_IMPELLER_GLES3_PROC(IMPELLER_PROC); + + if (description_->GetGlVersion().IsAtLeast(Version(3))) { + FOR_EACH_IMPELLER_GLES3_PROC(IMPELLER_PROC); + } + FOR_EACH_IMPELLER_EXT_PROC(IMPELLER_PROC); #undef IMPELLER_PROC @@ -175,12 +179,12 @@ void ProcTableGLES::ShaderSourceMapping( std::optional ProcTableGLES::ComputeShaderWithDefines( const fml::Mapping& mapping, const std::vector& defines) const { - auto shader_source = std::string{ + std::string shader_source = std::string{ reinterpret_cast(mapping.GetMapping()), mapping.GetSize()}; // Look for the first newline after the '#version' header, which impellerc // will always emit as the first line of a compiled shader. - auto index = shader_source.find('\n'); + size_t index = shader_source.find('\n'); if (index == std::string::npos) { VALIDATION_LOG << "Failed to append constant data to shader"; return std::nullopt; @@ -241,24 +245,24 @@ static const char* AttachmentTypeString(GLint type) { static std::string DescribeFramebufferAttachment(const ProcTableGLES& gl, GLenum attachment) { - GLint param = GL_NONE; + GLint type = GL_NONE; gl.GetFramebufferAttachmentParameteriv( GL_FRAMEBUFFER, // target attachment, // attachment GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, // parameter name - ¶m // parameter + &type // parameter ); - if (param != GL_NONE) { - param = GL_NONE; + if (type != GL_NONE) { + GLint object = GL_NONE; gl.GetFramebufferAttachmentParameteriv( GL_FRAMEBUFFER, // target attachment, // attachment GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, // parameter name - ¶m // parameter + &object // parameter ); std::stringstream stream; - stream << AttachmentTypeString(param) << "(" << param << ")"; + stream << AttachmentTypeString(type) << "(" << object << ")"; return stream.str(); } @@ -268,11 +272,15 @@ static std::string DescribeFramebufferAttachment(const ProcTableGLES& gl, std::string ProcTableGLES::DescribeCurrentFramebuffer() const { GLint framebuffer = GL_NONE; GetIntegerv(GL_FRAMEBUFFER_BINDING, &framebuffer); + if (framebuffer == GL_NONE) { + return "The default framebuffer (FBO0) was bound."; + } if (IsFramebuffer(framebuffer) == GL_FALSE) { - return "No framebuffer or the default window framebuffer is bound."; + return SPrintF("The framebuffer binding (%d) was not a valid framebuffer.", + framebuffer); } - GLenum status = CheckFramebufferStatus(framebuffer); + GLenum status = CheckFramebufferStatus(GL_FRAMEBUFFER); std::stringstream stream; stream << "FBO " << ((framebuffer == GL_NONE) ? "(Default)" @@ -287,10 +295,10 @@ std::string ProcTableGLES::DescribeCurrentFramebuffer() const { stream << "Color Attachment: " << DescribeFramebufferAttachment(*this, GL_COLOR_ATTACHMENT0) << std::endl; - stream << "Color Attachment: " + stream << "Depth Attachment: " << DescribeFramebufferAttachment(*this, GL_DEPTH_ATTACHMENT) << std::endl; - stream << "Color Attachment: " + stream << "Stencil Attachment: " << DescribeFramebufferAttachment(*this, GL_STENCIL_ATTACHMENT) << std::endl; return stream.str(); @@ -303,7 +311,7 @@ bool ProcTableGLES::IsCurrentFramebufferComplete() const { // The default framebuffer is always complete. return true; } - GLenum status = CheckFramebufferStatus(framebuffer); + GLenum status = CheckFramebufferStatus(GL_FRAMEBUFFER); return status == GL_FRAMEBUFFER_COMPLETE; } @@ -321,6 +329,8 @@ static std::optional ToDebugIdentifier(DebugResourceType type) { return GL_RENDERBUFFER; case DebugResourceType::kFrameBuffer: return GL_FRAMEBUFFER; + case DebugResourceType::kFence: + return GL_SYNC_FENCE; } FML_UNREACHABLE(); } @@ -341,13 +351,15 @@ static bool ResourceIsLive(const ProcTableGLES& gl, return gl.IsRenderbuffer(name); case DebugResourceType::kFrameBuffer: return gl.IsFramebuffer(name); + case DebugResourceType::kFence: + return true; } FML_UNREACHABLE(); } bool ProcTableGLES::SetDebugLabel(DebugResourceType type, GLint name, - const std::string& label) const { + std::string_view label) const { if (debug_label_max_length_ <= 0) { return true; } diff --git a/impeller/renderer/backend/gles/proc_table_gles.h b/impeller/renderer/backend/gles/proc_table_gles.h index bfbf32fef2ef8..ccc499af9dcca 100644 --- a/impeller/renderer/backend/gles/proc_table_gles.h +++ b/impeller/renderer/backend/gles/proc_table_gles.h @@ -45,6 +45,31 @@ struct AutoErrorCheck { } }; +template +void BuildGLArgumentsStream(std::stringstream& stream, Type arg) { + stream << arg; +} + +constexpr void BuildGLArgumentsStream(std::stringstream& stream) {} + +template +void BuildGLArgumentsStream(std::stringstream& stream, + Type arg, + Rest... other_args) { + BuildGLArgumentsStream(stream, arg); + stream << ", "; + BuildGLArgumentsStream(stream, other_args...); +} + +template +[[nodiscard]] std::string BuildGLArguments(Type... args) { + std::stringstream stream; + stream << "("; + BuildGLArgumentsStream(stream, args...); + stream << ")"; + return stream.str(); +} + template struct GLProc { using GLFunctionType = T; @@ -66,6 +91,14 @@ struct GLProc { /// PFNGLGETERRORPROC error_fn = nullptr; + //---------------------------------------------------------------------------- + /// Whether the OpenGL call and its arguments should be logged. + /// + /// Only works in IMPELLER_DEBUG and for environments where traditional + /// tracing is hard. Expect log spam and only use during development. + /// + bool log_calls = false; + //---------------------------------------------------------------------------- /// @brief Call the GL function with the appropriate parameters. Lookup /// the documentation for the GL function being called to @@ -81,6 +114,10 @@ struct GLProc { // validation log will at least give us a hint as to what's going on. FML_CHECK(IsAvailable()) << "GL function " << name << " is not available. " << "This is likely due to a missing extension."; + if (log_calls) { + FML_LOG(IMPORTANT) << name + << BuildGLArguments(std::forward(args)...); + } #endif // defined(IMPELLER_DEBUG) && !defined(NDEBUG) return function(std::forward(args)...); } @@ -101,6 +138,7 @@ struct GLProc { PROC(BindFramebuffer); \ PROC(BindRenderbuffer); \ PROC(BindTexture); \ + PROC(BindVertexArray); \ PROC(BlendEquationSeparate); \ PROC(BlendFuncSeparate); \ PROC(BufferData); \ @@ -120,6 +158,7 @@ struct GLProc { PROC(DeleteRenderbuffers); \ PROC(DeleteShader); \ PROC(DeleteTextures); \ + PROC(DeleteVertexArrays); \ PROC(DepthFunc); \ PROC(DepthMask); \ PROC(DetachShader); \ @@ -129,6 +168,7 @@ struct GLProc { PROC(DrawElements); \ PROC(Enable); \ PROC(EnableVertexAttribArray); \ + PROC(Finish); \ PROC(Flush); \ PROC(FramebufferRenderbuffer); \ PROC(FramebufferTexture2D); \ @@ -138,6 +178,7 @@ struct GLProc { PROC(GenFramebuffers); \ PROC(GenRenderbuffers); \ PROC(GenTextures); \ + PROC(GenVertexArrays); \ PROC(GetActiveUniform); \ PROC(GetBooleanv); \ PROC(GetFloatv); \ @@ -197,7 +238,16 @@ void(glDepthRange)(GLdouble n, GLdouble f); PROC(ClearDepth); \ PROC(DepthRange); -#define FOR_EACH_IMPELLER_GLES3_PROC(PROC) PROC(BlitFramebuffer); +#define FOR_EACH_IMPELLER_GLES3_PROC(PROC) \ + PROC(FenceSync); \ + PROC(DeleteSync); \ + PROC(GetActiveUniformBlockiv); \ + PROC(GetActiveUniformBlockName); \ + PROC(GetUniformBlockIndex); \ + PROC(UniformBlockBinding); \ + PROC(BindBufferRange); \ + PROC(WaitSync); \ + PROC(BlitFramebuffer); #define FOR_EACH_IMPELLER_EXT_PROC(PROC) \ PROC(DebugMessageControlKHR); \ @@ -221,6 +271,7 @@ enum class DebugResourceType { kShader, kRenderBuffer, kFrameBuffer, + kFence, }; class ProcTableGLES { @@ -265,7 +316,7 @@ class ProcTableGLES { bool SetDebugLabel(DebugResourceType type, GLint name, - const std::string& label) const; + std::string_view label) const; void PushDebugGroup(const std::string& string) const; diff --git a/impeller/renderer/backend/gles/reactor_gles.cc b/impeller/renderer/backend/gles/reactor_gles.cc index 544c1f1a4fdd0..726bcd2d37310 100644 --- a/impeller/renderer/backend/gles/reactor_gles.cc +++ b/impeller/renderer/backend/gles/reactor_gles.cc @@ -7,11 +7,69 @@ #include #include "flutter/fml/trace_event.h" +#include "fml/closure.h" #include "fml/logging.h" #include "impeller/base/validation.h" namespace impeller { +// static +std::optional ReactorGLES::CreateGLHandle( + const ProcTableGLES& gl, + HandleType type) { + GLStorage handle = GLStorage{.handle = GL_NONE}; + switch (type) { + case HandleType::kUnknown: + return std::nullopt; + case HandleType::kTexture: + gl.GenTextures(1u, &handle.handle); + return handle; + case HandleType::kBuffer: + gl.GenBuffers(1u, &handle.handle); + return handle; + case HandleType::kProgram: + return GLStorage{.handle = gl.CreateProgram()}; + case HandleType::kRenderBuffer: + gl.GenRenderbuffers(1u, &handle.handle); + return handle; + case HandleType::kFrameBuffer: + gl.GenFramebuffers(1u, &handle.handle); + return handle; + case HandleType::kFence: + return GLStorage{.sync = gl.FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)}; + } + return std::nullopt; +} + +// static +bool ReactorGLES::CollectGLHandle(const ProcTableGLES& gl, + HandleType type, + ReactorGLES::GLStorage handle) { + switch (type) { + case HandleType::kUnknown: + return false; + case HandleType::kTexture: + gl.DeleteTextures(1u, &handle.handle); + return true; + case HandleType::kBuffer: + gl.DeleteBuffers(1u, &handle.handle); + return true; + case HandleType::kProgram: + gl.DeleteProgram(handle.handle); + return true; + case HandleType::kRenderBuffer: + gl.DeleteRenderbuffers(1u, &handle.handle); + return true; + case HandleType::kFrameBuffer: + gl.DeleteFramebuffers(1u, &handle.handle); + return true; + case HandleType::kFence: + gl.DeleteSync(handle.sync); + break; + } + return false; +} + ReactorGLES::ReactorGLES(std::unique_ptr gl) : proc_table_(std::move(gl)) { if (!proc_table_ || !proc_table_->IsValid()) { @@ -22,7 +80,17 @@ ReactorGLES::ReactorGLES(std::unique_ptr gl) is_valid_ = true; } -ReactorGLES::~ReactorGLES() = default; +ReactorGLES::~ReactorGLES() { + if (CanReactOnCurrentThread()) { + for (auto& handle : handles_) { + if (handle.second.name.has_value()) { + CollectGLHandle(*proc_table_, handle.first.GetType(), + handle.second.name.value()); + } + } + proc_table_->Flush(); + } +} bool ReactorGLES::IsValid() const { return is_valid_; @@ -45,8 +113,10 @@ bool ReactorGLES::RemoveWorker(WorkerID worker) { } bool ReactorGLES::HasPendingOperations() const { + auto thread_id = std::this_thread::get_id(); Lock ops_lock(ops_mutex_); - return !ops_.empty(); + auto it = ops_.find(thread_id); + return it != ops_.end() ? !it->second.empty() : false; } const ProcTableGLES& ReactorGLES::GetProcTable() const { @@ -54,7 +124,12 @@ const ProcTableGLES& ReactorGLES::GetProcTable() const { return *proc_table_; } -std::optional ReactorGLES::GetGLHandle(const HandleGLES& handle) const { +std::optional ReactorGLES::GetHandle( + const HandleGLES& handle) const { + if (handle.untracked_id_.has_value()) { + return ReactorGLES::GLStorage{.integer = handle.untracked_id_.value()}; + } + ReaderLock handles_lock(handles_mutex_); if (auto found = handles_.find(handle); found != handles_.end()) { if (found->second.pending_collection) { @@ -62,76 +137,78 @@ std::optional ReactorGLES::GetGLHandle(const HandleGLES& handle) const { << "Attempted to acquire a handle that was pending collection."; return std::nullopt; } - if (!found->second.name.has_value()) { + std::optional name = found->second.name; + if (!name.has_value()) { VALIDATION_LOG << "Attempt to acquire a handle outside of an operation."; return std::nullopt; } - return found->second.name; + return name; } VALIDATION_LOG << "Attempted to acquire an invalid GL handle."; return std::nullopt; } -bool ReactorGLES::AddOperation(Operation operation) { +std::optional ReactorGLES::GetGLHandle(const HandleGLES& handle) const { + if (handle.GetType() == HandleType::kFence) { + return std::nullopt; + } + std::optional gl_handle = GetHandle(handle); + if (gl_handle.has_value()) { + return gl_handle->handle; + } + return std::nullopt; +} + +std::optional ReactorGLES::GetGLFence(const HandleGLES& handle) const { + if (handle.GetType() != HandleType::kFence) { + return std::nullopt; + } + std::optional gl_handle = GetHandle(handle); + if (gl_handle.has_value()) { + return gl_handle->sync; + } + return std::nullopt; +} + +bool ReactorGLES::AddOperation(Operation operation, bool defer) { if (!operation) { return false; } + auto thread_id = std::this_thread::get_id(); { Lock ops_lock(ops_mutex_); - ops_.emplace_back(std::move(operation)); + ops_[thread_id].emplace_back(std::move(operation)); } // Attempt a reaction if able but it is not an error if this isn't possible. - [[maybe_unused]] auto result = React(); + if (!defer) { + [[maybe_unused]] auto result = React(); + } return true; } -static std::optional CreateGLHandle(const ProcTableGLES& gl, - HandleType type) { - GLuint handle = GL_NONE; - switch (type) { - case HandleType::kUnknown: - return std::nullopt; - case HandleType::kTexture: - gl.GenTextures(1u, &handle); - return handle; - case HandleType::kBuffer: - gl.GenBuffers(1u, &handle); - return handle; - case HandleType::kProgram: - return gl.CreateProgram(); - case HandleType::kRenderBuffer: - gl.GenRenderbuffers(1u, &handle); - return handle; - case HandleType::kFrameBuffer: - gl.GenFramebuffers(1u, &handle); - return handle; +bool ReactorGLES::RegisterCleanupCallback(const HandleGLES& handle, + const fml::closure& callback) { + if (handle.IsDead()) { + return false; } - return std::nullopt; + FML_DCHECK(!handle.untracked_id_.has_value()); + WriterLock handles_lock(handles_mutex_); + if (auto found = handles_.find(handle); found != handles_.end()) { + found->second.callback = fml::ScopedCleanupClosure(callback); + return true; + } + return false; } -static bool CollectGLHandle(const ProcTableGLES& gl, - HandleType type, - GLuint handle) { - switch (type) { - case HandleType::kUnknown: - return false; - case HandleType::kTexture: - gl.DeleteTextures(1u, &handle); - return true; - case HandleType::kBuffer: - gl.DeleteBuffers(1u, &handle); - return true; - case HandleType::kProgram: - gl.DeleteProgram(handle); - return true; - case HandleType::kRenderBuffer: - gl.DeleteRenderbuffers(1u, &handle); - return true; - case HandleType::kFrameBuffer: - gl.DeleteFramebuffers(1u, &handle); - return true; +HandleGLES ReactorGLES::CreateUntrackedHandle(HandleType type) { + FML_DCHECK(CanReactOnCurrentThread()); + auto new_handle = HandleGLES::Create(type); + std::optional gl_handle = + CreateGLHandle(GetProcTable(), type); + if (gl_handle.has_value()) { + new_handle.untracked_id_ = gl_handle.value().integer; } - return false; + return new_handle; } HandleGLES ReactorGLES::CreateHandle(HandleType type, GLuint external_handle) { @@ -142,22 +219,33 @@ HandleGLES ReactorGLES::CreateHandle(HandleType type, GLuint external_handle) { if (new_handle.IsDead()) { return HandleGLES::DeadHandle(); } - WriterLock handles_lock(handles_mutex_); - std::optional gl_handle; + std::optional gl_handle; if (external_handle != GL_NONE) { - gl_handle = external_handle; + gl_handle = ReactorGLES::GLStorage{.handle = external_handle}; } else if (CanReactOnCurrentThread()) { gl_handle = CreateGLHandle(GetProcTable(), type); } + + WriterLock handles_lock(handles_mutex_); handles_[new_handle] = LiveHandle{gl_handle}; return new_handle; } void ReactorGLES::CollectHandle(HandleGLES handle) { - WriterLock handles_lock(handles_mutex_); - if (auto found = handles_.find(handle); found != handles_.end()) { - found->second.pending_collection = true; + if (handle.untracked_id_.has_value()) { + LiveHandle live_handle(GLStorage{.integer = handle.untracked_id_.value()}); + live_handle.pending_collection = true; + WriterLock handles_lock(handles_mutex_); + handles_[handle] = std::move(live_handle); + } else { + WriterLock handles_lock(handles_mutex_); + if (auto found = handles_.find(handle); found != handles_.end()) { + if (!found->second.pending_collection) { + handles_to_collect_count_ += 1; + } + found->second.pending_collection = true; + } } } @@ -167,10 +255,6 @@ bool ReactorGLES::React() { } TRACE_EVENT0("impeller", "ReactorGLES::React"); while (HasPendingOperations()) { - // Both the raster thread and the IO thread can flush queued operations. - // Ensure that execution of the ops is serialized. - Lock execution_lock(ops_execution_mutex_); - if (!ReactOnce()) { return false; } @@ -192,6 +276,8 @@ static DebugResourceType ToDebugResourceType(HandleType type) { return DebugResourceType::kRenderBuffer; case HandleType::kFrameBuffer: return DebugResourceType::kFrameBuffer; + case HandleType::kFence: + return DebugResourceType::kFence; } FML_UNREACHABLE(); } @@ -207,40 +293,58 @@ bool ReactorGLES::ReactOnce() { bool ReactorGLES::ConsolidateHandles() { TRACE_EVENT0("impeller", __FUNCTION__); const auto& gl = GetProcTable(); - WriterLock handles_lock(handles_mutex_); - std::vector handles_to_delete; - for (auto& handle : handles_) { - // Collect dead handles. - if (handle.second.pending_collection) { - // This could be false if the handle was created and collected without - // use. We still need to get rid of map entry. - if (handle.second.name.has_value()) { - CollectGLHandle(gl, handle.first.type, handle.second.name.value()); + std::vector>> + handles_to_delete; + std::vector> + handles_to_name; + { + WriterLock handles_lock(handles_mutex_); + handles_to_delete.reserve(handles_to_collect_count_); + handles_to_collect_count_ = 0; + for (auto& handle : handles_) { + // Collect dead handles. + if (handle.second.pending_collection) { + handles_to_delete.emplace_back( + std::make_tuple(handle.first, handle.second.name)); + continue; } - handles_to_delete.push_back(handle.first); - continue; - } - // Create live handles. - if (!handle.second.name.has_value()) { - auto gl_handle = CreateGLHandle(gl, handle.first.type); - if (!gl_handle) { - VALIDATION_LOG << "Could not create GL handle."; - return false; + // Create live handles. + if (!handle.second.name.has_value()) { + auto gl_handle = CreateGLHandle(gl, handle.first.GetType()); + if (!gl_handle) { + VALIDATION_LOG << "Could not create GL handle."; + return false; + } + handle.second.name = gl_handle; } - handle.second.name = gl_handle; - } - // Set pending debug labels. - if (handle.second.pending_debug_label.has_value()) { - if (gl.SetDebugLabel(ToDebugResourceType(handle.first.type), - handle.second.name.value(), - handle.second.pending_debug_label.value())) { + // Set pending debug labels. + if (handle.second.pending_debug_label.has_value() && + handle.first.GetType() != HandleType::kFence) { + handles_to_name.emplace_back(std::make_tuple( + ToDebugResourceType(handle.first.GetType()), + handle.second.name.value().handle, + std::move(handle.second.pending_debug_label.value()))); handle.second.pending_debug_label = std::nullopt; } } + for (const auto& handle_to_delete : handles_to_delete) { + handles_.erase(std::get<0>(handle_to_delete)); + } } - for (const auto& handle_to_delete : handles_to_delete) { - handles_.erase(handle_to_delete); + + for (const auto& handle : handles_to_name) { + gl.SetDebugLabel(std::get<0>(handle), std::get<1>(handle), + std::get<2>(handle)); } + for (const auto& handle : handles_to_delete) { + const std::optional& storage = std::get<1>(handle); + // This could be false if the handle was created and collected without + // use. We still need to get rid of map entry. + if (storage.has_value()) { + CollectGLHandle(gl, std::get<0>(handle).GetType(), storage.value()); + } + } + return true; } @@ -255,10 +359,11 @@ bool ReactorGLES::FlushOps() { // Do NOT hold the ops or handles locks while performing operations in case // the ops enqueue more ops. - decltype(ops_) ops; + decltype(ops_)::mapped_type ops; + auto thread_id = std::this_thread::get_id(); { Lock ops_lock(ops_mutex_); - std::swap(ops_, ops); + std::swap(ops_[thread_id], ops); } for (const auto& op : ops) { TRACE_EVENT0("impeller", "ReactorGLES::Operation"); @@ -281,15 +386,23 @@ void ReactorGLES::SetupDebugGroups() { void ReactorGLES::SetDebugLabel(const HandleGLES& handle, std::string_view label) { + FML_DCHECK(handle.GetType() != HandleType::kFence); if (!can_set_debug_labels_) { return; } if (handle.IsDead()) { return; } - WriterLock handles_lock(handles_mutex_); - if (auto found = handles_.find(handle); found != handles_.end()) { - found->second.pending_debug_label = label; + if (handle.untracked_id_.has_value()) { + FML_DCHECK(CanReactOnCurrentThread()); + const auto& gl = GetProcTable(); + gl.SetDebugLabel(ToDebugResourceType(handle.GetType()), + handle.untracked_id_.value(), label); + } else { + WriterLock handles_lock(handles_mutex_); + if (auto found = handles_.find(handle); found != handles_.end()) { + found->second.pending_debug_label = label; + } } } diff --git a/impeller/renderer/backend/gles/reactor_gles.h b/impeller/renderer/backend/gles/reactor_gles.h index fa727c0869466..a05dd22b8fb42 100644 --- a/impeller/renderer/backend/gles/reactor_gles.h +++ b/impeller/renderer/backend/gles/reactor_gles.h @@ -9,6 +9,8 @@ #include #include +#include "flutter/third_party/abseil-cpp/absl/container/flat_hash_map.h" +#include "fml/closure.h" #include "impeller/base/thread.h" #include "impeller/renderer/backend/gles/handle_gles.h" #include "impeller/renderer/backend/gles/proc_table_gles.h" @@ -83,8 +85,6 @@ class ReactorGLES { const ReactorGLES& reactor) const = 0; }; - using Ref = std::shared_ptr; - //---------------------------------------------------------------------------- /// @brief Create a new reactor. There are expensive and only one per /// application instance is necessary. @@ -157,6 +157,8 @@ class ReactorGLES { /// std::optional GetGLHandle(const HandleGLES& handle) const; + std::optional GetGLFence(const HandleGLES& handle) const; + //---------------------------------------------------------------------------- /// @brief Create a reactor handle. /// @@ -170,6 +172,16 @@ class ReactorGLES { /// HandleGLES CreateHandle(HandleType type, GLuint external_handle = GL_NONE); + /// @brief Create a handle that is not managed by `ReactorGLES`. + /// @details This behaves just like `CreateHandle` but it doesn't add the + /// handle to ReactorGLES::handles_ and the creation is executed + /// synchronously, so it must be called from a proper thread. The benefit of + /// this is that it avoid synchronization and hash table lookups when + /// creating/accessing the handle. + /// @param type The type of handle to create. + /// @return The reactor handle. + HandleGLES CreateUntrackedHandle(HandleType type); + //---------------------------------------------------------------------------- /// @brief Collect a reactor handle. /// @@ -211,10 +223,29 @@ class ReactorGLES { /// torn down. /// /// @param[in] operation The operation + /// @param[in] defer If false, the reactor attempts to React after + /// adding this operation. + /// + /// @return If the operation was successfully queued for completion. + /// + [[nodiscard]] bool AddOperation(Operation operation, bool defer = false); + + //---------------------------------------------------------------------------- + /// @brief Register a cleanup callback that will be invokved with the + /// provided user data when the handle is destroyed. + /// + /// This operation is not guaranteed to run immediately. It will + /// complete in a finite amount of time on any thread as long as + /// there is a reactor worker and the reactor itself is not being + /// torn down. + /// + /// @param[in] handle The handle to attach the cleanup to. + /// @param[in] callback The cleanup callback to execute. /// /// @return If the operation was successfully queued for completion. /// - [[nodiscard]] bool AddOperation(Operation operation); + bool RegisterCleanupCallback(const HandleGLES& handle, + const fml::closure& callback); //---------------------------------------------------------------------------- /// @brief Perform a reaction on the current thread if able. @@ -227,32 +258,42 @@ class ReactorGLES { [[nodiscard]] bool React(); private: + /// @brief Storage for either a GL handle or sync fence. + struct GLStorage { + union { + GLuint handle; + GLsync sync; + uint64_t integer; + }; + }; + static_assert(sizeof(GLStorage) == sizeof(uint64_t)); + struct LiveHandle { - std::optional name; + std::optional name; std::optional pending_debug_label; bool pending_collection = false; + fml::ScopedCleanupClosure callback = {}; LiveHandle() = default; - explicit LiveHandle(std::optional p_name) : name(p_name) {} + explicit LiveHandle(std::optional p_name) : name(p_name) {} constexpr bool IsLive() const { return name.has_value(); } }; std::unique_ptr proc_table_; - Mutex ops_execution_mutex_; mutable Mutex ops_mutex_; - std::vector ops_ IPLR_GUARDED_BY(ops_mutex_); - - // Make sure the container is one where erasing items during iteration doesn't - // invalidate other iterators. - using LiveHandles = std::unordered_map; + std::map> ops_ IPLR_GUARDED_BY( + ops_mutex_); + + using LiveHandles = absl::flat_hash_map; mutable RWMutex handles_mutex_; LiveHandles handles_ IPLR_GUARDED_BY(handles_mutex_); + int32_t handles_to_collect_count_ IPLR_GUARDED_BY(handles_mutex_) = 0; mutable Mutex workers_mutex_; mutable std::map> workers_ IPLR_GUARDED_BY( @@ -261,7 +302,7 @@ class ReactorGLES { bool can_set_debug_labels_ = false; bool is_valid_ = false; - bool ReactOnce() IPLR_REQUIRES(ops_execution_mutex_); + bool ReactOnce(); bool HasPendingOperations() const; @@ -273,6 +314,15 @@ class ReactorGLES { void SetupDebugGroups(); + std::optional GetHandle(const HandleGLES& handle) const; + + static std::optional CreateGLHandle(const ProcTableGLES& gl, + HandleType type); + + static bool CollectGLHandle(const ProcTableGLES& gl, + HandleType type, + GLStorage handle); + ReactorGLES(const ReactorGLES&) = delete; ReactorGLES& operator=(const ReactorGLES&) = delete; diff --git a/impeller/renderer/backend/gles/render_pass_gles.cc b/impeller/renderer/backend/gles/render_pass_gles.cc index d9e7191d7ea92..c8900d6e0c1c0 100644 --- a/impeller/renderer/backend/gles/render_pass_gles.cc +++ b/impeller/renderer/backend/gles/render_pass_gles.cc @@ -6,23 +6,26 @@ #include -#include "GLES3/gl3.h" #include "flutter/fml/trace_event.h" #include "fml/closure.h" #include "fml/logging.h" #include "impeller/base/validation.h" +#include "impeller/core/buffer_view.h" +#include "impeller/core/formats.h" +#include "impeller/renderer/backend/gles/buffer_bindings_gles.h" #include "impeller/renderer/backend/gles/context_gles.h" #include "impeller/renderer/backend/gles/device_buffer_gles.h" #include "impeller/renderer/backend/gles/formats_gles.h" #include "impeller/renderer/backend/gles/gpu_tracer_gles.h" #include "impeller/renderer/backend/gles/pipeline_gles.h" #include "impeller/renderer/backend/gles/texture_gles.h" +#include "impeller/renderer/command.h" namespace impeller { RenderPassGLES::RenderPassGLES(std::shared_ptr context, const RenderTarget& target, - ReactorGLES::Ref reactor) + std::shared_ptr reactor) : RenderPass(std::move(context), target), reactor_(std::move(reactor)), is_valid_(reactor_ && reactor_->IsValid()) {} @@ -149,7 +152,7 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, return false; } - auto vertex_buffer = vertex_buffer_view.buffer; + const DeviceBuffer* vertex_buffer = vertex_buffer_view.GetBuffer(); if (!vertex_buffer) { return false; @@ -165,25 +168,39 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, /// Bind the vertex attributes associated with vertex buffer. /// if (!vertex_desc_gles->BindVertexAttributes( - gl, buffer_index, vertex_buffer_view.range.offset)) { + gl, buffer_index, vertex_buffer_view.GetRange().offset)) { return false; } return true; } +void RenderPassGLES::ResetGLState(const ProcTableGLES& gl) { + gl.Disable(GL_SCISSOR_TEST); + gl.Disable(GL_DEPTH_TEST); + gl.Disable(GL_STENCIL_TEST); + gl.Disable(GL_CULL_FACE); + gl.Disable(GL_BLEND); + gl.Disable(GL_DITHER); + gl.ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + gl.DepthMask(GL_TRUE); + gl.StencilMaskSeparate(GL_FRONT, 0xFFFFFFFF); + gl.StencilMaskSeparate(GL_BACK, 0xFFFFFFFF); +} + [[nodiscard]] bool EncodeCommandsInReactor( const RenderPassData& pass_data, - const std::shared_ptr& transients_allocator, const ReactorGLES& reactor, const std::vector& commands, + const std::vector& vertex_buffers, + const std::vector& bound_textures, + const std::vector& bound_buffers, const std::shared_ptr& tracer) { TRACE_EVENT0("impeller", "RenderPassGLES::EncodeCommandsInReactor"); const auto& gl = reactor.GetProcTable(); #ifdef IMPELLER_DEBUG tracer->MarkFrameStart(gl); -#endif // IMPELLER_DEBUG fml::ScopedCleanupClosure pop_pass_debug_marker( [&gl]() { gl.PopDebugGroup(); }); @@ -192,15 +209,9 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, } else { pop_pass_debug_marker.Release(); } +#endif // IMPELLER_DEBUG GLuint fbo = GL_NONE; - fml::ScopedCleanupClosure delete_fbo([&gl, &fbo]() { - if (fbo != GL_NONE) { - gl.BindFramebuffer(GL_FRAMEBUFFER, GL_NONE); - gl.DeleteFramebuffers(1u, &fbo); - } - }); - TextureGLES& color_gles = TextureGLES::Cast(*pass_data.color_attachment); const bool is_default_fbo = color_gles.IsWrapped(); @@ -211,32 +222,40 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, } } else { // Create and bind an offscreen FBO. - gl.GenFramebuffers(1u, &fbo); - gl.BindFramebuffer(GL_FRAMEBUFFER, fbo); - - if (!color_gles.SetAsFramebufferAttachment( - GL_FRAMEBUFFER, TextureGLES::AttachmentType::kColor0)) { - return false; - } + GLuint cached_fbo = color_gles.GetCachedFBO(); + if (cached_fbo != GL_NONE) { + fbo = cached_fbo; + gl.BindFramebuffer(GL_FRAMEBUFFER, fbo); + } else { + gl.GenFramebuffers(1u, &fbo); + color_gles.SetCachedFBO(fbo); + gl.BindFramebuffer(GL_FRAMEBUFFER, fbo); - if (auto depth = TextureGLES::Cast(pass_data.depth_attachment.get())) { - if (!depth->SetAsFramebufferAttachment( - GL_FRAMEBUFFER, TextureGLES::AttachmentType::kDepth)) { + if (!color_gles.SetAsFramebufferAttachment( + GL_FRAMEBUFFER, TextureGLES::AttachmentType::kColor0)) { return false; } - } - if (auto stencil = TextureGLES::Cast(pass_data.stencil_attachment.get())) { - if (!stencil->SetAsFramebufferAttachment( - GL_FRAMEBUFFER, TextureGLES::AttachmentType::kStencil)) { - return false; + + if (auto depth = TextureGLES::Cast(pass_data.depth_attachment.get())) { + if (!depth->SetAsFramebufferAttachment( + GL_FRAMEBUFFER, TextureGLES::AttachmentType::kDepth)) { + return false; + } + } + if (auto stencil = + TextureGLES::Cast(pass_data.stencil_attachment.get())) { + if (!stencil->SetAsFramebufferAttachment( + GL_FRAMEBUFFER, TextureGLES::AttachmentType::kStencil)) { + return false; + } } - } - auto status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - VALIDATION_LOG << "Could not create a complete frambuffer: " - << DebugToFramebufferError(status); - return false; + auto status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + VALIDATION_LOG << "Could not create a complete frambuffer: " + << DebugToFramebufferError(status); + return false; + } } } @@ -267,30 +286,38 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, clear_bits |= GL_STENCIL_BUFFER_BIT; } - gl.Disable(GL_SCISSOR_TEST); - gl.Disable(GL_DEPTH_TEST); - gl.Disable(GL_STENCIL_TEST); - gl.Disable(GL_CULL_FACE); - gl.Disable(GL_BLEND); - gl.Disable(GL_DITHER); - gl.ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - gl.DepthMask(GL_TRUE); - gl.StencilMaskSeparate(GL_FRONT, 0xFFFFFFFF); - gl.StencilMaskSeparate(GL_BACK, 0xFFFFFFFF); + RenderPassGLES::ResetGLState(gl); gl.Clear(clear_bits); - for (const auto& command : commands) { - if (command.instance_count != 1u) { - VALIDATION_LOG << "GLES backend does not support instanced rendering."; - return false; - } + // Both the viewport and scissor are specified in framebuffer coordinates. + // Impeller's framebuffer coordinate system is top left origin, but OpenGL's + // is bottom left origin, so we convert the coordinates here. + ISize target_size = pass_data.color_attachment->GetSize(); - if (!command.pipeline) { - VALIDATION_LOG << "Command has no pipeline specified."; - return false; + //-------------------------------------------------------------------------- + /// Setup the viewport. + /// + const auto& viewport = pass_data.viewport; + gl.Viewport(viewport.rect.GetX(), // x + target_size.height - viewport.rect.GetY() - + viewport.rect.GetHeight(), // y + viewport.rect.GetWidth(), // width + viewport.rect.GetHeight() // height + ); + if (pass_data.depth_attachment) { + if (gl.DepthRangef.IsAvailable()) { + gl.DepthRangef(viewport.depth_range.z_near, viewport.depth_range.z_far); + } else { + gl.DepthRange(viewport.depth_range.z_near, viewport.depth_range.z_far); } + } + + CullMode current_cull_mode = CullMode::kNone; + WindingOrder current_winding_order = WindingOrder::kClockwise; + gl.FrontFace(GL_CW); + for (const auto& command : commands) { #ifdef IMPELLER_DEBUG fml::ScopedCleanupClosure pop_cmd_debug_marker( [&gl]() { gl.PopDebugGroup(); }); @@ -334,26 +361,24 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, gl.Disable(GL_DEPTH_TEST); } - // Both the viewport and scissor are specified in framebuffer coordinates. - // Impeller's framebuffer coordinate system is top left origin, but OpenGL's - // is bottom left origin, so we convert the coordinates here. - auto target_size = pass_data.color_attachment->GetSize(); - //-------------------------------------------------------------------------- /// Setup the viewport. /// - const auto& viewport = command.viewport.value_or(pass_data.viewport); - gl.Viewport(viewport.rect.GetX(), // x - target_size.height - viewport.rect.GetY() - - viewport.rect.GetHeight(), // y - viewport.rect.GetWidth(), // width - viewport.rect.GetHeight() // height - ); - if (pass_data.depth_attachment) { - if (gl.DepthRangef.IsAvailable()) { - gl.DepthRangef(viewport.depth_range.z_near, viewport.depth_range.z_far); - } else { - gl.DepthRange(viewport.depth_range.z_near, viewport.depth_range.z_far); + if (command.viewport.has_value()) { + gl.Viewport(viewport.rect.GetX(), // x + target_size.height - viewport.rect.GetY() - + viewport.rect.GetHeight(), // y + viewport.rect.GetWidth(), // width + viewport.rect.GetHeight() // height + ); + if (pass_data.depth_attachment) { + if (gl.DepthRangef.IsAvailable()) { + gl.DepthRangef(viewport.depth_range.z_near, + viewport.depth_range.z_far); + } else { + gl.DepthRange(viewport.depth_range.z_near, + viewport.depth_range.z_far); + } } } @@ -369,43 +394,47 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, scissor.GetWidth(), // width scissor.GetHeight() // height ); - } else { - gl.Disable(GL_SCISSOR_TEST); } //-------------------------------------------------------------------------- /// Setup culling. /// - switch (pipeline.GetDescriptor().GetCullMode()) { - case CullMode::kNone: - gl.Disable(GL_CULL_FACE); - break; - case CullMode::kFrontFace: - gl.Enable(GL_CULL_FACE); - gl.CullFace(GL_FRONT); - break; - case CullMode::kBackFace: - gl.Enable(GL_CULL_FACE); - gl.CullFace(GL_BACK); - break; + CullMode pipeline_cull_mode = pipeline.GetDescriptor().GetCullMode(); + if (current_cull_mode != pipeline_cull_mode) { + switch (pipeline_cull_mode) { + case CullMode::kNone: + gl.Disable(GL_CULL_FACE); + break; + case CullMode::kFrontFace: + gl.Enable(GL_CULL_FACE); + gl.CullFace(GL_FRONT); + break; + case CullMode::kBackFace: + gl.Enable(GL_CULL_FACE); + gl.CullFace(GL_BACK); + break; + } + current_cull_mode = pipeline_cull_mode; } + //-------------------------------------------------------------------------- /// Setup winding order. /// - switch (pipeline.GetDescriptor().GetWindingOrder()) { - case WindingOrder::kClockwise: - gl.FrontFace(GL_CW); - break; - case WindingOrder::kCounterClockwise: - gl.FrontFace(GL_CCW); - break; + WindingOrder pipeline_winding_order = + pipeline.GetDescriptor().GetWindingOrder(); + if (current_winding_order != pipeline_winding_order) { + switch (pipeline.GetDescriptor().GetWindingOrder()) { + case WindingOrder::kClockwise: + gl.FrontFace(GL_CW); + break; + case WindingOrder::kCounterClockwise: + gl.FrontFace(GL_CCW); + break; + } + current_winding_order = pipeline_winding_order; } - auto vertex_desc_gles = pipeline.GetBufferBindings(); - - if (command.index_type == IndexType::kUnknown) { - return false; - } + BufferBindingsGLES* vertex_desc_gles = pipeline.GetBufferBindings(); //-------------------------------------------------------------------------- /// Bind vertex buffers. @@ -414,8 +443,9 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, /// `RenderPass::ValidateIndexBuffer` here, as validation already runs /// when the vertex/index buffers are set on the command. /// - for (size_t i = 0; i < command.vertex_buffer_count; i++) { - if (!BindVertexBuffer(gl, vertex_desc_gles, command.vertex_buffers[i], + for (size_t i = 0; i < command.vertex_buffers.length; i++) { + if (!BindVertexBuffer(gl, vertex_desc_gles, + vertex_buffers[i + command.vertex_buffers.offset], i)) { return false; } @@ -431,11 +461,13 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, //-------------------------------------------------------------------------- /// Bind uniform data. /// - if (!vertex_desc_gles->BindUniformData(gl, // - *transients_allocator, // - command.vertex_bindings, // - command.fragment_bindings // - )) { + if (!vertex_desc_gles->BindUniformData( + gl, // + bound_textures, // + bound_buffers, // + /*texture_range=*/command.bound_textures, // + /*buffer_range=*/command.bound_buffers // + )) { return false; } @@ -447,9 +479,10 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, // correct; full triangle outlines won't be drawn and disconnected // geometry may appear connected. However this can still be useful for // wireframe debug views. - auto mode = pipeline.GetDescriptor().GetPolygonMode() == PolygonMode::kLine - ? GL_LINE_STRIP - : ToMode(pipeline.GetDescriptor().GetPrimitiveType()); + GLenum mode = + pipeline.GetDescriptor().GetPolygonMode() == PolygonMode::kLine + ? GL_LINE_STRIP + : ToMode(pipeline.GetDescriptor().GetPrimitiveType()); //-------------------------------------------------------------------------- /// Finally! Invoke the draw call. @@ -459,7 +492,7 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, } else { // Bind the index buffer if necessary. auto index_buffer_view = command.index_buffer; - auto index_buffer = index_buffer_view.buffer; + const DeviceBuffer* index_buffer = index_buffer_view.GetBuffer(); const auto& index_buffer_gles = DeviceBufferGLES::Cast(*index_buffer); if (!index_buffer_gles.BindAndUploadDataIfNecessary( DeviceBufferGLES::BindingType::kElementArrayBuffer)) { @@ -469,7 +502,7 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, command.element_count, // count ToIndexType(command.index_type), // type reinterpret_cast(static_cast( - index_buffer_view.range.offset)) // indices + index_buffer_view.GetRange().offset)) // indices ); } @@ -479,17 +512,11 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, if (!vertex_desc_gles->UnbindVertexAttributes(gl)) { return false; } - - //-------------------------------------------------------------------------- - /// Unbind the program pipeline. - /// - if (!pipeline.UnbindProgram()) { - return false; - } } if (gl.DiscardFramebufferEXT.IsAvailable()) { - std::vector attachments; + std::array attachments; + size_t attachment_count = 0; // TODO(130048): discarding stencil or depth on the default fbo causes Angle // to discard the entire render target. Until we know the reason, default to @@ -497,21 +524,21 @@ static bool BindVertexBuffer(const ProcTableGLES& gl, bool angle_safe = gl.GetCapabilities()->IsANGLE() ? !is_default_fbo : true; if (pass_data.discard_color_attachment) { - attachments.push_back(is_default_fbo ? GL_COLOR_EXT - : GL_COLOR_ATTACHMENT0); + attachments[attachment_count++] = + (is_default_fbo ? GL_COLOR_EXT : GL_COLOR_ATTACHMENT0); } if (pass_data.discard_depth_attachment && angle_safe) { - attachments.push_back(is_default_fbo ? GL_DEPTH_EXT - : GL_DEPTH_ATTACHMENT); + attachments[attachment_count++] = + (is_default_fbo ? GL_DEPTH_EXT : GL_DEPTH_ATTACHMENT); } if (pass_data.discard_stencil_attachment && angle_safe) { - attachments.push_back(is_default_fbo ? GL_STENCIL_EXT - : GL_STENCIL_ATTACHMENT); + attachments[attachment_count++] = + (is_default_fbo ? GL_STENCIL_EXT : GL_STENCIL_ATTACHMENT); } - gl.DiscardFramebufferEXT(GL_FRAMEBUFFER, // target - attachments.size(), // attachments to discard - attachments.data() // size + gl.DiscardFramebufferEXT(GL_FRAMEBUFFER, // target + attachment_count, // attachments to discard + attachments.data() // size ); } @@ -533,9 +560,11 @@ bool RenderPassGLES::OnEncodeCommands(const Context& context) const { if (!render_target.HasColorAttachment(0u)) { return false; } - const auto& color0 = render_target.GetColorAttachments().at(0u); - const auto& depth0 = render_target.GetDepthAttachment(); - const auto& stencil0 = render_target.GetStencilAttachment(); + const ColorAttachment& color0 = render_target.GetColorAttachment(0); + const std::optional& depth0 = + render_target.GetDepthAttachment(); + const std::optional& stencil0 = + render_target.GetStencilAttachment(); auto pass_data = std::make_shared(); pass_data->label = label_; @@ -581,16 +610,23 @@ bool RenderPassGLES::OnEncodeCommands(const Context& context) const { CanDiscardAttachmentWhenDone(stencil0->store_action); } - std::shared_ptr shared_this = shared_from_this(); - auto tracer = ContextGLES::Cast(context).GetGPUTracer(); - return reactor_->AddOperation([pass_data, - allocator = context.GetResourceAllocator(), - render_pass = std::move(shared_this), - tracer](const auto& reactor) { - auto result = EncodeCommandsInReactor(*pass_data, allocator, reactor, - render_pass->commands_, tracer); - FML_CHECK(result) << "Must be able to encode GL commands without error."; - }); + return reactor_->AddOperation( + [pass_data = std::move(pass_data), render_pass = shared_from_this(), + tracer = + ContextGLES::Cast(context).GetGPUTracer()](const auto& reactor) { + auto result = EncodeCommandsInReactor( + /*pass_data=*/*pass_data, // + /*reactor=*/reactor, // + /*commands=*/render_pass->commands_, // + /*vertex_buffers=*/render_pass->vertex_buffers_, // + /*bound_textures=*/render_pass->bound_textures_, // + /*bound_buffers=*/render_pass->bound_buffers_, // + /*tracer=*/tracer // + ); + FML_CHECK(result) + << "Must be able to encode GL commands without error."; + }, + /*defer=*/true); } } // namespace impeller diff --git a/impeller/renderer/backend/gles/render_pass_gles.h b/impeller/renderer/backend/gles/render_pass_gles.h index e753a75eb6922..1d524fdc490a1 100644 --- a/impeller/renderer/backend/gles/render_pass_gles.h +++ b/impeller/renderer/backend/gles/render_pass_gles.h @@ -19,16 +19,18 @@ class RenderPassGLES final // |RenderPass| ~RenderPassGLES() override; + static void ResetGLState(const ProcTableGLES& gl); + private: friend class CommandBufferGLES; - ReactorGLES::Ref reactor_; + std::shared_ptr reactor_; std::string label_; bool is_valid_ = false; RenderPassGLES(std::shared_ptr context, const RenderTarget& target, - ReactorGLES::Ref reactor); + std::shared_ptr reactor); // |RenderPass| bool IsValid() const override; diff --git a/impeller/renderer/backend/gles/shader_library_gles.cc b/impeller/renderer/backend/gles/shader_library_gles.cc index 3c8dbbb816dac..018fc50c6b321 100644 --- a/impeller/renderer/backend/gles/shader_library_gles.cc +++ b/impeller/renderer/backend/gles/shader_library_gles.cc @@ -57,7 +57,6 @@ ShaderLibraryGLES::ShaderLibraryGLES( ) -> bool { const auto stage = ToShaderStage(type); const auto key_name = GLESShaderNameToShaderKeyName(name, stage); - functions[ShaderKey{key_name, stage}] = std::shared_ptr( new ShaderFunctionGLES(library_id, // stage, // diff --git a/impeller/renderer/backend/gles/surface_gles.cc b/impeller/renderer/backend/gles/surface_gles.cc index d225831d0896d..494351913fde4 100644 --- a/impeller/renderer/backend/gles/surface_gles.cc +++ b/impeller/renderer/backend/gles/surface_gles.cc @@ -34,8 +34,8 @@ std::unique_ptr SurfaceGLES::WrapFBO( color0_tex.storage_mode = StorageMode::kDevicePrivate; ColorAttachment color0; - color0.texture = std::make_shared( - gl_context.GetReactor(), color0_tex, TextureGLES::IsWrapped::kWrapped); + color0.texture = + TextureGLES::WrapFBO(gl_context.GetReactor(), color0_tex, fbo); color0.clear_color = Color::DarkSlateGray(); color0.load_action = LoadAction::kClear; color0.store_action = StoreAction::kStore; @@ -47,9 +47,10 @@ std::unique_ptr SurfaceGLES::WrapFBO( depth_stencil_texture_desc.usage = TextureUsage::kRenderTarget; depth_stencil_texture_desc.sample_count = SampleCount::kCount1; - auto depth_stencil_tex = std::make_shared( - gl_context.GetReactor(), depth_stencil_texture_desc, - TextureGLES::IsWrapped::kWrapped); + auto depth_stencil_tex = + TextureGLES::CreatePlaceholder(gl_context.GetReactor(), // + depth_stencil_texture_desc // + ); DepthAttachment depth0; depth0.clear_depth = 0; diff --git a/impeller/renderer/backend/gles/test/capabilities_unittests.cc b/impeller/renderer/backend/gles/test/capabilities_unittests.cc index 9ae36118220fc..b0e69464cabbe 100644 --- a/impeller/renderer/backend/gles/test/capabilities_unittests.cc +++ b/impeller/renderer/backend/gles/test/capabilities_unittests.cc @@ -17,7 +17,7 @@ TEST(CapabilitiesGLES, CanInitializeWithDefaults) { EXPECT_FALSE(capabilities->SupportsOffscreenMSAA()); EXPECT_FALSE(capabilities->SupportsSSBO()); - EXPECT_FALSE(capabilities->SupportsTextureToTextureBlits()); + EXPECT_TRUE(capabilities->SupportsTextureToTextureBlits()); EXPECT_FALSE(capabilities->SupportsFramebufferFetch()); EXPECT_FALSE(capabilities->SupportsCompute()); EXPECT_FALSE(capabilities->SupportsComputeSubgroups()); @@ -33,9 +33,9 @@ TEST(CapabilitiesGLES, CanInitializeWithDefaults) { } TEST(CapabilitiesGLES, SupportsDecalSamplerAddressMode) { - auto const extensions = std::vector{ - reinterpret_cast("GL_KHR_debug"), // - reinterpret_cast("GL_EXT_texture_border_clamp"), // + auto const extensions = std::vector{ + "GL_KHR_debug", // + "GL_EXT_texture_border_clamp", // }; auto mock_gles = MockGLES::Init(extensions); auto capabilities = mock_gles->GetProcTable().GetCapabilities(); @@ -43,9 +43,9 @@ TEST(CapabilitiesGLES, SupportsDecalSamplerAddressMode) { } TEST(CapabilitiesGLES, SupportsDecalSamplerAddressModeNotOES) { - auto const extensions = std::vector{ - reinterpret_cast("GL_KHR_debug"), // - reinterpret_cast("GL_OES_texture_border_clamp"), // + auto const extensions = std::vector{ + "GL_KHR_debug", // + "GL_OES_texture_border_clamp", // }; auto mock_gles = MockGLES::Init(extensions); auto capabilities = mock_gles->GetProcTable().GetCapabilities(); @@ -53,15 +53,24 @@ TEST(CapabilitiesGLES, SupportsDecalSamplerAddressModeNotOES) { } TEST(CapabilitiesGLES, SupportsFramebufferFetch) { - auto const extensions = std::vector{ - reinterpret_cast("GL_KHR_debug"), // - reinterpret_cast( - "GL_EXT_shader_framebuffer_fetch"), // + auto const extensions = std::vector{ + "GL_KHR_debug", // + "GL_EXT_shader_framebuffer_fetch", // }; auto mock_gles = MockGLES::Init(extensions); auto capabilities = mock_gles->GetProcTable().GetCapabilities(); EXPECT_TRUE(capabilities->SupportsFramebufferFetch()); } +TEST(CapabilitiesGLES, SupportsMSAA) { + auto const extensions = std::vector{ + "GL_EXT_multisampled_render_to_texture", + }; + auto mock_gles = MockGLES::Init(extensions); + auto capabilities = mock_gles->GetProcTable().GetCapabilities(); + EXPECT_TRUE(capabilities->SupportsImplicitResolvingMSAA()); + EXPECT_FALSE(capabilities->SupportsOffscreenMSAA()); +} + } // namespace testing } // namespace impeller diff --git a/impeller/renderer/backend/gles/test/gpu_tracer_gles_unittests.cc b/impeller/renderer/backend/gles/test/gpu_tracer_gles_unittests.cc index d0579a2091b23..2ee1da6c1b386 100644 --- a/impeller/renderer/backend/gles/test/gpu_tracer_gles_unittests.cc +++ b/impeller/renderer/backend/gles/test/gpu_tracer_gles_unittests.cc @@ -10,38 +10,48 @@ namespace impeller { namespace testing { +using ::testing::_; + #ifdef IMPELLER_DEBUG TEST(GPUTracerGLES, CanFormatFramebufferErrorMessage) { - auto const extensions = std::vector{ - reinterpret_cast("GL_KHR_debug"), // - reinterpret_cast("GL_EXT_disjoint_timer_query"), // + auto const extensions = std::vector{ + "GL_KHR_debug", // + "GL_EXT_disjoint_timer_query", // }; - auto mock_gles = MockGLES::Init(extensions); + auto mock_gles_impl = std::make_unique(); + + { + ::testing::InSequence seq; + auto gen_queries = [](GLsizei n, GLuint* ids) { + for (int i = 0; i < n; ++i) { + ids[i] = i + 1; + } + }; + EXPECT_CALL(*mock_gles_impl, GenQueriesEXT(_, _)).WillOnce(gen_queries); + EXPECT_CALL(*mock_gles_impl, BeginQueryEXT(GL_TIME_ELAPSED_EXT, _)); + EXPECT_CALL(*mock_gles_impl, EndQueryEXT(GL_TIME_ELAPSED_EXT)); + EXPECT_CALL(*mock_gles_impl, + GetQueryObjectuivEXT(_, GL_QUERY_RESULT_AVAILABLE_EXT, _)) + .WillOnce([](GLuint id, GLenum target, GLuint* result) { + *result = GL_TRUE; + }); + EXPECT_CALL(*mock_gles_impl, + GetQueryObjectui64vEXT(_, GL_QUERY_RESULT_EXT, _)) + .WillOnce([](GLuint id, GLenum target, GLuint64* result) { + *result = 1000u; + }); + EXPECT_CALL(*mock_gles_impl, DeleteQueriesEXT(_, _)); + EXPECT_CALL(*mock_gles_impl, GenQueriesEXT(_, _)).WillOnce(gen_queries); + EXPECT_CALL(*mock_gles_impl, BeginQueryEXT(GL_TIME_ELAPSED_EXT, _)); + } + std::shared_ptr mock_gles = + MockGLES::Init(std::move(mock_gles_impl), extensions); auto tracer = std::make_shared(mock_gles->GetProcTable(), true); tracer->RecordRasterThread(); tracer->MarkFrameStart(mock_gles->GetProcTable()); tracer->MarkFrameEnd(mock_gles->GetProcTable()); - - auto calls = mock_gles->GetCapturedCalls(); - - std::vector expected = {"glGenQueriesEXT", "glBeginQueryEXT", - "glEndQueryEXT"}; - for (auto i = 0; i < 3; i++) { - EXPECT_EQ(calls[i], expected[i]); - } - - // Begin second frame, which prompts the tracer to query the result - // from the previous frame. tracer->MarkFrameStart(mock_gles->GetProcTable()); - - calls = mock_gles->GetCapturedCalls(); - std::vector expected_b = {"glGetQueryObjectuivEXT", - "glGetQueryObjectui64vEXT", - "glDeleteQueriesEXT"}; - for (auto i = 0; i < 3; i++) { - EXPECT_EQ(calls[i], expected_b[i]); - } } #endif // IMPELLER_DEBUG diff --git a/impeller/renderer/backend/gles/test/mock_gles.cc b/impeller/renderer/backend/gles/test/mock_gles.cc index d000dad400947..c62ff4fe71483 100644 --- a/impeller/renderer/backend/gles/test/mock_gles.cc +++ b/impeller/renderer/backend/gles/test/mock_gles.cc @@ -19,18 +19,9 @@ static std::mutex g_test_lock; static std::weak_ptr g_mock_gles; -static ProcTableGLES::Resolver g_resolver; +static std::vector g_extensions; -static std::vector g_extensions; - -static const unsigned char* g_version; - -// Has friend visibility into MockGLES to record calls. -void RecordGLCall(const char* name) { - if (auto mock_gles = g_mock_gles.lock()) { - mock_gles->RecordCall(name); - } -} +static const char* g_version; template struct CheckSameSignature : std::false_type {}; @@ -41,22 +32,34 @@ struct CheckSameSignature : std::true_type {}; // This is a stub function that does nothing/records nothing. void doNothing() {} -auto const kMockVendor = (unsigned char*)"MockGLES"; -const auto kMockShadingLanguageVersion = (unsigned char*)"GLSL ES 1.0"; -auto const kExtensions = std::vector{ - (unsigned char*)"GL_KHR_debug" // +auto const kMockVendor = "MockGLES"; +const auto kMockShadingLanguageVersion = "GLSL ES 1.0"; +auto const kExtensions = std::vector{ + "GL_KHR_debug" // }; +namespace { +template +void CallMockMethod(Func func, Args&&... args) { + if (auto mock_gles = g_mock_gles.lock()) { + if (mock_gles->GetImpl()) { + (mock_gles->GetImpl()->*func)(std::forward(args)...); + } + } +} +} // namespace + const unsigned char* mockGetString(GLenum name) { switch (name) { case GL_VENDOR: - return kMockVendor; + return reinterpret_cast(kMockVendor); case GL_VERSION: - return g_version; + return reinterpret_cast(g_version); case GL_SHADING_LANGUAGE_VERSION: - return kMockShadingLanguageVersion; + return reinterpret_cast( + kMockShadingLanguageVersion); default: - return (unsigned char*)""; + return reinterpret_cast(""); } } @@ -66,9 +69,9 @@ static_assert(CheckSameSignature(g_extensions[index]); default: - return (unsigned char*)""; + return reinterpret_cast(""); } } @@ -83,6 +86,9 @@ void mockGetIntegerv(GLenum name, int* value) { case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: *value = 8; break; + case GL_MAX_LABEL_LENGTH_KHR: + *value = 64; + break; default: *value = 0; break; @@ -99,9 +105,7 @@ GLenum mockGetError() { static_assert(CheckSameSignature::value); -void mockPopDebugGroupKHR() { - RecordGLCall("PopDebugGroupKHR"); -} +void mockPopDebugGroupKHR() {} static_assert(CheckSameSignature::value); @@ -109,69 +113,110 @@ static_assert(CheckSameSignature::value); void mockGenQueriesEXT(GLsizei n, GLuint* ids) { - RecordGLCall("glGenQueriesEXT"); - for (auto i = 0; i < n; i++) { - ids[i] = i + 1; - } + CallMockMethod(&IMockGLESImpl::GenQueriesEXT, n, ids); } static_assert(CheckSameSignature::value); void mockBeginQueryEXT(GLenum target, GLuint id) { - RecordGLCall("glBeginQueryEXT"); + CallMockMethod(&IMockGLESImpl::BeginQueryEXT, target, id); } static_assert(CheckSameSignature::value); void mockEndQueryEXT(GLuint id) { - RecordGLCall("glEndQueryEXT"); + CallMockMethod(&IMockGLESImpl::EndQueryEXT, id); } static_assert(CheckSameSignature::value); void mockGetQueryObjectuivEXT(GLuint id, GLenum target, GLuint* result) { - RecordGLCall("glGetQueryObjectuivEXT"); - *result = GL_TRUE; + CallMockMethod(&IMockGLESImpl::GetQueryObjectuivEXT, id, target, result); } static_assert(CheckSameSignature::value); void mockGetQueryObjectui64vEXT(GLuint id, GLenum target, GLuint64* result) { - RecordGLCall("glGetQueryObjectui64vEXT"); - *result = 1000u; + CallMockMethod(&IMockGLESImpl::GetQueryObjectui64vEXT, id, target, result); } static_assert(CheckSameSignature::value); void mockDeleteQueriesEXT(GLsizei size, const GLuint* queries) { - RecordGLCall("glDeleteQueriesEXT"); + CallMockMethod(&IMockGLESImpl::DeleteQueriesEXT, size, queries); +} + +void mockDeleteTextures(GLsizei size, const GLuint* queries) { + CallMockMethod(&IMockGLESImpl::DeleteTextures, size, queries); } static_assert(CheckSameSignature::value); +void mockUniform1fv(GLint location, GLsizei count, const GLfloat* value) { + CallMockMethod(&IMockGLESImpl::Uniform1fv, location, count, value); +} +static_assert(CheckSameSignature::value); + +void mockGenTextures(GLsizei n, GLuint* textures) { + CallMockMethod(&IMockGLESImpl::GenTextures, n, textures); +} + +static_assert(CheckSameSignature::value); + +void mockGenBuffers(GLsizei n, GLuint* buffers) { + CallMockMethod(&IMockGLESImpl::GenBuffers, n, buffers); +} + +static_assert(CheckSameSignature::value); + +void mockObjectLabelKHR(GLenum identifier, + GLuint name, + GLsizei length, + const GLchar* label) { + CallMockMethod(&IMockGLESImpl::ObjectLabelKHR, identifier, name, length, + label); +} +static_assert(CheckSameSignature::value); + +// static +std::shared_ptr MockGLES::Init( + std::unique_ptr impl, + const std::optional>& extensions) { + FML_CHECK(g_test_lock.try_lock()) + << "MockGLES is already being used by another test."; + g_extensions = extensions.value_or(kExtensions); + g_version = "OpenGL ES 3.0"; + auto mock_gles = std::shared_ptr(new MockGLES()); + mock_gles->impl_ = std::move(impl); + g_mock_gles = mock_gles; + return mock_gles; +} + std::shared_ptr MockGLES::Init( - const std::optional>& extensions, + const std::optional>& extensions, const char* version_string, ProcTableGLES::Resolver resolver) { // If we cannot obtain a lock, MockGLES is already being used elsewhere. FML_CHECK(g_test_lock.try_lock()) << "MockGLES is already being used by another test."; - g_version = (unsigned char*)version_string; g_extensions = extensions.value_or(kExtensions); + g_version = version_string; auto mock_gles = std::shared_ptr(new MockGLES(std::move(resolver))); g_mock_gles = mock_gles; return mock_gles; @@ -198,10 +243,20 @@ const ProcTableGLES::Resolver kMockResolverGLES = [](const char* name) { return reinterpret_cast(&mockEndQueryEXT); } else if (strcmp(name, "glDeleteQueriesEXT") == 0) { return reinterpret_cast(&mockDeleteQueriesEXT); + } else if (strcmp(name, "glDeleteTextures") == 0) { + return reinterpret_cast(&mockDeleteTextures); } else if (strcmp(name, "glGetQueryObjectui64vEXT") == 0) { return reinterpret_cast(mockGetQueryObjectui64vEXT); } else if (strcmp(name, "glGetQueryObjectuivEXT") == 0) { return reinterpret_cast(mockGetQueryObjectuivEXT); + } else if (strcmp(name, "glUniform1fv") == 0) { + return reinterpret_cast(mockUniform1fv); + } else if (strcmp(name, "glGenTextures") == 0) { + return reinterpret_cast(mockGenTextures); + } else if (strcmp(name, "glObjectLabelKHR") == 0) { + return reinterpret_cast(mockObjectLabelKHR); + } else if (strcmp(name, "glGenBuffers") == 0) { + return reinterpret_cast(mockGenBuffers); } else { return reinterpret_cast(&doNothing); } diff --git a/impeller/renderer/backend/gles/test/mock_gles.h b/impeller/renderer/backend/gles/test/mock_gles.h index 0ce2bcb6e31af..681b894ac410b 100644 --- a/impeller/renderer/backend/gles/test/mock_gles.h +++ b/impeller/renderer/backend/gles/test/mock_gles.h @@ -8,6 +8,7 @@ #include #include +#include "gmock/gmock.h" #include "impeller/renderer/backend/gles/proc_table_gles.h" namespace impeller { @@ -15,6 +16,62 @@ namespace testing { extern const ProcTableGLES::Resolver kMockResolverGLES; +class IMockGLESImpl { + public: + virtual ~IMockGLESImpl() = default; + virtual void DeleteTextures(GLsizei size, const GLuint* queries) {} + virtual void GenTextures(GLsizei n, GLuint* textures) {} + virtual void ObjectLabelKHR(GLenum identifier, + GLuint name, + GLsizei length, + const GLchar* label) {} + virtual void Uniform1fv(GLint location, GLsizei count, const GLfloat* value) { + } + virtual void GenQueriesEXT(GLsizei n, GLuint* ids) {} + virtual void BeginQueryEXT(GLenum target, GLuint id) {} + virtual void EndQueryEXT(GLuint id) {} + virtual void GetQueryObjectuivEXT(GLuint id, GLenum target, GLuint* result) {} + virtual void GetQueryObjectui64vEXT(GLuint id, + GLenum target, + GLuint64* result) {} + virtual void DeleteQueriesEXT(GLsizei size, const GLuint* queries) {} + virtual void GenBuffers(GLsizei n, GLuint* buffers) {} +}; + +class MockGLESImpl : public IMockGLESImpl { + public: + MOCK_METHOD(void, + DeleteTextures, + (GLsizei size, const GLuint* queries), + (override)); + MOCK_METHOD(void, GenTextures, (GLsizei n, GLuint* textures), (override)); + MOCK_METHOD( + void, + ObjectLabelKHR, + (GLenum identifier, GLuint name, GLsizei length, const GLchar* label), + (override)); + MOCK_METHOD(void, + Uniform1fv, + (GLint location, GLsizei count, const GLfloat* value), + (override)); + MOCK_METHOD(void, GenQueriesEXT, (GLsizei n, GLuint* ids), (override)); + MOCK_METHOD(void, BeginQueryEXT, (GLenum target, GLuint id), (override)); + MOCK_METHOD(void, EndQueryEXT, (GLuint id), (override)); + MOCK_METHOD(void, + GetQueryObjectuivEXT, + (GLuint id, GLenum target, GLuint* result), + (override)); + MOCK_METHOD(void, + GetQueryObjectui64vEXT, + (GLuint id, GLenum target, GLuint64* result), + (override)); + MOCK_METHOD(void, + DeleteQueriesEXT, + (GLsizei size, const GLuint* queries), + (override)); + MOCK_METHOD(void, GenBuffers, (GLsizei n, GLuint* buffers), (override)); +}; + /// @brief Provides a mocked version of the |ProcTableGLES| class. /// /// Typically, Open GLES at runtime will be provided the host's GLES bindings @@ -25,40 +82,35 @@ extern const ProcTableGLES::Resolver kMockResolverGLES; /// See `README.md` for more information. class MockGLES final { public: + static std::shared_ptr Init( + std::unique_ptr impl, + const std::optional>& extensions = std::nullopt); + /// @brief Returns an initialized |MockGLES| instance. /// /// This method overwrites mocked global GLES function pointers to record /// invocations on this instance of |MockGLES|. As such, it should only be /// called once per test. static std::shared_ptr Init( - const std::optional>& extensions = - std::nullopt, + const std::optional>& extensions = std::nullopt, const char* version_string = "OpenGL ES 3.0", ProcTableGLES::Resolver resolver = kMockResolverGLES); /// @brief Returns a configured |ProcTableGLES| instance. const ProcTableGLES& GetProcTable() const { return proc_table_; } - /// @brief Returns a vector of the names of all recorded calls. - /// - /// Calls are cleared after this method is called. - std::vector GetCapturedCalls() { - std::vector calls = captured_calls_; - captured_calls_.clear(); - return calls; - } - ~MockGLES(); + IMockGLESImpl* GetImpl() { return impl_.get(); } + private: friend void RecordGLCall(const char* name); + friend void mockGenTextures(GLsizei n, GLuint* textures); explicit MockGLES(ProcTableGLES::Resolver resolver = kMockResolverGLES); - void RecordCall(const char* name) { captured_calls_.emplace_back(name); } - ProcTableGLES proc_table_; - std::vector captured_calls_; + std::unique_ptr impl_; MockGLES(const MockGLES&) = delete; diff --git a/impeller/renderer/backend/gles/test/mock_gles_unittests.cc b/impeller/renderer/backend/gles/test/mock_gles_unittests.cc index 5345cdbd0054e..96dea6bffcbd9 100644 --- a/impeller/renderer/backend/gles/test/mock_gles_unittests.cc +++ b/impeller/renderer/backend/gles/test/mock_gles_unittests.cc @@ -21,19 +21,6 @@ TEST(MockGLES, CanInitialize) { EXPECT_EQ(vendor, "MockGLES"); } -// Tests we can call two functions and capture the calls. -TEST(MockGLES, CapturesPushAndPopDebugGroup) { - auto mock_gles = MockGLES::Init(); - - auto& gl = mock_gles->GetProcTable(); - gl.PushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION_KHR, 0, -1, "test"); - gl.PopDebugGroupKHR(); - - auto calls = mock_gles->GetCapturedCalls(); - EXPECT_EQ(calls, std::vector( - {"PushDebugGroupKHR", "PopDebugGroupKHR"})); -} - // Tests that if we call a function we have not mocked, it's OK. TEST(MockGLES, CanCallUnmockedFunction) { auto mock_gles = MockGLES::Init(); diff --git a/impeller/renderer/backend/gles/test/pipeline_library_gles_unittests.cc b/impeller/renderer/backend/gles/test/pipeline_library_gles_unittests.cc index 23d54c40ec272..34dcff509b7af 100644 --- a/impeller/renderer/backend/gles/test/pipeline_library_gles_unittests.cc +++ b/impeller/renderer/backend/gles/test/pipeline_library_gles_unittests.cc @@ -38,8 +38,8 @@ TEST_P(PipelineLibraryGLESTest, ProgramHandlesAreReused) { // The program handles should be live and equal. ASSERT_FALSE(pipeline_gles.GetProgramHandle().IsDead()); ASSERT_FALSE(new_pipeline_gles.GetProgramHandle().IsDead()); - ASSERT_EQ(pipeline_gles.GetProgramHandle().name.value(), - new_pipeline_gles.GetProgramHandle().name.value()); + ASSERT_EQ(pipeline_gles.GetProgramHandle().GetName().value(), + new_pipeline_gles.GetProgramHandle().GetName().value()); } TEST_P(PipelineLibraryGLESTest, ChangingSpecConstantsCausesNewProgramObject) { @@ -63,8 +63,8 @@ TEST_P(PipelineLibraryGLESTest, ChangingSpecConstantsCausesNewProgramObject) { // The program handles should be live and equal. ASSERT_FALSE(pipeline_gles.GetProgramHandle().IsDead()); ASSERT_FALSE(new_pipeline_gles.GetProgramHandle().IsDead()); - ASSERT_FALSE(pipeline_gles.GetProgramHandle().name.value() == - new_pipeline_gles.GetProgramHandle().name.value()); + ASSERT_FALSE(pipeline_gles.GetProgramHandle().GetName().value() == + new_pipeline_gles.GetProgramHandle().GetName().value()); } // NOLINTEND(bugprone-unchecked-optional-access) diff --git a/impeller/renderer/backend/gles/test/reactor_unittests.cc b/impeller/renderer/backend/gles/test/reactor_unittests.cc new file mode 100644 index 0000000000000..48ee45dd3a3be --- /dev/null +++ b/impeller/renderer/backend/gles/test/reactor_unittests.cc @@ -0,0 +1,176 @@ +// 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. + +#include +#include +#include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/testing/testing.h" // IWYU pragma: keep +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "impeller/renderer/backend/gles/handle_gles.h" +#include "impeller/renderer/backend/gles/proc_table_gles.h" +#include "impeller/renderer/backend/gles/reactor_gles.h" +#include "impeller/renderer/backend/gles/test/mock_gles.h" + +namespace impeller { +namespace testing { + +using ::testing::_; + +class TestWorker : public ReactorGLES::Worker { + public: + bool CanReactorReactOnCurrentThreadNow( + const ReactorGLES& reactor) const override { + return true; + } +}; + +TEST(ReactorGLES, CanAttachCleanupCallbacksToHandles) { + auto mock_gles = MockGLES::Init(); + ProcTableGLES::Resolver resolver = kMockResolverGLES; + auto proc_table = std::make_unique(resolver); + auto worker = std::make_shared(); + auto reactor = std::make_shared(std::move(proc_table)); + reactor->AddWorker(worker); + + int value = 0; + auto handle = reactor->CreateHandle(HandleType::kTexture, 1123); + auto added = + reactor->RegisterCleanupCallback(handle, [&value]() { value++; }); + + EXPECT_TRUE(added); + EXPECT_TRUE(reactor->React()); + + reactor->CollectHandle(handle); + EXPECT_TRUE(reactor->AddOperation([](const ReactorGLES& reactor) {})); + EXPECT_TRUE(reactor->React()); + EXPECT_EQ(value, 1); +} + +TEST(ReactorGLES, DeletesHandlesDuringShutdown) { + auto mock_gles_impl = std::make_unique(); + + EXPECT_CALL(*mock_gles_impl, GenTextures(1, _)) + .WillOnce([](GLsizei size, GLuint* queries) { queries[0] = 1234; }); + EXPECT_CALL(*mock_gles_impl, DeleteTextures(1, ::testing::Pointee(1234))) + .Times(1); + + std::shared_ptr mock_gles = + MockGLES::Init(std::move(mock_gles_impl)); + ProcTableGLES::Resolver resolver = kMockResolverGLES; + auto proc_table = std::make_unique(resolver); + auto worker = std::make_shared(); + auto reactor = std::make_shared(std::move(proc_table)); + reactor->AddWorker(worker); + reactor->CreateHandle(HandleType::kTexture); + reactor.reset(); +} + +TEST(ReactorGLES, UntrackedHandle) { + auto mock_gles_impl = std::make_unique(); + + EXPECT_CALL(*mock_gles_impl, GenTextures(1, _)) + .WillOnce([](GLsizei size, GLuint* queries) { queries[0] = 1234; }); + EXPECT_CALL(*mock_gles_impl, DeleteTextures(1, ::testing::Pointee(1234))) + .Times(1); + + std::shared_ptr mock_gles = + MockGLES::Init(std::move(mock_gles_impl)); + ProcTableGLES::Resolver resolver = kMockResolverGLES; + auto proc_table = std::make_unique(resolver); + auto worker = std::make_shared(); + auto reactor = std::make_shared(std::move(proc_table)); + reactor->AddWorker(worker); + + HandleGLES handle = reactor->CreateUntrackedHandle(HandleType::kTexture); + EXPECT_FALSE(handle.IsDead()); + std::optional glint = reactor->GetGLHandle(handle); + EXPECT_TRUE(glint.has_value()); + if (glint.has_value()) { + EXPECT_EQ(1234u, *glint); + } + reactor->CollectHandle(handle); + EXPECT_TRUE(reactor->AddOperation([&](const ReactorGLES&) {})); + EXPECT_TRUE(reactor->React()); +} + +TEST(ReactorGLES, NameUntrackedHandle) { + auto mock_gles_impl = std::make_unique(); + + EXPECT_CALL(*mock_gles_impl, GenTextures(1, _)) + .WillOnce([](GLsizei size, GLuint* queries) { queries[0] = 1234; }); + EXPECT_CALL(*mock_gles_impl, + ObjectLabelKHR(_, 1234, _, ::testing::StrEq("hello, joe!"))) + .Times(1); + + std::shared_ptr mock_gles = + MockGLES::Init(std::move(mock_gles_impl)); + ProcTableGLES::Resolver resolver = kMockResolverGLES; + auto proc_table = std::make_unique(resolver); + auto worker = std::make_shared(); + auto reactor = std::make_shared(std::move(proc_table)); + reactor->AddWorker(worker); + + HandleGLES handle = reactor->CreateUntrackedHandle(HandleType::kTexture); + reactor->SetDebugLabel(handle, "hello, joe!"); +} + +TEST(ReactorGLES, PerThreadOperationQueues) { + auto mock_gles = MockGLES::Init(); + ProcTableGLES::Resolver resolver = kMockResolverGLES; + auto proc_table = std::make_unique(resolver); + auto worker = std::make_shared(); + auto reactor = std::make_shared(std::move(proc_table)); + reactor->AddWorker(worker); + + bool op1_called = false; + EXPECT_TRUE( + reactor->AddOperation([&](const ReactorGLES&) { op1_called = true; })); + + fml::AutoResetWaitableEvent event; + bool op2_called = false; + std::thread thread([&] { + EXPECT_TRUE( + reactor->AddOperation([&](const ReactorGLES&) { op2_called = true; })); + event.Wait(); + EXPECT_TRUE(reactor->React()); + }); + + // Reacting on the main thread should only run the main thread's operation. + EXPECT_TRUE(reactor->React()); + EXPECT_TRUE(op1_called); + EXPECT_FALSE(op2_called); + + // Reacting on the second thread will run the second thread's operation. + event.Signal(); + thread.join(); + EXPECT_TRUE(op2_called); +} + +TEST(ReactorGLES, CanDeferOperations) { + auto mock_gles = MockGLES::Init(); + ProcTableGLES::Resolver resolver = kMockResolverGLES; + auto proc_table = std::make_unique(resolver); + auto worker = std::make_shared(); + auto reactor = std::make_shared(std::move(proc_table)); + reactor->AddWorker(worker); + + // Add operation executes tasks as long as the reactor can run tasks on + // the current thread. + bool did_run = false; + EXPECT_TRUE( + reactor->AddOperation([&](const ReactorGLES&) { did_run = true; })); + EXPECT_TRUE(did_run); + + //...unless defer=true is specified, which only enqueues in the reactor. + did_run = false; + EXPECT_TRUE(reactor->AddOperation([&](const ReactorGLES&) { did_run = true; }, + /*defer=*/true)); + EXPECT_FALSE(did_run); + EXPECT_TRUE(reactor->React()); + EXPECT_TRUE(did_run); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/renderer/backend/gles/test/surface_gles_unittests.cc b/impeller/renderer/backend/gles/test/surface_gles_unittests.cc new file mode 100644 index 0000000000000..a039e03786c22 --- /dev/null +++ b/impeller/renderer/backend/gles/test/surface_gles_unittests.cc @@ -0,0 +1,32 @@ +// 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. + +#include "flutter/impeller/playground/playground_test.h" +#include "flutter/impeller/renderer/backend/gles/context_gles.h" +#include "flutter/impeller/renderer/backend/gles/surface_gles.h" +#include "flutter/impeller/renderer/backend/gles/texture_gles.h" +#include "flutter/testing/testing.h" + +namespace impeller::testing { + +using SurfaceGLESTest = PlaygroundTest; +INSTANTIATE_OPENGLES_PLAYGROUND_SUITE(SurfaceGLESTest); + +TEST_P(SurfaceGLESTest, CanWrapNonZeroFBO) { + const GLuint fbo = 1988; + auto surface = + SurfaceGLES::WrapFBO(GetContext(), []() { return true; }, fbo, + PixelFormat::kR8G8B8A8UNormInt, {100, 100}); + ASSERT_TRUE(!!surface); + ASSERT_TRUE(surface->IsValid()); + ASSERT_TRUE(surface->GetRenderTarget().HasColorAttachment(0)); + const auto& texture = TextureGLES::Cast( + *(surface->GetRenderTarget().GetColorAttachment(0).texture)); + auto wrapped = texture.GetFBO(); + ASSERT_TRUE(wrapped.has_value()); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + ASSERT_EQ(wrapped.value(), fbo); +} + +} // namespace impeller::testing diff --git a/impeller/renderer/backend/gles/test/texture_gles_unittests.cc b/impeller/renderer/backend/gles/test/texture_gles_unittests.cc new file mode 100644 index 0000000000000..fd1797209a928 --- /dev/null +++ b/impeller/renderer/backend/gles/test/texture_gles_unittests.cc @@ -0,0 +1,68 @@ +// 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. + +#include "flutter/impeller/playground/playground_test.h" +#include "flutter/impeller/renderer/backend/gles/context_gles.h" +#include "flutter/impeller/renderer/backend/gles/texture_gles.h" +#include "flutter/testing/testing.h" +#include "gtest/gtest.h" +#include "impeller/core/formats.h" +#include "impeller/core/texture_descriptor.h" +#include "impeller/renderer/backend/gles/handle_gles.h" +#include "impeller/renderer/backend/gles/proc_table_gles.h" + +namespace impeller::testing { + +using TextureGLESTest = PlaygroundTest; +INSTANTIATE_OPENGLES_PLAYGROUND_SUITE(TextureGLESTest); + +TEST_P(TextureGLESTest, CanSetSyncFence) { + ContextGLES& context_gles = ContextGLES::Cast(*GetContext()); + if (!context_gles.GetReactor() + ->GetProcTable() + .GetDescription() + ->GetGlVersion() + .IsAtLeast(Version{3, 0, 0})) { + GTEST_SKIP() << "GL Version too low to test sync fence."; + } + + TextureDescriptor desc; + desc.storage_mode = StorageMode::kDevicePrivate; + desc.size = {100, 100}; + desc.format = PixelFormat::kR8G8B8A8UNormInt; + + auto texture = GetContext()->GetResourceAllocator()->CreateTexture(desc); + ASSERT_TRUE(!!texture); + + EXPECT_TRUE(GetContext()->AddTrackingFence(texture)); + EXPECT_TRUE(context_gles.GetReactor()->React()); + + std::optional sync_fence = + TextureGLES::Cast(*texture).GetSyncFence(); + ASSERT_TRUE(sync_fence.has_value()); + if (!sync_fence.has_value()) { + return; + } + EXPECT_EQ(sync_fence.value().GetType(), HandleType::kFence); + + std::optional sync = + context_gles.GetReactor()->GetGLFence(sync_fence.value()); + ASSERT_TRUE(sync.has_value()); + if (!sync.has_value()) { + return; + } + + // Now queue up operation that binds texture to verify that sync fence is + // waited and removed. + + EXPECT_TRUE( + context_gles.GetReactor()->AddOperation([&](const ReactorGLES& reactor) { + return TextureGLES::Cast(*texture).Bind(); + })); + + sync_fence = TextureGLES::Cast(*texture).GetSyncFence(); + ASSERT_FALSE(sync_fence.has_value()); +} + +} // namespace impeller::testing diff --git a/impeller/renderer/backend/gles/texture_gles.cc b/impeller/renderer/backend/gles/texture_gles.cc index f2167de515c08..e85aeb3772111 100644 --- a/impeller/renderer/backend/gles/texture_gles.cc +++ b/impeller/renderer/backend/gles/texture_gles.cc @@ -140,34 +140,54 @@ HandleType ToHandleType(TextureGLES::Type type) { FML_UNREACHABLE(); } -TextureGLES::TextureGLES(ReactorGLES::Ref reactor, TextureDescriptor desc) - : TextureGLES(std::move(reactor), desc, false, std::nullopt, std::nullopt) { +std::shared_ptr TextureGLES::WrapFBO( + std::shared_ptr reactor, + TextureDescriptor desc, + GLuint fbo) { + auto texture = std::shared_ptr( + new TextureGLES(std::move(reactor), desc, fbo, std::nullopt)); + if (!texture->IsValid()) { + return nullptr; + } + return texture; } -TextureGLES::TextureGLES(ReactorGLES::Ref reactor, - TextureDescriptor desc, - enum IsWrapped wrapped) - : TextureGLES(std::move(reactor), desc, true, std::nullopt, std::nullopt) {} - -TextureGLES::TextureGLES(ReactorGLES::Ref reactor, - TextureDescriptor desc, - HandleGLES external_handle) - : TextureGLES(std::move(reactor), - desc, - true, - std::nullopt, - external_handle) {} +std::shared_ptr TextureGLES::WrapTexture( + std::shared_ptr reactor, + TextureDescriptor desc, + HandleGLES external_handle) { + if (external_handle.IsDead()) { + VALIDATION_LOG << "Cannot wrap a dead handle."; + return nullptr; + } + if (external_handle.GetType() != HandleType::kTexture) { + VALIDATION_LOG << "Cannot wrap a non-texture handle."; + return nullptr; + } + auto texture = std::shared_ptr( + new TextureGLES(std::move(reactor), desc, std::nullopt, external_handle)); + if (!texture->IsValid()) { + return nullptr; + } + return texture; +} -std::shared_ptr TextureGLES::WrapFBO(ReactorGLES::Ref reactor, - TextureDescriptor desc, - GLuint fbo) { - return std::shared_ptr( - new TextureGLES(std::move(reactor), desc, true, fbo, std::nullopt)); +std::shared_ptr TextureGLES::CreatePlaceholder( + std::shared_ptr reactor, + TextureDescriptor desc) { + return TextureGLES::WrapFBO(std::move(reactor), desc, 0u); } +TextureGLES::TextureGLES(std::shared_ptr reactor, + TextureDescriptor desc) + : TextureGLES(std::move(reactor), // + desc, // + std::nullopt, // + std::nullopt // + ) {} + TextureGLES::TextureGLES(std::shared_ptr reactor, TextureDescriptor desc, - bool is_wrapped, std::optional fbo, std::optional external_handle) : Texture(desc), @@ -175,8 +195,8 @@ TextureGLES::TextureGLES(std::shared_ptr reactor, type_(GetTextureTypeFromDescriptor(GetTextureDescriptor())), handle_(external_handle.has_value() ? external_handle.value() - : reactor_->CreateHandle(ToHandleType(type_))), - is_wrapped_(is_wrapped), + : reactor_->CreateUntrackedHandle(ToHandleType(type_))), + is_wrapped_(fbo.has_value() || external_handle.has_value()), wrapped_fbo_(fbo) { // Ensure the texture descriptor itself is valid. if (!GetTextureDescriptor().IsValid()) { @@ -199,6 +219,9 @@ TextureGLES::TextureGLES(std::shared_ptr reactor, // |Texture| TextureGLES::~TextureGLES() { reactor_->CollectHandle(handle_); + if (cached_fbo_ != GL_NONE) { + reactor_->GetProcTable().DeleteFramebuffers(1, &cached_fbo_); + } } // |Texture| @@ -386,7 +409,7 @@ void TextureGLES::InitializeContentsIfNecessary() const { } const auto& gl = reactor_->GetProcTable(); - auto handle = reactor_->GetGLHandle(handle_); + std::optional handle = reactor_->GetGLHandle(handle_); if (!handle.has_value()) { VALIDATION_LOG << "Could not initialize the contents of texture."; return; @@ -460,6 +483,16 @@ bool TextureGLES::Bind() const { return false; } const auto& gl = reactor_->GetProcTable(); + + if (fence_.has_value()) { + std::optional fence = reactor_->GetGLFence(fence_.value()); + if (fence.has_value()) { + gl.WaitSync(fence.value(), 0, GL_TIMEOUT_IGNORED); + } + reactor_->CollectHandle(fence_.value()); + fence_ = std::nullopt; + } + switch (type_) { case Type::kTexture: case Type::kTextureMultisampled: { @@ -479,6 +512,12 @@ bool TextureGLES::Bind() const { return true; } +void TextureGLES::MarkContentsInitialized() { + for (size_t i = 0; i < slices_initialized_.size(); i++) { + slices_initialized_[i] = true; + } +} + void TextureGLES::MarkSliceInitialized(size_t slice) const { slices_initialized_[slice] = true; } @@ -601,4 +640,22 @@ std::optional TextureGLES::GetFBO() const { return wrapped_fbo_; } +void TextureGLES::SetFence(HandleGLES fence) { + FML_DCHECK(!fence_.has_value()); + fence_ = fence; +} + +// Visible for testing. +std::optional TextureGLES::GetSyncFence() const { + return fence_; +} + +void TextureGLES::SetCachedFBO(GLuint fbo) { + cached_fbo_ = fbo; +} + +GLuint TextureGLES::GetCachedFBO() const { + return cached_fbo_; +} + } // namespace impeller diff --git a/impeller/renderer/backend/gles/texture_gles.h b/impeller/renderer/backend/gles/texture_gles.h index 05ad1d952c11b..597fb726b21c7 100644 --- a/impeller/renderer/backend/gles/texture_gles.h +++ b/impeller/renderer/backend/gles/texture_gles.h @@ -7,6 +7,7 @@ #include +#include "fml/logging.h" #include "impeller/base/backend_cast.h" #include "impeller/core/texture.h" #include "impeller/renderer/backend/gles/handle_gles.h" @@ -24,23 +25,57 @@ class TextureGLES final : public Texture, kRenderBufferMultisampled, }; - enum class IsWrapped { - kWrapped, - }; - - TextureGLES(ReactorGLES::Ref reactor, TextureDescriptor desc); - - TextureGLES(ReactorGLES::Ref reactor, - TextureDescriptor desc, - IsWrapped wrapped); - - TextureGLES(ReactorGLES::Ref reactor, - TextureDescriptor desc, - HandleGLES external_handle); - - static std::shared_ptr WrapFBO(ReactorGLES::Ref reactor, - TextureDescriptor desc, - GLuint fbo); + //---------------------------------------------------------------------------- + /// @brief Create a texture by wrapping an external framebuffer object + /// whose lifecycle is owned by the caller. + /// + /// This is useful for creating a render target for the default + /// window managed framebuffer. + /// + /// @param[in] reactor The reactor + /// @param[in] desc The description + /// @param[in] fbo The fbo + /// + /// @return If a texture representation of the framebuffer could be + /// created. + /// + static std::shared_ptr WrapFBO( + std::shared_ptr reactor, + TextureDescriptor desc, + GLuint fbo); + + //---------------------------------------------------------------------------- + /// @brief Create a texture by wrapping an external OpenGL texture + /// handle. Ownership of the texture handle is assumed by the + /// reactor. + /// + /// @param[in] reactor The reactor + /// @param[in] desc The description + /// @param[in] external_handle The external handle + /// + /// @return If a texture representation of the framebuffer could be + /// created. + /// + static std::shared_ptr WrapTexture( + std::shared_ptr reactor, + TextureDescriptor desc, + HandleGLES external_handle); + + //---------------------------------------------------------------------------- + /// @brief Create a "texture" that is never expected to be bound/unbound + /// explicitly or initialized in any way. It only exists to setup + /// a render pass description. + /// + /// @param[in] reactor The reactor + /// @param[in] desc The description + /// + /// @return If a texture placeholder could be created. + /// + static std::shared_ptr CreatePlaceholder( + std::shared_ptr reactor, + TextureDescriptor desc); + + TextureGLES(std::shared_ptr reactor, TextureDescriptor desc); // |Texture| ~TextureGLES() override; @@ -69,25 +104,59 @@ class TextureGLES final : public Texture, std::optional GetFBO() const; - // For non cubemap textures, 0 indicates uninitialized and 1 indicates - // initialized. For cubemap textures, each face is initialized separately with - // each bit tracking the initialization of the corresponding slice. + //---------------------------------------------------------------------------- + /// @brief Indicates that all texture storage has already been allocated + /// and contents initialized. + /// + /// This is similar to calling `MarkSliceInitialized` with all + /// slices. + /// + /// @see MarkSliceInitialized. + /// + void MarkContentsInitialized(); + + //---------------------------------------------------------------------------- + /// @brief Indicates that a specific texture slice has been initialized. + /// + /// @param[in] slice The slice to mark as being initialized. + /// void MarkSliceInitialized(size_t slice) const; bool IsSliceInitialized(size_t slice) const; + //---------------------------------------------------------------------------- + /// @brief Attach a sync fence to this texture that will be waited on + /// before encoding a rendering operation that references it. + /// + /// @param[in] fence A handle to a sync fence. + /// + void SetFence(HandleGLES fence); + + /// Store the FBO object for recycling in the 2D renderer. + /// + /// The color0 texture used by the 2D renderer will use this texture + /// object to store the associated FBO the first time it is used. + void SetCachedFBO(GLuint fbo); + + /// Retrieve the cached FBO object, or GL_NONE if there is no object. + GLuint GetCachedFBO() const; + + // Visible for testing. + std::optional GetSyncFence() const; + private: - ReactorGLES::Ref reactor_; + std::shared_ptr reactor_; const Type type_; HandleGLES handle_; + mutable std::optional fence_ = std::nullopt; mutable std::bitset<6> slices_initialized_ = 0; const bool is_wrapped_; const std::optional wrapped_fbo_; + GLuint cached_fbo_ = GL_NONE; bool is_valid_ = false; TextureGLES(std::shared_ptr reactor, TextureDescriptor desc, - bool is_wrapped, std::optional fbo, std::optional external_handle); diff --git a/impeller/renderer/backend/gles/unique_handle_gles.cc b/impeller/renderer/backend/gles/unique_handle_gles.cc index ddf4081791672..d45dd4d79a550 100644 --- a/impeller/renderer/backend/gles/unique_handle_gles.cc +++ b/impeller/renderer/backend/gles/unique_handle_gles.cc @@ -8,14 +8,25 @@ namespace impeller { -UniqueHandleGLES::UniqueHandleGLES(ReactorGLES::Ref reactor, HandleType type) +UniqueHandleGLES::UniqueHandleGLES(std::shared_ptr reactor, + HandleType type) : reactor_(std::move(reactor)) { if (reactor_) { handle_ = reactor_->CreateHandle(type); } } -UniqueHandleGLES::UniqueHandleGLES(ReactorGLES::Ref reactor, HandleGLES handle) +// static +UniqueHandleGLES UniqueHandleGLES::MakeUntracked( + std::shared_ptr reactor, + HandleType type) { + FML_DCHECK(reactor); + HandleGLES handle = reactor->CreateUntrackedHandle(type); + return UniqueHandleGLES(std::move(reactor), handle); +} + +UniqueHandleGLES::UniqueHandleGLES(std::shared_ptr reactor, + HandleGLES handle) : reactor_(std::move(reactor)), handle_(handle) {} UniqueHandleGLES::~UniqueHandleGLES() { diff --git a/impeller/renderer/backend/gles/unique_handle_gles.h b/impeller/renderer/backend/gles/unique_handle_gles.h index 91c3bc2a67122..e5bbc33293bdc 100644 --- a/impeller/renderer/backend/gles/unique_handle_gles.h +++ b/impeller/renderer/backend/gles/unique_handle_gles.h @@ -17,9 +17,12 @@ namespace impeller { /// class UniqueHandleGLES { public: - UniqueHandleGLES(ReactorGLES::Ref reactor, HandleType type); + UniqueHandleGLES(std::shared_ptr reactor, HandleType type); - UniqueHandleGLES(ReactorGLES::Ref reactor, HandleGLES handle); + static UniqueHandleGLES MakeUntracked(std::shared_ptr reactor, + HandleType type); + + UniqueHandleGLES(std::shared_ptr reactor, HandleGLES handle); ~UniqueHandleGLES(); @@ -34,7 +37,7 @@ class UniqueHandleGLES { bool IsValid() const; private: - ReactorGLES::Ref reactor_ = nullptr; + std::shared_ptr reactor_ = nullptr; HandleGLES handle_ = HandleGLES::DeadHandle(); }; diff --git a/impeller/renderer/backend/gles/unique_handle_gles_unittests.cc b/impeller/renderer/backend/gles/unique_handle_gles_unittests.cc new file mode 100644 index 0000000000000..01ddf037f1187 --- /dev/null +++ b/impeller/renderer/backend/gles/unique_handle_gles_unittests.cc @@ -0,0 +1,45 @@ +// 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. + +#include "flutter/testing/testing.h" // IWYU pragma: keep +#include "gtest/gtest.h" +#include "impeller/renderer/backend/gles/reactor_gles.h" +#include "impeller/renderer/backend/gles/test/mock_gles.h" +#include "impeller/renderer/backend/gles/unique_handle_gles.h" + +namespace impeller { +namespace testing { + +using ::testing::_; + +namespace { +class TestWorker : public ReactorGLES::Worker { + public: + bool CanReactorReactOnCurrentThreadNow( + const ReactorGLES& reactor) const override { + return true; + } +}; +} // namespace + +TEST(UniqueHandleGLES, MakeUntracked) { + auto mock_gles_impl = std::make_unique(); + + EXPECT_CALL(*mock_gles_impl, GenTextures(1, _)).Times(1); + + std::shared_ptr mock_gled = + MockGLES::Init(std::move(mock_gles_impl)); + ProcTableGLES::Resolver resolver = kMockResolverGLES; + auto proc_table = std::make_unique(resolver); + auto worker = std::make_shared(); + auto reactor = std::make_shared(std::move(proc_table)); + reactor->AddWorker(worker); + + UniqueHandleGLES handle = + UniqueHandleGLES::MakeUntracked(reactor, HandleType::kTexture); + EXPECT_FALSE(handle.Get().IsDead()); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/renderer/backend/metal/blit_pass_mtl.mm b/impeller/renderer/backend/metal/blit_pass_mtl.mm index 09f381f5bfd9b..e41e24f05ef8d 100644 --- a/impeller/renderer/backend/metal/blit_pass_mtl.mm +++ b/impeller/renderer/backend/metal/blit_pass_mtl.mm @@ -194,7 +194,7 @@ uint32_t mip_level, uint32_t slice, bool convert_to_read) { - auto source_mtl = DeviceBufferMTL::Cast(*source.buffer).GetMTLBuffer(); + auto source_mtl = DeviceBufferMTL::Cast(*source.GetBuffer()).GetMTLBuffer(); if (!source_mtl) { return false; } @@ -221,7 +221,7 @@ #endif // IMPELLER_DEBUG [encoder_ copyFromBuffer:source_mtl - sourceOffset:source.range.offset + sourceOffset:source.GetRange().offset sourceBytesPerRow:source_bytes_per_row sourceBytesPerImage: 0 // 0 for 2D textures according to diff --git a/impeller/renderer/backend/metal/command_buffer_mtl.h b/impeller/renderer/backend/metal/command_buffer_mtl.h index 6c5c948593713..ed8247c6d846e 100644 --- a/impeller/renderer/backend/metal/command_buffer_mtl.h +++ b/impeller/renderer/backend/metal/command_buffer_mtl.h @@ -36,6 +36,9 @@ class CommandBufferMTL final : public CommandBuffer { // |CommandBuffer| bool OnSubmitCommands(CompletionCallback callback) override; + // |CommandBuffer| + void OnWaitUntilCompleted() override; + // |CommandBuffer| void OnWaitUntilScheduled() override; diff --git a/impeller/renderer/backend/metal/command_buffer_mtl.mm b/impeller/renderer/backend/metal/command_buffer_mtl.mm index 93bee9c7c1182..6eb9f2ea050ee 100644 --- a/impeller/renderer/backend/metal/command_buffer_mtl.mm +++ b/impeller/renderer/backend/metal/command_buffer_mtl.mm @@ -187,6 +187,8 @@ static bool LogMTLCommandBufferErrorIfPresent(id buffer) { return true; } +void CommandBufferMTL::OnWaitUntilCompleted() {} + void CommandBufferMTL::OnWaitUntilScheduled() {} std::shared_ptr CommandBufferMTL::OnCreateRenderPass( diff --git a/impeller/renderer/backend/metal/compute_pass_mtl.h b/impeller/renderer/backend/metal/compute_pass_mtl.h index 64ff69a70db7b..e44975eee1239 100644 --- a/impeller/renderer/backend/metal/compute_pass_mtl.h +++ b/impeller/renderer/backend/metal/compute_pass_mtl.h @@ -51,14 +51,14 @@ class ComputePassMTL final : public ComputePass { bool BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, BufferView view) override; // |ComputePass| bool BindResource(ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) override; diff --git a/impeller/renderer/backend/metal/compute_pass_mtl.mm b/impeller/renderer/backend/metal/compute_pass_mtl.mm index b5d29b3770edb..ccd51e67c91b7 100644 --- a/impeller/renderer/backend/metal/compute_pass_mtl.mm +++ b/impeller/renderer/backend/metal/compute_pass_mtl.mm @@ -82,13 +82,13 @@ bool ComputePassMTL::BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, BufferView view) { - if (!view.buffer) { + if (!view.GetBuffer()) { return false; } - const std::shared_ptr& device_buffer = view.buffer; + const DeviceBuffer* device_buffer = view.GetBuffer(); if (!device_buffer) { return false; } @@ -99,7 +99,8 @@ return false; } - pass_bindings_cache_.SetBuffer(slot.ext_res_0, view.range.offset, buffer); + pass_bindings_cache_.SetBuffer(slot.ext_res_0, view.GetRange().offset, + buffer); return true; } @@ -108,7 +109,7 @@ ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) { if (!sampler || !texture->IsValid()) { diff --git a/impeller/renderer/backend/metal/context_mtl.h b/impeller/renderer/backend/metal/context_mtl.h index 15bb2fd46e015..a0af10d442a73 100644 --- a/impeller/renderer/backend/metal/context_mtl.h +++ b/impeller/renderer/backend/metal/context_mtl.h @@ -118,6 +118,9 @@ class ContextMTL final : public Context, // |Context| const std::shared_ptr& GetCapabilities() const override; + // |Context| + RuntimeStageBackend GetRuntimeStageBackend() const override; + void SetCapabilities(const std::shared_ptr& capabilities); // |Context| diff --git a/impeller/renderer/backend/metal/context_mtl.mm b/impeller/renderer/backend/metal/context_mtl.mm index 0a66c82146a31..a4083bfa63eec 100644 --- a/impeller/renderer/backend/metal/context_mtl.mm +++ b/impeller/renderer/backend/metal/context_mtl.mm @@ -13,6 +13,7 @@ #include "flutter/fml/paths.h" #include "flutter/fml/synchronization/sync_switch.h" #include "impeller/core/formats.h" +#include "impeller/core/runtime_types.h" #include "impeller/core/sampler_descriptor.h" #include "impeller/renderer/backend/metal/gpu_tracer_mtl.h" #include "impeller/renderer/backend/metal/sampler_library_mtl.h" @@ -430,6 +431,11 @@ new ContextMTL(device, command_queue, return command_queue_ip_; } +// |Context| +RuntimeStageBackend ContextMTL::GetRuntimeStageBackend() const { + return RuntimeStageBackend::kMetal; +} + #ifdef IMPELLER_DEBUG const std::shared_ptr ContextMTL::GetCaptureManager() const { diff --git a/impeller/renderer/backend/metal/pipeline_library_mtl.mm b/impeller/renderer/backend/metal/pipeline_library_mtl.mm index 6c157d1aa1765..1e69aeb0a2357 100644 --- a/impeller/renderer/backend/metal/pipeline_library_mtl.mm +++ b/impeller/renderer/backend/metal/pipeline_library_mtl.mm @@ -16,6 +16,10 @@ #include "impeller/renderer/backend/metal/shader_function_mtl.h" #include "impeller/renderer/backend/metal/vertex_descriptor_mtl.h" +#if !__has_feature(objc_arc) +#error ARC must be enabled ! +#endif + namespace impeller { PipelineLibraryMTL::PipelineLibraryMTL(id device) @@ -127,37 +131,73 @@ static void GetMTLRenderPipelineDescriptor(const PipelineDescriptor& desc, pipelines_[descriptor] = pipeline_future; auto weak_this = weak_from_this(); - auto completion_handler = + auto get_pipeline_descriptor = + [descriptor, + device = device_](MTLNewRenderPipelineStateCompletionHandler handler) { + GetMTLRenderPipelineDescriptor( + descriptor, + [device, handler](MTLRenderPipelineDescriptor* descriptor) { + [device newRenderPipelineStateWithDescriptor:descriptor + completionHandler:handler]; + }); + }; + + // Extra info for https://github.com/flutter/flutter/issues/148320. + std::optional thread_name = +#if FLUTTER_RELEASE + std::nullopt; +#else + [NSThread isMainThread] ? "main" + : [[[NSThread currentThread] name] UTF8String]; +#endif + auto completion_handler = ^( + id _Nullable render_pipeline_state, + NSError* _Nullable error) { + if (error != nil) { + VALIDATION_LOG << "Could not create render pipeline for " + << descriptor.GetLabel() << " :" + << error.localizedDescription.UTF8String << " (thread: " + << (thread_name.has_value() ? *thread_name : "unknown") + << ")"; + promise->set_value(nullptr); + return; + } + + auto strong_this = weak_this.lock(); + if (!strong_this) { + promise->set_value(nullptr); + return; + } + + auto new_pipeline = std::shared_ptr(new PipelineMTL( + weak_this, + descriptor, // + render_pipeline_state, // + CreateDepthStencilDescriptor(descriptor, device_) // + )); + promise->set_value(new_pipeline); + }; + auto retry_handler = ^(id _Nullable render_pipeline_state, NSError* _Nullable error) { - if (error != nil) { - VALIDATION_LOG << "Could not create render pipeline for " - << descriptor.GetLabel() << " :" - << error.localizedDescription.UTF8String; - promise->set_value(nullptr); - return; - } - - auto strong_this = weak_this.lock(); - if (!strong_this) { - promise->set_value(nullptr); - return; + if (error) { + FML_LOG(INFO) << "pipeline creation retry"; + // The dispatch here is just to minimize the number of threads calling + // this. Executing on the platform thread matches the ContentContext + // path. It also serializes the retries. It may not be necessary. + dispatch_async(dispatch_get_main_queue(), ^{ + get_pipeline_descriptor(completion_handler); + }); + } else { + completion_handler(render_pipeline_state, error); } - - auto new_pipeline = std::shared_ptr(new PipelineMTL( - weak_this, - descriptor, // - render_pipeline_state, // - CreateDepthStencilDescriptor(descriptor, device_) // - )); - promise->set_value(new_pipeline); }; - GetMTLRenderPipelineDescriptor( - descriptor, [device = device_, completion_handler]( - MTLRenderPipelineDescriptor* descriptor) { - [device newRenderPipelineStateWithDescriptor:descriptor - completionHandler:completion_handler]; - }); +#if defined(FML_ARCH_CPU_X86_64) + get_pipeline_descriptor(retry_handler); +#else + get_pipeline_descriptor(completion_handler); + (void)retry_handler; +#endif return pipeline_future; } diff --git a/impeller/renderer/backend/metal/render_pass_mtl.h b/impeller/renderer/backend/metal/render_pass_mtl.h index ddef8a7b83c18..ee244ce1e92b6 100644 --- a/impeller/renderer/backend/metal/render_pass_mtl.h +++ b/impeller/renderer/backend/metal/render_pass_mtl.h @@ -48,9 +48,6 @@ class RenderPassMTL final : public RenderPass { const RenderTarget& target, id buffer); - // |RenderPass| - void ReserveCommands(size_t command_count) override {} - // |RenderPass| bool IsValid() const override; @@ -99,24 +96,33 @@ class RenderPassMTL final : public RenderPass { bool BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, - BufferView view) override; - - // |RenderPass| - bool BindResource(ShaderStage stage, - DescriptorType type, - const ShaderUniformSlot& slot, - const std::shared_ptr& metadata, + const ShaderMetadata* metadata, BufferView view) override; // |RenderPass| bool BindResource(ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) override; + // |RenderPass| + bool BindDynamicResource(ShaderStage stage, + DescriptorType type, + const ShaderUniformSlot& slot, + std::unique_ptr metadata, + BufferView view) override; + + // |RenderPass| + bool BindDynamicResource( + ShaderStage stage, + DescriptorType type, + const SampledImageSlot& slot, + std::unique_ptr metadata, + std::shared_ptr texture, + const std::unique_ptr& sampler) override; + RenderPassMTL(const RenderPassMTL&) = delete; RenderPassMTL& operator=(const RenderPassMTL&) = delete; diff --git a/impeller/renderer/backend/metal/render_pass_mtl.mm b/impeller/renderer/backend/metal/render_pass_mtl.mm index 6047fbea0a5ca..203e97e36ec90 100644 --- a/impeller/renderer/backend/metal/render_pass_mtl.mm +++ b/impeller/renderer/backend/metal/render_pass_mtl.mm @@ -104,15 +104,15 @@ static bool ConfigureStencilAttachment( const RenderTarget& desc) { auto result = [MTLRenderPassDescriptor renderPassDescriptor]; - const auto& colors = desc.GetColorAttachments(); - - for (const auto& color : colors) { - if (!ConfigureColorAttachment(color.second, - result.colorAttachments[color.first])) { - VALIDATION_LOG << "Could not configure color attachment at index " - << color.first; - return nil; - } + bool configured_attachment = desc.IterateAllColorAttachments( + [&result](size_t index, const ColorAttachment& attachment) -> bool { + return ConfigureColorAttachment(attachment, + result.colorAttachments[index]); + }); + + if (!configured_attachment) { + VALIDATION_LOG << "Could not configure color attachments"; + return nil; } const auto& depth = desc.GetDepthAttachment(); @@ -189,11 +189,11 @@ static bool Bind(PassBindingsCacheMTL& pass, ShaderStage stage, size_t bind_index, const BufferView& view) { - if (!view.buffer) { + if (!view.GetBuffer()) { return false; } - auto device_buffer = view.buffer; + const DeviceBuffer* device_buffer = view.GetBuffer(); if (!device_buffer) { return false; } @@ -204,7 +204,7 @@ static bool Bind(PassBindingsCacheMTL& pass, return false; } - return pass.SetBuffer(stage, bind_index, view.range.offset, buffer); + return pass.SetBuffer(stage, bind_index, view.GetRange().offset, buffer); } static bool Bind(PassBindingsCacheMTL& pass, @@ -346,13 +346,13 @@ static bool Bind(PassBindingsCacheMTL& pass, } } else { id mtl_index_buffer = - DeviceBufferMTL::Cast(*index_buffer_.buffer).GetMTLBuffer(); + DeviceBufferMTL::Cast(*index_buffer_.GetBuffer()).GetMTLBuffer(); if (instance_count_ != 1u) { [encoder_ drawIndexedPrimitives:ToMTLPrimitiveType(primitive_type_) indexCount:vertex_count_ indexType:index_type_ indexBuffer:mtl_index_buffer - indexBufferOffset:index_buffer_.range.offset + indexBufferOffset:index_buffer_.GetRange().offset instanceCount:instance_count_ baseVertex:base_vertex_ baseInstance:0u]; @@ -361,7 +361,7 @@ static bool Bind(PassBindingsCacheMTL& pass, indexCount:vertex_count_ indexType:index_type_ indexBuffer:mtl_index_buffer - indexBufferOffset:index_buffer_.range.offset]; + indexBufferOffset:index_buffer_.GetRange().offset]; } } @@ -385,17 +385,17 @@ static bool Bind(PassBindingsCacheMTL& pass, bool RenderPassMTL::BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, BufferView view) { return Bind(pass_bindings_, stage, slot.ext_res_0, view); } // |RenderPass| -bool RenderPassMTL::BindResource( +bool RenderPassMTL::BindDynamicResource( ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const std::shared_ptr& metadata, + std::unique_ptr metadata, BufferView view) { return Bind(pass_bindings_, stage, slot.ext_res_0, view); } @@ -405,9 +405,25 @@ static bool Bind(PassBindingsCacheMTL& pass, ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) { + if (!texture) { + return false; + } + return Bind(pass_bindings_, stage, slot.texture_index, sampler, *texture); +} + +bool RenderPassMTL::BindDynamicResource( + ShaderStage stage, + DescriptorType type, + const SampledImageSlot& slot, + std::unique_ptr metadata, + std::shared_ptr texture, + const std::unique_ptr& sampler) { + if (!texture) { + return false; + } return Bind(pass_bindings_, stage, slot.texture_index, sampler, *texture); } diff --git a/impeller/renderer/backend/metal/sampler_library_mtl.mm b/impeller/renderer/backend/metal/sampler_library_mtl.mm index 0f24029b42ea1..72538338075c3 100644 --- a/impeller/renderer/backend/metal/sampler_library_mtl.mm +++ b/impeller/renderer/backend/metal/sampler_library_mtl.mm @@ -34,9 +34,11 @@ if (@available(iOS 14.0, macos 10.12, *)) { desc.borderColor = MTLSamplerBorderColorTransparentBlack; } +#ifdef IMPELLER_DEBUG if (!descriptor.label.empty()) { - desc.label = @(descriptor.label.c_str()); + desc.label = @(descriptor.label.data()); } +#endif // IMPELLER_DEBUG auto mtl_sampler = [device_ newSamplerStateWithDescriptor:desc]; if (!mtl_sampler) { diff --git a/impeller/renderer/backend/metal/surface_mtl.mm b/impeller/renderer/backend/metal/surface_mtl.mm index 6299f4524f4f8..d28ab9d61f98d 100644 --- a/impeller/renderer/backend/metal/surface_mtl.mm +++ b/impeller/renderer/backend/metal/surface_mtl.mm @@ -13,6 +13,8 @@ #include "impeller/renderer/backend/metal/texture_mtl.h" #include "impeller/renderer/render_target.h" +static_assert(__has_feature(objc_arc), "ARC must be enabled."); + @protocol FlutterMetalDrawable - (void)flutterPrepareForPresent:(nonnull id)commandBuffer; @end diff --git a/impeller/renderer/backend/vulkan/BUILD.gn b/impeller/renderer/backend/vulkan/BUILD.gn index 590a53a6f0ce4..a750494b49565 100644 --- a/impeller/renderer/backend/vulkan/BUILD.gn +++ b/impeller/renderer/backend/vulkan/BUILD.gn @@ -15,6 +15,7 @@ impeller_component("vulkan_unittests") { "descriptor_pool_vk_unittests.cc", "driver_info_vk_unittests.cc", "fence_waiter_vk_unittests.cc", + "formats_vk_unittests.cc", "pipeline_cache_data_vk_unittests.cc", "render_pass_builder_vk_unittests.cc", "render_pass_cache_unittests.cc", diff --git a/impeller/renderer/backend/vulkan/allocator_vk.cc b/impeller/renderer/backend/vulkan/allocator_vk.cc index 18ef93d8e6769..f66bbab100b26 100644 --- a/impeller/renderer/backend/vulkan/allocator_vk.cc +++ b/impeller/renderer/backend/vulkan/allocator_vk.cc @@ -84,6 +84,7 @@ static PoolVMA CreateBufferPool(VmaAllocator allocator) { VmaPoolCreateInfo pool_create_info = {}; pool_create_info.memoryTypeIndex = memTypeIndex; pool_create_info.flags = VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT; + pool_create_info.minBlockCount = 1; VmaPool pool = {}; result = vk::Result{::vmaCreatePool(allocator, &pool_create_info, &pool)}; @@ -144,6 +145,8 @@ AllocatorVK::AllocatorVK(std::weak_ptr context, allocator_info.physicalDevice = physical_device; allocator_info.device = device_holder->GetDevice(); allocator_info.instance = instance; + // 4 MB, matching the default used by Skia Vulkan. + allocator_info.preferredLargeHeapBlockSize = 4 * 1024 * 1024; allocator_info.pVulkanFunctions = &proc_table; VmaAllocator allocator = {}; @@ -506,7 +509,6 @@ std::shared_ptr AllocatorVK::OnCreateBuffer( !desc.readback) { allocation_info.pool = staging_buffer_pool_.get().pool; } - VkBuffer buffer = {}; VmaAllocation buffer_allocation = {}; VmaAllocationInfo buffer_allocation_info = {}; @@ -518,6 +520,10 @@ std::shared_ptr AllocatorVK::OnCreateBuffer( &buffer_allocation_info // )}; + auto type = memory_properties_.memoryTypes[buffer_allocation_info.memoryType]; + bool is_host_coherent = + !!(type.propertyFlags & vk::MemoryPropertyFlagBits::eHostCoherent); + if (result != vk::Result::eSuccess) { VALIDATION_LOG << "Unable to allocate a device buffer: " << vk::to_string(result); @@ -530,8 +536,8 @@ std::shared_ptr AllocatorVK::OnCreateBuffer( UniqueBufferVMA{BufferVMA{allocator_.get(), // buffer_allocation, // vk::Buffer{buffer}}}, // - buffer_allocation_info // - ); + buffer_allocation_info, // + is_host_coherent); } Bytes AllocatorVK::DebugGetHeapUsage() const { diff --git a/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc b/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc index cc00ec119079d..94cb0dec5ba13 100644 --- a/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc +++ b/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc @@ -5,9 +5,11 @@ #include "flutter/testing/testing.h" // IWYU pragma: keep #include "gtest/gtest.h" #include "impeller/base/allocation_size.h" +#include "impeller/core/device_buffer.h" #include "impeller/core/device_buffer_descriptor.h" #include "impeller/core/formats.h" #include "impeller/renderer/backend/vulkan/allocator_vk.h" +#include "impeller/renderer/backend/vulkan/device_buffer_vk.h" #include "impeller/renderer/backend/vulkan/test/mock_vulkan.h" #include "vulkan/vulkan_enums.hpp" diff --git a/impeller/renderer/backend/vulkan/blit_pass_vk.cc b/impeller/renderer/backend/vulkan/blit_pass_vk.cc index 5a3b2ef25db61..02ca97265540a 100644 --- a/impeller/renderer/backend/vulkan/blit_pass_vk.cc +++ b/impeller/renderer/backend/vulkan/blit_pass_vk.cc @@ -248,9 +248,10 @@ bool BlitPassVK::OnCopyBufferToTextureCommand( // cast destination to TextureVK const auto& dst = TextureVK::Cast(*destination); - const auto& src = DeviceBufferVK::Cast(*source.buffer); + const auto& src = DeviceBufferVK::Cast(*source.GetBuffer()); - if (!command_buffer_->Track(source.buffer) || + std::shared_ptr source_buffer = source.TakeBuffer(); + if ((source_buffer && !command_buffer_->Track(source_buffer)) || !command_buffer_->Track(destination)) { return false; } @@ -266,7 +267,7 @@ bool BlitPassVK::OnCopyBufferToTextureCommand( vk::PipelineStageFlagBits::eTransfer; vk::BufferImageCopy image_copy; - image_copy.setBufferOffset(source.range.offset); + image_copy.setBufferOffset(source.GetRange().offset); image_copy.setBufferRowLength(0); image_copy.setBufferImageHeight(0); image_copy.setImageSubresource(vk::ImageSubresourceLayers( diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.cc b/impeller/renderer/backend/vulkan/capabilities_vk.cc index 7e68a23beffbe..bce833a15c947 100644 --- a/impeller/renderer/backend/vulkan/capabilities_vk.cc +++ b/impeller/renderer/backend/vulkan/capabilities_vk.cc @@ -16,27 +16,39 @@ namespace impeller { static constexpr const char* kInstanceLayer = "ImpellerInstance"; CapabilitiesVK::CapabilitiesVK(bool enable_validations, - bool fatal_missing_validations) { - auto extensions = vk::enumerateInstanceExtensionProperties(); - auto layers = vk::enumerateInstanceLayerProperties(); - - if (extensions.result != vk::Result::eSuccess || - layers.result != vk::Result::eSuccess) { - return; - } + bool fatal_missing_validations, + bool use_embedder_extensions, + std::vector instance_extensions, + std::vector device_extensions) + : use_embedder_extensions_(use_embedder_extensions), + embedder_instance_extensions_(std::move(instance_extensions)), + embedder_device_extensions_(std::move(device_extensions)) { + if (!use_embedder_extensions_) { + auto extensions = vk::enumerateInstanceExtensionProperties(); + auto layers = vk::enumerateInstanceLayerProperties(); + + if (extensions.result != vk::Result::eSuccess || + layers.result != vk::Result::eSuccess) { + return; + } - for (const auto& ext : extensions.value) { - exts_[kInstanceLayer].insert(ext.extensionName); - } + for (const auto& ext : extensions.value) { + exts_[kInstanceLayer].insert(ext.extensionName); + } - for (const auto& layer : layers.value) { - const std::string layer_name = layer.layerName; - auto layer_exts = vk::enumerateInstanceExtensionProperties(layer_name); - if (layer_exts.result != vk::Result::eSuccess) { - return; + for (const auto& layer : layers.value) { + const std::string layer_name = layer.layerName; + auto layer_exts = vk::enumerateInstanceExtensionProperties(layer_name); + if (layer_exts.result != vk::Result::eSuccess) { + return; + } + for (const auto& layer_ext : layer_exts.value) { + exts_[layer_name].insert(layer_ext.extensionName); + } } - for (const auto& layer_ext : layer_exts.value) { - exts_[layer_name].insert(layer_ext.extensionName); + } else { + for (const auto& ext : embedder_instance_extensions_) { + exts_[kInstanceLayer].insert(ext); } } @@ -239,17 +251,25 @@ static std::optional> GetSupportedDeviceExtensions( std::optional> CapabilitiesVK::GetEnabledDeviceExtensions( const vk::PhysicalDevice& physical_device) const { - auto exts = GetSupportedDeviceExtensions(physical_device); + std::set exts; - if (!exts.has_value()) { - return std::nullopt; + if (!use_embedder_extensions_) { + auto maybe_exts = GetSupportedDeviceExtensions(physical_device); + + if (!maybe_exts.has_value()) { + return std::nullopt; + } + exts = maybe_exts.value(); + } else { + exts = std::set(embedder_device_extensions_.begin(), + embedder_device_extensions_.end()); } std::vector enabled; auto for_each_common_extension = [&](RequiredCommonDeviceExtensionVK ext) { auto name = GetExtensionName(ext); - if (exts->find(name) == exts->end()) { + if (exts.find(name) == exts.end()) { VALIDATION_LOG << "Device does not support required extension: " << name; return false; } @@ -260,7 +280,7 @@ CapabilitiesVK::GetEnabledDeviceExtensions( auto for_each_android_extension = [&](RequiredAndroidDeviceExtensionVK ext) { #ifdef FML_OS_ANDROID auto name = GetExtensionName(ext); - if (exts->find(name) == exts->end()) { + if (exts.find(name) == exts.end()) { VALIDATION_LOG << "Device does not support required Android extension: " << name; return false; @@ -272,7 +292,7 @@ CapabilitiesVK::GetEnabledDeviceExtensions( auto for_each_optional_extension = [&](OptionalDeviceExtensionVK ext) { auto name = GetExtensionName(ext); - if (exts->find(name) != exts->end()) { + if (exts.find(name) != exts.end()) { enabled.push_back(name); } return true; @@ -461,6 +481,10 @@ bool CapabilitiesVK::HasExtension(const std::string& ext) const { return false; } +bool CapabilitiesVK::SupportsPrimitiveRestart() const { + return true; +} + void CapabilitiesVK::SetOffscreenFormat(PixelFormat pixel_format) const { default_color_format_ = pixel_format; } @@ -524,27 +548,36 @@ bool CapabilitiesVK::SetPhysicalDevice( required_common_device_extensions_.clear(); required_android_device_extensions_.clear(); optional_device_extensions_.clear(); - auto exts = GetSupportedDeviceExtensions(device); - if (!exts.has_value()) { - return false; + + std::set exts; + if (!use_embedder_extensions_) { + auto maybe_exts = GetSupportedDeviceExtensions(device); + if (!maybe_exts.has_value()) { + return false; + } + exts = maybe_exts.value(); + } else { + exts = std::set(embedder_device_extensions_.begin(), + embedder_device_extensions_.end()); } + IterateExtensions([&](auto ext) -> bool { auto ext_name = GetExtensionName(ext); - if (exts->find(ext_name) != exts->end()) { + if (exts.find(ext_name) != exts.end()) { required_common_device_extensions_.insert(ext); } return true; }); IterateExtensions([&](auto ext) -> bool { auto ext_name = GetExtensionName(ext); - if (exts->find(ext_name) != exts->end()) { + if (exts.find(ext_name) != exts.end()) { required_android_device_extensions_.insert(ext); } return true; }); IterateExtensions([&](auto ext) -> bool { auto ext_name = GetExtensionName(ext); - if (exts->find(ext_name) != exts->end()) { + if (exts.find(ext_name) != exts.end()) { optional_device_extensions_.insert(ext); } return true; @@ -562,6 +595,12 @@ bool CapabilitiesVK::SetPhysicalDevice( ISize{device_properties_.limits.maxFramebufferWidth, device_properties_.limits.maxFramebufferHeight}; + // Molten, Vulkan on Metal, cannot support triangle fans because Metal doesn't + // support triangle fans. + // See VUID-VkPipelineInputAssemblyStateCreateInfo-triangleFans-04452. + has_triangle_fans_ = + !HasExtension(OptionalDeviceExtensionVK::kVKKHRPortabilitySubset); + return true; } @@ -713,7 +752,7 @@ CapabilitiesVK::GetSupportedFRCRate(CompressionType compression_type, } bool CapabilitiesVK::SupportsTriangleFan() const { - return true; + return has_triangle_fans_; } ISize CapabilitiesVK::GetMaximumRenderPassAttachmentSize() const { diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.h b/impeller/renderer/backend/vulkan/capabilities_vk.h index a42907da338e3..2f1811f54a2d1 100644 --- a/impeller/renderer/backend/vulkan/capabilities_vk.h +++ b/impeller/renderer/backend/vulkan/capabilities_vk.h @@ -170,7 +170,10 @@ class CapabilitiesVK final : public Capabilities, public BackendCast { public: explicit CapabilitiesVK(bool enable_validations, - bool fatal_missing_validations = false); + bool fatal_missing_validations = false, + bool use_embedder_extensions = false, + std::vector instance_extensions = {}, + std::vector device_extensions = {}); ~CapabilitiesVK(); @@ -241,6 +244,9 @@ class CapabilitiesVK final : public Capabilities, // |Capabilities| bool SupportsTriangleFan() const override; + // |Capabilities| + bool SupportsPrimitiveRestart() const override; + // |Capabilities| PixelFormat GetDefaultColorFormat() const override; @@ -291,8 +297,15 @@ class CapabilitiesVK final : public Capabilities, bool supports_device_transient_textures_ = false; bool supports_texture_fixed_rate_compression_ = false; ISize max_render_pass_attachment_size_ = ISize{0, 0}; + bool has_triangle_fans_ = true; bool is_valid_ = false; + // The embedder.h API is responsible for providing the instance and device + // extensions. + bool use_embedder_extensions_ = false; + std::vector embedder_instance_extensions_; + std::vector embedder_device_extensions_; + bool HasExtension(const std::string& ext) const; bool HasLayer(const std::string& layer) const; diff --git a/impeller/renderer/backend/vulkan/command_buffer_vk.cc b/impeller/renderer/backend/vulkan/command_buffer_vk.cc index e8341a352c8f9..780b423b54a41 100644 --- a/impeller/renderer/backend/vulkan/command_buffer_vk.cc +++ b/impeller/renderer/backend/vulkan/command_buffer_vk.cc @@ -47,6 +47,8 @@ bool CommandBufferVK::OnSubmitCommands(CompletionCallback callback) { FML_UNREACHABLE() } +void CommandBufferVK::OnWaitUntilCompleted() {} + void CommandBufferVK::OnWaitUntilScheduled() {} std::shared_ptr CommandBufferVK::OnCreateRenderPass( @@ -116,11 +118,11 @@ vk::CommandBuffer CommandBufferVK::GetCommandBuffer() const { return {}; } -bool CommandBufferVK::Track(std::shared_ptr object) { +bool CommandBufferVK::Track(const std::shared_ptr& object) { if (!IsValid()) { return false; } - tracked_objects_->Track(std::move(object)); + tracked_objects_->Track(object); return true; } @@ -132,19 +134,12 @@ bool CommandBufferVK::Track(const std::shared_ptr& buffer) { return true; } -bool CommandBufferVK::IsTracking( - const std::shared_ptr& buffer) const { - if (!IsValid()) { - return false; - } - return tracked_objects_->IsTracking(buffer); -} - -bool CommandBufferVK::Track(std::shared_ptr texture) { +bool CommandBufferVK::Track( + const std::shared_ptr& texture) { if (!IsValid()) { return false; } - tracked_objects_->Track(std::move(texture)); + tracked_objects_->Track(texture); return true; } @@ -158,16 +153,6 @@ bool CommandBufferVK::Track(const std::shared_ptr& texture) { return Track(TextureVK::Cast(*texture).GetTextureSource()); } -bool CommandBufferVK::IsTracking( - const std::shared_ptr& texture) const { - if (!IsValid()) { - return false; - } - std::shared_ptr source = - TextureVK::Cast(*texture).GetTextureSource(); - return tracked_objects_->IsTracking(source); -} - fml::StatusOr CommandBufferVK::AllocateDescriptorSets( const vk::DescriptorSetLayout& layout, const ContextVK& context) { diff --git a/impeller/renderer/backend/vulkan/command_buffer_vk.h b/impeller/renderer/backend/vulkan/command_buffer_vk.h index ef9dba2494975..47ded4867d47a 100644 --- a/impeller/renderer/backend/vulkan/command_buffer_vk.h +++ b/impeller/renderer/backend/vulkan/command_buffer_vk.h @@ -33,7 +33,7 @@ class CommandBufferVK final /// @brief Ensure that [object] is kept alive until this command buffer /// completes execution. - bool Track(std::shared_ptr object); + bool Track(const std::shared_ptr& object); /// @brief Ensure that [buffer] is kept alive until this command buffer /// completes execution. @@ -45,7 +45,7 @@ class CommandBufferVK final /// @brief Ensure that [texture] is kept alive until this command buffer /// completes execution. - bool Track(std::shared_ptr texture); + bool Track(const std::shared_ptr& texture); /// @brief Retrieve the native command buffer from this object. vk::CommandBuffer GetCommandBuffer() const; @@ -76,12 +76,6 @@ class CommandBufferVK final const vk::DescriptorSetLayout& layout, const ContextVK& context); - // Visible for testing. - bool IsTracking(const std::shared_ptr& texture) const; - - // Visible for testing. - bool IsTracking(const std::shared_ptr& texture) const; - // Visible for testing. DescriptorPoolVK& GetDescriptorPool() const; @@ -105,6 +99,9 @@ class CommandBufferVK final // |CommandBuffer| bool OnSubmitCommands(CompletionCallback callback) override; + // |CommandBuffer| + void OnWaitUntilCompleted() override; + // |CommandBuffer| void OnWaitUntilScheduled() override; diff --git a/impeller/renderer/backend/vulkan/compute_pass_vk.cc b/impeller/renderer/backend/vulkan/compute_pass_vk.cc index 309710b7854f0..f4b61cdba643f 100644 --- a/impeller/renderer/backend/vulkan/compute_pass_vk.cc +++ b/impeller/renderer/backend/vulkan/compute_pass_vk.cc @@ -134,7 +134,7 @@ fml::Status ComputePassVK::Compute(const ISize& grid_size) { bool ComputePassVK::BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, BufferView view) { return BindResource(slot.binding, type, view); } @@ -144,7 +144,7 @@ bool ComputePassVK::BindResource( ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) { if (bound_image_offset_ >= kMaxBindings) { @@ -178,27 +178,27 @@ bool ComputePassVK::BindResource( bool ComputePassVK::BindResource(size_t binding, DescriptorType type, - const BufferView& view) { + BufferView view) { if (bound_buffer_offset_ >= kMaxBindings) { return false; } - const std::shared_ptr& device_buffer = view.buffer; - auto buffer = DeviceBufferVK::Cast(*device_buffer).GetBuffer(); + auto buffer = DeviceBufferVK::Cast(*view.GetBuffer()).GetBuffer(); if (!buffer) { return false; } - if (!command_buffer_->Track(device_buffer)) { + std::shared_ptr device_buffer = view.TakeBuffer(); + if (device_buffer && !command_buffer_->Track(device_buffer)) { return false; } - uint32_t offset = view.range.offset; + uint32_t offset = view.GetRange().offset; vk::DescriptorBufferInfo buffer_info; buffer_info.buffer = buffer; buffer_info.offset = offset; - buffer_info.range = view.range.length; + buffer_info.range = view.GetRange().length; buffer_workspace_[bound_buffer_offset_++] = buffer_info; vk::WriteDescriptorSet write_set; diff --git a/impeller/renderer/backend/vulkan/compute_pass_vk.h b/impeller/renderer/backend/vulkan/compute_pass_vk.h index 968542fc9731c..78aac09c6da45 100644 --- a/impeller/renderer/backend/vulkan/compute_pass_vk.h +++ b/impeller/renderer/backend/vulkan/compute_pass_vk.h @@ -71,20 +71,18 @@ class ComputePassVK final : public ComputePass { bool BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, BufferView view) override; // |ResourceBinder| bool BindResource(ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) override; - bool BindResource(size_t binding, - DescriptorType type, - const BufferView& view); + bool BindResource(size_t binding, DescriptorType type, BufferView view); }; } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/context_vk.cc b/impeller/renderer/backend/vulkan/context_vk.cc index eaef668e47412..c72f944f48fb6 100644 --- a/impeller/renderer/backend/vulkan/context_vk.cc +++ b/impeller/renderer/backend/vulkan/context_vk.cc @@ -7,6 +7,8 @@ #include #include "fml/concurrent_message_loop.h" +#include "impeller/core/formats.h" +#include "impeller/core/runtime_types.h" #include "impeller/renderer/backend/vulkan/command_queue_vk.h" #include "impeller/renderer/backend/vulkan/descriptor_pool_vk.h" #include "impeller/renderer/backend/vulkan/render_pass_builder_vk.h" @@ -160,8 +162,19 @@ void ContextVK::Setup(Settings settings) { auto& dispatcher = VULKAN_HPP_DEFAULT_DISPATCHER; dispatcher.init(settings.proc_address_callback); + std::vector embedder_instance_extensions; + std::vector embedder_device_extensions; + if (settings.embedder_data.has_value()) { + embedder_instance_extensions = settings.embedder_data->instance_extensions; + embedder_device_extensions = settings.embedder_data->device_extensions; + } auto caps = std::shared_ptr(new CapabilitiesVK( - settings.enable_validation, settings.fatal_missing_validations)); + settings.enable_validation, // + settings.fatal_missing_validations, // + /*use_embedder_extensions=*/settings.embedder_data.has_value(), // + embedder_instance_extensions, // + embedder_device_extensions // + )); if (!caps->IsValid()) { VALIDATION_LOG << "Could not determine device capabilities."; @@ -226,7 +239,7 @@ void ContextVK::Setup(Settings settings) { instance_info.setFlags(instance_flags); auto device_holder = std::make_shared(); - { + if (!settings.embedder_data.has_value()) { auto instance = vk::createInstanceUnique(instance_info); if (instance.result != vk::Result::eSuccess) { VALIDATION_LOG << "Could not create Vulkan instance: " @@ -234,6 +247,9 @@ void ContextVK::Setup(Settings settings) { return; } device_holder->instance = std::move(instance.value); + } else { + device_holder->instance.reset(settings.embedder_data->instance); + device_holder->owned = false; } dispatcher.init(device_holder->instance.get()); @@ -254,7 +270,7 @@ void ContextVK::Setup(Settings settings) { //---------------------------------------------------------------------------- /// Pick the physical device. /// - { + if (!settings.embedder_data.has_value()) { auto physical_device = PickPhysicalDevice(*caps, device_holder->instance.get()); if (!physical_device.has_value()) { @@ -262,6 +278,8 @@ void ContextVK::Setup(Settings settings) { return; } device_holder->physical_device = physical_device.value(); + } else { + device_holder->physical_device = settings.embedder_data->physical_device; } //---------------------------------------------------------------------------- @@ -320,7 +338,7 @@ void ContextVK::Setup(Settings settings) { device_info.setPEnabledExtensionNames(enabled_device_extensions_c); // Device layers are deprecated and ignored. - { + if (!settings.embedder_data.has_value()) { auto device_result = device_holder->physical_device.createDeviceUnique(device_info); if (device_result.result != vk::Result::eSuccess) { @@ -328,6 +346,8 @@ void ContextVK::Setup(Settings settings) { return; } device_holder->device = std::move(device_result.value); + } else { + device_holder->device.reset(settings.embedder_data->device); } if (!caps->SetPhysicalDevice(device_holder->physical_device, @@ -413,11 +433,18 @@ void ContextVK::Setup(Settings settings) { //---------------------------------------------------------------------------- /// Fetch the queues. /// - QueuesVK queues(device_holder->device.get(), // - graphics_queue.value(), // - compute_queue.value(), // - transfer_queue.value() // - ); + QueuesVK queues; + if (!settings.embedder_data.has_value()) { + queues = QueuesVK::FromQueueIndices(device_holder->device.get(), // + graphics_queue.value(), // + compute_queue.value(), // + transfer_queue.value() // + ); + } else { + queues = + QueuesVK::FromEmbedderQueue(settings.embedder_data->queue, + settings.embedder_data->queue_family_index); + } if (!queues.IsValid()) { VALIDATION_LOG << "Could not fetch device queues."; return; @@ -431,6 +458,7 @@ void ContextVK::Setup(Settings settings) { /// All done! /// device_holder_ = std::move(device_holder); + idle_waiter_vk_ = std::make_shared(device_holder_); driver_info_ = std::make_unique(device_holder_->physical_device); debug_report_ = std::move(debug_report); @@ -503,15 +531,18 @@ std::shared_ptr ContextVK::CreateCommandBuffer() const { // look up a cached descriptor pool for the current frame and reuse it // if it exists, otherwise create a new pool. - DescriptorPoolMap::iterator current_pool = - cached_descriptor_pool_.find(std::this_thread::get_id()); std::shared_ptr descriptor_pool; - if (current_pool == cached_descriptor_pool_.end()) { - descriptor_pool = - (cached_descriptor_pool_[std::this_thread::get_id()] = - std::make_shared(weak_from_this())); - } else { - descriptor_pool = current_pool->second; + { + Lock lock(desc_pool_mutex_); + DescriptorPoolMap::iterator current_pool = + cached_descriptor_pool_.find(std::this_thread::get_id()); + if (current_pool == cached_descriptor_pool_.end()) { + descriptor_pool = + (cached_descriptor_pool_[std::this_thread::get_id()] = + std::make_shared(weak_from_this())); + } else { + descriptor_pool = current_pool->second; + } } auto tracked_objects = std::make_shared( @@ -637,15 +668,18 @@ void ContextVK::InitializeCommonlyUsedShadersIfNeeded() const { rt_allocator.CreateOffscreenMSAA(*this, {1, 1}, 1); RenderPassBuilderVK builder; - for (const auto& [bind_point, color] : render_target.GetColorAttachments()) { - builder.SetColorAttachment( - bind_point, // - color.texture->GetTextureDescriptor().format, // - color.texture->GetTextureDescriptor().sample_count, // - color.load_action, // - color.store_action // - ); - } + + render_target.IterateAllColorAttachments( + [&builder](size_t index, const ColorAttachment& attachment) -> bool { + builder.SetColorAttachment( + index, // + attachment.texture->GetTextureDescriptor().format, // + attachment.texture->GetTextureDescriptor().sample_count, // + attachment.load_action, // + attachment.store_action // + ); + return true; + }); if (auto depth = render_target.GetDepthAttachment(); depth.has_value()) { builder.SetDepthStencilAttachment( @@ -668,7 +702,10 @@ void ContextVK::InitializeCommonlyUsedShadersIfNeeded() const { } void ContextVK::DisposeThreadLocalCachedResources() { - cached_descriptor_pool_.erase(std::this_thread::get_id()); + { + Lock lock(desc_pool_mutex_); + cached_descriptor_pool_.erase(std::this_thread::get_id()); + } command_pool_recycler_->Dispose(); } @@ -685,4 +722,8 @@ bool ContextVK::GetShouldDisableSurfaceControlSwapchain() const { return should_disable_surface_control_; } +RuntimeStageBackend ContextVK::GetRuntimeStageBackend() const { + return RuntimeStageBackend::kVulkan; +} + } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/context_vk.h b/impeller/renderer/backend/vulkan/context_vk.h index 91d4506312d0c..08ed6569b0097 100644 --- a/impeller/renderer/backend/vulkan/context_vk.h +++ b/impeller/renderer/backend/vulkan/context_vk.h @@ -13,6 +13,7 @@ #include "impeller/base/backend_cast.h" #include "impeller/base/strings.h" #include "impeller/core/formats.h" +#include "impeller/core/runtime_types.h" #include "impeller/renderer/backend/vulkan/command_pool_vk.h" #include "impeller/renderer/backend/vulkan/device_holder_vk.h" #include "impeller/renderer/backend/vulkan/driver_info_vk.h" @@ -41,10 +42,39 @@ class DescriptorPoolRecyclerVK; class CommandQueueVK; class DescriptorPoolVK; +class IdleWaiterVK : public IdleWaiter { + public: + explicit IdleWaiterVK(std::weak_ptr device_holder) + : device_holder_(std::move(device_holder)) {} + + void WaitIdle() const override { + std::shared_ptr strong_device_holder_ = + device_holder_.lock(); + if (strong_device_holder_ && strong_device_holder_->GetDevice()) { + [[maybe_unused]] auto result = + strong_device_holder_->GetDevice().waitIdle(); + } + } + + private: + std::weak_ptr device_holder_; +}; + class ContextVK final : public Context, public BackendCast, public std::enable_shared_from_this { public: + /// Embedder Stuff + struct EmbedderData { + VkInstance instance; + VkPhysicalDevice physical_device; + VkDevice device; + uint32_t queue_family_index; + VkQueue queue; + std::vector instance_extensions; + std::vector device_extensions; + }; + struct Settings { PFN_vkGetInstanceProcAddr proc_address_callback = nullptr; std::vector> shader_libraries_data; @@ -55,6 +85,8 @@ class ContextVK final : public Context, /// If validations are requested but cannot be enabled, log a fatal error. bool fatal_missing_validations = false; + std::optional embedder_data; + Settings() = default; Settings(Settings&&) = default; @@ -198,6 +230,12 @@ class ContextVK final : public Context, // | Context | bool FlushCommandBuffers() override; + RuntimeStageBackend GetRuntimeStageBackend() const override; + + std::shared_ptr GetIdleWaiter() const override { + return idle_waiter_vk_; + } + private: struct DeviceHolderImpl : public DeviceHolderVK { // |DeviceHolder| @@ -207,9 +245,17 @@ class ContextVK final : public Context, return physical_device; } + ~DeviceHolderImpl() { + if (!owned) { + instance.release(); + device.release(); + } + } + vk::UniqueInstance instance; vk::PhysicalDevice physical_device; vk::UniqueDevice device; + bool owned = true; }; std::shared_ptr device_holder_; @@ -230,11 +276,14 @@ class ContextVK final : public Context, std::shared_ptr raster_message_loop_; std::shared_ptr gpu_tracer_; std::shared_ptr command_queue_vk_; + std::shared_ptr idle_waiter_vk_; using DescriptorPoolMap = std::unordered_map>; - mutable DescriptorPoolMap cached_descriptor_pool_; + mutable Mutex desc_pool_mutex_; + mutable DescriptorPoolMap IPLR_GUARDED_BY(desc_pool_mutex_) + cached_descriptor_pool_; bool should_disable_surface_control_ = false; bool should_batch_cmd_buffers_ = false; std::vector> pending_command_buffers_; diff --git a/impeller/renderer/backend/vulkan/context_vk_unittests.cc b/impeller/renderer/backend/vulkan/context_vk_unittests.cc index 38cc53867d1af..84b242c6480f4 100644 --- a/impeller/renderer/backend/vulkan/context_vk_unittests.cc +++ b/impeller/renderer/backend/vulkan/context_vk_unittests.cc @@ -9,6 +9,7 @@ #include "impeller/renderer/backend/vulkan/command_pool_vk.h" #include "impeller/renderer/backend/vulkan/context_vk.h" #include "impeller/renderer/backend/vulkan/test/mock_vulkan.h" +#include "vulkan/vulkan_core.h" namespace impeller { namespace testing { @@ -260,6 +261,48 @@ TEST(ContextVKTest, HasDefaultColorFormat) { ASSERT_NE(capabilites_vk->GetDefaultColorFormat(), PixelFormat::kUnknown); } +TEST(ContextVKTest, EmbedderOverridesUsesInstanceExtensions) { + ContextVK::EmbedderData data; + auto other_context = MockVulkanContextBuilder().Build(); + + data.instance = other_context->GetInstance(); + data.device = other_context->GetDevice(); + data.physical_device = other_context->GetPhysicalDevice(); + data.queue = VkQueue{}; + data.queue_family_index = 0; + // Missing surface extension. + data.instance_extensions = {}; + data.device_extensions = {"VK_KHR_swapchain"}; + + ScopedValidationDisable scoped; + auto context = MockVulkanContextBuilder().SetEmbedderData(data).Build(); + + EXPECT_EQ(context, nullptr); +} + +TEST(ContextVKTest, EmbedderOverrides) { + ContextVK::EmbedderData data; + auto other_context = MockVulkanContextBuilder().Build(); + + data.instance = other_context->GetInstance(); + data.device = other_context->GetDevice(); + data.physical_device = other_context->GetPhysicalDevice(); + data.queue = VkQueue{}; + data.queue_family_index = 0; + data.instance_extensions = {"VK_KHR_surface", + "VK_KHR_portability_enumeration"}; + data.device_extensions = {"VK_KHR_swapchain"}; + + auto context = MockVulkanContextBuilder().SetEmbedderData(data).Build(); + + EXPECT_TRUE(context->IsValid()); + EXPECT_EQ(context->GetInstance(), other_context->GetInstance()); + EXPECT_EQ(context->GetDevice(), other_context->GetDevice()); + EXPECT_EQ(context->GetPhysicalDevice(), other_context->GetPhysicalDevice()); + EXPECT_EQ(context->GetGraphicsQueue()->GetIndex().index, 0u); + EXPECT_EQ(context->GetGraphicsQueue()->GetIndex().family, 0u); +} + TEST(ContextVKTest, BatchSubmitCommandBuffersOnArm) { std::shared_ptr context = MockVulkanContextBuilder() diff --git a/impeller/renderer/backend/vulkan/device_buffer_vk.cc b/impeller/renderer/backend/vulkan/device_buffer_vk.cc index 5e135a3b00a2a..f713d9198d57e 100644 --- a/impeller/renderer/backend/vulkan/device_buffer_vk.cc +++ b/impeller/renderer/backend/vulkan/device_buffer_vk.cc @@ -5,23 +5,23 @@ #include "impeller/renderer/backend/vulkan/device_buffer_vk.h" #include "flutter/flutter_vma/flutter_vma.h" -#include "flutter/fml/trace_event.h" #include "impeller/renderer/backend/vulkan/context_vk.h" -#include "vulkan/vulkan_core.h" namespace impeller { DeviceBufferVK::DeviceBufferVK(DeviceBufferDescriptor desc, std::weak_ptr context, UniqueBufferVMA buffer, - VmaAllocationInfo info) + VmaAllocationInfo info, + bool is_host_coherent) : DeviceBuffer(desc), context_(std::move(context)), resource_(ContextVK::Cast(*context_.lock().get()).GetResourceManager(), BufferResource{ std::move(buffer), // info // - }) {} + }), + is_host_coherent_(is_host_coherent) {} DeviceBufferVK::~DeviceBufferVK() = default; @@ -69,12 +69,20 @@ bool DeviceBufferVK::SetLabel(std::string_view label) { } void DeviceBufferVK::Flush(std::optional range) const { + if (is_host_coherent_) { + return; + } auto flush_range = range.value_or(Range{0, GetDeviceBufferDescriptor().size}); ::vmaFlushAllocation(resource_->buffer.get().allocator, resource_->buffer.get().allocation, flush_range.offset, flush_range.length); } +// Visible for testing. +bool DeviceBufferVK::IsHostCoherent() const { + return is_host_coherent_; +} + void DeviceBufferVK::Invalidate(std::optional range) const { auto flush_range = range.value_or(Range{0, GetDeviceBufferDescriptor().size}); ::vmaInvalidateAllocation(resource_->buffer.get().allocator, diff --git a/impeller/renderer/backend/vulkan/device_buffer_vk.h b/impeller/renderer/backend/vulkan/device_buffer_vk.h index 540c165be3892..8ec31cae8ce0a 100644 --- a/impeller/renderer/backend/vulkan/device_buffer_vk.h +++ b/impeller/renderer/backend/vulkan/device_buffer_vk.h @@ -20,13 +20,17 @@ class DeviceBufferVK final : public DeviceBuffer, DeviceBufferVK(DeviceBufferDescriptor desc, std::weak_ptr context, UniqueBufferVMA buffer, - VmaAllocationInfo info); + VmaAllocationInfo info, + bool is_host_coherent); // |DeviceBuffer| ~DeviceBufferVK() override; vk::Buffer GetBuffer() const; + // Visible for testing. + bool IsHostCoherent() const; + private: friend class AllocatorVK; @@ -51,6 +55,7 @@ class DeviceBufferVK final : public DeviceBuffer, std::weak_ptr context_; UniqueResourceVKT resource_; + bool is_host_coherent_ = false; // |DeviceBuffer| uint8_t* OnGetContents() const override; diff --git a/impeller/renderer/backend/vulkan/driver_info_vk.cc b/impeller/renderer/backend/vulkan/driver_info_vk.cc index 007aa47392da3..a53c456560484 100644 --- a/impeller/renderer/backend/vulkan/driver_info_vk.cc +++ b/impeller/renderer/backend/vulkan/driver_info_vk.cc @@ -336,20 +336,25 @@ bool DriverInfoVK::IsEmulator() const { bool DriverInfoVK::IsKnownBadDriver() const { if (adreno_gpu_.has_value()) { - auto adreno = adreno_gpu_.value(); - switch (adreno) { - // see: - // https://github.com/flutter/flutter/issues/154103 - // - // Reports "VK_INCOMPLETE" when compiling certain entity shader with - // vkCreateGraphicsPipelines, which is not a valid return status. - // See https://github.com/flutter/flutter/issues/155185 . - case AdrenoGPU::kAdreno630: - return true; - default: - return false; + AdrenoGPU adreno = adreno_gpu_.value(); + // See: + // https://github.com/flutter/flutter/issues/154103 + // + // Reports "VK_INCOMPLETE" when compiling certain entity shader with + // vkCreateGraphicsPipelines, which is not a valid return status. + // See https://github.com/flutter/flutter/issues/155185 . + // + // https://github.com/flutter/flutter/issues/155185 + // Unknown crashes but device is not easily acquirable. + if (adreno <= AdrenoGPU::kAdreno630) { + return true; } } + // Disable Maleoon series GPUs, see: + // https://github.com/flutter/flutter/issues/156623 + if (vendor_ == VendorVK::kHuawei) { + return true; + } return false; } diff --git a/impeller/renderer/backend/vulkan/driver_info_vk_unittests.cc b/impeller/renderer/backend/vulkan/driver_info_vk_unittests.cc index 44f014acbcd28..c1b40ed98e75c 100644 --- a/impeller/renderer/backend/vulkan/driver_info_vk_unittests.cc +++ b/impeller/renderer/backend/vulkan/driver_info_vk_unittests.cc @@ -36,6 +36,19 @@ TEST_P(DriverInfoVKTest, CanDumpToLog) { EXPECT_TRUE(log.str().find("Driver Information") != std::string::npos); } +TEST(DriverInfoVKTest, CanIdentifyBadMaleoonDriver) { + auto const context = + MockVulkanContextBuilder() + .SetPhysicalPropertiesCallback( + [](VkPhysicalDevice device, VkPhysicalDeviceProperties* prop) { + prop->vendorID = 0x19E5; // Huawei + prop->deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; + }) + .Build(); + + EXPECT_TRUE(context->GetDriverInfo()->IsKnownBadDriver()); +} + bool IsBadVersionTest(std::string_view driver_name, bool qc = true) { auto const context = MockVulkanContextBuilder() @@ -93,6 +106,18 @@ TEST(DriverInfoVKTest, DriverParsingAdreno) { TEST(DriverInfoVKTest, DisabledDevices) { EXPECT_TRUE(IsBadVersionTest("Adreno (TM) 630")); + EXPECT_TRUE(IsBadVersionTest("Adreno (TM) 620")); + EXPECT_TRUE(IsBadVersionTest("Adreno (TM) 610")); + EXPECT_TRUE(IsBadVersionTest("Adreno (TM) 530")); + EXPECT_TRUE(IsBadVersionTest("Adreno (TM) 512")); + EXPECT_TRUE(IsBadVersionTest("Adreno (TM) 509")); + EXPECT_TRUE(IsBadVersionTest("Adreno (TM) 508")); + EXPECT_TRUE(IsBadVersionTest("Adreno (TM) 506")); + EXPECT_TRUE(IsBadVersionTest("Adreno (TM) 505")); + EXPECT_TRUE(IsBadVersionTest("Adreno (TM) 504")); + + EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 640")); + EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 650")); } TEST(DriverInfoVKTest, EnabledDevicesMali) { @@ -109,13 +134,6 @@ TEST(DriverInfoVKTest, EnabledDevicesAdreno) { EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 720")); EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 710")); EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 702")); - EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 530")); - EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 512")); - EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 509")); - EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 508")); - EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 506")); - EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 505")); - EXPECT_FALSE(IsBadVersionTest("Adreno (TM) 504")); } } // namespace impeller::testing diff --git a/impeller/renderer/backend/vulkan/formats_vk.h b/impeller/renderer/backend/vulkan/formats_vk.h index 8ff6ac4a8f78f..2dbb21c5af08e 100644 --- a/impeller/renderer/backend/vulkan/formats_vk.h +++ b/impeller/renderer/backend/vulkan/formats_vk.h @@ -8,6 +8,7 @@ #include #include +#include "fml/logging.h" #include "impeller/base/validation.h" #include "impeller/core/formats.h" #include "impeller/core/shader_types.h" @@ -16,6 +17,18 @@ namespace impeller { +constexpr std::optional VkFormatToImpellerFormat( + vk::Format format) { + switch (format) { + case vk::Format::eR8G8B8A8Unorm: + return PixelFormat::kR8G8B8A8UNormInt; + case vk::Format::eB8G8R8A8Unorm: + return PixelFormat::kB8G8R8A8UNormInt; + default: + return std::nullopt; + } +} + constexpr vk::SampleCountFlagBits ToVKSampleCountFlagBits(SampleCount count) { switch (count) { case SampleCount::kCount1: @@ -263,28 +276,21 @@ constexpr vk::ShaderStageFlags ToVkShaderStage(ShaderStage stage) { FML_UNREACHABLE(); } -constexpr vk::DescriptorType ToVKDescriptorType(DescriptorType type) { - switch (type) { - case DescriptorType::kSampledImage: - return vk::DescriptorType::eCombinedImageSampler; - break; - case DescriptorType::kUniformBuffer: - return vk::DescriptorType::eUniformBuffer; - break; - case DescriptorType::kStorageBuffer: - return vk::DescriptorType::eStorageBuffer; - break; - case DescriptorType::kImage: - return vk::DescriptorType::eSampledImage; - break; - case DescriptorType::kSampler: - return vk::DescriptorType::eSampler; - break; - case DescriptorType::kInputAttachment: - return vk::DescriptorType::eInputAttachment; - } +static_assert(static_cast(DescriptorType::kSampledImage) == + static_cast(vk::DescriptorType::eCombinedImageSampler)); +static_assert(static_cast(DescriptorType::kUniformBuffer) == + static_cast(vk::DescriptorType::eUniformBuffer)); +static_assert(static_cast(DescriptorType::kStorageBuffer) == + static_cast(vk::DescriptorType::eStorageBuffer)); +static_assert(static_cast(DescriptorType::kImage) == + static_cast(vk::DescriptorType::eSampledImage)); +static_assert(static_cast(DescriptorType::kSampler) == + static_cast(vk::DescriptorType::eSampler)); +static_assert(static_cast(DescriptorType::kInputAttachment) == + static_cast(vk::DescriptorType::eInputAttachment)); - FML_UNREACHABLE(); +constexpr vk::DescriptorType ToVKDescriptorType(DescriptorType type) { + return static_cast(type); } constexpr vk::DescriptorSetLayoutBinding ToVKDescriptorSetLayoutBinding( @@ -371,6 +377,21 @@ constexpr vk::PolygonMode ToVKPolygonMode(PolygonMode mode) { FML_UNREACHABLE(); } +constexpr bool PrimitiveTopologySupportsPrimitiveRestart( + PrimitiveType primitive) { + switch (primitive) { + case PrimitiveType::kTriangleStrip: + case PrimitiveType::kLine: + case PrimitiveType::kPoint: + case PrimitiveType::kTriangleFan: + return true; + case PrimitiveType::kTriangle: + case PrimitiveType::kLineStrip: + return false; + } + FML_UNREACHABLE(); +} + constexpr vk::PrimitiveTopology ToVKPrimitiveTopology(PrimitiveType primitive) { switch (primitive) { case PrimitiveType::kTriangle: diff --git a/impeller/renderer/backend/vulkan/formats_vk_unittests.cc b/impeller/renderer/backend/vulkan/formats_vk_unittests.cc new file mode 100644 index 0000000000000..4c0ecbfdd393a --- /dev/null +++ b/impeller/renderer/backend/vulkan/formats_vk_unittests.cc @@ -0,0 +1,27 @@ +// 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. + +#include "gtest/gtest.h" // IWYU pragma: keep +#include "impeller/renderer/backend/vulkan/formats_vk.h" + +namespace impeller { +namespace testing { + +TEST(FormatsVKTest, DescriptorMapping) { + EXPECT_EQ(ToVKDescriptorType(DescriptorType::kSampledImage), + vk::DescriptorType::eCombinedImageSampler); + EXPECT_EQ(ToVKDescriptorType(DescriptorType::kUniformBuffer), + vk::DescriptorType::eUniformBuffer); + EXPECT_EQ(ToVKDescriptorType(DescriptorType::kStorageBuffer), + vk::DescriptorType::eStorageBuffer); + EXPECT_EQ(ToVKDescriptorType(DescriptorType::kImage), + vk::DescriptorType::eSampledImage); + EXPECT_EQ(ToVKDescriptorType(DescriptorType::kSampler), + vk::DescriptorType::eSampler); + EXPECT_EQ(ToVKDescriptorType(DescriptorType::kInputAttachment), + vk::DescriptorType::eInputAttachment); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/pipeline_vk.cc b/impeller/renderer/backend/vulkan/pipeline_vk.cc index fba6034a183f2..4ad838498244a 100644 --- a/impeller/renderer/backend/vulkan/pipeline_vk.cc +++ b/impeller/renderer/backend/vulkan/pipeline_vk.cc @@ -361,6 +361,8 @@ fml::StatusOr MakePipeline( vk::PipelineInputAssemblyStateCreateInfo input_assembly; const auto topology = ToVKPrimitiveTopology(desc.GetPrimitiveType()); input_assembly.setTopology(topology); + input_assembly.setPrimitiveRestartEnable( + PrimitiveTopologySupportsPrimitiveRestart(desc.GetPrimitiveType())); pipeline_info.setPInputAssemblyState(&input_assembly); //---------------------------------------------------------------------------- diff --git a/impeller/renderer/backend/vulkan/queue_vk.cc b/impeller/renderer/backend/vulkan/queue_vk.cc index c5cea22dafe8b..a96e2b0882b45 100644 --- a/impeller/renderer/backend/vulkan/queue_vk.cc +++ b/impeller/renderer/backend/vulkan/queue_vk.cc @@ -4,6 +4,8 @@ #include "impeller/renderer/backend/vulkan/queue_vk.h" +#include + #include "impeller/renderer/backend/vulkan/context_vk.h" namespace impeller { @@ -45,19 +47,37 @@ void QueueVK::InsertDebugMarker(std::string_view label) const { QueuesVK::QueuesVK() = default; -QueuesVK::QueuesVK(const vk::Device& device, - QueueIndexVK graphics, - QueueIndexVK compute, - QueueIndexVK transfer) { +QueuesVK::QueuesVK(std::shared_ptr graphics_queue, + std::shared_ptr compute_queue, + std::shared_ptr transfer_queue) + : graphics_queue(std::move(graphics_queue)), + compute_queue(std::move(compute_queue)), + transfer_queue(std::move(transfer_queue)) {} + +// static +QueuesVK QueuesVK::FromEmbedderQueue(vk::Queue queue, + uint32_t queue_family_index) { + auto graphics_queue = std::make_shared( + QueueIndexVK{.family = queue_family_index, .index = 0}, queue); + + return QueuesVK(graphics_queue, graphics_queue, graphics_queue); +} + +// static +QueuesVK QueuesVK::FromQueueIndices(const vk::Device& device, + QueueIndexVK graphics, + QueueIndexVK compute, + QueueIndexVK transfer) { auto vk_graphics = device.getQueue(graphics.family, graphics.index); auto vk_compute = device.getQueue(compute.family, compute.index); auto vk_transfer = device.getQueue(transfer.family, transfer.index); // Always set up the graphics queue. - graphics_queue = std::make_shared(graphics, vk_graphics); + auto graphics_queue = std::make_shared(graphics, vk_graphics); ContextVK::SetDebugName(device, vk_graphics, "ImpellerGraphicsQ"); // Setup the compute queue if its different from the graphics queue. + std::shared_ptr compute_queue; if (compute == graphics) { compute_queue = graphics_queue; } else { @@ -67,6 +87,7 @@ QueuesVK::QueuesVK(const vk::Device& device, // Setup the transfer queue if its different from the graphics or compute // queues. + std::shared_ptr transfer_queue; if (transfer == graphics) { transfer_queue = graphics_queue; } else if (transfer == compute) { @@ -75,6 +96,9 @@ QueuesVK::QueuesVK(const vk::Device& device, transfer_queue = std::make_shared(transfer, vk_transfer); ContextVK::SetDebugName(device, vk_transfer, "ImpellerTransferQ"); } + + return QueuesVK(std::move(graphics_queue), std::move(compute_queue), + std::move(transfer_queue)); } bool QueuesVK::IsValid() const { diff --git a/impeller/renderer/backend/vulkan/queue_vk.h b/impeller/renderer/backend/vulkan/queue_vk.h index eab025a34ff8d..2fcf7fba09d7d 100644 --- a/impeller/renderer/backend/vulkan/queue_vk.h +++ b/impeller/renderer/backend/vulkan/queue_vk.h @@ -67,10 +67,17 @@ struct QueuesVK { QueuesVK(); - QueuesVK(const vk::Device& device, - QueueIndexVK graphics, - QueueIndexVK compute, - QueueIndexVK transfer); + QueuesVK(std::shared_ptr graphics_queue, + std::shared_ptr compute_queue, + std::shared_ptr transfer_queue); + + static QueuesVK FromEmbedderQueue(vk::Queue queue, + uint32_t queue_family_index); + + static QueuesVK FromQueueIndices(const vk::Device& device, + QueueIndexVK graphics, + QueueIndexVK compute, + QueueIndexVK transfer); bool IsValid() const; }; diff --git a/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc b/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc index 1433f3811ba31..32f62dddf1907 100644 --- a/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc +++ b/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc @@ -6,7 +6,9 @@ #include +#include "impeller/core/formats.h" #include "impeller/renderer/backend/vulkan/formats_vk.h" +#include "vulkan/vulkan_enums.hpp" namespace impeller { @@ -31,7 +33,8 @@ RenderPassBuilderVK& RenderPassBuilderVK::SetColorAttachment( PixelFormat format, SampleCount sample_count, LoadAction load_action, - StoreAction store_action) { + StoreAction store_action, + vk::ImageLayout current_layout) { vk::AttachmentDescription desc; desc.format = ToVKImageFormat(format); desc.samples = ToVKSampleCount(sample_count); @@ -39,16 +42,33 @@ RenderPassBuilderVK& RenderPassBuilderVK::SetColorAttachment( desc.storeOp = ToVKAttachmentStoreOp(store_action, false); desc.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; desc.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; - desc.initialLayout = vk::ImageLayout::eUndefined; + if (load_action == LoadAction::kLoad) { + desc.initialLayout = current_layout; + } else { + desc.initialLayout = vk::ImageLayout::eUndefined; + } desc.finalLayout = vk::ImageLayout::eGeneral; - colors_[index] = desc; - if (StoreActionPerformsResolve(store_action)) { - desc.storeOp = ToVKAttachmentStoreOp(store_action, true); - desc.samples = vk::SampleCountFlagBits::e1; - resolves_[index] = desc; + const bool performs_resolves = StoreActionPerformsResolve(store_action); + if (index == 0u) { + color0_ = desc; + + if (performs_resolves) { + desc.storeOp = ToVKAttachmentStoreOp(store_action, true); + desc.samples = vk::SampleCountFlagBits::e1; + color0_resolve_ = desc; + } else { + color0_resolve_ = std::nullopt; + } } else { - resolves_.erase(index); + colors_[index] = desc; + if (performs_resolves) { + desc.storeOp = ToVKAttachmentStoreOp(store_action, true); + desc.samples = vk::SampleCountFlagBits::e1; + resolves_[index] = desc; + } else { + resolves_.erase(index); + } } return *this; } @@ -93,8 +113,11 @@ vk::UniqueRenderPass RenderPassBuilderVK::Build( const vk::Device& device) const { // This must be less than `VkPhysicalDeviceLimits::maxColorAttachments` but we // are not checking. - const auto color_attachments_count = + auto color_attachments_count = colors_.empty() ? 0u : colors_.rbegin()->first + 1u; + if (color0_.has_value()) { + color_attachments_count++; + } std::vector attachments; @@ -104,6 +127,22 @@ vk::UniqueRenderPass RenderPassBuilderVK::Build( kUnusedAttachmentReference); vk::AttachmentReference depth_stencil_ref = kUnusedAttachmentReference; + if (color0_.has_value()) { + vk::AttachmentReference color_ref; + color_ref.attachment = attachments.size(); + color_ref.layout = vk::ImageLayout::eGeneral; + color_refs[0] = color_ref; + attachments.push_back(color0_.value()); + + if (color0_resolve_.has_value()) { + vk::AttachmentReference resolve_ref; + resolve_ref.attachment = attachments.size(); + resolve_ref.layout = vk::ImageLayout::eGeneral; + resolve_refs[0] = resolve_ref; + attachments.push_back(color0_resolve_.value()); + } + } + for (const auto& color : colors_) { vk::AttachmentReference color_ref; color_ref.attachment = attachments.size(); @@ -200,4 +239,16 @@ RenderPassBuilderVK::GetDepthStencil() const { return depth_stencil_; } +// Visible for testing. +std::optional RenderPassBuilderVK::GetColor0() + const { + return color0_; +} + +// Visible for testing. +std::optional RenderPassBuilderVK::GetColor0Resolve() + const { + return color0_resolve_; +} + } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/render_pass_builder_vk.h b/impeller/renderer/backend/vulkan/render_pass_builder_vk.h index 4ef1924e8fe32..4e9632d9a6d13 100644 --- a/impeller/renderer/backend/vulkan/render_pass_builder_vk.h +++ b/impeller/renderer/backend/vulkan/render_pass_builder_vk.h @@ -8,6 +8,7 @@ #include #include +#include "flutter/fml/macros.h" #include "impeller/core/formats.h" #include "impeller/renderer/backend/vulkan/context_vk.h" #include "impeller/renderer/backend/vulkan/vk.h" @@ -24,11 +25,13 @@ class RenderPassBuilderVK { RenderPassBuilderVK& operator=(const RenderPassBuilderVK&) = delete; - RenderPassBuilderVK& SetColorAttachment(size_t index, - PixelFormat format, - SampleCount sample_count, - LoadAction load_action, - StoreAction store_action); + RenderPassBuilderVK& SetColorAttachment( + size_t index, + PixelFormat format, + SampleCount sample_count, + LoadAction load_action, + StoreAction store_action, + vk::ImageLayout current_layout = vk::ImageLayout::eUndefined); RenderPassBuilderVK& SetDepthStencilAttachment(PixelFormat format, SampleCount sample_count, @@ -52,10 +55,20 @@ class RenderPassBuilderVK { // Visible for testing. const std::optional& GetDepthStencil() const; + // Visible for testing. + std::optional GetColor0() const; + + // Visible for testing. + std::optional GetColor0Resolve() const; + private: + std::optional color0_; + std::optional color0_resolve_; + std::optional depth_stencil_; + + // Color attachment 0 is stored in the field above and not in these maps. std::map colors_; std::map resolves_; - std::optional depth_stencil_; }; //------------------------------------------------------------------------------ diff --git a/impeller/renderer/backend/vulkan/render_pass_builder_vk_unittests.cc b/impeller/renderer/backend/vulkan/render_pass_builder_vk_unittests.cc index 325dd826e5f1d..2aa44c42de0db 100644 --- a/impeller/renderer/backend/vulkan/render_pass_builder_vk_unittests.cc +++ b/impeller/renderer/backend/vulkan/render_pass_builder_vk_unittests.cc @@ -27,6 +27,32 @@ TEST(RenderPassBuilder, CreatesRenderPassWithNoDepthStencil) { EXPECT_FALSE(builder.GetDepthStencil().has_value()); } +TEST(RenderPassBuilder, RenderPassWithLoadOpUsesCurrentLayout) { + RenderPassBuilderVK builder = RenderPassBuilderVK(); + auto const context = MockVulkanContextBuilder().Build(); + + builder.SetColorAttachment(0, PixelFormat::kR8G8B8A8UNormInt, + SampleCount::kCount1, LoadAction::kLoad, + StoreAction::kStore, + vk::ImageLayout::eColorAttachmentOptimal); + + auto render_pass = builder.Build(context->GetDevice()); + + EXPECT_TRUE(!!render_pass); + + std::optional maybe_color = builder.GetColor0(); + ASSERT_TRUE(maybe_color.has_value()); + if (!maybe_color.has_value()) { + return; + } + vk::AttachmentDescription color = maybe_color.value(); + + EXPECT_EQ(color.initialLayout, vk::ImageLayout::eColorAttachmentOptimal); + EXPECT_EQ(color.finalLayout, vk::ImageLayout::eGeneral); + EXPECT_EQ(color.loadOp, vk::AttachmentLoadOp::eLoad); + EXPECT_EQ(color.storeOp, vk::AttachmentStoreOp::eStore); +} + TEST(RenderPassBuilder, CreatesRenderPassWithCombinedDepthStencil) { RenderPassBuilderVK builder = RenderPassBuilderVK(); auto const context = MockVulkanContextBuilder().Build(); @@ -34,7 +60,7 @@ TEST(RenderPassBuilder, CreatesRenderPassWithCombinedDepthStencil) { // Create a single color attachment with a transient depth stencil. builder.SetColorAttachment(0, PixelFormat::kR8G8B8A8UNormInt, SampleCount::kCount1, LoadAction::kClear, - StoreAction::kStore); + StoreAction::kStore, vk::ImageLayout::eGeneral); builder.SetDepthStencilAttachment(PixelFormat::kD24UnormS8Uint, SampleCount::kCount1, LoadAction::kDontCare, StoreAction::kDontCare); @@ -43,21 +69,25 @@ TEST(RenderPassBuilder, CreatesRenderPassWithCombinedDepthStencil) { EXPECT_TRUE(!!render_pass); - auto maybe_color = builder.GetColorAttachments().find(0u); - ASSERT_NE(maybe_color, builder.GetColorAttachments().end()); - auto color = maybe_color->second; + std::optional maybe_color = builder.GetColor0(); + ASSERT_TRUE(maybe_color.has_value()); + if (!maybe_color.has_value()) { + return; + } + vk::AttachmentDescription color = maybe_color.value(); EXPECT_EQ(color.initialLayout, vk::ImageLayout::eUndefined); EXPECT_EQ(color.finalLayout, vk::ImageLayout::eGeneral); EXPECT_EQ(color.loadOp, vk::AttachmentLoadOp::eClear); EXPECT_EQ(color.storeOp, vk::AttachmentStoreOp::eStore); - auto maybe_depth_stencil = builder.GetDepthStencil(); + std::optional maybe_depth_stencil = + builder.GetDepthStencil(); ASSERT_TRUE(maybe_depth_stencil.has_value()); if (!maybe_depth_stencil.has_value()) { return; } - auto depth_stencil = maybe_depth_stencil.value(); + vk::AttachmentDescription depth_stencil = maybe_depth_stencil.value(); EXPECT_EQ(depth_stencil.initialLayout, vk::ImageLayout::eUndefined); EXPECT_EQ(depth_stencil.finalLayout, @@ -83,12 +113,13 @@ TEST(RenderPassBuilder, CreatesRenderPassWithOnlyStencil) { EXPECT_TRUE(!!render_pass); - auto maybe_depth_stencil = builder.GetDepthStencil(); + std::optional maybe_depth_stencil = + builder.GetDepthStencil(); ASSERT_TRUE(maybe_depth_stencil.has_value()); if (!maybe_depth_stencil.has_value()) { return; } - auto depth_stencil = maybe_depth_stencil.value(); + vk::AttachmentDescription depth_stencil = maybe_depth_stencil.value(); EXPECT_EQ(depth_stencil.initialLayout, vk::ImageLayout::eUndefined); EXPECT_EQ(depth_stencil.finalLayout, @@ -112,9 +143,12 @@ TEST(RenderPassBuilder, CreatesMSAAResolveWithCorrectStore) { EXPECT_TRUE(!!render_pass); - auto maybe_color = builder.GetColorAttachments().find(0u); - ASSERT_NE(maybe_color, builder.GetColorAttachments().end()); - auto color = maybe_color->second; + auto maybe_color = builder.GetColor0(); + ASSERT_TRUE(maybe_color.has_value()); + if (!maybe_color.has_value()) { + return; + } + vk::AttachmentDescription color = maybe_color.value(); // MSAA Texture. EXPECT_EQ(color.initialLayout, vk::ImageLayout::eUndefined); @@ -122,9 +156,12 @@ TEST(RenderPassBuilder, CreatesMSAAResolveWithCorrectStore) { EXPECT_EQ(color.loadOp, vk::AttachmentLoadOp::eClear); EXPECT_EQ(color.storeOp, vk::AttachmentStoreOp::eDontCare); - auto maybe_resolve = builder.GetResolves().find(0u); - ASSERT_NE(maybe_resolve, builder.GetResolves().end()); - auto resolve = maybe_resolve->second; + auto maybe_resolve = builder.GetColor0Resolve(); + ASSERT_TRUE(maybe_resolve.has_value()); + if (!maybe_resolve.has_value()) { + return; + } + vk::AttachmentDescription resolve = maybe_resolve.value(); // MSAA Resolve Texture. EXPECT_EQ(resolve.initialLayout, vk::ImageLayout::eUndefined); diff --git a/impeller/renderer/backend/vulkan/render_pass_cache_unittests.cc b/impeller/renderer/backend/vulkan/render_pass_cache_unittests.cc index 055c91a3ccb10..f4b470ebe2a3f 100644 --- a/impeller/renderer/backend/vulkan/render_pass_cache_unittests.cc +++ b/impeller/renderer/backend/vulkan/render_pass_cache_unittests.cc @@ -23,8 +23,7 @@ TEST_P(RendererTest, CachesRenderPassAndFramebuffer) { auto render_target = allocator->CreateOffscreenMSAA(*GetContext(), {100, 100}, 1); - auto resolve_texture = - render_target.GetColorAttachments().find(0u)->second.resolve_texture; + auto resolve_texture = render_target.GetColorAttachment(0).resolve_texture; auto& texture_vk = TextureVK::Cast(*resolve_texture); EXPECT_EQ(texture_vk.GetCachedFramebuffer(), nullptr); diff --git a/impeller/renderer/backend/vulkan/render_pass_vk.cc b/impeller/renderer/backend/vulkan/render_pass_vk.cc index d9c01e1add031..f8a169adfb670 100644 --- a/impeller/renderer/backend/vulkan/render_pass_vk.cc +++ b/impeller/renderer/backend/vulkan/render_pass_vk.cc @@ -56,12 +56,14 @@ static std::vector GetVKClearValues( const RenderTarget& target) { std::vector clears; - for (const auto& [_, color] : target.GetColorAttachments()) { - clears.emplace_back(VKClearValueFromColor(color.clear_color)); - if (color.resolve_texture) { - clears.emplace_back(VKClearValueFromColor(color.clear_color)); - } - } + target.IterateAllColorAttachments( + [&clears](size_t index, const ColorAttachment& attachment) -> bool { + clears.emplace_back(VKClearValueFromColor(attachment.clear_color)); + if (attachment.resolve_texture) { + clears.emplace_back(VKClearValueFromColor(attachment.clear_color)); + } + return true; + }); const auto& depth = target.GetDepthAttachment(); const auto& stencil = target.GetStencilAttachment(); @@ -83,21 +85,24 @@ SharedHandleVK RenderPassVK::CreateVKRenderPass( const std::shared_ptr& command_buffer) const { RenderPassBuilderVK builder; - for (const auto& [bind_point, color] : render_target_.GetColorAttachments()) { - builder.SetColorAttachment( - bind_point, // - color.texture->GetTextureDescriptor().format, // - color.texture->GetTextureDescriptor().sample_count, // - color.load_action, // - color.store_action // - ); - TextureVK::Cast(*color.texture) - .SetLayoutWithoutEncoding(vk::ImageLayout::eGeneral); - if (color.resolve_texture) { - TextureVK::Cast(*color.resolve_texture) - .SetLayoutWithoutEncoding(vk::ImageLayout::eGeneral); - } - } + render_target_.IterateAllColorAttachments( + [&](size_t bind_point, const ColorAttachment& attachment) -> bool { + builder.SetColorAttachment( + bind_point, // + attachment.texture->GetTextureDescriptor().format, // + attachment.texture->GetTextureDescriptor().sample_count, // + attachment.load_action, // + attachment.store_action, // + TextureVK::Cast(*attachment.texture).GetLayout() // + ); + TextureVK::Cast(*attachment.texture) + .SetLayoutWithoutEncoding(vk::ImageLayout::eGeneral); + if (attachment.resolve_texture) { + TextureVK::Cast(*attachment.resolve_texture) + .SetLayoutWithoutEncoding(vk::ImageLayout::eGeneral); + } + return true; + }); if (auto depth = render_target_.GetDepthAttachment(); depth.has_value()) { builder.SetDepthStencilAttachment( @@ -136,10 +141,9 @@ RenderPassVK::RenderPassVK(const std::shared_ptr& context, const RenderTarget& target, std::shared_ptr command_buffer) : RenderPass(context, target), command_buffer_(std::move(command_buffer)) { - color_image_vk_ = - render_target_.GetColorAttachments().find(0u)->second.texture; - resolve_image_vk_ = - render_target_.GetColorAttachments().find(0u)->second.resolve_texture; + const ColorAttachment& color0 = render_target_.GetColorAttachment(0); + color_image_vk_ = color0.texture; + resolve_image_vk_ = color0.resolve_texture; const auto& vk_context = ContextVK::Cast(*context); command_buffer_vk_ = command_buffer_->GetCommandBuffer(); @@ -253,16 +257,19 @@ SharedHandleVK RenderPassVK::CreateVKFramebuffer( // This bit must be consistent to ensure compatibility with the pass created // earlier. Follow this order: Color attachments, then depth-stencil, then // stencil. - for (const auto& [_, color] : render_target_.GetColorAttachments()) { - // The bind point doesn't matter here since that information is present in - // the render pass. - attachments.emplace_back( - TextureVK::Cast(*color.texture).GetRenderTargetView()); - if (color.resolve_texture) { - attachments.emplace_back( - TextureVK::Cast(*color.resolve_texture).GetRenderTargetView()); - } - } + render_target_.IterateAllColorAttachments( + [&attachments](size_t index, const ColorAttachment& attachment) -> bool { + // The bind point doesn't matter here since that information is present + // in the render pass. + attachments.emplace_back( + TextureVK::Cast(*attachment.texture).GetRenderTargetView()); + if (attachment.resolve_texture) { + attachments.emplace_back(TextureVK::Cast(*attachment.resolve_texture) + .GetRenderTargetView()); + } + return true; + }); + if (auto depth = render_target_.GetDepthAttachment(); depth.has_value()) { attachments.emplace_back( TextureVK::Cast(*depth->texture).GetRenderTargetView()); @@ -376,9 +383,14 @@ bool RenderPassVK::SetVertexBuffer(BufferView vertex_buffers[], vk::Buffer buffers[kMaxVertexBuffers]; vk::DeviceSize vertex_buffer_offsets[kMaxVertexBuffers]; for (size_t i = 0; i < vertex_buffer_count; i++) { - buffers[i] = DeviceBufferVK::Cast(*vertex_buffers[i].buffer).GetBuffer(); - vertex_buffer_offsets[i] = vertex_buffers[i].range.offset; - command_buffer_->Track(vertex_buffers[i].buffer); + buffers[i] = + DeviceBufferVK::Cast(*vertex_buffers[i].GetBuffer()).GetBuffer(); + vertex_buffer_offsets[i] = vertex_buffers[i].GetRange().offset; + std::shared_ptr device_buffer = + vertex_buffers[i].TakeBuffer(); + if (device_buffer) { + command_buffer_->Track(device_buffer); + } } // Bind the vertex buffers. @@ -398,27 +410,27 @@ bool RenderPassVK::SetIndexBuffer(BufferView index_buffer, if (index_type != IndexType::kNone) { has_index_buffer_ = true; - const BufferView& index_buffer_view = index_buffer; + BufferView index_buffer_view = std::move(index_buffer); if (!index_buffer_view) { return false; } - const std::shared_ptr& index_buffer = - index_buffer_view.buffer; - if (!index_buffer) { + if (!index_buffer_view.GetBuffer()) { VALIDATION_LOG << "Failed to acquire device buffer" << " for index buffer view"; return false; } - if (!command_buffer_->Track(index_buffer)) { + std::shared_ptr index_buffer = + index_buffer_view.TakeBuffer(); + if (index_buffer && !command_buffer_->Track(index_buffer)) { return false; } vk::Buffer index_buffer_handle = - DeviceBufferVK::Cast(*index_buffer).GetBuffer(); + DeviceBufferVK::Cast(*index_buffer_view.GetBuffer()).GetBuffer(); command_buffer_vk_.bindIndexBuffer(index_buffer_handle, - index_buffer_view.range.offset, + index_buffer_view.GetRange().offset, ToVKIndexType(index_type)); } else { has_index_buffer_ = false; @@ -538,43 +550,42 @@ fml::Status RenderPassVK::Draw() { bool RenderPassVK::BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, BufferView view) { return BindResource(slot.binding, type, view); } -bool RenderPassVK::BindResource( - ShaderStage stage, - DescriptorType type, - const ShaderUniformSlot& slot, - const std::shared_ptr& metadata, - BufferView view) { +bool RenderPassVK::BindDynamicResource(ShaderStage stage, + DescriptorType type, + const ShaderUniformSlot& slot, + std::unique_ptr metadata, + BufferView view) { return BindResource(slot.binding, type, view); } bool RenderPassVK::BindResource(size_t binding, DescriptorType type, - const BufferView& view) { + BufferView view) { if (bound_buffer_offset_ >= kMaxBindings) { return false; } - const std::shared_ptr& device_buffer = view.buffer; - auto buffer = DeviceBufferVK::Cast(*device_buffer).GetBuffer(); + auto buffer = DeviceBufferVK::Cast(*view.GetBuffer()).GetBuffer(); if (!buffer) { return false; } - if (!command_buffer_->Track(device_buffer)) { + std::shared_ptr device_buffer = view.TakeBuffer(); + if (device_buffer && !command_buffer_->Track(device_buffer)) { return false; } - uint32_t offset = view.range.offset; + uint32_t offset = view.GetRange().offset; vk::DescriptorBufferInfo buffer_info; buffer_info.buffer = buffer; buffer_info.offset = offset; - buffer_info.range = view.range.length; + buffer_info.range = view.GetRange().length; buffer_workspace_[bound_buffer_offset_++] = buffer_info; vk::WriteDescriptorSet write_set; @@ -587,16 +598,26 @@ bool RenderPassVK::BindResource(size_t binding, return true; } +bool RenderPassVK::BindDynamicResource( + ShaderStage stage, + DescriptorType type, + const SampledImageSlot& slot, + std::unique_ptr metadata, + std::shared_ptr texture, + const std::unique_ptr& sampler) { + return BindResource(stage, type, slot, nullptr, texture, sampler); +} + bool RenderPassVK::BindResource(ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) { if (bound_buffer_offset_ >= kMaxBindings) { return false; } - if (!texture->IsValid() || !sampler) { + if (!texture || !texture->IsValid() || !sampler) { return false; } const TextureVK& texture_vk = TextureVK::Cast(*texture); diff --git a/impeller/renderer/backend/vulkan/render_pass_vk.h b/impeller/renderer/backend/vulkan/render_pass_vk.h index 9f3c3ea299ced..d442e08882cd2 100644 --- a/impeller/renderer/backend/vulkan/render_pass_vk.h +++ b/impeller/renderer/backend/vulkan/render_pass_vk.h @@ -92,34 +92,38 @@ class RenderPassVK final : public RenderPass { // |RenderPass| fml::Status Draw() override; - // |RenderPass| - void ReserveCommands(size_t command_count) override {} - // |ResourceBinder| bool BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, - BufferView view) override; - - // |RenderPass| - bool BindResource(ShaderStage stage, - DescriptorType type, - const ShaderUniformSlot& slot, - const std::shared_ptr& metadata, + const ShaderMetadata* metadata, BufferView view) override; // |ResourceBinder| bool BindResource(ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) override; - bool BindResource(size_t binding, - DescriptorType type, - const BufferView& view); + // |RenderPass| + bool BindDynamicResource(ShaderStage stage, + DescriptorType type, + const ShaderUniformSlot& slot, + std::unique_ptr metadata, + BufferView view) override; + + // |RenderPass| + bool BindDynamicResource( + ShaderStage stage, + DescriptorType type, + const SampledImageSlot& slot, + std::unique_ptr metadata, + std::shared_ptr texture, + const std::unique_ptr& sampler) override; + + bool BindResource(size_t binding, DescriptorType type, BufferView view); // |RenderPass| bool IsValid() const override; diff --git a/impeller/renderer/backend/vulkan/sampler_vk.cc b/impeller/renderer/backend/vulkan/sampler_vk.cc index 836f9bdd24f7d..8a1eaccf72ae3 100644 --- a/impeller/renderer/backend/vulkan/sampler_vk.cc +++ b/impeller/renderer/backend/vulkan/sampler_vk.cc @@ -92,7 +92,7 @@ static vk::UniqueSampler CreateSampler( } if (!desc.label.empty()) { - ContextVK::SetDebugName(device, sampler.value.get(), desc.label.c_str()); + ContextVK::SetDebugName(device, sampler.value.get(), desc.label.data()); } return std::move(sampler.value); diff --git a/impeller/renderer/backend/vulkan/surface_context_vk.cc b/impeller/renderer/backend/vulkan/surface_context_vk.cc index ca896f66aaba4..07837626663f2 100644 --- a/impeller/renderer/backend/vulkan/surface_context_vk.cc +++ b/impeller/renderer/backend/vulkan/surface_context_vk.cc @@ -5,6 +5,7 @@ #include "impeller/renderer/backend/vulkan/surface_context_vk.h" #include "flutter/fml/trace_event.h" +#include "impeller/core/runtime_types.h" #include "impeller/renderer/backend/vulkan/command_pool_vk.h" #include "impeller/renderer/backend/vulkan/context_vk.h" #include "impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.h" @@ -58,6 +59,10 @@ const std::shared_ptr& SurfaceContextVK::GetCapabilities() return parent_->GetCapabilities(); } +std::shared_ptr SurfaceContextVK::GetIdleWaiter() const { + return parent_->GetIdleWaiter(); +} + void SurfaceContextVK::Shutdown() { parent_->Shutdown(); } @@ -82,13 +87,17 @@ std::unique_ptr SurfaceContextVK::AcquireNextSurface() { if (!surface) { return nullptr; } + MarkFrameEnd(); + return surface; +} + +void SurfaceContextVK::MarkFrameEnd() { if (auto pipeline_library = parent_->GetPipelineLibrary()) { impeller::PipelineLibraryVK::Cast(*pipeline_library) .DidAcquireSurfaceFrame(); } parent_->DisposeThreadLocalCachedResources(); parent_->GetResourceAllocator()->DebugTraceMemoryStatistics(); - return surface; } void SurfaceContextVK::UpdateSurfaceSize(const ISize& size) const { @@ -120,4 +129,8 @@ bool SurfaceContextVK::FlushCommandBuffers() { return parent_->FlushCommandBuffers(); } +RuntimeStageBackend SurfaceContextVK::GetRuntimeStageBackend() const { + return parent_->GetRuntimeStageBackend(); +} + } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/surface_context_vk.h b/impeller/renderer/backend/vulkan/surface_context_vk.h index 444eb91bf19f9..488dc19f900cc 100644 --- a/impeller/renderer/backend/vulkan/surface_context_vk.h +++ b/impeller/renderer/backend/vulkan/surface_context_vk.h @@ -8,6 +8,7 @@ #include #include "impeller/base/backend_cast.h" +#include "impeller/core/runtime_types.h" #include "impeller/renderer/backend/vulkan/vk.h" #include "impeller/renderer/command_queue.h" #include "impeller/renderer/context.h" @@ -66,6 +67,12 @@ class SurfaceContextVK : public Context, // |Context| std::shared_ptr GetCommandQueue() const override; + // |Context| + std::shared_ptr GetIdleWaiter() const override; + + // |Context| + RuntimeStageBackend GetRuntimeStageBackend() const override; + // |Context| void Shutdown() override; @@ -76,6 +83,12 @@ class SurfaceContextVK : public Context, std::unique_ptr AcquireNextSurface(); + /// @brief Performs frame incrementing processes like AcquireNextSurface but + /// without the surface. + /// + /// Used by the embedder.h implementations. + void MarkFrameEnd(); + /// @brief Mark the current swapchain configuration as dirty, forcing it to be /// recreated on the next frame. void UpdateSurfaceSize(const ISize& size) const; diff --git a/impeller/renderer/backend/vulkan/test/mock_vulkan.cc b/impeller/renderer/backend/vulkan/test/mock_vulkan.cc index 93a6ba452fc7b..447b0665b5241 100644 --- a/impeller/renderer/backend/vulkan/test/mock_vulkan.cc +++ b/impeller/renderer/backend/vulkan/test/mock_vulkan.cc @@ -928,6 +928,7 @@ std::shared_ptr MockVulkanContextBuilder::Build() { g_instance_layers = instance_layers_; g_format_properties_callback = format_properties_callback_; g_physical_device_properties_callback = physical_properties_callback_; + settings.embedder_data = embedder_data_; std::shared_ptr result = ContextVK::Create(std::move(settings)); return result; } diff --git a/impeller/renderer/backend/vulkan/test/mock_vulkan.h b/impeller/renderer/backend/vulkan/test/mock_vulkan.h index adffe42202b8d..25da5720c3b5a 100644 --- a/impeller/renderer/backend/vulkan/test/mock_vulkan.h +++ b/impeller/renderer/backend/vulkan/test/mock_vulkan.h @@ -108,10 +108,17 @@ class MockVulkanContextBuilder { return *this; } + MockVulkanContextBuilder SetEmbedderData( + const ContextVK::EmbedderData& embedder_data) { + embedder_data_ = embedder_data; + return *this; + } + private: std::function settings_callback_; std::vector instance_extensions_; std::vector instance_layers_; + std::optional embedder_data_; std::function diff --git a/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc b/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc index 7589f849a15e4..4347df46aa561 100644 --- a/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc +++ b/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc @@ -86,8 +86,7 @@ TEST(SwapchainTest, CachesRenderPassOnSwapchainImage) { auto& depth = render_target.GetDepthAttachment(); depth_stencil_textures.push_back(depth.has_value() ? depth->texture : nullptr); - msaa_textures.push_back( - render_target.GetColorAttachments().find(0u)->second.texture); + msaa_textures.push_back(render_target.GetColorAttachment(0).texture); } for (auto i = 1; i < 3; i++) { diff --git a/impeller/renderer/backend/vulkan/texture_vk.cc b/impeller/renderer/backend/vulkan/texture_vk.cc index 28668b6d3f667..7d432ef32de25 100644 --- a/impeller/renderer/backend/vulkan/texture_vk.cc +++ b/impeller/renderer/backend/vulkan/texture_vk.cc @@ -4,6 +4,7 @@ #include "impeller/renderer/backend/vulkan/texture_vk.h" +#include "impeller/core/texture_descriptor.h" #include "impeller/renderer/backend/vulkan/command_buffer_vk.h" #include "impeller/renderer/backend/vulkan/formats_vk.h" #include "impeller/renderer/backend/vulkan/sampler_vk.h" diff --git a/impeller/renderer/backend/vulkan/tracked_objects_vk.cc b/impeller/renderer/backend/vulkan/tracked_objects_vk.cc index 249d2483b01b9..055e5c4697b1e 100644 --- a/impeller/renderer/backend/vulkan/tracked_objects_vk.cc +++ b/impeller/renderer/backend/vulkan/tracked_objects_vk.cc @@ -25,6 +25,11 @@ TrackedObjectsVK::TrackedObjectsVK( pool_ = pool; buffer_ = std::move(buffer); is_valid_ = true; + // Starting values were selected by looking at values from + // AiksTest.CanRenderMultipleBackdropBlurWithSingleBackdropId. + tracked_objects_.reserve(5); + tracked_buffers_.reserve(5); + tracked_textures_.reserve(5); } TrackedObjectsVK::~TrackedObjectsVK() { @@ -38,41 +43,30 @@ bool TrackedObjectsVK::IsValid() const { return is_valid_; } -void TrackedObjectsVK::Track(std::shared_ptr object) { - if (!object) { +void TrackedObjectsVK::Track(const std::shared_ptr& object) { + if (!object || (!tracked_objects_.empty() && + object.get() == tracked_objects_.back().get())) { return; } - tracked_objects_.insert(std::move(object)); + tracked_objects_.emplace_back(object); } -void TrackedObjectsVK::Track(std::shared_ptr buffer) { - if (!buffer) { +void TrackedObjectsVK::Track( + const std::shared_ptr& buffer) { + if (!buffer || (!tracked_buffers_.empty() && + buffer.get() == tracked_buffers_.back().get())) { return; } - tracked_buffers_.insert(std::move(buffer)); -} - -bool TrackedObjectsVK::IsTracking( - const std::shared_ptr& buffer) const { - if (!buffer) { - return false; - } - return tracked_buffers_.find(buffer) != tracked_buffers_.end(); + tracked_buffers_.emplace_back(buffer); } -void TrackedObjectsVK::Track(std::shared_ptr texture) { - if (!texture) { +void TrackedObjectsVK::Track( + const std::shared_ptr& texture) { + if (!texture || (!tracked_textures_.empty() && + texture.get() == tracked_textures_.back().get())) { return; } - tracked_textures_.insert(std::move(texture)); -} - -bool TrackedObjectsVK::IsTracking( - const std::shared_ptr& texture) const { - if (!texture) { - return false; - } - return tracked_textures_.find(texture) != tracked_textures_.end(); + tracked_textures_.emplace_back(texture); } vk::CommandBuffer TrackedObjectsVK::GetCommandBuffer() const { diff --git a/impeller/renderer/backend/vulkan/tracked_objects_vk.h b/impeller/renderer/backend/vulkan/tracked_objects_vk.h index 8305f51160380..2ce5d6c81b2b5 100644 --- a/impeller/renderer/backend/vulkan/tracked_objects_vk.h +++ b/impeller/renderer/backend/vulkan/tracked_objects_vk.h @@ -27,15 +27,11 @@ class TrackedObjectsVK { bool IsValid() const; - void Track(std::shared_ptr object); + void Track(const std::shared_ptr& object); - void Track(std::shared_ptr buffer); + void Track(const std::shared_ptr& buffer); - bool IsTracking(const std::shared_ptr& buffer) const; - - void Track(std::shared_ptr texture); - - bool IsTracking(const std::shared_ptr& texture) const; + void Track(const std::shared_ptr& texture); vk::CommandBuffer GetCommandBuffer() const; @@ -48,9 +44,9 @@ class TrackedObjectsVK { // `shared_ptr` since command buffers have a link to the command pool. std::shared_ptr pool_; vk::UniqueCommandBuffer buffer_; - std::set> tracked_objects_; - std::set> tracked_buffers_; - std::set> tracked_textures_; + std::vector> tracked_objects_; + std::vector> tracked_buffers_; + std::vector> tracked_textures_; std::unique_ptr probe_; bool is_valid_ = false; diff --git a/impeller/renderer/blit_pass.cc b/impeller/renderer/blit_pass.cc index 7d199d38aeed9..bc5c603eee0af 100644 --- a/impeller/renderer/blit_pass.cc +++ b/impeller/renderer/blit_pass.cc @@ -146,7 +146,7 @@ bool BlitPass::AddCopy(BufferView source, BytesPerPixelForPixelFormat(destination->GetTextureDescriptor().format); auto bytes_per_region = destination_region_value.Area() * bytes_per_pixel; - if (source.range.length != bytes_per_region) { + if (source.GetRange().length != bytes_per_region) { VALIDATION_LOG << "Attempted to add a texture blit with out of bounds access."; return false; diff --git a/impeller/renderer/blit_pass_unittests.cc b/impeller/renderer/blit_pass_unittests.cc index ceae7e96c3d42..d8b2d1b7f6bf6 100644 --- a/impeller/renderer/blit_pass_unittests.cc +++ b/impeller/renderer/blit_pass_unittests.cc @@ -3,6 +3,9 @@ // found in the LICENSE file. #include +#include "flutter/display_list/dl_builder.h" +#include "flutter/impeller/display_list/aiks_unittests.h" +#include "flutter/impeller/display_list/dl_image_impeller.h" #include "fml/mapping.h" #include "gtest/gtest.h" #include "impeller/base/validation.h" @@ -16,7 +19,12 @@ namespace impeller { namespace testing { -using BlitPassTest = PlaygroundTest; +using flutter::DisplayListBuilder; +using flutter::DlColor; +using flutter::DlImageSampling; +using flutter::DlPaint; + +using BlitPassTest = AiksTest; INSTANTIATE_PLAYGROUND_SUITE(BlitPassTest); TEST_P(BlitPassTest, BlitAcrossDifferentPixelFormatsFails) { @@ -230,5 +238,36 @@ TEST_P(BlitPassTest, CanResizeTextures) { EXPECT_TRUE(context->GetCommandQueue()->Submit({std::move(cmd_buffer)}).ok()); } +TEST_P(BlitPassTest, CanResizeTexturesPlayground) { + auto context = GetContext(); + auto cmd_buffer = context->CreateCommandBuffer(); + auto blit_pass = cmd_buffer->CreateBlitPass(); + + std::shared_ptr src = CreateTextureForFixture("kalimba.jpg"); + + TextureDescriptor dst_format; + dst_format.storage_mode = StorageMode::kDevicePrivate; + dst_format.format = PixelFormat::kR8G8B8A8UNormInt; + dst_format.size = {src->GetSize().width / 2, src->GetSize().height}; + dst_format.usage = TextureUsage::kShaderRead | TextureUsage::kShaderWrite; + auto dst = context->GetResourceAllocator()->CreateTexture(dst_format); + + ASSERT_TRUE(dst); + ASSERT_TRUE(src); + + EXPECT_TRUE(blit_pass->ResizeTexture(src, dst)); + EXPECT_TRUE(blit_pass->EncodeCommands(GetContext()->GetResourceAllocator())); + EXPECT_TRUE(context->GetCommandQueue()->Submit({std::move(cmd_buffer)}).ok()); + + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + DlPaint paint; + paint.setColor(DlColor::kRed()); + auto image = DlImageImpeller::Make(dst); + builder.DrawImage(image, SkPoint::Make(100.0, 100.0), + DlImageSampling::kNearestNeighbor, &paint); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/renderer/capabilities.cc b/impeller/renderer/capabilities.cc index 338e5ff67a110..5637d8458c35e 100644 --- a/impeller/renderer/capabilities.cc +++ b/impeller/renderer/capabilities.cc @@ -88,6 +88,9 @@ class StandardCapabilities final : public Capabilities { return default_maximum_render_pass_attachment_size_; } + // |Capabilities| + bool SupportsPrimitiveRestart() const override { return true; } + private: StandardCapabilities(bool supports_offscreen_msaa, bool supports_ssbo, diff --git a/impeller/renderer/capabilities.h b/impeller/renderer/capabilities.h index 2129c9e27e35c..1a4d2015516cb 100644 --- a/impeller/renderer/capabilities.h +++ b/impeller/renderer/capabilities.h @@ -86,6 +86,9 @@ class Capabilities { /// @brief Whether the primitive type TriangleFan is supported by the backend. virtual bool SupportsTriangleFan() const = 0; + /// @brief Whether primitive restart is supported. + virtual bool SupportsPrimitiveRestart() const = 0; + /// @brief Returns a supported `PixelFormat` for textures that store /// 4-channel colors (red/green/blue/alpha). virtual PixelFormat GetDefaultColorFormat() const = 0; diff --git a/impeller/renderer/command.cc b/impeller/renderer/command.cc index b95d93cb06952..a981cac62651d 100644 --- a/impeller/renderer/command.cc +++ b/impeller/renderer/command.cc @@ -4,108 +4,8 @@ #include "impeller/renderer/command.h" -#include - -#include "impeller/base/validation.h" -#include "impeller/core/formats.h" -#include "impeller/renderer/vertex_descriptor.h" - namespace impeller { -bool Command::BindVertices(const VertexBuffer& buffer) { - if (buffer.index_type == IndexType::kUnknown) { - VALIDATION_LOG << "Cannot bind vertex buffer with an unknown index type."; - return false; - } - - vertex_buffers = {buffer.vertex_buffer}; - vertex_buffer_count = 1u; - element_count = buffer.vertex_count; - index_buffer = buffer.index_buffer; - index_type = buffer.index_type; - return true; -} - -bool Command::BindResource(ShaderStage stage, - DescriptorType type, - const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, - BufferView view) { - return DoBindResource(stage, slot, &metadata, std::move(view)); -} - -bool Command::BindResource( - ShaderStage stage, - DescriptorType type, - const ShaderUniformSlot& slot, - const std::shared_ptr& metadata, - BufferView view) { - return DoBindResource(stage, slot, metadata, std::move(view)); -} - -template -bool Command::DoBindResource(ShaderStage stage, - const ShaderUniformSlot& slot, - const T metadata, - BufferView view) { - FML_DCHECK(slot.ext_res_0 != VertexDescriptor::kReservedVertexBufferIndex); - if (!view) { - return false; - } - - switch (stage) { - case ShaderStage::kVertex: - vertex_bindings.buffers.emplace_back(BufferAndUniformSlot{ - .slot = slot, .view = BufferResource(metadata, std::move(view))}); - return true; - case ShaderStage::kFragment: - fragment_bindings.buffers.emplace_back(BufferAndUniformSlot{ - .slot = slot, .view = BufferResource(metadata, std::move(view))}); - return true; - case ShaderStage::kCompute: - VALIDATION_LOG << "Use ComputeCommands for compute shader stages."; - case ShaderStage::kUnknown: - return false; - } - - return false; -} - -bool Command::BindResource(ShaderStage stage, - DescriptorType type, - const SampledImageSlot& slot, - const ShaderMetadata& metadata, - std::shared_ptr texture, - const std::unique_ptr& sampler) { - if (!sampler) { - return false; - } - if (!texture || !texture->IsValid()) { - return false; - } - - switch (stage) { - case ShaderStage::kVertex: - vertex_bindings.sampled_images.emplace_back(TextureAndSampler{ - .slot = slot, - .texture = {&metadata, std::move(texture)}, - .sampler = &sampler, - }); - return true; - case ShaderStage::kFragment: - fragment_bindings.sampled_images.emplace_back(TextureAndSampler{ - .slot = slot, - .texture = {&metadata, std::move(texture)}, - .sampler = &sampler, - }); - return true; - case ShaderStage::kCompute: - VALIDATION_LOG << "Use ComputeCommands for compute shader stages."; - case ShaderStage::kUnknown: - return false; - } - - return false; -} +// } // namespace impeller diff --git a/impeller/renderer/command.h b/impeller/renderer/command.h index fee137d669f66..24b64716663cf 100644 --- a/impeller/renderer/command.h +++ b/impeller/renderer/command.h @@ -12,24 +12,17 @@ #include "impeller/core/buffer_view.h" #include "impeller/core/formats.h" -#include "impeller/core/resource_binder.h" #include "impeller/core/sampler.h" #include "impeller/core/shader_types.h" #include "impeller/core/texture.h" -#include "impeller/core/vertex_buffer.h" #include "impeller/geometry/rect.h" #include "impeller/renderer/pipeline.h" namespace impeller { -#ifdef IMPELLER_DEBUG -#define DEBUG_COMMAND_INFO(obj, arg) obj.label = arg; -#else -#define DEBUG_COMMAND_INFO(obj, arg) -#endif // IMPELLER_DEBUG - template -struct Resource { +class Resource { + public: using ResourceType = T; ResourceType resource; @@ -38,20 +31,24 @@ struct Resource { Resource(const ShaderMetadata* metadata, ResourceType p_resource) : resource(p_resource), metadata_(metadata) {} - Resource(std::shared_ptr& metadata, - ResourceType p_resource) - : resource(p_resource), dynamic_metadata_(metadata) {} - const ShaderMetadata* GetMetadata() const { return dynamic_metadata_ ? dynamic_metadata_.get() : metadata_; } + static Resource MakeDynamic(std::unique_ptr metadata, + ResourceType p_resource) { + return Resource(std::move(metadata), p_resource); + } + private: + Resource(std::unique_ptr metadata, ResourceType p_resource) + : resource(p_resource), dynamic_metadata_(std::move(metadata)) {} + // Static shader metadata (typically generated by ImpellerC). const ShaderMetadata* metadata_ = nullptr; // Dynamically generated shader metadata. - std::shared_ptr dynamic_metadata_; + std::unique_ptr dynamic_metadata_ = nullptr; }; using BufferResource = Resource; @@ -60,21 +57,11 @@ using TextureResource = Resource>; /// @brief combines the texture, sampler and sampler slot information. struct TextureAndSampler { SampledImageSlot slot; + ShaderStage stage; TextureResource texture; const std::unique_ptr* sampler; }; -/// @brief combines the buffer resource and its uniform slot information. -struct BufferAndUniformSlot { - ShaderUniformSlot slot; - BufferResource view; -}; - -struct Bindings { - std::vector sampled_images; - std::vector buffers; -}; - //------------------------------------------------------------------------------ /// @brief An object used to specify work to the GPU along with references /// to resources the GPU will used when doing said work. @@ -89,21 +76,16 @@ struct Bindings { /// referenced in commands views into buffers managed by other /// allocators and resource managers. /// -struct Command : public ResourceBinder { +struct Command { //---------------------------------------------------------------------------- /// The pipeline to use for this command. /// std::shared_ptr> pipeline; - //---------------------------------------------------------------------------- - /// The buffer, texture, and sampler bindings used by the vertex pipeline - /// stage. - /// - Bindings vertex_bindings; - //---------------------------------------------------------------------------- - /// The buffer, texture, and sampler bindings used by the fragment pipeline - /// stage. - /// - Bindings fragment_bindings; + + /// An offset into render pass storage where bound buffers/texture metadata is + /// stored. + Range bound_buffers = Range{0, 0}; + Range bound_textures = Range{0, 0}; #ifdef IMPELLER_DEBUG //---------------------------------------------------------------------------- @@ -147,13 +129,10 @@ struct Command : public ResourceBinder { size_t instance_count = 1u; //---------------------------------------------------------------------------- - /// The vertex buffers used by the vertex shader stage. - std::array vertex_buffers; - - //---------------------------------------------------------------------------- - /// The number of vertex buffers in the vertex_buffers array. Must not exceed - /// kMaxVertexBuffers. - size_t vertex_buffer_count = 0u; + /// An offset and range of vertex buffers bound for this draw call. + /// + /// The vertex buffers are stored on the render pass object. + Range vertex_buffers; //---------------------------------------------------------------------------- /// The index buffer binding used by the vertex shader stage. @@ -169,45 +148,7 @@ struct Command : public ResourceBinder { /// packed in the index buffer. IndexType index_type = IndexType::kUnknown; - //---------------------------------------------------------------------------- - /// @brief Specify the vertex and index buffer to use for this command. - /// - /// @param[in] buffer The vertex and index buffer definition. If possible, - /// this value should be moved and not copied. - /// - /// @return returns if the binding was updated. - /// - bool BindVertices(const VertexBuffer& buffer); - - // |ResourceBinder| - bool BindResource(ShaderStage stage, - DescriptorType type, - const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, - BufferView view) override; - - bool BindResource(ShaderStage stage, - DescriptorType type, - const ShaderUniformSlot& slot, - const std::shared_ptr& metadata, - BufferView view); - - // |ResourceBinder| - bool BindResource(ShaderStage stage, - DescriptorType type, - const SampledImageSlot& slot, - const ShaderMetadata& metadata, - std::shared_ptr texture, - const std::unique_ptr& sampler) override; - bool IsValid() const { return pipeline && pipeline->IsValid(); } - - private: - template - bool DoBindResource(ShaderStage stage, - const ShaderUniformSlot& slot, - T metadata, - BufferView view); }; } // namespace impeller diff --git a/impeller/renderer/command_buffer.cc b/impeller/renderer/command_buffer.cc index 5ff1113c100ec..12a79eacaa5fd 100644 --- a/impeller/renderer/command_buffer.cc +++ b/impeller/renderer/command_buffer.cc @@ -30,6 +30,10 @@ bool CommandBuffer::SubmitCommands() { return SubmitCommands(nullptr); } +void CommandBuffer::WaitUntilCompleted() { + return OnWaitUntilCompleted(); +} + void CommandBuffer::WaitUntilScheduled() { return OnWaitUntilScheduled(); } diff --git a/impeller/renderer/command_buffer.h b/impeller/renderer/command_buffer.h index 958b4d0bd41f1..578bf003ac845 100644 --- a/impeller/renderer/command_buffer.h +++ b/impeller/renderer/command_buffer.h @@ -61,7 +61,14 @@ class CommandBuffer { virtual void SetLabel(std::string_view label) const = 0; //---------------------------------------------------------------------------- - /// @brief Force execution of pending GPU commands. + /// @brief Block the current thread until the GPU has completed execution + /// of the commands. + /// + void WaitUntilCompleted(); + + //---------------------------------------------------------------------------- + /// @brief Block the current thread until the GPU has completed + /// scheduling execution of the commands. /// void WaitUntilScheduled(); @@ -102,6 +109,8 @@ class CommandBuffer { [[nodiscard]] virtual bool OnSubmitCommands(CompletionCallback callback) = 0; + virtual void OnWaitUntilCompleted() = 0; + virtual void OnWaitUntilScheduled() = 0; virtual std::shared_ptr OnCreateComputePass() = 0; diff --git a/impeller/renderer/compute_unittests.cc b/impeller/renderer/compute_unittests.cc index 39e14a0a99b23..0d2ae2194e2e2 100644 --- a/impeller/renderer/compute_unittests.cc +++ b/impeller/renderer/compute_unittests.cc @@ -30,7 +30,8 @@ TEST_P(ComputeTest, CapabilitiesReportSupport) { TEST_P(ComputeTest, CanCreateComputePass) { using CS = SampleComputeShader; auto context = GetContext(); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); ASSERT_TRUE(context); ASSERT_TRUE(context->GetCapabilities()->SupportsCompute()); @@ -84,7 +85,7 @@ TEST_P(ComputeTest, CanCreateComputePass) { EXPECT_EQ(status, CommandBuffer::Status::kCompleted); auto view = DeviceBuffer::AsBufferView(output_buffer); - EXPECT_EQ(view.range.length, sizeof(CS::Output)); + EXPECT_EQ(view.GetRange().length, sizeof(CS::Output)); CS::Output* output = reinterpret_cast*>( @@ -109,7 +110,8 @@ TEST_P(ComputeTest, CanCreateComputePass) { TEST_P(ComputeTest, CanComputePrefixSum) { using CS = PrefixSumTestComputeShader; auto context = GetContext(); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); ASSERT_TRUE(context); ASSERT_TRUE(context->GetCapabilities()->SupportsCompute()); @@ -152,7 +154,7 @@ TEST_P(ComputeTest, CanComputePrefixSum) { EXPECT_EQ(status, CommandBuffer::Status::kCompleted); auto view = DeviceBuffer::AsBufferView(output_buffer); - EXPECT_EQ(view.range.length, + EXPECT_EQ(view.GetRange().length, sizeof(CS::OutputData)); CS::OutputData* output = @@ -210,7 +212,7 @@ TEST_P(ComputeTest, 1DThreadgroupSizingIsCorrect) { EXPECT_EQ(status, CommandBuffer::Status::kCompleted); auto view = DeviceBuffer::AsBufferView(output_buffer); - EXPECT_EQ(view.range.length, + EXPECT_EQ(view.GetRange().length, sizeof(CS::OutputData)); CS::OutputData* output = @@ -229,7 +231,8 @@ TEST_P(ComputeTest, CanComputePrefixSumLargeInteractive) { using CS = PrefixSumTestComputeShader; auto context = GetContext(); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); ASSERT_TRUE(context); ASSERT_TRUE(context->GetCapabilities()->SupportsCompute()); @@ -275,7 +278,8 @@ TEST_P(ComputeTest, MultiStageInputAndOutput) { using Stage2PipelineBuilder = ComputePipelineBuilder; auto context = GetContext(); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); ASSERT_TRUE(context); ASSERT_TRUE(context->GetCapabilities()->SupportsCompute()); @@ -373,7 +377,8 @@ TEST_P(ComputeTest, MultiStageInputAndOutput) { TEST_P(ComputeTest, CanCompute1DimensionalData) { using CS = SampleComputeShader; auto context = GetContext(); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); ASSERT_TRUE(context); ASSERT_TRUE(context->GetCapabilities()->SupportsCompute()); @@ -427,7 +432,7 @@ TEST_P(ComputeTest, CanCompute1DimensionalData) { EXPECT_EQ(status, CommandBuffer::Status::kCompleted); auto view = DeviceBuffer::AsBufferView(output_buffer); - EXPECT_EQ(view.range.length, sizeof(CS::Output)); + EXPECT_EQ(view.GetRange().length, sizeof(CS::Output)); CS::Output* output = reinterpret_cast*>( @@ -452,7 +457,8 @@ TEST_P(ComputeTest, CanCompute1DimensionalData) { TEST_P(ComputeTest, ReturnsEarlyWhenAnyGridDimensionIsZero) { using CS = SampleComputeShader; auto context = GetContext(); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); ASSERT_TRUE(context); ASSERT_TRUE(context->GetCapabilities()->SupportsCompute()); diff --git a/impeller/renderer/context.cc b/impeller/renderer/context.cc index e23874191a713..b7922caf213e9 100644 --- a/impeller/renderer/context.cc +++ b/impeller/renderer/context.cc @@ -25,4 +25,16 @@ bool Context::FlushCommandBuffers() { return true; } +std::shared_ptr Context::GetIdleWaiter() const { + return nullptr; +} + +void Context::ResetThreadLocalState() const { + // Nothing to do. +} + +bool Context::AddTrackingFence(const std::shared_ptr& texture) const { + return false; +} + } // namespace impeller diff --git a/impeller/renderer/context.h b/impeller/renderer/context.h index 91c5ee7a77207..71bad511288d2 100644 --- a/impeller/renderer/context.h +++ b/impeller/renderer/context.h @@ -222,6 +222,26 @@ class Context { /// rendering a 2D workload. [[nodiscard]] virtual bool FlushCommandBuffers(); + virtual bool AddTrackingFence(const std::shared_ptr& texture) const; + + virtual std::shared_ptr GetIdleWaiter() const; + + //---------------------------------------------------------------------------- + /// Resets any thread local state that may interfere with embedders. + /// + /// Today, only the OpenGL backend can trample on thread local state that the + /// embedder can access. This call puts the GL state in a sane "clean" state. + /// + /// Impeller itself is resilient to a dirty thread local state table. + /// + virtual void ResetThreadLocalState() const; + + /// @brief Retrieve the runtime stage for this context type. + /// + /// This is used by the engine shell and other subsystems for loading the + /// correct shader types. + virtual RuntimeStageBackend GetRuntimeStageBackend() const = 0; + protected: Context(); diff --git a/impeller/renderer/render_pass.cc b/impeller/renderer/render_pass.cc index 016a7a16f13bf..a7f00ff286a4c 100644 --- a/impeller/renderer/render_pass.cc +++ b/impeller/renderer/render_pass.cc @@ -63,15 +63,6 @@ bool RenderPass::AddCommand(Command&& command) { return false; } - if (command.scissor.has_value()) { - auto target_rect = IRect::MakeSize(render_target_.GetRenderTargetSize()); - if (!target_rect.Contains(command.scissor.value())) { - VALIDATION_LOG << "Cannot apply a scissor that lies outside the bounds " - "of the render target."; - return false; - } - } - if (command.element_count == 0u || command.instance_count == 0u) { // Essentially a no-op. Don't record the command but this is not necessary // an error either. @@ -151,9 +142,13 @@ bool RenderPass::SetVertexBuffer(BufferView vertex_buffers[], return false; } - pending_.vertex_buffer_count = vertex_buffer_count; + if (!vertex_buffers_start_.has_value()) { + vertex_buffers_start_ = vertex_buffers_.size(); + } + + pending_.vertex_buffers.length += vertex_buffer_count; for (size_t i = 0; i < vertex_buffer_count; i++) { - pending_.vertex_buffers[i] = std::move(vertex_buffers[i]); + vertex_buffers_.push_back(vertex_buffers[i]); } return true; } @@ -203,8 +198,14 @@ bool RenderPass::ValidateIndexBuffer(const BufferView& index_buffer, } fml::Status RenderPass::Draw() { + pending_.bound_buffers.offset = bound_buffers_start_.value_or(0u); + pending_.bound_textures.offset = bound_textures_start_.value_or(0u); + pending_.vertex_buffers.offset = vertex_buffers_start_.value_or(0u); auto result = AddCommand(std::move(pending_)); pending_ = Command{}; + bound_textures_start_ = std::nullopt; + bound_buffers_start_ = std::nullopt; + vertex_buffers_start_ = std::nullopt; if (result) { return fml::Status(); } @@ -216,29 +217,95 @@ fml::Status RenderPass::Draw() { bool RenderPass::BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, BufferView view) { - return pending_.BindResource(stage, type, slot, metadata, view); -} - -bool RenderPass::BindResource( - ShaderStage stage, - DescriptorType type, - const ShaderUniformSlot& slot, - const std::shared_ptr& metadata, - BufferView view) { - return pending_.BindResource(stage, type, slot, metadata, std::move(view)); + FML_DCHECK(slot.ext_res_0 != VertexDescriptor::kReservedVertexBufferIndex); + if (!view) { + return false; + } + BufferResource resouce = BufferResource(metadata, std::move(view)); + return BindBuffer(stage, slot, std::move(resouce)); } // |ResourceBinder| bool RenderPass::BindResource(ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, std::shared_ptr texture, const std::unique_ptr& sampler) { - return pending_.BindResource(stage, type, slot, metadata, std::move(texture), - sampler); + if (!sampler) { + return false; + } + if (!texture || !texture->IsValid()) { + return false; + } + TextureResource resource = TextureResource(metadata, std::move(texture)); + return BindTexture(stage, slot, std::move(resource), sampler); +} + +bool RenderPass::BindDynamicResource(ShaderStage stage, + DescriptorType type, + const ShaderUniformSlot& slot, + std::unique_ptr metadata, + BufferView view) { + FML_DCHECK(slot.ext_res_0 != VertexDescriptor::kReservedVertexBufferIndex); + if (!view) { + return false; + } + BufferResource resouce = + BufferResource::MakeDynamic(std::move(metadata), std::move(view)); + + return BindBuffer(stage, slot, std::move(resouce)); +} + +bool RenderPass::BindDynamicResource( + ShaderStage stage, + DescriptorType type, + const SampledImageSlot& slot, + std::unique_ptr metadata, + std::shared_ptr texture, + const std::unique_ptr& sampler) { + if (!sampler) { + return false; + } + if (!texture || !texture->IsValid()) { + return false; + } + TextureResource resource = + TextureResource::MakeDynamic(std::move(metadata), std::move(texture)); + return BindTexture(stage, slot, std::move(resource), sampler); +} + +bool RenderPass::BindBuffer(ShaderStage stage, + const ShaderUniformSlot& slot, + BufferResource resource) { + if (!bound_buffers_start_.has_value()) { + bound_buffers_start_ = bound_buffers_.size(); + } + + pending_.bound_buffers.length++; + bound_buffers_.push_back(std::move(resource)); + return true; +} + +bool RenderPass::BindTexture(ShaderStage stage, + const SampledImageSlot& slot, + TextureResource resource, + const std::unique_ptr& sampler) { + TextureAndSampler data = TextureAndSampler{ + .stage = stage, + .texture = std::move(resource), + .sampler = &sampler, + }; + + if (!bound_textures_start_.has_value()) { + bound_textures_start_ = bound_textures_.size(); + } + + pending_.bound_textures.length++; + bound_textures_.push_back(std::move(data)); + return true; } } // namespace impeller diff --git a/impeller/renderer/render_pass.h b/impeller/renderer/render_pass.h index 0ada25e744513..20d85111d90fc 100644 --- a/impeller/renderer/render_pass.h +++ b/impeller/renderer/render_pass.h @@ -18,9 +18,6 @@ namespace impeller { -class HostBuffer; -class Allocator; - //------------------------------------------------------------------------------ /// @brief Render passes encode render commands directed as one specific /// render target into an underlying command buffer. @@ -46,13 +43,6 @@ class RenderPass : public ResourceBinder { void SetLabel(std::string_view label); - /// @brief Reserve [command_count] commands in the HAL command buffer. - /// - /// Note: this is not the native command buffer. - virtual void ReserveCommands(size_t command_count) { - commands_.reserve(command_count); - } - //---------------------------------------------------------------------------- /// The pipeline to use for this command. virtual void SetPipeline( @@ -181,24 +171,33 @@ class RenderPass : public ResourceBinder { virtual bool BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot& slot, - const ShaderMetadata& metadata, + const ShaderMetadata* metadata, BufferView view) override; + // |ResourceBinder| virtual bool BindResource( ShaderStage stage, DescriptorType type, - const ShaderUniformSlot& slot, - const std::shared_ptr& metadata, - BufferView view); + const SampledImageSlot& slot, + const ShaderMetadata* metadata, + std::shared_ptr texture, + const std::unique_ptr& sampler) override; - // |ResourceBinder| - virtual bool BindResource( + /// @brief Bind with dynamically generated shader metadata. + virtual bool BindDynamicResource( ShaderStage stage, DescriptorType type, const SampledImageSlot& slot, - const ShaderMetadata& metadata, + std::unique_ptr metadata, std::shared_ptr texture, - const std::unique_ptr& sampler) override; + const std::unique_ptr& sampler); + + /// @brief Bind with dynamically generated shader metadata. + virtual bool BindDynamicResource(ShaderStage stage, + DescriptorType type, + const ShaderUniformSlot& slot, + std::unique_ptr metadata, + BufferView view); //---------------------------------------------------------------------------- /// @brief Encode the recorded commands to the underlying command buffer. @@ -245,6 +244,9 @@ class RenderPass : public ResourceBinder { const ISize render_target_size_; const RenderTarget render_target_; std::vector commands_; + std::vector vertex_buffers_; + std::vector bound_buffers_; + std::vector bound_textures_; const Matrix orthographic_; //---------------------------------------------------------------------------- @@ -276,7 +278,19 @@ class RenderPass : public ResourceBinder { RenderPass& operator=(const RenderPass&) = delete; + bool BindBuffer(ShaderStage stage, + const ShaderUniformSlot& slot, + BufferResource resource); + + bool BindTexture(ShaderStage stage, + const SampledImageSlot& slot, + TextureResource resource, + const std::unique_ptr& sampler); + Command pending_; + std::optional bound_buffers_start_ = std::nullopt; + std::optional bound_textures_start_ = std::nullopt; + std::optional vertex_buffers_start_ = std::nullopt; }; } // namespace impeller diff --git a/impeller/renderer/render_target.cc b/impeller/renderer/render_target.cc index dd70f9f202a9d..b3621e706b659 100644 --- a/impeller/renderer/render_target.cc +++ b/impeller/renderer/render_target.cc @@ -28,6 +28,7 @@ bool RenderTarget::IsValid() const { return false; } +#ifndef NDEBUG // Validate that all attachments are of the same size. { std::optional size; @@ -87,12 +88,34 @@ bool RenderTarget::IsValid() const { return false; } } +#endif // NDEBUG return true; } +bool RenderTarget::IterateAllColorAttachments( + const std::function& + iterator) const { + if (color0_.has_value()) { + if (!iterator(0, color0_.value())) { + return false; + } + } + for (const auto& [index, attachment] : colors_) { + if (!iterator(index, attachment)) { + return false; + } + } + return true; +} + void RenderTarget::IterateAllAttachments( const std::function& iterator) const { + if (color0_.has_value()) { + if (!iterator(color0_.value())) { + return; + } + } for (const auto& color : colors_) { if (!iterator(color.second)) { return; @@ -113,13 +136,16 @@ void RenderTarget::IterateAllAttachments( } SampleCount RenderTarget::GetSampleCount() const { - if (auto found = colors_.find(0u); found != colors_.end()) { - return found->second.texture->GetTextureDescriptor().sample_count; + if (color0_.has_value()) { + return color0_.value().texture->GetTextureDescriptor().sample_count; } return SampleCount::kCount1; } bool RenderTarget::HasColorAttachment(size_t index) const { + if (index == 0u) { + return color0_.has_value(); + } if (auto found = colors_.find(index); found != colors_.end()) { return true; } @@ -127,6 +153,12 @@ bool RenderTarget::HasColorAttachment(size_t index) const { } std::optional RenderTarget::GetColorAttachmentSize(size_t index) const { + if (index == 0u) { + if (color0_.has_value()) { + return color0_.value().texture->GetSize(); + } + return std::nullopt; + } auto found = colors_.find(index); if (found == colors_.end()) { @@ -142,12 +174,10 @@ ISize RenderTarget::GetRenderTargetSize() const { } std::shared_ptr RenderTarget::GetRenderTargetTexture() const { - auto found = colors_.find(0u); - if (found == colors_.end()) { + if (!color0_.has_value()) { return nullptr; } - return found->second.resolve_texture ? found->second.resolve_texture - : found->second.texture; + return color0_->resolve_texture ? color0_->resolve_texture : color0_->texture; } PixelFormat RenderTarget::GetRenderTargetPixelFormat() const { @@ -169,7 +199,12 @@ size_t RenderTarget::GetMaxColorAttacmentBindIndex() const { RenderTarget& RenderTarget::SetColorAttachment( const ColorAttachment& attachment, size_t index) { - if (attachment.IsValid()) { + if (!attachment.IsValid()) { + return *this; + } + if (index == 0u) { + color0_ = attachment; + } else { colors_[index] = attachment; } return *this; @@ -195,9 +230,18 @@ RenderTarget& RenderTarget::SetStencilAttachment( return *this; } -const std::map& RenderTarget::GetColorAttachments() - const { - return colors_; +ColorAttachment RenderTarget::GetColorAttachment(size_t index) const { + if (index == 0) { + if (color0_.has_value()) { + return color0_.value(); + } + return ColorAttachment{}; + } + std::map::const_iterator it = colors_.find(index); + if (it != colors_.end()) { + return it->second; + } + return ColorAttachment{}; } const std::optional& RenderTarget::GetDepthAttachment() const { @@ -219,6 +263,9 @@ size_t RenderTarget::GetTotalAttachmentCount() const { count++; } } + if (color0_.has_value()) { + count++; + } if (depth_.has_value()) { count++; } @@ -231,6 +278,10 @@ size_t RenderTarget::GetTotalAttachmentCount() const { std::string RenderTarget::ToString() const { std::stringstream stream; + if (color0_.has_value()) { + stream << SPrintF("Color[%d]=(%s)", 0, + ColorAttachmentToString(color0_.value()).c_str()); + } for (const auto& [index, color] : colors_) { stream << SPrintF("Color[%zu]=(%s)", index, ColorAttachmentToString(color).c_str()); @@ -248,6 +299,18 @@ std::string RenderTarget::ToString() const { return stream.str(); } +RenderTargetConfig RenderTarget::ToConfig() const { + if (!color0_.has_value()) { + return RenderTargetConfig{}; + } + const auto& color_attachment = color0_.value(); + return RenderTargetConfig{ + .size = color_attachment.texture->GetSize(), + .mip_count = color_attachment.texture->GetMipCount(), + .has_msaa = color_attachment.resolve_texture != nullptr, + .has_depth_stencil = depth_.has_value() && stencil_.has_value()}; +} + RenderTargetAllocator::RenderTargetAllocator( std::shared_ptr allocator) : allocator_(std::move(allocator)) {} diff --git a/impeller/renderer/render_target.h b/impeller/renderer/render_target.h index 77f5bf43f5e38..a7a1e0bd32c9a 100644 --- a/impeller/renderer/render_target.h +++ b/impeller/renderer/render_target.h @@ -102,6 +102,13 @@ class RenderTarget final { RenderTarget& SetColorAttachment(const ColorAttachment& attachment, size_t index); + /// @brief Get the color attachment at [index]. + /// + /// This function does not validate whether or not the attachment was + /// previously defined and will return a default constructed attachment + /// if none is set. + ColorAttachment GetColorAttachment(size_t index) const; + RenderTarget& SetDepthAttachment(std::optional attachment); RenderTarget& SetStencilAttachment( @@ -109,32 +116,32 @@ class RenderTarget final { size_t GetMaxColorAttacmentBindIndex() const; - const std::map& GetColorAttachments() const; - const std::optional& GetDepthAttachment() const; const std::optional& GetStencilAttachment() const; size_t GetTotalAttachmentCount() const; + bool IterateAllColorAttachments( + const std::function& iterator) + const; + void IterateAllAttachments( const std::function& iterator) const; std::string ToString() const; - RenderTargetConfig ToConfig() const { - auto& color_attachment = GetColorAttachments().find(0)->second; - return RenderTargetConfig{ - .size = color_attachment.texture->GetSize(), - .mip_count = color_attachment.texture->GetMipCount(), - .has_msaa = color_attachment.resolve_texture != nullptr, - .has_depth_stencil = depth_.has_value() && stencil_.has_value()}; - } + RenderTargetConfig ToConfig() const; private: - std::map colors_; + std::optional color0_; std::optional depth_; std::optional stencil_; + // Note: color0 is stored in the color0_ field above and not in this map, + // to avoid heap allocations for the commonly created render target formats + // in Flutter. + std::map colors_; }; /// @brief a wrapper around the impeller [Allocator] instance that can be used diff --git a/impeller/renderer/renderer_dart_unittests.cc b/impeller/renderer/renderer_dart_unittests.cc index fb33252a90077..3368ae759e742 100644 --- a/impeller/renderer/renderer_dart_unittests.cc +++ b/impeller/renderer/renderer_dart_unittests.cc @@ -224,7 +224,8 @@ class RendererDartTest : public PlaygroundTest, return false; } - auto buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); pass.SetVertexBuffer(texture_vtx_builder.CreateVertexBuffer( *context->GetResourceAllocator())); @@ -295,10 +296,6 @@ TEST_P(RendererDartTest, CanReflectUniformStructs) { ASSERT_TRUE(RunDartFunction("canReflectUniformStructs")); } -TEST_P(RendererDartTest, UniformBindFailsForInvalidHostBufferOffset) { - ASSERT_TRUE(RunDartFunction("uniformBindFailsForInvalidHostBufferOffset")); -} - TEST_P(RendererDartTest, CanCreateRenderPassAndSubmit) { ASSERT_TRUE(RenderDartToPlayground("canCreateRenderPassAndSubmit")); } diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc index ab4520ed6348c..0e84ea50cfd17 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -31,7 +31,6 @@ #include "impeller/fixtures/swizzle.frag.h" #include "impeller/fixtures/texture.frag.h" #include "impeller/fixtures/texture.vert.h" -#include "impeller/geometry/path_builder.h" #include "impeller/playground/playground.h" #include "impeller/playground/playground_test.h" #include "impeller/renderer/command_buffer.h" @@ -80,7 +79,8 @@ TEST_P(RendererTest, CanCreateBoxPrimitive) { context->GetSamplerLibrary()->GetSampler({}); ASSERT_TRUE(sampler); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); SinglePassCallback callback = [&](RenderPass& pass) { ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); static bool wireframe; @@ -145,7 +145,8 @@ TEST_P(RendererTest, BabysFirstTriangle) { auto pipeline = context->GetPipelineLibrary()->GetPipeline(desc).Get(); // Create a host side buffer to build the vertex and uniform information. - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); // Specify the vertex buffer information. VertexBufferBuilder vertex_buffer_builder; @@ -165,7 +166,8 @@ TEST_P(RendererTest, BabysFirstTriangle) { FS::FragInfo frag_info; frag_info.time = fml::TimePoint::Now().ToEpochDelta().ToSecondsF(); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); FS::BindFragInfo(pass, host_buffer->EmplaceUniform(frag_info)); return pass.Draw().ok(); @@ -241,7 +243,8 @@ TEST_P(RendererTest, CanRenderPerspectiveCube) { ASSERT_TRUE(sampler); Vector3 euler_angles; - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); SinglePassCallback callback = [&](RenderPass& pass) { static Degrees fov_y(60); static Scalar distance = 10; @@ -255,17 +258,14 @@ TEST_P(RendererTest, CanRenderPerspectiveCube) { pass.SetPipeline(pipeline); std::array vertex_buffers = { - BufferView{ - .buffer = device_buffer, - .range = Range(offsetof(Cube, positions), sizeof(Cube::positions))}, - BufferView{ - .buffer = device_buffer, - .range = Range(offsetof(Cube, colors), sizeof(Cube::colors))}, + BufferView(device_buffer, + Range(offsetof(Cube, positions), sizeof(Cube::positions))), + BufferView(device_buffer, + Range(offsetof(Cube, colors), sizeof(Cube::colors))), }; - BufferView index_buffer = { - .buffer = device_buffer, - .range = Range(offsetof(Cube, indices), sizeof(Cube::indices))}; + BufferView index_buffer( + device_buffer, Range(offsetof(Cube, indices), sizeof(Cube::indices))); pass.SetVertexBuffer(vertex_buffers.data(), vertex_buffers.size()); pass.SetElementCount(36); pass.SetIndexBuffer(index_buffer, IndexType::k16bit); @@ -324,7 +324,8 @@ TEST_P(RendererTest, CanRenderMultiplePrimitives) { context->GetSamplerLibrary()->GetSampler({}); ASSERT_TRUE(sampler); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); SinglePassCallback callback = [&](RenderPass& pass) { for (size_t i = 0; i < 1; i++) { for (size_t j = 0; j < 1; j++) { @@ -377,7 +378,8 @@ TEST_P(RendererTest, CanRenderToTexture) { auto box_pipeline = context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get(); ASSERT_TRUE(box_pipeline); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); VertexBufferBuilder vertex_builder; vertex_builder.SetLabel("Box"); @@ -499,7 +501,8 @@ TEST_P(RendererTest, CanRenderInstanced) { instances.colors[i] = Color::Random(); } - auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); ASSERT_TRUE(OpenPlaygroundHere([&](RenderPass& pass) -> bool { pass.SetPipeline(pipeline); pass.SetCommandLabel("InstancedDraw"); @@ -570,7 +573,8 @@ TEST_P(RendererTest, CanBlitTextureToTexture) { vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); ASSERT_TRUE(vertex_buffer); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); Playground::RenderCallback callback = [&](RenderTarget& render_target) { auto buffer = context->CreateCommandBuffer(); if (!buffer) { @@ -585,10 +589,6 @@ TEST_P(RendererTest, CanBlitTextureToTexture) { } pass->SetLabel("Playground Blit Pass"); - if (render_target.GetColorAttachments().empty()) { - return false; - } - // Blit `bridge` to the top left corner of the texture. pass->AddCopy(bridge, texture); @@ -690,7 +690,8 @@ TEST_P(RendererTest, CanBlitTextureToBuffer) { vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); ASSERT_TRUE(vertex_buffer); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); Playground::RenderCallback callback = [&](RenderTarget& render_target) { { auto buffer = context->CreateCommandBuffer(); @@ -704,10 +705,6 @@ TEST_P(RendererTest, CanBlitTextureToBuffer) { } pass->SetLabel("Playground Blit Pass"); - if (render_target.GetColorAttachments().empty()) { - return false; - } - // Blit `bridge` to the top left corner of the texture. pass->AddCopy(bridge, device_buffer); pass->EncodeCommands(context->GetResourceAllocator()); @@ -751,7 +748,7 @@ TEST_P(RendererTest, CanBlitTextureToBuffer) { auto texture = context->GetResourceAllocator()->CreateTexture(texture_desc); if (!texture->SetContents(device_buffer->OnGetContents(), - buffer_view.range.length)) { + buffer_view.GetRange().length)) { VALIDATION_LOG << "Could not upload texture to device memory"; return false; } @@ -807,7 +804,8 @@ TEST_P(RendererTest, CanGenerateMipmaps) { ASSERT_TRUE(vertex_buffer); bool first_frame = true; - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); Playground::RenderCallback callback = [&](RenderTarget& render_target) { const char* mip_filter_names[] = {"Base", "Nearest", "Linear"}; const MipFilter mip_filters[] = {MipFilter::kBase, MipFilter::kNearest, @@ -919,7 +917,8 @@ TEST_P(RendererTest, TheImpeller) { "table_mountain_pz.png", "table_mountain_nz.png"}); const std::unique_ptr& cube_map_sampler = context->GetSamplerLibrary()->GetSampler({}); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); SinglePassCallback callback = [&](RenderPass& pass) { auto size = pass.GetRenderTargetSize(); @@ -968,7 +967,8 @@ TEST_P(RendererTest, Planet) { context->GetPipelineLibrary()->GetPipeline(pipeline_descriptor).Get(); ASSERT_TRUE(pipeline && pipeline->IsValid()); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); SinglePassCallback callback = [&](RenderPass& pass) { static Scalar speed = 0.1; @@ -1034,7 +1034,8 @@ TEST_P(RendererTest, ArrayUniforms) { context->GetPipelineLibrary()->GetPipeline(pipeline_descriptor).Get(); ASSERT_TRUE(pipeline && pipeline->IsValid()); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); SinglePassCallback callback = [&](RenderPass& pass) { auto size = pass.GetRenderTargetSize(); @@ -1091,7 +1092,8 @@ TEST_P(RendererTest, InactiveUniforms) { context->GetPipelineLibrary()->GetPipeline(pipeline_descriptor).Get(); ASSERT_TRUE(pipeline && pipeline->IsValid()); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); SinglePassCallback callback = [&](RenderPass& pass) { auto size = pass.GetRenderTargetSize(); @@ -1256,7 +1258,8 @@ TEST_P(RendererTest, StencilMask) { static int current_back_compare = CompareFunctionUI().IndexOf(CompareFunction::kLessEqual); - auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); Playground::RenderCallback callback = [&](RenderTarget& render_target) { auto buffer = context->CreateCommandBuffer(); if (!buffer) { @@ -1469,7 +1472,8 @@ TEST_P(RendererTest, CanSepiaToneWithSubpasses) { ASSERT_TRUE(sampler); SinglePassCallback callback = [&](RenderPass& pass) { - auto buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); // Draw the texture. { @@ -1562,7 +1566,8 @@ TEST_P(RendererTest, CanSepiaToneThenSwizzleWithSubpasses) { ASSERT_TRUE(sampler); SinglePassCallback callback = [&](RenderPass& pass) { - auto buffer = HostBuffer::Create(context->GetResourceAllocator()); + auto buffer = HostBuffer::Create(context->GetResourceAllocator(), + context->GetIdleWaiter()); // Draw the texture. { @@ -1612,6 +1617,21 @@ TEST_P(RendererTest, CanSepiaToneThenSwizzleWithSubpasses) { OpenPlaygroundHere(callback); } +TEST_P(RendererTest, BindingNullTexturesDoesNotCrash) { + using FS = BoxFadeFragmentShader; + + auto context = GetContext(); + const std::unique_ptr& sampler = + context->GetSamplerLibrary()->GetSampler({}); + auto command_buffer = context->CreateCommandBuffer(); + + RenderTargetAllocator allocator(context->GetResourceAllocator()); + RenderTarget target = allocator.CreateOffscreen(*context, {1, 1}, 1); + + auto pass = command_buffer->CreateRenderPass(target); + EXPECT_FALSE(FS::BindContents2(*pass, nullptr, sampler)); +} + } // namespace testing } // namespace impeller diff --git a/impeller/renderer/testing/mocks.h b/impeller/renderer/testing/mocks.h index fd4172134b4db..d8eca488be5ea 100644 --- a/impeller/renderer/testing/mocks.h +++ b/impeller/renderer/testing/mocks.h @@ -7,6 +7,7 @@ #include "gmock/gmock.h" #include "impeller/core/allocator.h" +#include "impeller/core/runtime_types.h" #include "impeller/core/sampler_descriptor.h" #include "impeller/core/texture.h" #include "impeller/renderer/command_buffer.h" @@ -128,6 +129,7 @@ class MockCommandBuffer : public CommandBuffer { OnSubmitCommands, (CompletionCallback callback), (override)); + MOCK_METHOD(void, OnWaitUntilCompleted, (), (override)); MOCK_METHOD(void, OnWaitUntilScheduled, (), (override)); MOCK_METHOD(std::shared_ptr, OnCreateComputePass, @@ -183,6 +185,11 @@ class MockImpellerContext : public Context { GetCommandQueue, (), (const, override)); + + MOCK_METHOD(RuntimeStageBackend, + GetRuntimeStageBackend, + (), + (const, override)); }; class MockTexture : public Texture { @@ -218,6 +225,7 @@ class MockCapabilities : public Capabilities { MOCK_METHOD(bool, SupportsDecalSamplerAddressMode, (), (const, override)); MOCK_METHOD(bool, SupportsDeviceTransientTextures, (), (const, override)); MOCK_METHOD(bool, SupportsTriangleFan, (), (const override)); + MOCK_METHOD(bool, SupportsPrimitiveRestart, (), (const override)); MOCK_METHOD(PixelFormat, GetDefaultColorFormat, (), (const, override)); MOCK_METHOD(PixelFormat, GetDefaultStencilFormat, (), (const, override)); MOCK_METHOD(PixelFormat, GetDefaultDepthStencilFormat, (), (const, override)); diff --git a/impeller/renderer/vertex_buffer_builder.h b/impeller/renderer/vertex_buffer_builder.h index bcbde03daabae..974268caab983 100644 --- a/impeller/renderer/vertex_buffer_builder.h +++ b/impeller/renderer/vertex_buffer_builder.h @@ -138,7 +138,7 @@ class VertexBufferBuilder { if (!label_.empty()) { buffer->SetLabel(SPrintF("%s Vertices", label_.c_str())); } - return DeviceBuffer::AsBufferView(buffer); + return DeviceBuffer::AsBufferView(std::move(buffer)); } std::vector CreateIndexBuffer() const { return indices_; } @@ -167,7 +167,7 @@ class VertexBufferBuilder { if (!label_.empty()) { buffer->SetLabel(SPrintF("%s Indices", label_.c_str())); } - return DeviceBuffer::AsBufferView(buffer); + return DeviceBuffer::AsBufferView(std::move(buffer)); } }; diff --git a/impeller/runtime_stage/runtime_stage.cc b/impeller/runtime_stage/runtime_stage.cc index bf46e805a93eb..634274ac214b4 100644 --- a/impeller/runtime_stage/runtime_stage.cc +++ b/impeller/runtime_stage/runtime_stage.cc @@ -75,6 +75,8 @@ RuntimeStage::Map RuntimeStage::DecodeRuntimeStages( RuntimeStageIfPresent(raw_stages->metal(), payload)}, {RuntimeStageBackend::kOpenGLES, RuntimeStageIfPresent(raw_stages->opengles(), payload)}, + {RuntimeStageBackend::kOpenGLES3, + RuntimeStageIfPresent(raw_stages->opengles3(), payload)}, {RuntimeStageBackend::kVulkan, RuntimeStageIfPresent(raw_stages->vulkan(), payload)}, }; diff --git a/impeller/runtime_stage/runtime_stage_types.fbs b/impeller/runtime_stage/runtime_stage_types.fbs index 39c8af71cfdbc..dd9f7f5a66d71 100644 --- a/impeller/runtime_stage/runtime_stage_types.fbs +++ b/impeller/runtime_stage/runtime_stage_types.fbs @@ -83,5 +83,6 @@ table RuntimeStages { sksl: RuntimeStage; metal: RuntimeStage; opengles: RuntimeStage; + opengles3: RuntimeStage; vulkan: RuntimeStage; } diff --git a/impeller/tessellator/dart/pubspec.yaml b/impeller/tessellator/dart/pubspec.yaml index ef84e428bd7db..618220c2e93e0 100644 --- a/impeller/tessellator/dart/pubspec.yaml +++ b/impeller/tessellator/dart/pubspec.yaml @@ -6,7 +6,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/impeller/tessellator/tessellator.cc b/impeller/tessellator/tessellator.cc index a36889bc6a577..383440658cc7f 100644 --- a/impeller/tessellator/tessellator.cc +++ b/impeller/tessellator/tessellator.cc @@ -3,18 +3,28 @@ // found in the LICENSE file. #include "impeller/tessellator/tessellator.h" +#include +#include + +#include "impeller/core/device_buffer.h" +#include "impeller/geometry/path_component.h" namespace impeller { Tessellator::Tessellator() : point_buffer_(std::make_unique>()), - index_buffer_(std::make_unique>()) { + index_buffer_(std::make_unique>()), + stroke_points_(kPointArenaSize) { point_buffer_->reserve(2048); index_buffer_->reserve(2048); } Tessellator::~Tessellator() = default; +std::vector& Tessellator::GetStrokePointCache() { + return stroke_points_; +} + Path::Polyline Tessellator::CreateTempPolyline(const Path& path, Scalar tolerance) { FML_DCHECK(point_buffer_); @@ -29,7 +39,55 @@ Path::Polyline Tessellator::CreateTempPolyline(const Path& path, VertexBuffer Tessellator::TessellateConvex(const Path& path, HostBuffer& host_buffer, - Scalar tolerance) { + Scalar tolerance, + bool supports_primitive_restart, + bool supports_triangle_fan) { + if (supports_primitive_restart) { + // Primitive Restart. + const auto [point_count, contour_count] = path.CountStorage(tolerance); + BufferView point_buffer = host_buffer.Emplace( + nullptr, sizeof(Point) * point_count, alignof(Point)); + BufferView index_buffer = host_buffer.Emplace( + nullptr, sizeof(uint16_t) * (point_count + contour_count), + alignof(uint16_t)); + + if (supports_triangle_fan) { + FanVertexWriter writer( + reinterpret_cast(point_buffer.GetBuffer()->OnGetContents() + + point_buffer.GetRange().offset), + reinterpret_cast( + index_buffer.GetBuffer()->OnGetContents() + + index_buffer.GetRange().offset)); + path.WritePolyline(tolerance, writer); + point_buffer.GetBuffer()->Flush(point_buffer.GetRange()); + index_buffer.GetBuffer()->Flush(index_buffer.GetRange()); + + return VertexBuffer{ + .vertex_buffer = std::move(point_buffer), + .index_buffer = std::move(index_buffer), + .vertex_count = writer.GetIndexCount(), + .index_type = IndexType::k16bit, + }; + } else { + StripVertexWriter writer( + reinterpret_cast(point_buffer.GetBuffer()->OnGetContents() + + point_buffer.GetRange().offset), + reinterpret_cast( + index_buffer.GetBuffer()->OnGetContents() + + index_buffer.GetRange().offset)); + path.WritePolyline(tolerance, writer); + point_buffer.GetBuffer()->Flush(point_buffer.GetRange()); + index_buffer.GetBuffer()->Flush(index_buffer.GetRange()); + + return VertexBuffer{ + .vertex_buffer = std::move(point_buffer), + .index_buffer = std::move(index_buffer), + .vertex_count = writer.GetIndexCount(), + .index_type = IndexType::k16bit, + }; + } + } + FML_DCHECK(point_buffer_); FML_DCHECK(index_buffer_); TessellateConvexInternal(path, *point_buffer_, *index_buffer_, tolerance); @@ -59,6 +117,50 @@ VertexBuffer Tessellator::TessellateConvex(const Path& path, }; } +VertexBuffer Tessellator::GenerateLineStrip(const Path& path, + HostBuffer& host_buffer, + Scalar tolerance) { + LineStripVertexWriter writer(stroke_points_); + path.WritePolyline(tolerance, writer); + + const auto [arena_length, oversized_length] = writer.GetVertexCount(); + + if (oversized_length == 0) { + return VertexBuffer{ + .vertex_buffer = + host_buffer.Emplace(stroke_points_.data(), + arena_length * sizeof(Point), alignof(Point)), + .index_buffer = {}, + .vertex_count = arena_length, + .index_type = IndexType::kNone, + }; + } + const std::vector& oversized_data = writer.GetOversizedBuffer(); + BufferView buffer_view = host_buffer.Emplace( + /*buffer=*/nullptr, // + (arena_length + oversized_length) * sizeof(Point), // + alignof(Point) // + ); + memcpy(buffer_view.GetBuffer()->OnGetContents() + + buffer_view.GetRange().offset, // + stroke_points_.data(), // + arena_length * sizeof(Point) // + ); + memcpy(buffer_view.GetBuffer()->OnGetContents() + + buffer_view.GetRange().offset + arena_length * sizeof(Point), // + oversized_data.data(), // + oversized_data.size() * sizeof(Point) // + ); + buffer_view.GetBuffer()->Flush(buffer_view.GetRange()); + + return VertexBuffer{ + .vertex_buffer = buffer_view, + .index_buffer = {}, + .vertex_count = arena_length + oversized_length, + .index_type = IndexType::kNone, + }; +} + void Tessellator::TessellateConvexInternal(const Path& path, std::vector& point_buffer, std::vector& index_buffer, @@ -66,7 +168,7 @@ void Tessellator::TessellateConvexInternal(const Path& path, point_buffer.clear(); index_buffer.clear(); - VertexWriter writer(point_buffer, index_buffer); + GLESVertexWriter writer(point_buffer, index_buffer); path.WritePolyline(tolerance, writer); } diff --git a/impeller/tessellator/tessellator.h b/impeller/tessellator/tessellator.h index 06e4a1228eb58..604b7ce4a77dd 100644 --- a/impeller/tessellator/tessellator.h +++ b/impeller/tessellator/tessellator.h @@ -18,6 +18,9 @@ namespace impeller { +/// The size of the point arena buffer stored on the tessellator. +static constexpr size_t kPointArenaSize = 4096u; + //------------------------------------------------------------------------------ /// @brief A utility that generates triangles of the specified fill type /// given a polyline. This happens on the CPU. @@ -175,19 +178,40 @@ class Tessellator { /// @brief Given a convex path, create a triangle fan structure. /// /// @param[in] path The path to tessellate. + /// @param[in] host_buffer The host buffer for allocation of vertices/index + /// data. /// @param[in] tolerance The tolerance value for conversion of the path to /// a polyline. This value is often derived from the - /// Matrix::GetMaxBasisLength of the CTM applied to the - /// path for rendering. + /// Matrix::GetMaxBasisLengthXY of the CTM applied to + /// the path for rendering. + /// + /// @return A vertex buffer containing all data from the provided curve. + VertexBuffer TessellateConvex(const Path& path, + HostBuffer& host_buffer, + Scalar tolerance, + bool supports_primitive_restart = false, + bool supports_triangle_fan = false); + + //---------------------------------------------------------------------------- + /// @brief Given a path, create a line strip primitive structure. /// - /// @return A point vector containing the vertices in triangle strip format. + /// A line strip is a series of vertices that draws a line + /// rendered at a specified width (in our case, always 1.0 + /// physical pixel) that is tessellated by the rasterizer. See + /// also PrimitiveType::kLineStrip. /// + /// @param[in] path The path to tessellate. /// @param[in] host_buffer The host buffer for allocation of vertices/index /// data. + /// @param[in] tolerance The tolerance value for conversion of the path to + /// a polyline. This value is often derived from the + /// Matrix::GetMaxBasisLengthXY of the CTM applied to + /// the path for rendering. + /// /// @return A vertex buffer containing all data from the provided curve. - VertexBuffer TessellateConvex(const Path& path, - HostBuffer& host_buffer, - Scalar tolerance); + VertexBuffer GenerateLineStrip(const Path& path, + HostBuffer& host_buffer, + Scalar tolerance); /// Visible for testing. /// @@ -282,10 +306,15 @@ class Tessellator { const Rect& bounds, const Size& radii); + /// Retrieve a pre-allocated arena of kPointArenaSize points. + std::vector& GetStrokePointCache(); + protected: /// Used for polyline generation. std::unique_ptr> point_buffer_; std::unique_ptr> index_buffer_; + /// Used for stroke path generation. + std::vector stroke_points_; private: // Data for various Circle/EllipseGenerator classes, cached per diff --git a/impeller/toolkit/android/shadow_realm.cc b/impeller/toolkit/android/shadow_realm.cc index 6d19e6fe459f4..7aeb4ac0746b5 100644 --- a/impeller/toolkit/android/shadow_realm.cc +++ b/impeller/toolkit/android/shadow_realm.cc @@ -15,13 +15,25 @@ bool ShadowRealm::ShouldDisableAHB() { __system_property_get("ro.com.google.clientidbase", clientidbase); auto api_level = android_get_device_api_level(); + char first_api_level[PROP_VALUE_MAX]; + __system_property_get("ro.product.first_api_level", first_api_level); - return ShouldDisableAHBInternal(clientidbase, api_level); + return ShouldDisableAHBInternal(clientidbase, first_api_level, api_level); } // static bool ShadowRealm::ShouldDisableAHBInternal(std::string_view clientidbase, + std::string_view first_api_level, uint32_t api_level) { + // Most devices that have updated to API 29 don't seem to correctly + // support AHBs: https://github.com/flutter/flutter/issues/157113 + if (first_api_level.compare("28") == 0 || + first_api_level.compare("27") == 0 || + first_api_level.compare("26") == 0 || + first_api_level.compare("25") == 0 || + first_api_level.compare("24") == 0) { + return true; + } // From local testing, neither the swapchain nor AHB import works, see also: // https://github.com/flutter/flutter/issues/154068 if (clientidbase == kAndroidHuawei && api_level <= 29) { diff --git a/impeller/toolkit/android/shadow_realm.h b/impeller/toolkit/android/shadow_realm.h index 90f913b5af3bc..90f265ec612b6 100644 --- a/impeller/toolkit/android/shadow_realm.h +++ b/impeller/toolkit/android/shadow_realm.h @@ -18,6 +18,7 @@ class ShadowRealm { // For testing. static bool ShouldDisableAHBInternal(std::string_view clientidbase, + std::string_view first_api_level, uint32_t api_level); }; diff --git a/impeller/toolkit/android/toolkit_android_unittests.cc b/impeller/toolkit/android/toolkit_android_unittests.cc index c96e04c2dfe9c..ca8837d732ee2 100644 --- a/impeller/toolkit/android/toolkit_android_unittests.cc +++ b/impeller/toolkit/android/toolkit_android_unittests.cc @@ -136,11 +136,17 @@ TEST(ToolkitAndroidTest, CanPostAndWaitForFrameCallbacks) { } TEST(ToolkitAndroidTest, ShouldDisableAHB) { - EXPECT_FALSE(ShadowRealm::ShouldDisableAHB()); - - EXPECT_TRUE(ShadowRealm::ShouldDisableAHBInternal("android-huawei", 29)); - EXPECT_FALSE(ShadowRealm::ShouldDisableAHBInternal("android-huawei", 30)); - EXPECT_FALSE(ShadowRealm::ShouldDisableAHBInternal("something made up", 29)); + EXPECT_FALSE( + ShadowRealm::ShouldDisableAHBInternal("android-huawei", "30", 30)); + EXPECT_FALSE( + ShadowRealm::ShouldDisableAHBInternal("something made up", "29", 29)); + + EXPECT_TRUE( + ShadowRealm::ShouldDisableAHBInternal("android-huawei", "29", 29)); + EXPECT_TRUE( + ShadowRealm::ShouldDisableAHBInternal("something made up", "27", 29)); + EXPECT_TRUE( + ShadowRealm::ShouldDisableAHBInternal("android-huawei", "garbage", 29)); } } // namespace impeller::android::testing diff --git a/impeller/toolkit/egl/config.h b/impeller/toolkit/egl/config.h index 37de0e1374485..6690ee2db6f67 100644 --- a/impeller/toolkit/egl/config.h +++ b/impeller/toolkit/egl/config.h @@ -35,6 +35,7 @@ enum class StencilBits { enum class DepthBits { kZero = 0, kEight = 8, + kTwentyFour = 24, }; enum class SurfaceType { diff --git a/impeller/toolkit/interop/BUILD.gn b/impeller/toolkit/interop/BUILD.gn index 3cfe8441c4fc9..500410359dcdb 100644 --- a/impeller/toolkit/interop/BUILD.gn +++ b/impeller/toolkit/interop/BUILD.gn @@ -31,7 +31,9 @@ impeller_component("interop") { "image_filter.h", "impeller.cc", "impeller.h", + "impeller.hpp", "impeller_c.c", + "impeller_cc.cc", "mask_filter.cc", "mask_filter.h", "object.cc", @@ -119,10 +121,18 @@ zip_bundle("sdk") { deps = [ ":library" ] files = [ + { + source = "README.md" + destination = "README.md" + }, { source = "impeller.h" destination = "include/impeller.h" }, + { + source = "impeller.hpp" + destination = "include/impeller.hpp" + }, ] if (is_mac) { diff --git a/impeller/toolkit/interop/README.md b/impeller/toolkit/interop/README.md index 1b0758b5abbbd..ac36b128abc0c 100644 --- a/impeller/toolkit/interop/README.md +++ b/impeller/toolkit/interop/README.md @@ -1,67 +1,93 @@ # Impeller Standalone SDK -## Goals +A single-header C API for 2D graphics and text rendering. [Impeller](../../README.md) is used by Flutter for rendering but can be consumed by non-Flutter applications and projects. + +* **Full-featured** + * The library supports all rendering operations supported by Flutter with few exceptions. + * An optional text-layout and shaping engine is included by default. +* **Easy to Embed** + * The entire library is distributed as a single library with a C API. + * The C API is single-header with no platform dependencies. + * For the common platforms, [prebuilt artifacts](#prebuilt-artifacts) are generated per Flutter Engine commit. +* **Easy Interoperability** + * The C API allows for explicit management of object lifecycle and is well suited for the generation of automated bindings to languages like Rust, Dart, Lua, etc… +* **Lightweight** + * The core rendering engine is less than 200 KB compressed. + * The text layout and shaping engine along with the bundled ICU data tables brings the size up to ~2.5 MB. + * If the application does not need text layout and shaping, or can interface with an existing library on the target platform, it is recommended to generate the SDK without built-in support for typography. +* **Performant** + * Built to perform the best when using a modern graphics API like Metal or Vulkan (not all may be available to start) and when running on mobile tiler GPUs like the ones found in smartphones and AppleSilicon/ARM desktops. + * Impeller does need a GPU. Performance will likely be inadequate for interactive use cases when using software rendering. Software rendering can be enabled using projects like SwiftShader, Angle, LLVMPipe, etc… If you are using software rendering in your projects, restrict its use to testing on CI. Impeller will likely never have a dedicated software renderer. -This Impeller Standalone API is a single-header C API with no external dependencies. - -### Full-featured - -The library supports all rendering operations supported by Flutter with few exceptions. An optional text-layout and shaping engine is included by default. - -### Easy to Embed - -The entire library is distributed as a single dynamic library. The public API can be found in a single C header with no dependencies. +# Prebuilt Artifacts -For the common platforms, prebuilt artifacts are generated per Flutter Engine commit. +> [!IMPORTANT] +> Users of these prebuilt artifacts should strip the binaries before deployment as these contain debug symbols. -### Easy Interoperability +Users may plug in a custom toolchain into the Flutter Engine build system to build the `libimpeller.so` dynamic library. However, for the common platforms, the CI bots upload a tarball containing the library and headers. This URL for the SDK tarball for a particular platform can be constructed as follows: -The C API allows for explicit management of object lifecycle and is well suited for the generation of automated bindings to languages like Rust, Dart, Lua, etc… +```sh +https://storage.googleapis.com/flutter_infra_release/flutter/$FLUTTER_ENGINE_SHA/$PLATFORM_ARCH/impeller_sdk.zip +``` -### Lightweight +The `$FLUTTER_ENGINE_SHA` is the Git hash in the Flutter Engine repository. To make sure all artifacts for a specific hash have been successfully generated, look up the Flutter Engine SHA currently used by the Flutter Framework in the [engine.version](https://github.com/flutter/flutter/blob/master/bin/internal/engine.version) file. The `$PLATFORM_ARCH` can be determined from the table below. -The core rendering engine is less than 200 KB compressed. +| | macOS | Linux | Android | Windows | +|:-----:|:------------:|:-----------:|:--------------:|:-------------:| +| armv7 | | | android-arm | | +| arm64 | darwin-arm64 | linux-arm64 | android-arm64 | windows-arm64 | +| x86 | | | android-x86 | | +| x64 | darwin-x64 | linux-x64 | android-x64 | windows-x64 | -The text layout and shaping engine along with the bundled ICU data tables brings the size up to ~2.5 MB. If the application does not need text layout and shaping, or can interface with an existing library on the target platform, it is recommended to generate the SDK without built-in support for typography. Impeller is a rendering engine but typography is really hard to get right and needed by most users of a rendering engine. That is why one is included by default. But it is optional. -> [!CAUTION] -> Users of the prebuilt artifacts should strip the binaries before deployment as these contain debug symbols. +_For example, the SDK for `Linux x64` at engine SHA `202506d686e317862d81548b8afcae9c9eecaa90` would be [this link](https://storage.googleapis.com/flutter_infra_release/flutter/202506d686e317862d81548b8afcae9c9eecaa90/linux-x64/impeller_sdk.zip)_ -### Performant +# Examples -Built to perform the best when using a modern graphics API like Metal or Vulkan (not all may be available to start) and when running on mobile tiler GPUs like the ones found in smartphones and AppleSilicon/ARM desktops. +A quick peek at the API that shows rendering different shapes using the provided [C++ wrapper to the C API](#c-wrapper) is as follows: -Impeller does need a GPU. Performance will likely be inadequate for interactive use cases when using software rendering. Software rendering can be enabled using projects like SwiftShader, Angle, LLVMPipe, etc… If you are using software rendering in your projects, restrict its use to testing on CI. Impeller will likely never have a dedicated software renderer. +```c++ +DisplayListBuilder builder; -# API Stability +Paint red_paint; +red_paint.SetColor({1.0, 0.0, 0.0, 1.0}); +red_paint.SetStrokeWidth(10.0); -Unlike the Flutter Embedder API which has a stable API as well as ABI, the Impeller API does **not** currently have stability guarantees. +builder.Translate(10, 10); +builder.DrawRect({0, 0, 100, 100}, red_paint); +builder.Translate(100, 100); +builder.DrawOval({0, 0, 100, 100}, red_paint); +builder.Translate(100, 100); +builder.DrawLine({0, 0}, {100, 100}, red_paint); -The API does look similar to the one used by Flutter in Dart so major overhauls are not realistically on the horizon. +builder.Translate(100, 100); +ImpellerRoundingRadii radii = {}; +radii.top_left = {10, 10}; +radii.bottom_right = {10, 10}; +builder.DrawRoundedRect({0, 0, 100, 100}, radii, red_paint); -The API is also versioned and there may be stability guarantees between specific versions in the future. +builder.Translate(100, 100); +builder.DrawPath(hpp::PathBuilder{}.AddOval({0, 0, 100, 100}).Build(), + red_paint); -# Prebuilt Artifacts +auto dl = builder.Build(); -Users may plug in any toolchain into the Flutter Engine build system to build the libimpeller.so dynamic library. However, for the common platforms, the CI bots upload a tarball containing the library and headers. This URL for the SDK tarball for a particular platform can be constructed as follows: - -```sh -https://storage.googleapis.com/flutter_infra_release/flutter/$FLUTTER_ENGINE_SHA/$PLATFORM_ARCH/impeller_sdk.zip +// Per frame +hpp::Surface window(surface); +window.Draw(dl); ``` -The `$FLUTTER_ENGINE_SHA` is the Git hash in the Flutter Engine repository. To make sure all artifacts for a specific hash have been successfully generated, look up the Flutter Engine SHA currently used by the Flutter Framework in the [engine.version](https://github.com/flutter/flutter/blob/master/bin/internal/engine.version) file. +### Standalone -The `$PLATFORM_ARCH` can be determined from the table below. +A fully functional example of using Impeller to draw using GLFW is available in [`example.c`](example.c). This example is also present in the `impeller_sdk.zip` [prebuilts](#prebuilt-artifacts) along with necessary artifacts. -| | macOS | Linux | Android | Windows | -|:-----:|:------------:|:-----------:|:--------------:|:-------------:| -| armv7 | | | android-arm | | -| arm64 | darwin-arm64 | linux-arm64 | android-arm64 | windows-arm64 | -| x86 | | | android-x86 | | -| x64 | darwin-x64 | linux-x64 | android-x64 | windows-x64 | +### CMake + +A demo of using CMake to fetch prebuilt artifacts and build the demo is [available here](https://github.com/chinmaygarde/impeller_cmake_demo). +## C++ Wrapper -For example, the SDK for `Linux x64` at engine SHA `31aaaaad868743b38ac3b7165f0d58eca94e521b` would be https://storage.googleapis.com/flutter_infra_release/flutter/31aaaaad868743b38ac3b7165f0d58eca94e521b/linux-x64/impeller_sdk.zip +For users of the library using C++, a single-header-only C++ 17 library is provided that wraps the single-header C API ([`impeller.h`](impeller.h)). This headers ([`impeller.hpp`](impeller.hpp)) is distributed as part of the [prebuilt artifacts](#prebuilt-artifacts) as well. # API Fundamentals @@ -93,3 +119,11 @@ Methods in the Impeller API follow a very strict convention. This makes it easy ## Null Safety The Impeller API passes [nullability completeness](https://clang.llvm.org/docs/DiagnosticsReference.html#wnullability-completeness) checks. All pointer arguments and return values are decorated with `IMPELLER_NULLABLE` and `IMPELLER_NONNULL`. Passing a null pointer to an argument decorated with `IMPELLER_NONNULL` will very likely result in a null pointer dereference. When generating automated bindings to other languages, it is recommended that these decorations be used to inform the API and perform additional checks. + +## API Stability + +Unlike the [Flutter Embedder API]([url](https://docs.flutter.dev/embedded)) which has a stable API as well as ABI, the Impeller API does **not** currently have stability guarantees. + +However, the API does look similar to the one used by Flutter in Dart. So major overhauls are not realistically on the horizon. + +The API is also versioned and there may be stability guarantees between specific versions in the future. diff --git a/impeller/toolkit/interop/color_filter.cc b/impeller/toolkit/interop/color_filter.cc index 7e5b2da2b2673..01825aa266137 100644 --- a/impeller/toolkit/interop/color_filter.cc +++ b/impeller/toolkit/interop/color_filter.cc @@ -7,7 +7,7 @@ namespace impeller::interop { ScopedObject ColorFilter::MakeBlend(Color color, BlendMode mode) { - auto filter = flutter::DlBlendColorFilter::Make(ToDisplayListType(color), + auto filter = flutter::DlColorFilter::MakeBlend(ToDisplayListType(color), ToDisplayListType(mode)); if (!filter) { return nullptr; @@ -16,20 +16,20 @@ ScopedObject ColorFilter::MakeBlend(Color color, BlendMode mode) { } ScopedObject ColorFilter::MakeMatrix(const float matrix[20]) { - auto filter = flutter::DlMatrixColorFilter::Make(matrix); + auto filter = flutter::DlColorFilter::MakeMatrix(matrix); if (!filter) { return nullptr; } return Create(std::move(filter)); } -ColorFilter::ColorFilter(std::shared_ptr filter) +ColorFilter::ColorFilter(std::shared_ptr filter) : filter_(std::move(filter)) {} ColorFilter::~ColorFilter() = default; -const std::shared_ptr& ColorFilter::GetColorFilter() - const { +const std::shared_ptr& +ColorFilter::GetColorFilter() const { return filter_; } diff --git a/impeller/toolkit/interop/color_filter.h b/impeller/toolkit/interop/color_filter.h index 70c0c6ec98c19..2f0b066a98b3e 100644 --- a/impeller/toolkit/interop/color_filter.h +++ b/impeller/toolkit/interop/color_filter.h @@ -20,7 +20,7 @@ class ColorFilter final static ScopedObject MakeMatrix(const float matrix[20]); - explicit ColorFilter(std::shared_ptr filter); + explicit ColorFilter(std::shared_ptr filter); ~ColorFilter() override; @@ -28,10 +28,10 @@ class ColorFilter final ColorFilter& operator=(const ColorFilter&) = delete; - const std::shared_ptr& GetColorFilter() const; + const std::shared_ptr& GetColorFilter() const; private: - std::shared_ptr filter_; + std::shared_ptr filter_; }; } // namespace impeller::interop diff --git a/impeller/toolkit/interop/color_source.cc b/impeller/toolkit/interop/color_source.cc index 11c108a9cacd2..8fff4c16cb42f 100644 --- a/impeller/toolkit/interop/color_source.cc +++ b/impeller/toolkit/interop/color_source.cc @@ -13,16 +13,14 @@ ScopedObject ColorSource::MakeLinearGradient( const std::vector& stops, flutter::DlTileMode tile_mode, const Matrix& transformation) { - const auto sk_transformation = ToSkMatrix(transformation); - auto dl_filter = - flutter::DlColorSource::MakeLinear(ToSkiaType(start_point), // - ToSkiaType(end_point), // - stops.size(), // - colors.data(), // - stops.data(), // - tile_mode, // - &sk_transformation // - ); + auto dl_filter = flutter::DlColorSource::MakeLinear(start_point, // + end_point, // + stops.size(), // + colors.data(), // + stops.data(), // + tile_mode, // + &transformation // + ); if (!dl_filter) { return nullptr; } @@ -36,14 +34,13 @@ ScopedObject ColorSource::MakeRadialGradient( const std::vector& stops, flutter::DlTileMode tile_mode, const Matrix& transformation) { - const auto sk_transformation = ToSkMatrix(transformation); - auto dl_filter = flutter::DlColorSource::MakeRadial(ToSkiaType(center), // - radius, // - stops.size(), // - colors.data(), // - stops.data(), // - tile_mode, // - &sk_transformation // + auto dl_filter = flutter::DlColorSource::MakeRadial(center, // + radius, // + stops.size(), // + colors.data(), // + stops.data(), // + tile_mode, // + &transformation // ); if (!dl_filter) { return nullptr; @@ -60,18 +57,16 @@ ScopedObject ColorSource::MakeConicalGradient( const std::vector& stops, flutter::DlTileMode tile_mode, const Matrix& transformation) { - const auto sk_transformation = ToSkMatrix(transformation); - auto dl_filter = - flutter::DlColorSource::MakeConical(ToSkiaType(start_center), // - start_radius, // - ToSkiaType(end_center), // - end_radius, // - stops.size(), // - colors.data(), // - stops.data(), // - tile_mode, // - &sk_transformation // - ); + auto dl_filter = flutter::DlColorSource::MakeConical(start_center, // + start_radius, // + end_center, // + end_radius, // + stops.size(), // + colors.data(), // + stops.data(), // + tile_mode, // + &transformation // + ); if (!dl_filter) { return nullptr; } @@ -86,15 +81,14 @@ ScopedObject ColorSource::MakeSweepGradient( const std::vector& stops, flutter::DlTileMode tile_mode, const Matrix& transformation) { - const auto sk_transformation = ToSkMatrix(transformation); - auto dl_filter = flutter::DlColorSource::MakeSweep(ToSkiaType(center), // - start, // - end, // - stops.size(), // - colors.data(), // - stops.data(), // - tile_mode, // - &sk_transformation // + auto dl_filter = flutter::DlColorSource::MakeSweep(center, // + start, // + end, // + stops.size(), // + colors.data(), // + stops.data(), // + tile_mode, // + &transformation // ); if (!dl_filter) { return nullptr; @@ -108,14 +102,12 @@ ScopedObject ColorSource::MakeImage( flutter::DlTileMode vertical_tile_mode, flutter::DlImageSampling sampling, const Matrix& transformation) { - const auto sk_transformation = ToSkMatrix(transformation); - auto dl_filter = - std::make_shared(image.MakeImage(), // - horizontal_tile_mode, // - vertical_tile_mode, // - sampling, // - &sk_transformation // - ); + auto dl_filter = flutter::DlColorSource::MakeImage(image.MakeImage(), // + horizontal_tile_mode, // + vertical_tile_mode, // + sampling, // + &transformation // + ); return Create(std::move(dl_filter)); } diff --git a/impeller/toolkit/interop/dl_builder.cc b/impeller/toolkit/interop/dl_builder.cc index c0c5885354e12..d1a4a1964355e 100644 --- a/impeller/toolkit/interop/dl_builder.cc +++ b/impeller/toolkit/interop/dl_builder.cc @@ -9,8 +9,7 @@ namespace impeller::interop { DisplayListBuilder::DisplayListBuilder(const ImpellerRect* rect) - : builder_(ToSkiaType(rect).value_or( - flutter::DisplayListBuilder::kMaxCullRect)) {} + : builder_(rect) {} DisplayListBuilder::~DisplayListBuilder() = default; @@ -55,6 +54,11 @@ void DisplayListBuilder::SetTransform(const Matrix& matrix) { builder_.SetTransform(&sk_matrix); } +void DisplayListBuilder::Transform(const Matrix& matrix) { + const auto sk_matrix = SkM44::ColMajor(matrix.m); + builder_.Transform(&sk_matrix); +} + void DisplayListBuilder::ResetTransform() { builder_.TransformReset(); } diff --git a/impeller/toolkit/interop/dl_builder.h b/impeller/toolkit/interop/dl_builder.h index 59468d37e4864..142ff0635129a 100644 --- a/impeller/toolkit/interop/dl_builder.h +++ b/impeller/toolkit/interop/dl_builder.h @@ -51,6 +51,8 @@ class DisplayListBuilder final void SetTransform(const Matrix& matrix); + void Transform(const Matrix& matrix); + void ResetTransform(); uint32_t GetSaveCount() const; diff --git a/impeller/toolkit/interop/image_filter.cc b/impeller/toolkit/interop/image_filter.cc index b5dbf8955812c..4929200483f5a 100644 --- a/impeller/toolkit/interop/image_filter.cc +++ b/impeller/toolkit/interop/image_filter.cc @@ -4,9 +4,11 @@ #include "impeller/toolkit/interop/image_filter.h" +#include "flutter/display_list/effects/dl_image_filters.h" + namespace impeller::interop { -ImageFilter::ImageFilter(std::shared_ptr filter) +ImageFilter::ImageFilter(std::shared_ptr filter) : filter_(std::move(filter)) {} ImageFilter::~ImageFilter() = default; @@ -42,8 +44,7 @@ ScopedObject ImageFilter::MakeErode(Scalar x_radius, ScopedObject ImageFilter::MakeMatrix( const Matrix& matrix, flutter::DlImageSampling sampling) { - auto filter = - flutter::DlMatrixImageFilter::Make(ToSkMatrix(matrix), sampling); + auto filter = flutter::DlMatrixImageFilter::Make(matrix, sampling); if (!filter) { return nullptr; } @@ -60,8 +61,8 @@ ScopedObject ImageFilter::MakeCompose(const ImageFilter& outer, return Create(std::move(filter)); } -const std::shared_ptr& -ImageFilter::GetImageFilter() const { +const std::shared_ptr& ImageFilter::GetImageFilter() + const { return filter_; } diff --git a/impeller/toolkit/interop/image_filter.h b/impeller/toolkit/interop/image_filter.h index 4c6ae4e9beba9..4db52f98eb99f 100644 --- a/impeller/toolkit/interop/image_filter.h +++ b/impeller/toolkit/interop/image_filter.h @@ -31,7 +31,7 @@ class ImageFilter final static ScopedObject MakeCompose(const ImageFilter& outer, const ImageFilter& inner); - explicit ImageFilter(std::shared_ptr filter); + explicit ImageFilter(std::shared_ptr filter); ~ImageFilter() override; @@ -39,10 +39,10 @@ class ImageFilter final ImageFilter& operator=(const ImageFilter&) = delete; - const std::shared_ptr& GetImageFilter() const; + const std::shared_ptr& GetImageFilter() const; private: - std::shared_ptr filter_; + std::shared_ptr filter_; }; } // namespace impeller::interop diff --git a/impeller/toolkit/interop/impeller.cc b/impeller/toolkit/interop/impeller.cc index 931b2a71025a7..7681efe515f9c 100644 --- a/impeller/toolkit/interop/impeller.cc +++ b/impeller/toolkit/interop/impeller.cc @@ -167,6 +167,12 @@ void ImpellerDisplayListBuilderSetTransform(ImpellerDisplayListBuilder builder, GetPeer(builder)->SetTransform(ToImpellerType(*transform)); } +IMPELLER_EXTERN_C +void ImpellerDisplayListBuilderTransform(ImpellerDisplayListBuilder builder, + const ImpellerMatrix* transform) { + GetPeer(builder)->Transform(ToImpellerType(*transform)); +} + IMPELLER_EXTERN_C void ImpellerDisplayListBuilderGetTransform(ImpellerDisplayListBuilder builder, ImpellerMatrix* out_transform) { @@ -488,8 +494,9 @@ ImpellerTexture ImpellerTextureCreateWithContentsNew( auto wrapped_contents = std::make_shared( contents->data, // data ptr contents->length, // data length - [contents, contents_on_release_user_data](auto, auto) { - contents->on_release(contents_on_release_user_data); + [on_release = contents->on_release, contents_on_release_user_data]( + auto, auto) { + on_release(contents_on_release_user_data); } // release callback ); if (!texture->SetContents(std::move(wrapped_contents))) { @@ -521,13 +528,6 @@ ImpellerTexture ImpellerTextureCreateWithOpenGLTextureHandleNew( const auto& impeller_context_gl = ContextGLES::Cast(*impeller_context); const auto& reactor = impeller_context_gl.GetReactor(); - auto wrapped_external_gl_handle = - reactor->CreateHandle(HandleType::kTexture, external_gl_handle); - if (wrapped_external_gl_handle.IsDead()) { - VALIDATION_LOG << "Could not wrap external handle."; - return nullptr; - } - TextureDescriptor desc; desc.storage_mode = StorageMode::kDevicePrivate; desc.type = TextureType::kTexture2D; @@ -536,9 +536,11 @@ ImpellerTexture ImpellerTextureCreateWithOpenGLTextureHandleNew( desc.mip_count = std::min(descriptor->mip_count, 1u); desc.usage = TextureUsage::kShaderRead; desc.compression_type = CompressionType::kLossless; - auto texture = std::make_shared(reactor, // - desc, // - wrapped_external_gl_handle // + + auto texture = TextureGLES::WrapTexture( + reactor, // + desc, // + reactor->CreateHandle(HandleType::kTexture, external_gl_handle) // ); if (!texture || !texture->IsValid()) { VALIDATION_LOG << "Could not wrap external texture."; @@ -1141,8 +1143,9 @@ bool ImpellerTypographyContextRegisterFont(ImpellerTypographyContext context, auto wrapped_contents = std::make_unique( contents->data, // data ptr contents->length, // data length - [contents, contents_on_release_user_data](auto, auto) { - contents->on_release(contents_on_release_user_data); + [on_release = contents->on_release, contents_on_release_user_data](auto, + auto) { + on_release(contents_on_release_user_data); } // release callback ); return GetPeer(context)->RegisterFont(std::move(wrapped_contents), diff --git a/impeller/toolkit/interop/impeller.h b/impeller/toolkit/interop/impeller.h index 5fb0fb332b022..fe1b3bb98094f 100644 --- a/impeller/toolkit/interop/impeller.h +++ b/impeller/toolkit/interop/impeller.h @@ -9,6 +9,23 @@ #include #include +///----------------------------------------------------------------------------- +///----------------------------------------------------------------------------- +/// ------- ___ _ _ _ ____ ___ -------- +/// ------- |_ _|_ __ ___ _ __ ___| | | ___ _ __ / \ | _ \_ _| -------- +/// ------- | || '_ ` _ \| '_ \ / _ \ | |/ _ \ '__| / _ \ | |_) | | -------- +/// ------- | || | | | | | |_) | __/ | | __/ | / ___ \| __/| | -------- +/// ------- |___|_| |_| |_| .__/ \___|_|_|\___|_| /_/ \_\_| |___| -------- +/// ------- |_| -------- +///----------------------------------------------------------------------------- +///----------------------------------------------------------------------------- +/// +/// This file describes a high-level, single-header, dependency-free, 2D +/// graphics API. +/// +/// The API fundamentals that include details about the object model, reference +/// counting, and null-safety are described in the README. +/// #if defined(__cplusplus) #define IMPELLER_EXTERN_C extern "C" #define IMPELLER_EXTERN_C_BEGIN IMPELLER_EXTERN_C { @@ -48,9 +65,15 @@ IMPELLER_EXTERN_C_BEGIN //------------------------------------------------------------------------------ -// Versioning -//------------------------------------------------------------------------------ - +/// @brief Pack a version in a uint32_t. +/// +/// @param[in] variant The version variant. +/// @param[in] major The major version. +/// @param[in] minor The minor version. +/// @param[in] patch The patch version. +/// +/// @return The packed version number. +/// #define IMPELLER_MAKE_VERSION(variant, major, minor, patch) \ ((((uint32_t)(variant)) << 29U) | (((uint32_t)(major)) << 22U) | \ (((uint32_t)(minor)) << 12U) | ((uint32_t)(patch))) @@ -60,15 +83,53 @@ IMPELLER_EXTERN_C_BEGIN #define IMPELLER_VERSION_MINOR 2 #define IMPELLER_VERSION_PATCH 0 +//------------------------------------------------------------------------------ +/// The current Impeller API version. +/// +/// This version must be passed to APIs that create top-level objects like +/// graphics contexts. Construction of the context may fail if the API version +/// expected by the caller is not supported by the library. +/// +/// The version currently supported by the library is returned by a call to +/// `ImpellerGetVersion` +/// +/// Since there are no API stability guarantees today, passing a version that is +/// different to the one returned by `ImpellerGetVersion` will always fail. +/// +/// @see `ImpellerGetVersion` +/// #define IMPELLER_VERSION \ IMPELLER_MAKE_VERSION(IMPELLER_VERSION_VARIANT, IMPELLER_VERSION_MAJOR, \ IMPELLER_VERSION_MINOR, IMPELLER_VERSION_PATCH) +//------------------------------------------------------------------------------ +/// @param[in] version The packed version. +/// +/// @return The version variant. +/// #define IMPELLER_VERSION_GET_VARIANT(version) ((uint32_t)(version) >> 29U) + +//------------------------------------------------------------------------------ +/// @param[in] version The packed version. +/// +/// @return The major version. +/// #define IMPELLER_VERSION_GET_MAJOR(version) \ (((uint32_t)(version) >> 22U) & 0x7FU) + +//------------------------------------------------------------------------------ +/// @param[in] version The packed version. +/// +/// @return The minor version. +/// #define IMPELLER_VERSION_GET_MINOR(version) \ (((uint32_t)(version) >> 12U) & 0x3FFU) + +//------------------------------------------------------------------------------ +/// @param[in] version The packed version. +/// +/// @return The patch version. +/// #define IMPELLER_VERSION_GET_PATCH(version) ((uint32_t)(version) & 0xFFFU) //------------------------------------------------------------------------------ @@ -79,35 +140,180 @@ IMPELLER_EXTERN_C_BEGIN #define IMPELLER_DEFINE_HANDLE(handle) \ typedef struct IMPELLER_INTERNAL_HANDLE_NAME(handle) * handle; -IMPELLER_DEFINE_HANDLE(ImpellerColorFilter); -IMPELLER_DEFINE_HANDLE(ImpellerColorSource); +//------------------------------------------------------------------------------ +/// An Impeller graphics context. Contexts are platform and client-rendering-API +/// specific. +/// +/// Contexts are thread-safe objects that are expensive to create. Most +/// applications will only ever create a single context during their lifetimes. +/// Once setup, Impeller is ready to render frames as performantly as possible. +/// +/// During setup, context create the underlying graphics pipelines, allocators, +/// worker threads, etc... +/// +/// The general guidance is to create as few contexts as possible (typically +/// just one) and share them as much as possible. +/// IMPELLER_DEFINE_HANDLE(ImpellerContext); + +//------------------------------------------------------------------------------ +/// Display lists represent encoded rendering intent. These objects are +/// immutable, reusable, thread-safe, and context-agnostic. +/// +/// While it is perfectly fine to create new display lists per frame, there may +/// be opportunities for optimization when display lists are reused multiple +/// times. +/// IMPELLER_DEFINE_HANDLE(ImpellerDisplayList); + +//------------------------------------------------------------------------------ +/// Display list builders allow for the incremental creation of display lists. +/// +/// Display list builders are context-agnostic. +/// IMPELLER_DEFINE_HANDLE(ImpellerDisplayListBuilder); + +//------------------------------------------------------------------------------ +/// Paints control the behavior of draw calls encoded in a display list. +/// +/// Like display lists, paints are context-agnostic. +/// +IMPELLER_DEFINE_HANDLE(ImpellerPaint); + +//------------------------------------------------------------------------------ +/// Color filters are functions that take two colors and mix them to produce a +/// single color. This color is then merged with the destination during +/// blending. +/// +IMPELLER_DEFINE_HANDLE(ImpellerColorFilter); + +//------------------------------------------------------------------------------ +/// Color sources are functions that generate colors for each texture element +/// covered by a draw call. The colors for each element can be generated using a +/// mathematical function (to produce gradients for example) or sampled from a +/// texture. +/// +IMPELLER_DEFINE_HANDLE(ImpellerColorSource); + +//------------------------------------------------------------------------------ +/// Image filters are functions that are applied regions of a texture to produce +/// a single color. Contrast this with color filters that operate independently +/// on a per-pixel basis. The generated color is then merged with the +/// destination during blending. +/// IMPELLER_DEFINE_HANDLE(ImpellerImageFilter); + +//------------------------------------------------------------------------------ +/// Mask filters are functions that are applied over a shape after it has been +/// drawn but before it has been blended into the final image. +/// IMPELLER_DEFINE_HANDLE(ImpellerMaskFilter); -IMPELLER_DEFINE_HANDLE(ImpellerPaint); + +//------------------------------------------------------------------------------ +/// Typography contexts allow for the layout and rendering of text. +/// +/// These are typically expensive to create and applications will only ever need +/// to create a single one of these during their lifetimes. +/// +/// Unlike graphics context, typograhy contexts are not thread-safe. These must +/// be created, used, and collected on a single thread. +/// +IMPELLER_DEFINE_HANDLE(ImpellerTypographyContext); + +//------------------------------------------------------------------------------ +/// An immutable, fully laid out paragraph. +/// IMPELLER_DEFINE_HANDLE(ImpellerParagraph); + +//------------------------------------------------------------------------------ +/// Paragraph builders allow for the creation of fully laid out paragraphs +/// (which themselves are immutable). +/// +/// To build a paragraph, users push/pop paragraph styles onto a stack then add +/// UTF-8 encoded text. The properties on the top of paragraph style stack when +/// the text is added are used to layout and shape that subset of the paragraph. +/// +/// @see `ImpellerParagraphStyle` +/// IMPELLER_DEFINE_HANDLE(ImpellerParagraphBuilder); + +//------------------------------------------------------------------------------ +/// Specified when building a paragraph, paragraph styles are managed in a stack +/// with specify text properties to apply to text that is added to the paragraph +/// builder. +/// IMPELLER_DEFINE_HANDLE(ImpellerParagraphStyle); + +//------------------------------------------------------------------------------ +/// Represents a two-dimensional path that is immutable and graphics context +/// agnostic. +/// +/// Paths in Impeller consist of linear, cubic Bézier curve, and quadratic +/// Bézier curve segments. All other shapes are approximations using these +/// building blocks. +/// +/// Paths are created using path builder that allow for the configuration of the +/// path segments, how they are filled, and/or stroked. +/// IMPELLER_DEFINE_HANDLE(ImpellerPath); + +//------------------------------------------------------------------------------ +/// Path builders allow for the incremental building up of paths. +/// IMPELLER_DEFINE_HANDLE(ImpellerPathBuilder); + +//------------------------------------------------------------------------------ +/// A surface represents a render target for Impeller to direct the rendering +/// intent specified the form of display lists to. +/// +/// Render targets are how Impeller API users perform Window System Integration +/// (WSI). Users wrap swapchain images as surfaces and draw display lists onto +/// these surfaces to present content. +/// +/// Creating surfaces is typically platform and client-rendering-API specific. +/// IMPELLER_DEFINE_HANDLE(ImpellerSurface); + +//------------------------------------------------------------------------------ +/// A reference to a texture whose data is resident on the GPU. These can be +/// referenced in draw calls and paints. +/// +/// Creating textures is extremely expensive. Creating a single one can +/// typically comfortably blow the frame budget of an application. Textures +/// should be created on background threads. +/// +/// @warning While textures themselves are thread safe, some context types +/// (like OpenGL) may need extra configuration to be able to operate +/// from multiple threads. +/// IMPELLER_DEFINE_HANDLE(ImpellerTexture); -IMPELLER_DEFINE_HANDLE(ImpellerTypographyContext); //------------------------------------------------------------------------------ // Signatures //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// A callback invoked by Impeller that passes a user supplied baton back to the +/// user. Impeller does not interpret the baton in any way. The way the baton is +/// specified and the thread on which the callback is invoked depends on how the +/// user supplies the callback to Impeller. +/// typedef void (*ImpellerCallback)(void* IMPELLER_NULLABLE user_data); + +//------------------------------------------------------------------------------ +/// A callback used by Impeller to allow the user to resolve function pointers. +/// A user supplied baton that is uninterpreted by Impeller is passed back to +/// the user in the callback. How the baton is specified to Impeller and the +/// thread on which the callback is invoked depends on how the callback is +/// specified to Impeller. +/// typedef void* IMPELLER_NULLABLE (*ImpellerProcAddressCallback)( const char* IMPELLER_NONNULL proc_name, void* IMPELLER_NULLABLE user_data); //------------------------------------------------------------------------------ // Enumerations -//------------------------------------------------------------------------------ +// ----------------------------------------------------------------------------- typedef enum ImpellerFillType { kImpellerFillTypeNonZero, kImpellerFillTypeOdd, @@ -230,7 +436,7 @@ typedef enum ImpellerTextDirection { //------------------------------------------------------------------------------ // Non-opaque structs -//------------------------------------------------------------------------------ +// ----------------------------------------------------------------------------- typedef struct ImpellerRect { float x; float y; @@ -253,10 +459,78 @@ typedef struct ImpellerISize { int64_t height; } ImpellerISize; +//------------------------------------------------------------------------------ +/// A 4x4 transformation matrix using column-major storage. +/// +/// ``` +/// | m[0] m[4] m[8] m[12] | +/// | m[1] m[5] m[9] m[13] | +/// | m[2] m[6] m[10] m[14] | +/// | m[3] m[7] m[11] m[15] | +/// ``` +/// typedef struct ImpellerMatrix { float m[16]; } ImpellerMatrix; +//------------------------------------------------------------------------------ +/// A 4x5 matrix using row-major storage used for transforming color values. +/// +/// To transform color values, a 5x5 matrix is constructed with the 5th row +/// being identity. Then the following transformation is performed: +/// +/// ``` +/// | R' | | m[0] m[1] m[2] m[3] m[4] | | R | +/// | G' | | m[5] m[6] m[7] m[8] m[9] | | G | +/// | B' | = | m[10] m[11] m[12] m[13] m[14] | * | B | +/// | A' | | m[15] m[16] m[17] m[18] m[19] | | A | +/// | 1 | | 0 0 0 0 1 | | 1 | +/// ``` +/// +/// The translation column (m[4], m[9], m[14], m[19]) must be specified in +/// non-normalized 8-bit unsigned integer space (0 to 255). Values outside this +/// range will produce undefined results. +/// +/// The identity transformation is thus: +/// +/// ``` +/// 1, 0, 0, 0, 0, +/// 0, 1, 0, 0, 0, +/// 0, 0, 1, 0, 0, +/// 0, 0, 0, 1, 0, +/// ``` +/// +/// Some examples: +/// +/// To invert all colors: +/// +/// ``` +/// -1, 0, 0, 0, 255, +/// 0, -1, 0, 0, 255, +/// 0, 0, -1, 0, 255, +/// 0, 0, 0, 1, 0, +/// ``` +/// +/// To apply a sepia filter: +/// +/// ``` +/// 0.393, 0.769, 0.189, 0, 0, +/// 0.349, 0.686, 0.168, 0, 0, +/// 0.272, 0.534, 0.131, 0, 0, +/// 0, 0, 0, 1, 0, +/// ``` +/// +/// To apply a grayscale conversion filter: +/// +/// ``` +/// 0.2126, 0.7152, 0.0722, 0, 0, +/// 0.2126, 0.7152, 0.0722, 0, 0, +/// 0.2126, 0.7152, 0.0722, 0, 0, +/// 0, 0, 0, 1, 0, +/// ``` +/// +/// @see ImpellerColorFilter +/// typedef struct ImpellerColorMatrix { float m[20]; } ImpellerColorMatrix; @@ -292,6 +566,25 @@ typedef struct ImpellerMapping { // Version //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Get the version of Impeller standalone API. This is the API that +/// will be accepted for validity checks when provided to the +/// context creation methods. +/// +/// The current version of the API is denoted by the +/// `IMPELLER_VERSION` macro. This version must be passed to APIs +/// that create top-level objects like graphics contexts. +/// Construction of the context may fail if the API version expected +/// by the caller is not supported by the library. +/// +/// Since there are no API stability guarantees today, passing a +/// version that is different to the one returned by +/// `ImpellerGetVersion` will always fail. +/// +/// @see `ImpellerContextCreateOpenGLESNew` +/// +/// @return The version of the standalone API. +/// IMPELLER_EXPORT uint32_t ImpellerGetVersion(); @@ -299,15 +592,54 @@ uint32_t ImpellerGetVersion(); // Context //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Create an OpenGL(ES) Impeller context. +/// +/// @warning Unlike other context types, the OpenGL ES context can only be +/// created, used, and collected on the calling thread. This +/// restriction may be lifted in the future once reactor workers are +/// exposed in the API. No other context types have threading +/// restrictions. Till reactor workers can be used, using the +/// context on a background thread will cause a stall of OpenGL +/// operations. +/// +/// @param[in] version The version of the Impeller +/// standalone API. See `ImpellerGetVersion`. If the +/// specified here is not compatible with the version +/// of the library, context creation will fail and NULL +/// context returned from this call. +/// @param[in] gl_proc_address_callback +/// The gl proc address callback. For instance, +/// `eglGetProcAddress`. +/// @param[in] gl_proc_address_callback_user_data +/// The gl proc address callback user data baton. This +/// pointer is not interpreted by Impeller and will be +/// returned as user data in the proc address callback. +/// user data. +/// +/// @return The context or NULL if one cannot be created. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerContext IMPELLER_NULLABLE ImpellerContextCreateOpenGLESNew( uint32_t version, ImpellerProcAddressCallback IMPELLER_NONNULL gl_proc_address_callback, void* IMPELLER_NULLABLE gl_proc_address_callback_user_data); +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] context The context. +/// IMPELLER_EXPORT void ImpellerContextRetain(ImpellerContext IMPELLER_NULLABLE context); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] context The context. +/// IMPELLER_EXPORT void ImpellerContextRelease(ImpellerContext IMPELLER_NULLABLE context); @@ -315,18 +647,61 @@ void ImpellerContextRelease(ImpellerContext IMPELLER_NULLABLE context); // Surface //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Create a new surface by wrapping an existing framebuffer object. +/// The framebuffer must be complete as determined by +/// `glCheckFramebufferStatus`. The framebuffer is still owned by +/// the caller and it must be collected once the surface is +/// collected. +/// +/// @param[in] context The context. +/// @param[in] fbo The framebuffer object handle. +/// @param[in] format The format of the framebuffer. +/// @param[in] size The size of the framebuffer is texels. +/// +/// @return The surface if once can be created, NULL otherwise. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerSurface IMPELLER_NULLABLE ImpellerSurfaceCreateWrappedFBONew(ImpellerContext IMPELLER_NULLABLE context, uint64_t fbo, ImpellerPixelFormat format, const ImpellerISize* IMPELLER_NULLABLE size); +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] surface The surface. +/// IMPELLER_EXPORT void ImpellerSurfaceRetain(ImpellerSurface IMPELLER_NULLABLE surface); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] surface The surface. +/// IMPELLER_EXPORT void ImpellerSurfaceRelease(ImpellerSurface IMPELLER_NULLABLE surface); +//------------------------------------------------------------------------------ +/// @brief Draw a display list onto the surface. The same display list can +/// be drawn multiple times to different surfaces. +/// +/// @warning In the OpenGL backend, Impeller will not make an effort to +/// preserve the OpenGL state that is current in the context. +/// Embedders that perform additional OpenGL operations in the +/// context should expect the reset state after control transitions +/// back to them. Key state to watch out for would be the viewports, +/// stencil rects, test toggles, resource (texture, framebuffer, +/// buffer) bindings, etc... +/// +/// @param[in] surface The surface to draw the display list to. +/// @param[in] display_list The display list to draw onto the surface. +/// +/// @return If the display list could be drawn onto the surface. +/// IMPELLER_EXPORT bool ImpellerSurfaceDrawDisplayList( ImpellerSurface IMPELLER_NULLABLE surface, @@ -336,9 +711,21 @@ bool ImpellerSurfaceDrawDisplayList( // Path //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] path The path. +/// IMPELLER_EXPORT void ImpellerPathRetain(ImpellerPath IMPELLER_NULLABLE path); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] path The path. +/// IMPELLER_EXPORT void ImpellerPathRelease(ImpellerPath IMPELLER_NULLABLE path); @@ -346,29 +733,83 @@ void ImpellerPathRelease(ImpellerPath IMPELLER_NULLABLE path); // Path Builder //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Create a new path builder. Paths themselves are immutable. +/// A builder builds these immutable paths. +/// +/// @return The path builder. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerPathBuilder IMPELLER_NULLABLE ImpellerPathBuilderNew(); +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] builder The builder. +/// IMPELLER_EXPORT void ImpellerPathBuilderRetain(ImpellerPathBuilder IMPELLER_NULLABLE builder); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] builder The builder. +/// IMPELLER_EXPORT void ImpellerPathBuilderRelease(ImpellerPathBuilder IMPELLER_NULLABLE builder); +//------------------------------------------------------------------------------ +/// @brief Move the cursor to the specified location. +/// +/// @param[in] builder The builder. +/// @param[in] location The location. +/// IMPELLER_EXPORT void ImpellerPathBuilderMoveTo(ImpellerPathBuilder IMPELLER_NONNULL builder, const ImpellerPoint* IMPELLER_NONNULL location); +//------------------------------------------------------------------------------ +/// @brief Add a line segment from the current cursor location to the given +/// location. The cursor location is updated to be at the endpoint. +/// +/// @param[in] builder The builder. +/// @param[in] location The location. +/// IMPELLER_EXPORT void ImpellerPathBuilderLineTo(ImpellerPathBuilder IMPELLER_NONNULL builder, const ImpellerPoint* IMPELLER_NONNULL location); +//------------------------------------------------------------------------------ +/// @brief Add a quadratic curve from whose start point is the cursor to +/// the specified end point using the a single control point. +/// +/// The new location of the cursor after this call is the end point. +/// +/// @param[in] builder The builder. +/// @param[in] control_point The control point. +/// @param[in] end_point The end point. +/// IMPELLER_EXPORT void ImpellerPathBuilderQuadraticCurveTo( ImpellerPathBuilder IMPELLER_NONNULL builder, const ImpellerPoint* IMPELLER_NONNULL control_point, const ImpellerPoint* IMPELLER_NONNULL end_point); +//------------------------------------------------------------------------------ +/// @brief Add a cubic curve whose start point is current cursor location +/// to the specified end point using the two specified control +/// points. +/// +/// The new location of the cursor after this call is the end point +/// supplied. +/// +/// @param[in] builder The builder +/// @param[in] control_point_1 The control point 1 +/// @param[in] control_point_2 The control point 2 +/// @param[in] end_point The end point +/// IMPELLER_EXPORT void ImpellerPathBuilderCubicCurveTo( ImpellerPathBuilder IMPELLER_NONNULL builder, @@ -376,34 +817,85 @@ void ImpellerPathBuilderCubicCurveTo( const ImpellerPoint* IMPELLER_NONNULL control_point_2, const ImpellerPoint* IMPELLER_NONNULL end_point); +//------------------------------------------------------------------------------ +/// @brief Adds a rectangle to the path. +/// +/// @param[in] builder The builder. +/// @param[in] rect The rectangle. +/// IMPELLER_EXPORT void ImpellerPathBuilderAddRect(ImpellerPathBuilder IMPELLER_NONNULL builder, const ImpellerRect* IMPELLER_NONNULL rect); +//------------------------------------------------------------------------------ +/// @brief Add an arc to the path. +/// +/// @param[in] builder The builder. +/// @param[in] oval_bounds The oval bounds. +/// @param[in] start_angle_degrees The start angle in degrees. +/// @param[in] end_angle_degrees The end angle in degrees. +/// IMPELLER_EXPORT void ImpellerPathBuilderAddArc(ImpellerPathBuilder IMPELLER_NONNULL builder, const ImpellerRect* IMPELLER_NONNULL oval_bounds, float start_angle_degrees, float end_angle_degrees); +//------------------------------------------------------------------------------ +/// @brief Add an oval to the path. +/// +/// @param[in] builder The builder. +/// @param[in] oval_bounds The oval bounds. +/// IMPELLER_EXPORT void ImpellerPathBuilderAddOval( ImpellerPathBuilder IMPELLER_NONNULL builder, const ImpellerRect* IMPELLER_NONNULL oval_bounds); +//------------------------------------------------------------------------------ +/// @brief Add a rounded rect with potentially non-uniform radii to the +/// path. +/// +/// @param[in] builder The builder. +/// @param[in] rect The rectangle. +/// @param[in] rounding_radii The rounding radii. +/// IMPELLER_EXPORT void ImpellerPathBuilderAddRoundedRect( ImpellerPathBuilder IMPELLER_NONNULL builder, const ImpellerRect* IMPELLER_NONNULL rect, const ImpellerRoundingRadii* IMPELLER_NONNULL rounding_radii); +//------------------------------------------------------------------------------ +/// @brief Close the path. +/// +/// @param[in] builder The builder. +/// IMPELLER_EXPORT void ImpellerPathBuilderClose(ImpellerPathBuilder IMPELLER_NONNULL builder); +//------------------------------------------------------------------------------ +/// @brief Create a new path by copying the existing built-up path. The +/// existing path can continue being added to. +/// +/// @param[in] builder The builder. +/// @param[in] fill The fill. +/// +/// @return The impeller path. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerPath IMPELLER_NULLABLE ImpellerPathBuilderCopyPathNew(ImpellerPathBuilder IMPELLER_NONNULL builder, ImpellerFillType fill); +//------------------------------------------------------------------------------ +/// @brief Create a new path using the existing built-up path. The existing +/// path builder now contains an empty path. +/// +/// @param[in] builder The builder. +/// @param[in] fill The fill. +/// +/// @return The impeller path. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerPath IMPELLER_NULLABLE ImpellerPathBuilderTakePathNew(ImpellerPathBuilder IMPELLER_NONNULL builder, ImpellerFillType fill); @@ -412,58 +904,154 @@ ImpellerPathBuilderTakePathNew(ImpellerPathBuilder IMPELLER_NONNULL builder, // Paint //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Create a new paint with default values. +/// +/// @return The impeller paint. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerPaint IMPELLER_NULLABLE ImpellerPaintNew(); +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerPaintRetain(ImpellerPaint IMPELLER_NULLABLE paint); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerPaintRelease(ImpellerPaint IMPELLER_NULLABLE paint); +//------------------------------------------------------------------------------ +/// @brief Set the paint color. +/// +/// @param[in] paint The paint. +/// @param[in] color The color. +/// IMPELLER_EXPORT void ImpellerPaintSetColor(ImpellerPaint IMPELLER_NONNULL paint, const ImpellerColor* IMPELLER_NONNULL color); +//------------------------------------------------------------------------------ +/// @brief Set the paint blend mode. The blend mode controls how the new +/// paints contents are mixed with the values already drawn using +/// previous draw calls. +/// +/// @param[in] paint The paint. +/// @param[in] mode The mode. +/// IMPELLER_EXPORT void ImpellerPaintSetBlendMode(ImpellerPaint IMPELLER_NONNULL paint, ImpellerBlendMode mode); +//------------------------------------------------------------------------------ +/// @brief Set the paint draw style. The style controls if the closed +/// shapes are filled and/or stroked. +/// +/// @param[in] paint The paint. +/// @param[in] style The style. +/// IMPELLER_EXPORT void ImpellerPaintSetDrawStyle(ImpellerPaint IMPELLER_NONNULL paint, ImpellerDrawStyle style); +//------------------------------------------------------------------------------ +/// @brief Sets how strokes rendered using this paint are capped. +/// +/// @param[in] paint The paint. +/// @param[in] cap The stroke cap style. +/// IMPELLER_EXPORT void ImpellerPaintSetStrokeCap(ImpellerPaint IMPELLER_NONNULL paint, ImpellerStrokeCap cap); +//------------------------------------------------------------------------------ +/// @brief Sets how strokes rendered using this paint are joined. +/// +/// @param[in] paint The paint. +/// @param[in] join The join. +/// IMPELLER_EXPORT void ImpellerPaintSetStrokeJoin(ImpellerPaint IMPELLER_NONNULL paint, ImpellerStrokeJoin join); +//------------------------------------------------------------------------------ +/// @brief Set the width of the strokes rendered using this paint. +/// +/// @param[in] paint The paint. +/// @param[in] width The width. +/// IMPELLER_EXPORT void ImpellerPaintSetStrokeWidth(ImpellerPaint IMPELLER_NONNULL paint, float width); +//------------------------------------------------------------------------------ +/// @brief Set the miter limit of the strokes rendered using this paint. +/// +/// @param[in] paint The paint. +/// @param[in] miter The miter limit. +/// IMPELLER_EXPORT void ImpellerPaintSetStrokeMiter(ImpellerPaint IMPELLER_NONNULL paint, float miter); +//------------------------------------------------------------------------------ +/// @brief Set the color filter of the paint. +/// +/// Color filters are functions that take two colors and mix them to +/// produce a single color. This color is then usually merged with +/// the destination during blending. +/// +/// @param[in] paint The paint. +/// @param[in] color_filter The color filter. +/// IMPELLER_EXPORT void ImpellerPaintSetColorFilter( ImpellerPaint IMPELLER_NONNULL paint, ImpellerColorFilter IMPELLER_NONNULL color_filter); +//------------------------------------------------------------------------------ +/// @brief Set the color source of the paint. +/// +/// Color sources are functions that generate colors for each +/// texture element covered by a draw call. +/// +/// @param[in] paint The paint. +/// @param[in] color_source The color source. +/// IMPELLER_EXPORT void ImpellerPaintSetColorSource( ImpellerPaint IMPELLER_NONNULL paint, ImpellerColorSource IMPELLER_NONNULL color_source); +//------------------------------------------------------------------------------ +/// @brief Set the image filter of a paint. +/// +/// Image filters are functions that are applied to regions of a +/// texture to produce a single color. +/// +/// @param[in] paint The paint. +/// @param[in] image_filter The image filter. +/// IMPELLER_EXPORT void ImpellerPaintSetImageFilter( ImpellerPaint IMPELLER_NONNULL paint, ImpellerImageFilter IMPELLER_NONNULL image_filter); +//------------------------------------------------------------------------------ +/// @brief Set the mask filter of a paint. +/// +/// @param[in] paint The paint. +/// @param[in] mask_filter The mask filter. +/// IMPELLER_EXPORT void ImpellerPaintSetMaskFilter( ImpellerPaint IMPELLER_NONNULL paint, @@ -473,6 +1061,39 @@ void ImpellerPaintSetMaskFilter( // Texture //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Create a texture with decompressed bytes. +/// +/// Impeller will do its best to perform the transfer of this data +/// to GPU memory with a minimal number of copies. Towards this +/// end, it may need to send this data to a different thread for +/// preparation and transfer. To facilitate this transfer, it is +/// recommended that the content mapping have a release callback +/// attach to it. When there is a release callback, Impeller assumes +/// that collection of the data can be deferred till texture upload +/// is done and can happen on a background thread. When there is no +/// release callback, Impeller may try to perform an eager copy of +/// the data if it needs to perform data preparation and transfer on +/// a background thread. +/// +/// Whether an extra data copy actually occurs will always depend on +/// the rendering backend in use. But it is best practice to provide +/// a release callback and be resilient to the data being released +/// in a deferred manner on a background thread. +/// +/// @warning Do **not** supply compressed image data directly (PNG, JPEG, +/// etc...). This function only works with tightly packed +/// decompressed data. +/// +/// @param[in] context The context. +/// @param[in] descriptor The texture descriptor. +/// @param[in] contents The contents. +/// @param[in] contents_on_release_user_data The baton passes to the contents +/// release callback if one exists. +/// +/// @return The texture if one can be created using the provided data, NULL +/// otherwise. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerTexture IMPELLER_NULLABLE ImpellerTextureCreateWithContentsNew( ImpellerContext IMPELLER_NONNULL context, @@ -480,6 +1101,30 @@ ImpellerTextureCreateWithContentsNew( const ImpellerMapping* IMPELLER_NONNULL contents, void* IMPELLER_NULLABLE contents_on_release_user_data); +//------------------------------------------------------------------------------ +/// @brief Create a texture with an externally created OpenGL texture +/// handle. +/// +/// Ownership of the handle is transferred over to Impeller after a +/// successful call to this method. Impeller is responsible for +/// calling glDeleteTextures on this handle. Do **not** collect this +/// handle yourself as this will lead to a double-free. +/// +/// The handle must be created in the same context as the one used +/// by Impeller. If a different context is used, that context must +/// be in the same sharegroup as Impellers OpenGL context and all +/// synchronization of texture contents must already be complete. +/// +/// If the context is not an OpenGL context, this call will always +/// fail. +/// +/// @param[in] context The context +/// @param[in] descriptor The descriptor +/// @param[in] handle The handle +/// +/// @return The texture if one could be created by adopting the supplied +/// texture handle, NULL otherwise. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerTexture IMPELLER_NULLABLE ImpellerTextureCreateWithOpenGLTextureHandleNew( ImpellerContext IMPELLER_NONNULL context, @@ -487,12 +1132,37 @@ ImpellerTextureCreateWithOpenGLTextureHandleNew( uint64_t handle // transfer-in ownership ); +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] texture The texture. +/// IMPELLER_EXPORT void ImpellerTextureRetain(ImpellerTexture IMPELLER_NULLABLE texture); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] texture The texture. +/// IMPELLER_EXPORT void ImpellerTextureRelease(ImpellerTexture IMPELLER_NULLABLE texture); +//------------------------------------------------------------------------------ +/// @brief Get the OpenGL handle associated with this texture. If this is +/// not an OpenGL texture, this method will always return 0. +/// +/// OpenGL handles are lazily created, this method will return +/// GL_NONE is no OpenGL handle is available. To ensure that this +/// call eagerly creates an OpenGL texture, call this on a thread +/// where Impeller knows there is an OpenGL context available. +/// +/// @param[in] texture The texture. +/// +/// @return The OpenGL handle if one is available, GL_NONE otherwise. +/// IMPELLER_EXPORT uint64_t ImpellerTextureGetOpenGLHandle( ImpellerTexture IMPELLER_NONNULL texture); @@ -501,14 +1171,40 @@ uint64_t ImpellerTextureGetOpenGLHandle( // Color Sources //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] color_source The color source. +/// + IMPELLER_EXPORT void ImpellerColorSourceRetain( ImpellerColorSource IMPELLER_NULLABLE color_source); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] color_source The color source. +/// IMPELLER_EXPORT void ImpellerColorSourceRelease( ImpellerColorSource IMPELLER_NULLABLE color_source); +//------------------------------------------------------------------------------ +/// @brief Create a color source that forms a linear gradient. +/// +/// @param[in] start_point The start point. +/// @param[in] end_point The end point. +/// @param[in] stop_count The stop count. +/// @param[in] colors The colors. +/// @param[in] stops The stops. +/// @param[in] tile_mode The tile mode. +/// @param[in] transformation The transformation. +/// +/// @return The color source. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerColorSource IMPELLER_NULLABLE ImpellerColorSourceCreateLinearGradientNew( const ImpellerPoint* IMPELLER_NONNULL start_point, @@ -519,6 +1215,19 @@ ImpellerColorSourceCreateLinearGradientNew( ImpellerTileMode tile_mode, const ImpellerMatrix* IMPELLER_NULLABLE transformation); +//------------------------------------------------------------------------------ +/// @brief Create a color source that forms a radial gradient. +/// +/// @param[in] center The center. +/// @param[in] radius The radius. +/// @param[in] stop_count The stop count. +/// @param[in] colors The colors. +/// @param[in] stops The stops. +/// @param[in] tile_mode The tile mode. +/// @param[in] transformation The transformation. +/// +/// @return The color source. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerColorSource IMPELLER_NULLABLE ImpellerColorSourceCreateRadialGradientNew( const ImpellerPoint* IMPELLER_NONNULL center, @@ -529,6 +1238,21 @@ ImpellerColorSourceCreateRadialGradientNew( ImpellerTileMode tile_mode, const ImpellerMatrix* IMPELLER_NULLABLE transformation); +//------------------------------------------------------------------------------ +/// @brief Create a color source that forms a conical gradient. +/// +/// @param[in] start_center The start center. +/// @param[in] start_radius The start radius. +/// @param[in] end_center The end center. +/// @param[in] end_radius The end radius. +/// @param[in] stop_count The stop count. +/// @param[in] colors The colors. +/// @param[in] stops The stops. +/// @param[in] tile_mode The tile mode. +/// @param[in] transformation The transformation. +/// +/// @return The color source. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerColorSource IMPELLER_NULLABLE ImpellerColorSourceCreateConicalGradientNew( const ImpellerPoint* IMPELLER_NONNULL start_center, @@ -541,6 +1265,20 @@ ImpellerColorSourceCreateConicalGradientNew( ImpellerTileMode tile_mode, const ImpellerMatrix* IMPELLER_NULLABLE transformation); +//------------------------------------------------------------------------------ +/// @brief Create a color source that forms a sweep gradient. +/// +/// @param[in] center The center. +/// @param[in] start The start. +/// @param[in] end The end. +/// @param[in] stop_count The stop count. +/// @param[in] colors The colors. +/// @param[in] stops The stops. +/// @param[in] tile_mode The tile mode. +/// @param[in] transformation The transformation. +/// +/// @return The color source. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerColorSource IMPELLER_NULLABLE ImpellerColorSourceCreateSweepGradientNew( const ImpellerPoint* IMPELLER_NONNULL center, @@ -552,6 +1290,17 @@ ImpellerColorSourceCreateSweepGradientNew( ImpellerTileMode tile_mode, const ImpellerMatrix* IMPELLER_NULLABLE transformation); +//------------------------------------------------------------------------------ +/// @brief Create a color source that samples from an image. +/// +/// @param[in] image The image. +/// @param[in] horizontal_tile_mode The horizontal tile mode. +/// @param[in] vertical_tile_mode The vertical tile mode. +/// @param[in] sampling The sampling. +/// @param[in] transformation The transformation. +/// +/// @return The color source. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerColorSource IMPELLER_NULLABLE ImpellerColorSourceCreateImageNew( ImpellerTexture IMPELLER_NONNULL image, @@ -564,18 +1313,47 @@ ImpellerColorSourceCreateImageNew( // Color Filters //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] color_filter The color filter. +/// IMPELLER_EXPORT void ImpellerColorFilterRetain( ImpellerColorFilter IMPELLER_NULLABLE color_filter); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] color_filter The color filter. +/// IMPELLER_EXPORT void ImpellerColorFilterRelease( ImpellerColorFilter IMPELLER_NULLABLE color_filter); +//------------------------------------------------------------------------------ +/// @brief Create a color filter that performs blending of pixel values +/// independently. +/// +/// @param[in] color The color. +/// @param[in] blend_mode The blend mode. +/// +/// @return The color filter. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerColorFilter IMPELLER_NULLABLE ImpellerColorFilterCreateBlendNew(const ImpellerColor* IMPELLER_NONNULL color, ImpellerBlendMode blend_mode); +//------------------------------------------------------------------------------ +/// @brief Create a color filter that transforms pixel color values +/// independently. +/// +/// @param[in] color_matrix The color matrix. +/// +/// @return The color filter. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerColorFilter IMPELLER_NULLABLE ImpellerColorFilterCreateColorMatrixNew( const ImpellerColorMatrix* IMPELLER_NONNULL color_matrix); @@ -584,13 +1362,33 @@ ImpellerColorFilterCreateColorMatrixNew( // Mask Filters //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] mask_filter The mask filter. +/// IMPELLER_EXPORT void ImpellerMaskFilterRetain(ImpellerMaskFilter IMPELLER_NULLABLE mask_filter); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] mask_filter The mask filter. +/// IMPELLER_EXPORT void ImpellerMaskFilterRelease( ImpellerMaskFilter IMPELLER_NULLABLE mask_filter); +//------------------------------------------------------------------------------ +/// @brief Create a mask filter that blurs contents in the masked shape. +/// +/// @param[in] style The style. +/// @param[in] sigma The sigma. +/// +/// @return The mask filter. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerMaskFilter IMPELLER_NULLABLE ImpellerMaskFilterCreateBlurNew(ImpellerBlurStyle style, float sigma); @@ -598,30 +1396,96 @@ ImpellerMaskFilterCreateBlurNew(ImpellerBlurStyle style, float sigma); // Image Filters //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] image_filter The image filter. +/// IMPELLER_EXPORT void ImpellerImageFilterRetain( ImpellerImageFilter IMPELLER_NULLABLE image_filter); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] image_filter The image filter. +/// IMPELLER_EXPORT void ImpellerImageFilterRelease( ImpellerImageFilter IMPELLER_NULLABLE image_filter); +//------------------------------------------------------------------------------ +/// @brief Creates an image filter that applies a Gaussian blur. +/// +/// The Gaussian blur applied may be an approximation for +/// performance. +/// +/// +/// @param[in] x_sigma The x sigma. +/// @param[in] y_sigma The y sigma. +/// @param[in] tile_mode The tile mode. +/// +/// @return The image filter. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerImageFilter IMPELLER_NULLABLE ImpellerImageFilterCreateBlurNew(float x_sigma, float y_sigma, ImpellerTileMode tile_mode); +//------------------------------------------------------------------------------ +/// @brief Creates an image filter that enhances the per-channel pixel +/// values to the maximum value in a circle around the pixel. +/// +/// @param[in] x_radius The x radius. +/// @param[in] y_radius The y radius. +/// +/// @return The image filter. +/// + IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerImageFilter IMPELLER_NULLABLE ImpellerImageFilterCreateDilateNew(float x_radius, float y_radius); +//------------------------------------------------------------------------------ +/// @brief Creates an image filter that dampens the per-channel pixel +/// values to the minimum value in a circle around the pixel. +/// +/// @param[in] x_radius The x radius. +/// @param[in] y_radius The y radius. +/// +/// @return The image filter. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerImageFilter IMPELLER_NULLABLE ImpellerImageFilterCreateErodeNew(float x_radius, float y_radius); +//------------------------------------------------------------------------------ +/// @brief Creates an image filter that applies a transformation matrix to +/// the underlying image. +/// +/// @param[in] matrix The transformation matrix. +/// @param[in] sampling The image sampling mode. +/// +/// @return The image filter. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerImageFilter IMPELLER_NULLABLE ImpellerImageFilterCreateMatrixNew( const ImpellerMatrix* IMPELLER_NONNULL matrix, ImpellerTextureSampling sampling); +//------------------------------------------------------------------------------ +/// @brief Creates a composed filter that when applied is identical to +/// subsequently applying the inner and then the outer filters. +/// +/// ``` +/// destination = outer_filter(inner_filter(source)) +/// ``` +/// +/// @param[in] outer The outer image filter. +/// @param[in] inner The inner image filter. +/// +/// @return The combined image filter. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerImageFilter IMPELLER_NULLABLE ImpellerImageFilterCreateComposeNew(ImpellerImageFilter IMPELLER_NONNULL outer, ImpellerImageFilter IMPELLER_NONNULL inner); @@ -630,10 +1494,22 @@ ImpellerImageFilterCreateComposeNew(ImpellerImageFilter IMPELLER_NONNULL outer, // Display List //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] display_list The display list. +/// IMPELLER_EXPORT void ImpellerDisplayListRetain( ImpellerDisplayList IMPELLER_NULLABLE display_list); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] display_list The display list. +/// IMPELLER_EXPORT void ImpellerDisplayListRelease( ImpellerDisplayList IMPELLER_NULLABLE display_list); @@ -642,17 +1518,48 @@ void ImpellerDisplayListRelease( // Display List Builder //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Create a new display list builder. +/// +/// An optional cull rectangle may be specified. Impeller is allowed +/// to treat the contents outside this rectangle as being undefined. +/// This may aid performance optimizations. +/// +/// @param[in] cull_rect The cull rectangle or NULL. +/// +/// @return The display list builder. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerDisplayListBuilder IMPELLER_NULLABLE ImpellerDisplayListBuilderNew(const ImpellerRect* IMPELLER_NULLABLE cull_rect); +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] builder The display list builder. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderRetain( ImpellerDisplayListBuilder IMPELLER_NULLABLE builder); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] builder The display list builder. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderRelease( ImpellerDisplayListBuilder IMPELLER_NULLABLE builder); +//------------------------------------------------------------------------------ +/// @brief Create a new display list using the rendering intent already +/// encoded in the builder. The builder is reset after this call. +/// +/// @param[in] builder The builder. +/// +/// @return The display list. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerDisplayList IMPELLER_NULLABLE ImpellerDisplayListBuilderCreateDisplayListNew( ImpellerDisplayListBuilder IMPELLER_NONNULL builder); @@ -660,10 +1567,31 @@ ImpellerDisplayListBuilderCreateDisplayListNew( //------------------------------------------------------------------------------ // Display List Builder: Managing the transformation stack. //------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +/// @brief Stashes the current transformation and clip state onto a save +/// stack. +/// +/// @param[in] builder The builder. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderSave( ImpellerDisplayListBuilder IMPELLER_NONNULL builder); +//------------------------------------------------------------------------------ +/// @brief Stashes the current transformation and clip state onto a save +/// stack and creates and creates an offscreen layer onto which +/// subsequent rendering intent will be directed to. +/// +/// On the balancing call to restore, the supplied paints filters +/// and blend modes will be used to composite the offscreen contents +/// back onto the display display list. +/// +/// @param[in] builder The builder. +/// @param[in] bounds The bounds. +/// @param[in] paint The paint. +/// @param[in] backdrop The backdrop. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderSaveLayer( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, @@ -671,45 +1599,122 @@ void ImpellerDisplayListBuilderSaveLayer( ImpellerPaint IMPELLER_NULLABLE paint, ImpellerImageFilter IMPELLER_NULLABLE backdrop); +//------------------------------------------------------------------------------ +/// @brief Pops the last entry pushed onto the save stack using a call to +/// `ImpellerDisplayListBuilderSave` or +/// `ImpellerDisplayListBuilderSaveLayer`. +/// +/// @param[in] builder The builder. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderRestore( ImpellerDisplayListBuilder IMPELLER_NONNULL builder); +//------------------------------------------------------------------------------ +/// @brief Apply a scale to the transformation matrix currently on top of +/// the save stack. +/// +/// @param[in] builder The builder. +/// @param[in] x_scale The x scale. +/// @param[in] y_scale The y scale. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderScale( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, float x_scale, float y_scale); +//------------------------------------------------------------------------------ +/// @brief Apply a clockwise rotation to the transformation matrix +/// currently on top of the save stack. +/// +/// @param[in] builder The builder. +/// @param[in] angle_degrees The angle in degrees. +/// + IMPELLER_EXPORT void ImpellerDisplayListBuilderRotate( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, float angle_degrees); +//------------------------------------------------------------------------------ +/// @brief Apply a translation to the transformation matrix currently on +/// top of the save stack. +/// +/// @param[in] builder The builder. +/// @param[in] x_translation The x translation. +/// @param[in] y_translation The y translation. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderTranslate( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, float x_translation, float y_translation); +//------------------------------------------------------------------------------ +/// @brief Appends the the provided transformation to the transformation +/// already on the save stack. +/// +/// @param[in] builder The builder. +/// @param[in] transform The transform to append. +/// +IMPELLER_EXPORT +void ImpellerDisplayListBuilderTransform( + ImpellerDisplayListBuilder IMPELLER_NONNULL builder, + const ImpellerMatrix* IMPELLER_NONNULL transform); + +//------------------------------------------------------------------------------ +/// @brief Clear the transformation on top of the save stack and replace it +/// with a new value. +/// +/// @param[in] builder The builder. +/// @param[in] transform The new transform. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderSetTransform( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, const ImpellerMatrix* IMPELLER_NONNULL transform); +//------------------------------------------------------------------------------ +/// @brief Get the transformation currently built up on the top of the +/// transformation stack. +/// +/// @param[in] builder The builder. +/// @param[out] out_transform The transform. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderGetTransform( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, ImpellerMatrix* IMPELLER_NONNULL out_transform); +//------------------------------------------------------------------------------ +/// @brief Reset the transformation on top of the transformation stack to +/// identity. +/// +/// @param[in] builder The builder. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderResetTransform( ImpellerDisplayListBuilder IMPELLER_NONNULL builder); +//------------------------------------------------------------------------------ +/// @brief Get the current size of the save stack. +/// +/// @param[in] builder The builder. +/// +/// @return The save stack size. +/// IMPELLER_EXPORT uint32_t ImpellerDisplayListBuilderGetSaveCount( ImpellerDisplayListBuilder IMPELLER_NONNULL builder); +//------------------------------------------------------------------------------ +/// @brief Effectively calls ImpellerDisplayListBuilderRestore till the +/// size of the save stack becomes a specified count. +/// +/// @param[in] builder The builder. +/// @param[in] count The count. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderRestoreToCount( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, @@ -719,18 +1724,44 @@ void ImpellerDisplayListBuilderRestoreToCount( // Display List Builder: Clipping //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Reduces the clip region to the intersection of the current clip +/// and the given rectangle taking into account the clip operation. +/// +/// @param[in] builder The builder. +/// @param[in] rect The rectangle. +/// @param[in] op The operation. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderClipRect( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, const ImpellerRect* IMPELLER_NONNULL rect, ImpellerClipOperation op); +//------------------------------------------------------------------------------ +/// @brief Reduces the clip region to the intersection of the current clip +/// and the given oval taking into account the clip operation. +/// +/// @param[in] builder The builder. +/// @param[in] oval_bounds The oval bounds. +/// @param[in] op The operation. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderClipOval( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, const ImpellerRect* IMPELLER_NONNULL oval_bounds, ImpellerClipOperation op); +//------------------------------------------------------------------------------ +/// @brief Reduces the clip region to the intersection of the current clip +/// and the given rounded rectangle taking into account the clip +/// operation. +/// +/// @param[in] builder The builder. +/// @param[in] rect The rectangle. +/// @param[in] radii The radii. +/// @param[in] op The operation. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderClipRoundedRect( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, @@ -738,6 +1769,14 @@ void ImpellerDisplayListBuilderClipRoundedRect( const ImpellerRoundingRadii* IMPELLER_NONNULL radii, ImpellerClipOperation op); +//------------------------------------------------------------------------------ +/// @brief Reduces the clip region to the intersection of the current clip +/// and the given path taking into account the clip operation. +/// +/// @param[in] builder The builder. +/// @param[in] path The path. +/// @param[in] op The operation. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderClipPath( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, @@ -748,11 +1787,25 @@ void ImpellerDisplayListBuilderClipPath( // Display List Builder: Drawing Shapes //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Fills the current clip with the specified paint. +/// +/// @param[in] builder The builder. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawPaint( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, ImpellerPaint IMPELLER_NONNULL paint); +//------------------------------------------------------------------------------ +/// @brief Draws a line segment. +/// +/// @param[in] builder The builder. +/// @param[in] from The starting point of the line. +/// @param[in] to The end point of the line. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawLine( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, @@ -760,6 +1813,16 @@ void ImpellerDisplayListBuilderDrawLine( const ImpellerPoint* IMPELLER_NONNULL to, ImpellerPaint IMPELLER_NONNULL paint); +//------------------------------------------------------------------------------ +/// @brief Draws a dash line segment. +/// +/// @param[in] builder The builder. +/// @param[in] from The starting point of the line. +/// @param[in] to The end point of the line. +/// @param[in] on_length On length. +/// @param[in] off_length Off length. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawDashedLine( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, @@ -769,18 +1832,40 @@ void ImpellerDisplayListBuilderDrawDashedLine( float off_length, ImpellerPaint IMPELLER_NONNULL paint); +//------------------------------------------------------------------------------ +/// @brief Draws a rectangle. +/// +/// @param[in] builder The builder. +/// @param[in] rect The rectangle. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawRect( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, const ImpellerRect* IMPELLER_NONNULL rect, ImpellerPaint IMPELLER_NONNULL paint); +//------------------------------------------------------------------------------ +/// @brief Draws an oval. +/// +/// @param[in] builder The builder. +/// @param[in] oval_bounds The oval bounds. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawOval( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, const ImpellerRect* IMPELLER_NONNULL oval_bounds, ImpellerPaint IMPELLER_NONNULL paint); +//------------------------------------------------------------------------------ +/// @brief Draws a rounded rect. +/// +/// @param[in] builder The builder. +/// @param[in] rect The rectangle. +/// @param[in] radii The radii. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawRoundedRect( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, @@ -788,6 +1873,17 @@ void ImpellerDisplayListBuilderDrawRoundedRect( const ImpellerRoundingRadii* IMPELLER_NONNULL radii, ImpellerPaint IMPELLER_NONNULL paint); +//------------------------------------------------------------------------------ +/// @brief Draws a shape that is the different between the specified +/// rectangles (each with configurable corner radii). +/// +/// @param[in] builder The builder. +/// @param[in] outer_rect The outer rectangle. +/// @param[in] outer_radii The outer radii. +/// @param[in] inner_rect The inner rectangle. +/// @param[in] inner_radii The inner radii. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawRoundedRectDifference( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, @@ -797,18 +1893,40 @@ void ImpellerDisplayListBuilderDrawRoundedRectDifference( const ImpellerRoundingRadii* IMPELLER_NONNULL inner_radii, ImpellerPaint IMPELLER_NONNULL paint); +//------------------------------------------------------------------------------ +/// @brief Draws the specified shape. +/// +/// @param[in] builder The builder. +/// @param[in] path The path. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawPath( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, ImpellerPath IMPELLER_NONNULL path, ImpellerPaint IMPELLER_NONNULL paint); +//------------------------------------------------------------------------------ +/// @brief Flattens the contents of another display list into the one +/// currently being built. +/// +/// @param[in] builder The builder. +/// @param[in] display_list The display list. +/// @param[in] opacity The opacity. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawDisplayList( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, ImpellerDisplayList IMPELLER_NONNULL display_list, float opacity); +//------------------------------------------------------------------------------ +/// @brief Draw a paragraph at the specified point. +/// +/// @param[in] builder The builder. +/// @param[in] paragraph The paragraph. +/// @param[in] point The point. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawParagraph( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, @@ -819,6 +1937,15 @@ void ImpellerDisplayListBuilderDrawParagraph( // Display List Builder: Drawing Textures //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Draw a texture at the specified point. +/// +/// @param[in] builder The builder. +/// @param[in] texture The texture. +/// @param[in] point The point. +/// @param[in] sampling The sampling. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawTexture( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, @@ -827,6 +1954,16 @@ void ImpellerDisplayListBuilderDrawTexture( ImpellerTextureSampling sampling, ImpellerPaint IMPELLER_NULLABLE paint); +//------------------------------------------------------------------------------ +/// @brief Draw a portion of texture at the specified location. +/// +/// @param[in] builder The builder. +/// @param[in] texture The texture. +/// @param[in] src_rect The source rectangle. +/// @param[in] dst_rect The destination rectangle. +/// @param[in] sampling The sampling. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerDisplayListBuilderDrawTextureRect( ImpellerDisplayListBuilder IMPELLER_NONNULL builder, @@ -840,17 +1977,74 @@ void ImpellerDisplayListBuilderDrawTextureRect( // Typography Context //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Create a new typography contents. +/// +/// @return The typography context. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerTypographyContext IMPELLER_NULLABLE ImpellerTypographyContextNew(); +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] context The typography context. +/// IMPELLER_EXPORT void ImpellerTypographyContextRetain( ImpellerTypographyContext IMPELLER_NULLABLE context); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] context The typography context. +/// IMPELLER_EXPORT void ImpellerTypographyContextRelease( ImpellerTypographyContext IMPELLER_NULLABLE context); +//------------------------------------------------------------------------------ +/// @brief Register a custom font. +/// +/// The following font formats are supported: +/// * OpenType font collections (.ttc extension) +/// * TrueType fonts: (.ttf extension) +/// * OpenType fonts: (.otf extension) +/// +/// @warning Web Open Font Formats (.woff and .woff2 extensions) are **not** +/// supported. +/// +/// The font data is specified as a mapping. It is possible for the +/// release callback of the mapping to not be called even past the +/// destruction of the typography context. Care must be taken to not +/// collect the mapping till the release callback is invoked by +/// Impeller. +/// +/// The family alias name can be NULL. In such cases, the font +/// family specified in paragraph styles must match the family that +/// is specified in the font data. +/// +/// If the family name alias is not NULL, that family name must be +/// used in the paragraph style to reference glyphs from this font +/// instead of the one encoded in the font itself. +/// +/// Multiple fonts (with glyphs for different styles) can be +/// specified with the same family. +/// +/// @see `ImpellerParagraphStyleSetFontFamily` +/// +/// @param[in] context The context. +/// @param[in] contents The contents. +/// @param[in] contents_on_release_user_data The user data baton to be passed +/// to the contents release callback. +/// @param[in] family_name_alias The family name alias or NULL if +/// the one specified in the font +/// data is to be used. +/// +/// @return If the font could be successfully registered. +/// IMPELLER_EXPORT bool ImpellerTypographyContextRegisterFont( ImpellerTypographyContext IMPELLER_NONNULL context, @@ -862,67 +2056,155 @@ bool ImpellerTypographyContextRegisterFont( // Paragraph Style //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Create a new paragraph style. +/// +/// @return The paragraph style. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerParagraphStyle IMPELLER_NULLABLE ImpellerParagraphStyleNew(); +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] paragraph_style The paragraph style. +/// IMPELLER_EXPORT void ImpellerParagraphStyleRetain( ImpellerParagraphStyle IMPELLER_NULLABLE paragraph_style); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] paragraph_style The paragraph style. +/// IMPELLER_EXPORT void ImpellerParagraphStyleRelease( ImpellerParagraphStyle IMPELLER_NULLABLE paragraph_style); +//------------------------------------------------------------------------------ +/// @brief Set the paint used to render the text glyph contents. +/// +/// @param[in] paragraph_style The paragraph style. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerParagraphStyleSetForeground( ImpellerParagraphStyle IMPELLER_NONNULL paragraph_style, ImpellerPaint IMPELLER_NONNULL paint); +//------------------------------------------------------------------------------ +/// @brief Set the paint used to render the background of the text glyphs. +/// +/// @param[in] paragraph_style The paragraph style. +/// @param[in] paint The paint. +/// IMPELLER_EXPORT void ImpellerParagraphStyleSetBackground( ImpellerParagraphStyle IMPELLER_NONNULL paragraph_style, ImpellerPaint IMPELLER_NONNULL paint); +//------------------------------------------------------------------------------ +/// @brief Set the weight of the font to select when rendering glyphs. +/// +/// @param[in] paragraph_style The paragraph style. +/// @param[in] weight The weight. +/// IMPELLER_EXPORT void ImpellerParagraphStyleSetFontWeight( ImpellerParagraphStyle IMPELLER_NONNULL paragraph_style, ImpellerFontWeight weight); +//------------------------------------------------------------------------------ +/// @brief Set whether the glyphs should be bolded or italicized. +/// +/// @param[in] paragraph_style The paragraph style. +/// @param[in] style The style. +/// IMPELLER_EXPORT void ImpellerParagraphStyleSetFontStyle( ImpellerParagraphStyle IMPELLER_NONNULL paragraph_style, ImpellerFontStyle style); +//------------------------------------------------------------------------------ +/// @brief Set the font family. +/// +/// @param[in] paragraph_style The paragraph style. +/// @param[in] family_name The family name. +/// IMPELLER_EXPORT void ImpellerParagraphStyleSetFontFamily( ImpellerParagraphStyle IMPELLER_NONNULL paragraph_style, const char* IMPELLER_NONNULL family_name); +//------------------------------------------------------------------------------ +/// @brief Set the font size. +/// +/// @param[in] paragraph_style The paragraph style. +/// @param[in] size The size. +/// IMPELLER_EXPORT void ImpellerParagraphStyleSetFontSize( ImpellerParagraphStyle IMPELLER_NONNULL paragraph_style, float size); +//------------------------------------------------------------------------------ +/// @brief The height of the text as a multiple of text size. +/// +/// When height is 0.0, the line height will be determined by the +/// font's metrics directly, which may differ from the font size. +/// Otherwise the line height of the text will be a multiple of font +/// size, and be exactly fontSize * height logical pixels tall. +/// +/// @param[in] paragraph_style The paragraph style. +/// @param[in] height The height. +/// IMPELLER_EXPORT void ImpellerParagraphStyleSetHeight( ImpellerParagraphStyle IMPELLER_NONNULL paragraph_style, float height); +//------------------------------------------------------------------------------ +/// @brief Set the alignment of text within the paragraph. +/// +/// @param[in] paragraph_style The paragraph style. +/// @param[in] align The align. +/// IMPELLER_EXPORT void ImpellerParagraphStyleSetTextAlignment( ImpellerParagraphStyle IMPELLER_NONNULL paragraph_style, ImpellerTextAlignment align); +//------------------------------------------------------------------------------ +/// @brief Set the directionality of the text within the paragraph. +/// +/// @param[in] paragraph_style The paragraph style. +/// @param[in] direction The direction. +/// IMPELLER_EXPORT void ImpellerParagraphStyleSetTextDirection( ImpellerParagraphStyle IMPELLER_NONNULL paragraph_style, ImpellerTextDirection direction); +//------------------------------------------------------------------------------ +/// @brief Set the maximum line count within the paragraph. +/// +/// @param[in] paragraph_style The paragraph style. +/// @param[in] max_lines The maximum lines. +/// IMPELLER_EXPORT void ImpellerParagraphStyleSetMaxLines( ImpellerParagraphStyle IMPELLER_NONNULL paragraph_style, uint32_t max_lines); +//------------------------------------------------------------------------------ +/// @brief Set the paragraph locale. +/// +/// @param[in] paragraph_style The paragraph style. +/// @param[in] locale The locale. +/// IMPELLER_EXPORT void ImpellerParagraphStyleSetLocale( ImpellerParagraphStyle IMPELLER_NONNULL paragraph_style, @@ -932,32 +2214,95 @@ void ImpellerParagraphStyleSetLocale( // Paragraph Builder //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Create a new paragraph builder. +/// +/// @param[in] context The context. +/// +/// @return The paragraph builder. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerParagraphBuilder IMPELLER_NULLABLE ImpellerParagraphBuilderNew(ImpellerTypographyContext IMPELLER_NONNULL context); +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] paragraph_builder The paragraph builder. +/// IMPELLER_EXPORT void ImpellerParagraphBuilderRetain( ImpellerParagraphBuilder IMPELLER_NULLABLE paragraph_builder); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] paragraph_builder The paragraph_builder. +/// IMPELLER_EXPORT void ImpellerParagraphBuilderRelease( ImpellerParagraphBuilder IMPELLER_NULLABLE paragraph_builder); +//------------------------------------------------------------------------------ +/// @brief Push a new paragraph style onto the paragraph style stack +/// managed by the paragraph builder. +/// +/// Not all paragraph styles can be combined. For instance, it does +/// not make sense to mix text alignment for different text runs +/// within a paragraph. In such cases, the preference of the the +/// first paragraph style on the style stack will take hold. +/// +/// If text is pushed onto the paragraph builder without a style +/// previously pushed onto the stack, a default paragraph text style +/// will be used. This may not always be desirable because some +/// style element cannot be overridden. It is recommended that a +/// default paragraph style always be pushed onto the stack before +/// the addition of any text. +/// +/// @param[in] paragraph_builder The paragraph builder. +/// @param[in] style The style. +/// IMPELLER_EXPORT void ImpellerParagraphBuilderPushStyle( ImpellerParagraphBuilder IMPELLER_NONNULL paragraph_builder, ImpellerParagraphStyle IMPELLER_NONNULL style); +//------------------------------------------------------------------------------ +/// @brief Pop a previously pushed paragraph style from the paragraph style +/// stack. +/// +/// @param[in] paragraph_builder The paragraph builder. +/// IMPELLER_EXPORT void ImpellerParagraphBuilderPopStyle( ImpellerParagraphBuilder IMPELLER_NONNULL paragraph_builder); +//------------------------------------------------------------------------------ +/// @brief Add UTF-8 encoded text to the paragraph. The text will be styled +/// according to the paragraph style already on top of the paragraph +/// style stack. +/// +/// @param[in] paragraph_builder The paragraph builder. +/// @param[in] data The data. +/// @param[in] length The length. +/// IMPELLER_EXPORT void ImpellerParagraphBuilderAddText( ImpellerParagraphBuilder IMPELLER_NONNULL paragraph_builder, const uint8_t* IMPELLER_NULLABLE data, uint32_t length); +//------------------------------------------------------------------------------ +/// @brief Layout and build a new paragraph using the specified width. The +/// resulting paragraph is immutable. The paragraph builder must be +/// discarded and a new one created to build more paragraphs. +/// +/// @param[in] paragraph_builder The paragraph builder. +/// @param[in] width The paragraph width. +/// +/// @return The paragraph if one can be created, NULL otherwise. +/// IMPELLER_EXPORT IMPELLER_NODISCARD ImpellerParagraph IMPELLER_NULLABLE ImpellerParagraphBuilderBuildParagraphNew( ImpellerParagraphBuilder IMPELLER_NONNULL paragraph_builder, @@ -967,39 +2312,111 @@ ImpellerParagraphBuilderBuildParagraphNew( // Paragraph //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +/// @brief Retain a strong reference to the object. The object can be NULL +/// in which case this method is a no-op. +/// +/// @param[in] paragraph The paragraph. +/// IMPELLER_EXPORT void ImpellerParagraphRetain(ImpellerParagraph IMPELLER_NULLABLE paragraph); +//------------------------------------------------------------------------------ +/// @brief Release a previously retained reference to the object. The +/// object can be NULL in which case this method is a no-op. +/// +/// @param[in] paragraph The paragraph. +/// IMPELLER_EXPORT void ImpellerParagraphRelease(ImpellerParagraph IMPELLER_NULLABLE paragraph); +//------------------------------------------------------------------------------ +/// @see `ImpellerParagraphGetMinIntrinsicWidth` +/// +/// @param[in] paragraph The paragraph. +/// +/// +/// @return The width provided to the paragraph builder during the call to +/// layout. This is the maximum width any line in the laid out +/// paragraph can occupy. But, it is not necessarily the actual +/// width of the paragraph after layout. +/// IMPELLER_EXPORT float ImpellerParagraphGetMaxWidth( ImpellerParagraph IMPELLER_NONNULL paragraph); +//------------------------------------------------------------------------------ +/// @param[in] paragraph The paragraph. +/// +/// @return The height of the laid out paragraph. This is **not** a tight +/// bounding box and some glyphs may not reach the minimum location +/// they are allowed to reach. +/// IMPELLER_EXPORT float ImpellerParagraphGetHeight(ImpellerParagraph IMPELLER_NONNULL paragraph); +//------------------------------------------------------------------------------ +/// @param[in] paragraph The paragraph. +/// +/// @return The length of the longest line in the paragraph. This is the +/// horizontal distance between the left edge of the leftmost glyph +/// and the right edge of the rightmost glyph, in the longest line +/// in the paragraph. +/// IMPELLER_EXPORT float ImpellerParagraphGetLongestLineWidth( ImpellerParagraph IMPELLER_NONNULL paragraph); +//------------------------------------------------------------------------------ +/// @see `ImpellerParagraphGetMaxWidth` +/// +/// @param[in] paragraph The paragraph. +/// +/// @return The actual width of the longest line in the paragraph after +/// layout. This is expected to be less than or equal to +/// `ImpellerParagraphGetMaxWidth`. +/// IMPELLER_EXPORT float ImpellerParagraphGetMinIntrinsicWidth( ImpellerParagraph IMPELLER_NONNULL paragraph); +//------------------------------------------------------------------------------ +/// @param[in] paragraph The paragraph. +/// +/// @return The width of the paragraph without line breaking. +/// IMPELLER_EXPORT float ImpellerParagraphGetMaxIntrinsicWidth( ImpellerParagraph IMPELLER_NONNULL paragraph); +//------------------------------------------------------------------------------ +/// @param[in] paragraph The paragraph. +/// +/// @return The distance from the top of the paragraph to the ideographic +/// baseline of the first line when using ideographic fonts +/// (Japanese, Korean, etc...). +/// IMPELLER_EXPORT float ImpellerParagraphGetIdeographicBaseline( ImpellerParagraph IMPELLER_NONNULL paragraph); +//------------------------------------------------------------------------------ +/// @param[in] paragraph The paragraph. +/// +/// @return The distance from the top of the paragraph to the alphabetic +/// baseline of the first line when using alphabetic fonts (A-Z, +/// a-z, Greek, etc...). +/// IMPELLER_EXPORT float ImpellerParagraphGetAlphabeticBaseline( ImpellerParagraph IMPELLER_NONNULL paragraph); +//------------------------------------------------------------------------------ +/// @param[in] paragraph The paragraph. +/// +/// @return The number of lines visible in the paragraph after line +/// breaking. +/// IMPELLER_EXPORT uint32_t ImpellerParagraphGetLineCount( ImpellerParagraph IMPELLER_NONNULL paragraph); diff --git a/impeller/toolkit/interop/impeller.hpp b/impeller/toolkit/interop/impeller.hpp new file mode 100644 index 0000000000000..25a75d9323c81 --- /dev/null +++ b/impeller/toolkit/interop/impeller.hpp @@ -0,0 +1,1476 @@ +// 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. + +#ifndef FLUTTER_IMPELLER_TOOLKIT_INTEROP_IMPELLER_HPP_ +#define FLUTTER_IMPELLER_TOOLKIT_INTEROP_IMPELLER_HPP_ + +#include +#include +#include +#include +#include + +#include "impeller.h" + +//------------------------------------------------------------------------------ +/// A C++ 17 wrapper to the C Impeller API. This is a convenience wrapper for +/// the C++ API and only depends on standard libc++ utilities in addition to +/// impeller.h +/// + +#ifndef IMPELLER_HPP_NAMESPACE +#define IMPELLER_HPP_NAMESPACE impeller::hpp +#endif // IMPELLER_HPP_NAMESPACE + +// Tripping this assertion means that the C++ wrapper needs to be updated to +// account for impeller.h changes as necessary. +static_assert(IMPELLER_VERSION == IMPELLER_MAKE_VERSION(1, 1, 2, 0), + "C++ bindings must be for the same version as the C API."); + +namespace IMPELLER_HPP_NAMESPACE { + +template +struct Proc { + using FunctionType = T; + + const char* name = nullptr; + + FunctionType* function = nullptr; + + template + auto operator()(Args&&... args) const { + return function(std::forward(args)...); + } +}; + +#define IMPELLER_HPP_EACH_PROC(PROC) \ + PROC(ImpellerColorFilterCreateBlendNew) \ + PROC(ImpellerColorFilterCreateColorMatrixNew) \ + PROC(ImpellerColorFilterRelease) \ + PROC(ImpellerColorFilterRetain) \ + PROC(ImpellerColorSourceCreateConicalGradientNew) \ + PROC(ImpellerColorSourceCreateImageNew) \ + PROC(ImpellerColorSourceCreateLinearGradientNew) \ + PROC(ImpellerColorSourceCreateRadialGradientNew) \ + PROC(ImpellerColorSourceCreateSweepGradientNew) \ + PROC(ImpellerColorSourceRelease) \ + PROC(ImpellerColorSourceRetain) \ + PROC(ImpellerContextCreateOpenGLESNew) \ + PROC(ImpellerContextRelease) \ + PROC(ImpellerContextRetain) \ + PROC(ImpellerDisplayListBuilderClipOval) \ + PROC(ImpellerDisplayListBuilderClipPath) \ + PROC(ImpellerDisplayListBuilderClipRect) \ + PROC(ImpellerDisplayListBuilderClipRoundedRect) \ + PROC(ImpellerDisplayListBuilderCreateDisplayListNew) \ + PROC(ImpellerDisplayListBuilderDrawDashedLine) \ + PROC(ImpellerDisplayListBuilderDrawDisplayList) \ + PROC(ImpellerDisplayListBuilderDrawLine) \ + PROC(ImpellerDisplayListBuilderDrawOval) \ + PROC(ImpellerDisplayListBuilderDrawPaint) \ + PROC(ImpellerDisplayListBuilderDrawParagraph) \ + PROC(ImpellerDisplayListBuilderDrawPath) \ + PROC(ImpellerDisplayListBuilderDrawRect) \ + PROC(ImpellerDisplayListBuilderDrawRoundedRect) \ + PROC(ImpellerDisplayListBuilderDrawRoundedRectDifference) \ + PROC(ImpellerDisplayListBuilderDrawTexture) \ + PROC(ImpellerDisplayListBuilderDrawTextureRect) \ + PROC(ImpellerDisplayListBuilderGetSaveCount) \ + PROC(ImpellerDisplayListBuilderGetTransform) \ + PROC(ImpellerDisplayListBuilderNew) \ + PROC(ImpellerDisplayListBuilderRelease) \ + PROC(ImpellerDisplayListBuilderResetTransform) \ + PROC(ImpellerDisplayListBuilderRestore) \ + PROC(ImpellerDisplayListBuilderRestoreToCount) \ + PROC(ImpellerDisplayListBuilderRetain) \ + PROC(ImpellerDisplayListBuilderRotate) \ + PROC(ImpellerDisplayListBuilderSave) \ + PROC(ImpellerDisplayListBuilderSaveLayer) \ + PROC(ImpellerDisplayListBuilderScale) \ + PROC(ImpellerDisplayListBuilderSetTransform) \ + PROC(ImpellerDisplayListBuilderTransform) \ + PROC(ImpellerDisplayListBuilderTranslate) \ + PROC(ImpellerDisplayListRelease) \ + PROC(ImpellerDisplayListRetain) \ + PROC(ImpellerGetVersion) \ + PROC(ImpellerImageFilterCreateBlurNew) \ + PROC(ImpellerImageFilterCreateComposeNew) \ + PROC(ImpellerImageFilterCreateDilateNew) \ + PROC(ImpellerImageFilterCreateErodeNew) \ + PROC(ImpellerImageFilterCreateMatrixNew) \ + PROC(ImpellerImageFilterRelease) \ + PROC(ImpellerImageFilterRetain) \ + PROC(ImpellerMaskFilterCreateBlurNew) \ + PROC(ImpellerMaskFilterRelease) \ + PROC(ImpellerMaskFilterRetain) \ + PROC(ImpellerPaintNew) \ + PROC(ImpellerPaintRelease) \ + PROC(ImpellerPaintRetain) \ + PROC(ImpellerPaintSetBlendMode) \ + PROC(ImpellerPaintSetColor) \ + PROC(ImpellerPaintSetColorFilter) \ + PROC(ImpellerPaintSetColorSource) \ + PROC(ImpellerPaintSetDrawStyle) \ + PROC(ImpellerPaintSetImageFilter) \ + PROC(ImpellerPaintSetMaskFilter) \ + PROC(ImpellerPaintSetStrokeCap) \ + PROC(ImpellerPaintSetStrokeJoin) \ + PROC(ImpellerPaintSetStrokeMiter) \ + PROC(ImpellerPaintSetStrokeWidth) \ + PROC(ImpellerParagraphBuilderAddText) \ + PROC(ImpellerParagraphBuilderBuildParagraphNew) \ + PROC(ImpellerParagraphBuilderNew) \ + PROC(ImpellerParagraphBuilderPopStyle) \ + PROC(ImpellerParagraphBuilderPushStyle) \ + PROC(ImpellerParagraphBuilderRelease) \ + PROC(ImpellerParagraphBuilderRetain) \ + PROC(ImpellerParagraphGetAlphabeticBaseline) \ + PROC(ImpellerParagraphGetHeight) \ + PROC(ImpellerParagraphGetIdeographicBaseline) \ + PROC(ImpellerParagraphGetLineCount) \ + PROC(ImpellerParagraphGetLongestLineWidth) \ + PROC(ImpellerParagraphGetMaxIntrinsicWidth) \ + PROC(ImpellerParagraphGetMaxWidth) \ + PROC(ImpellerParagraphGetMinIntrinsicWidth) \ + PROC(ImpellerParagraphRelease) \ + PROC(ImpellerParagraphRetain) \ + PROC(ImpellerParagraphStyleNew) \ + PROC(ImpellerParagraphStyleRelease) \ + PROC(ImpellerParagraphStyleRetain) \ + PROC(ImpellerParagraphStyleSetBackground) \ + PROC(ImpellerParagraphStyleSetFontFamily) \ + PROC(ImpellerParagraphStyleSetFontSize) \ + PROC(ImpellerParagraphStyleSetFontStyle) \ + PROC(ImpellerParagraphStyleSetFontWeight) \ + PROC(ImpellerParagraphStyleSetForeground) \ + PROC(ImpellerParagraphStyleSetHeight) \ + PROC(ImpellerParagraphStyleSetLocale) \ + PROC(ImpellerParagraphStyleSetMaxLines) \ + PROC(ImpellerParagraphStyleSetTextAlignment) \ + PROC(ImpellerParagraphStyleSetTextDirection) \ + PROC(ImpellerPathBuilderAddArc) \ + PROC(ImpellerPathBuilderAddOval) \ + PROC(ImpellerPathBuilderAddRect) \ + PROC(ImpellerPathBuilderAddRoundedRect) \ + PROC(ImpellerPathBuilderClose) \ + PROC(ImpellerPathBuilderCopyPathNew) \ + PROC(ImpellerPathBuilderCubicCurveTo) \ + PROC(ImpellerPathBuilderLineTo) \ + PROC(ImpellerPathBuilderMoveTo) \ + PROC(ImpellerPathBuilderNew) \ + PROC(ImpellerPathBuilderQuadraticCurveTo) \ + PROC(ImpellerPathBuilderRelease) \ + PROC(ImpellerPathBuilderRetain) \ + PROC(ImpellerPathBuilderTakePathNew) \ + PROC(ImpellerPathRelease) \ + PROC(ImpellerPathRetain) \ + PROC(ImpellerSurfaceCreateWrappedFBONew) \ + PROC(ImpellerSurfaceDrawDisplayList) \ + PROC(ImpellerSurfaceRelease) \ + PROC(ImpellerSurfaceRetain) \ + PROC(ImpellerTextureCreateWithContentsNew) \ + PROC(ImpellerTextureCreateWithOpenGLTextureHandleNew) \ + PROC(ImpellerTextureGetOpenGLHandle) \ + PROC(ImpellerTextureRelease) \ + PROC(ImpellerTextureRetain) \ + PROC(ImpellerTypographyContextNew) \ + PROC(ImpellerTypographyContextRegisterFont) \ + PROC(ImpellerTypographyContextRelease) \ + PROC(ImpellerTypographyContextRetain) + +struct ProcTable { + bool Initialize( + const std::function& resolver) { +#define IMPELLER_HPP_PROC(proc) \ + { \ + proc.function = \ + reinterpret_cast(resolver(proc.name)); \ + if (proc.function == nullptr) { \ + return false; \ + } \ + } + IMPELLER_HPP_EACH_PROC(IMPELLER_HPP_PROC) +#undef IMPELLER_HPP_PROC + return true; + } + +#define IMPELLER_HPP_PROC(name) Proc name = {#name, nullptr}; + IMPELLER_HPP_EACH_PROC(IMPELLER_HPP_PROC) +#undef IMPELLER_HPP_PROC +}; + +extern ProcTable gGlobalProcTable; + +enum class AdoptTag { + kAdopt, +}; + +template +class Object { + public: + Object() = default; + + explicit Object(T object) { Reset(object); } + + Object(T object, AdoptTag) : object_(object) {} + + ~Object() { Reset(); } + + Object(Object&& other) { std::swap(object_, other.object_); } + + Object(const Object& other) { Reset(other.Get()); } + + Object& operator=(Object&& other) { + std::swap(object_, other.object_); + return *this; + } + + Object& operator=(const Object& other) { + Reset(other.Get()); + return *this; + } + + T Get() const { return object_; } + + explicit operator bool() const { return object_ != nullptr; } + + private: + T object_ = nullptr; + + void Reset(T other = nullptr) { + if (object_ == other) { + return; + } + if (object_) { + Traits::Release(object_); + object_ = nullptr; + } + if (other) { + Traits::Retain(other); + object_ = other; + } + } + + [[nodiscard]] T Leak() { + T result = object_; + object_ = nullptr; + return result; + } +}; + +#define IMPELLER_HPP_DEFINE_TRAITS(object) \ + struct object##Traits { \ + static void Retain(object ctx) { \ + gGlobalProcTable.object##Retain(ctx); \ + } \ + static void Release(object ctx) { \ + gGlobalProcTable.object##Release(ctx); \ + } \ + }; + +IMPELLER_HPP_DEFINE_TRAITS(ImpellerColorFilter); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerColorSource); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerContext); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerDisplayList); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerDisplayListBuilder); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerImageFilter); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerMaskFilter); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerPaint); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerParagraph); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerParagraphBuilder); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerParagraphStyle); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerPath); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerPathBuilder); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerSurface); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerTexture); +IMPELLER_HPP_DEFINE_TRAITS(ImpellerTypographyContext); + +#undef IMPELLER_HPP_DEFINE_TRAITS + +class Mapping final { + public: + Mapping(const uint8_t* mapping, + size_t size, + std::function release_callback) + : mapping_(mapping), + size_(size), + release_callback_(std::move(release_callback)) {} + + const uint8_t* GetMapping() const { return mapping_; } + + size_t GetSize() const { return size_; } + + private: + const uint8_t* mapping_ = nullptr; + size_t size_ = 0u; + std::function release_callback_; +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerContext +/// +class Context final : public Object { + public: + Context(ImpellerContext context, AdoptTag tag) : Object(context, tag) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerContextCreateOpenGLESNew + /// + static Context OpenGLES( + const std::function& gl_proc_address_resolver) { + struct UserData { + std::function resolver; + }; + UserData user_data; + user_data.resolver = gl_proc_address_resolver; + ImpellerProcAddressCallback callback = [](const char* proc_name, + void* user_data) -> void* { + return reinterpret_cast(user_data)->resolver(proc_name); + }; + return Context( + gGlobalProcTable.ImpellerContextCreateOpenGLESNew(IMPELLER_VERSION, // + callback, // + &user_data // + ), + AdoptTag::kAdopt); + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerTexture +/// +class Texture final : public Object { + public: + Texture(ImpellerTexture texture, AdoptTag adopt) : Object(texture, adopt) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerTextureCreateWithContentsNew + /// + static Texture WithContents(const Context& context, + const ImpellerTextureDescriptor& descriptor, + std::unique_ptr mapping = nullptr) { + if (mapping == nullptr) { + mapping = std::make_unique(nullptr, 0u, nullptr); + } + ImpellerMapping c_mapping = {}; + c_mapping.data = mapping->GetMapping(); + c_mapping.length = mapping->GetSize(); + c_mapping.on_release = [](void* user_data) -> void { + delete reinterpret_cast(user_data); + }; + return Texture(gGlobalProcTable.ImpellerTextureCreateWithContentsNew( + context.Get(), // + &descriptor, // + &c_mapping, // + mapping.release() // + ), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerTextureCreateWithOpenGLTextureHandleNew + /// + static Texture WithOpenGLTexture(const Context& context, + const ImpellerTextureDescriptor& descriptor, + uint64_t handle) { + return Texture( + gGlobalProcTable.ImpellerTextureCreateWithOpenGLTextureHandleNew( + context.Get(), // + &descriptor, // + handle // + ), + AdoptTag::kAdopt); + } + + uint64_t GetOpenGLHandle() const { + return gGlobalProcTable.ImpellerTextureGetOpenGLHandle(Get()); + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerColorFilter +/// +class ColorFilter final + : public Object { + public: + ColorFilter(ImpellerColorFilter filter, AdoptTag tag) : Object(filter, tag) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerColorFilterCreateBlendNew + /// + static ColorFilter Blend(const ImpellerColor& color, ImpellerBlendMode mode) { + return ColorFilter( + gGlobalProcTable.ImpellerColorFilterCreateBlendNew(&color, mode), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerColorFilterCreateColorMatrixNew + /// + static ColorFilter Matrix(const ImpellerColorMatrix& color_matrix) { + return ColorFilter( + gGlobalProcTable.ImpellerColorFilterCreateColorMatrixNew(&color_matrix), + AdoptTag::kAdopt); + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerColorSource +/// +class ColorSource final + : public Object { + public: + ColorSource(ImpellerColorSource source, AdoptTag tag) : Object(source, tag) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerColorSourceCreateConicalGradientNew + /// + static ColorSource ConicalGradient( + const ImpellerPoint& start_center, + float start_radius, + const ImpellerPoint& end_center, + float end_radius, + uint32_t stop_count, + const ImpellerColor* colors, + const float* stops, + ImpellerTileMode tile_mode, + const ImpellerMatrix* transformation = nullptr) { + return ColorSource( + gGlobalProcTable.ImpellerColorSourceCreateConicalGradientNew( + &start_center, // + start_radius, // + &end_center, // + end_radius, // + stop_count, // + colors, // + stops, // + tile_mode, // + transformation // + ), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerColorSourceCreateImageNew + /// + static ColorSource Image(const Texture& image, + ImpellerTileMode horizontal_tile_mode, + ImpellerTileMode vertical_tile_mode, + ImpellerTextureSampling sampling, + const ImpellerMatrix* transformation = nullptr) { + return ColorSource(gGlobalProcTable.ImpellerColorSourceCreateImageNew( + image.Get(), // + horizontal_tile_mode, // + vertical_tile_mode, // + sampling, // + transformation // + ), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerColorSourceCreateLinearGradientNew + /// + static ColorSource LinearGradient( + const ImpellerPoint& start_point, + const ImpellerPoint& end_point, + uint32_t stop_count, + const ImpellerColor* colors, + const float* stops, + ImpellerTileMode tile_mode, + const ImpellerMatrix* transformation = nullptr) { + return ColorSource( + gGlobalProcTable.ImpellerColorSourceCreateLinearGradientNew( + &start_point, + &end_point, // + stop_count, // + colors, // + stops, // + tile_mode, // + transformation // + ), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerColorSourceCreateRadialGradientNew + /// + static ColorSource RadialGradient( + const ImpellerPoint& center, + float radius, + uint32_t stop_count, + const ImpellerColor* colors, + const float* stops, + ImpellerTileMode tile_mode, + const ImpellerMatrix* transformation = nullptr) { + return ColorSource( + gGlobalProcTable.ImpellerColorSourceCreateRadialGradientNew( + ¢er, // + radius, // + stop_count, // + colors, // + stops, // + tile_mode, // + transformation // + ), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerColorSourceCreateSweepGradientNew + /// + static ColorSource SweepGradient( + const ImpellerPoint& center, + float start, + float end, + uint32_t stop_count, + const ImpellerColor* colors, + const float* stops, + ImpellerTileMode tile_mode, + const ImpellerMatrix* transformation = nullptr) { + return ColorSource( + gGlobalProcTable.ImpellerColorSourceCreateSweepGradientNew( + ¢er, // + start, // + end, // + stop_count, // + colors, // + stops, // + tile_mode, // + transformation // + ), + AdoptTag::kAdopt); + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerImageFilter +/// +class ImageFilter final + : public Object { + public: + ImageFilter(ImpellerImageFilter filter, AdoptTag tag) : Object(filter, tag) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerImageFilterCreateBlurNew + /// + static ImageFilter Blur(float x_sigma, + float y_sigma, + ImpellerTileMode tile_mode) { + return ImageFilter(gGlobalProcTable.ImpellerImageFilterCreateBlurNew( + x_sigma, y_sigma, tile_mode), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerImageFilterCreateComposeNew + /// + static ImageFilter Compose(const ImageFilter& outer, + const ImageFilter& inner) { + return ImageFilter(gGlobalProcTable.ImpellerImageFilterCreateComposeNew( + outer.Get(), inner.Get()), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerImageFilterCreateDilateNew + /// + static ImageFilter Dilate(float x_radius, float y_radius) { + return ImageFilter( + gGlobalProcTable.ImpellerImageFilterCreateDilateNew(x_radius, y_radius), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerImageFilterCreateErodeNew + /// + static ImageFilter Erode(float x_radius, float y_radius) { + return ImageFilter( + gGlobalProcTable.ImpellerImageFilterCreateErodeNew(x_radius, y_radius), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerImageFilterCreateMatrixNew + /// + static ImageFilter Matrix(const ImpellerMatrix& matrix, + ImpellerTextureSampling sampling) { + return ImageFilter( + gGlobalProcTable.ImpellerImageFilterCreateMatrixNew(&matrix, sampling), + AdoptTag::kAdopt); + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerMaskFilter +/// +class MaskFilter final + : public Object { + public: + MaskFilter(ImpellerMaskFilter filter, AdoptTag tag) : Object(filter, tag) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerMaskFilterCreateBlurNew + /// + static MaskFilter Blur(ImpellerBlurStyle style, float sigma) { + return MaskFilter( + gGlobalProcTable.ImpellerMaskFilterCreateBlurNew(style, sigma), + AdoptTag::kAdopt); + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerParagraph +/// +class Paragraph final + : public Object { + public: + Paragraph(ImpellerParagraph paragraph, AdoptTag tag) + : Object(paragraph, AdoptTag::kAdopt) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphGetAlphabeticBaseline + /// + float GetAlphabeticBaseline() { + return gGlobalProcTable.ImpellerParagraphGetAlphabeticBaseline(Get()); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphGetHeight + /// + float GetHeight() { + return gGlobalProcTable.ImpellerParagraphGetHeight(Get()); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphGetIdeographicBaseline + /// + float GetIdeographicBaseline() { + return gGlobalProcTable.ImpellerParagraphGetIdeographicBaseline(Get()); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphGetLineCount + /// + uint32_t GetLineCount() { + return gGlobalProcTable.ImpellerParagraphGetLineCount(Get()); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphGetLongestLineWidth + /// + float GetLongestLineWidth() { + return gGlobalProcTable.ImpellerParagraphGetLongestLineWidth(Get()); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphGetMaxIntrinsicWidth + /// + float GetMaxIntrinsicWidth() { + return gGlobalProcTable.ImpellerParagraphGetMaxIntrinsicWidth(Get()); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphGetMaxWidth + /// + float GetMaxWidth() { + return gGlobalProcTable.ImpellerParagraphGetMaxWidth(Get()); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphGetMinIntrinsicWidth + /// + float GetMinIntrinsicWidth() { + return gGlobalProcTable.ImpellerParagraphGetMinIntrinsicWidth(Get()); + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerPaint +/// +class Paint final : public Object { + public: + Paint() : Object(gGlobalProcTable.ImpellerPaintNew(), AdoptTag::kAdopt) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerPaintSetColor + /// + Paint& SetColor(const ImpellerColor& color) { + gGlobalProcTable.ImpellerPaintSetColor(Get(), &color); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPaintSetBlendMode + /// + Paint& SetBlendMode(ImpellerBlendMode mode) { + gGlobalProcTable.ImpellerPaintSetBlendMode(Get(), mode); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPaintSetDrawStyle + /// + Paint& SetDrawStyle(ImpellerDrawStyle style) { + gGlobalProcTable.ImpellerPaintSetDrawStyle(Get(), style); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPaintSetStrokeCap + /// + Paint& SetStrokeCap(ImpellerStrokeCap cap) { + gGlobalProcTable.ImpellerPaintSetStrokeCap(Get(), cap); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPaintSetStrokeJoin + /// + Paint& SetStrokeJoin(ImpellerStrokeJoin join) { + gGlobalProcTable.ImpellerPaintSetStrokeJoin(Get(), join); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPaintSetStrokeWidth + /// + Paint& SetStrokeWidth(float width) { + gGlobalProcTable.ImpellerPaintSetStrokeWidth(Get(), width); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPaintSetStrokeMiter + /// + Paint& SetStrokeMiter(float miter) { + gGlobalProcTable.ImpellerPaintSetStrokeMiter(Get(), miter); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPaintSetColorFilter + /// + Paint& SetColorFilter(const ColorFilter& filter) { + gGlobalProcTable.ImpellerPaintSetColorFilter(Get(), filter.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPaintSetColorSource + /// + Paint& SetColorSource(const ColorSource& source) { + gGlobalProcTable.ImpellerPaintSetColorSource(Get(), source.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPaintSetImageFilter + /// + Paint& SetImageFilter(const ImageFilter& filter) { + gGlobalProcTable.ImpellerPaintSetImageFilter(Get(), filter.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPaintSetMaskFilter + /// + Paint& SetMaskFilter(const MaskFilter& filter) { + gGlobalProcTable.ImpellerPaintSetMaskFilter(Get(), filter.Get()); + return *this; + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerParagraphStyle +/// +class ParagraphStyle final + : public Object { + public: + ParagraphStyle() + : Object(gGlobalProcTable.ImpellerParagraphStyleNew(), AdoptTag::kAdopt) { + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphStyleSetBackground + /// + ParagraphStyle& SetBackground(const Paint& paint) { + gGlobalProcTable.ImpellerParagraphStyleSetBackground(Get(), paint.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphStyleSetFontFamily + /// + ParagraphStyle& SetFontFamily(const char* family_name) { + gGlobalProcTable.ImpellerParagraphStyleSetFontFamily(Get(), family_name); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphStyleSetFontSize + /// + ParagraphStyle& SetFontSize(float size) { + gGlobalProcTable.ImpellerParagraphStyleSetFontSize(Get(), size); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphStyleSetFontStyle + /// + ParagraphStyle& SetFontStyle(ImpellerFontStyle style) { + gGlobalProcTable.ImpellerParagraphStyleSetFontStyle(Get(), style); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphStyleSetFontWeight + /// + ParagraphStyle& SetFontWeight(ImpellerFontWeight weight) { + gGlobalProcTable.ImpellerParagraphStyleSetFontWeight(Get(), weight); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphStyleSetForeground + /// + ParagraphStyle& SetForeground(const Paint& paint) { + gGlobalProcTable.ImpellerParagraphStyleSetForeground(Get(), paint.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphStyleSetHeight + /// + ParagraphStyle& SetHeight(float height) { + gGlobalProcTable.ImpellerParagraphStyleSetHeight(Get(), height); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphStyleSetLocale + /// + ParagraphStyle& SetLocale(const char* locale) { + gGlobalProcTable.ImpellerParagraphStyleSetLocale(Get(), locale); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphStyleSetMaxLines + /// + ParagraphStyle& SetMaxLines(uint32_t max_lines) { + gGlobalProcTable.ImpellerParagraphStyleSetMaxLines(Get(), max_lines); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphStyleSetTextAlignment + /// + ParagraphStyle& SetTextAlignment(ImpellerTextAlignment align) { + gGlobalProcTable.ImpellerParagraphStyleSetTextAlignment(Get(), align); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphStyleSetTextDirection + /// + ParagraphStyle& SetTextDirection(ImpellerTextDirection direction) { + gGlobalProcTable.ImpellerParagraphStyleSetTextDirection(Get(), direction); + return *this; + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerTypographyContext +/// +class TypographyContext final : public Object { + public: + TypographyContext() + : Object(gGlobalProcTable.ImpellerTypographyContextNew(), + AdoptTag::kAdopt) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerTypographyContextRegisterFont + /// + bool RegisterFont(std::unique_ptr mapping, + const char* optional_family_name_alias = nullptr) { + if (!mapping) { + return false; + } + ImpellerMapping c_mapping = {}; + c_mapping.data = mapping->GetMapping(); + c_mapping.length = mapping->GetSize(); + c_mapping.on_release = [](void* user_data) { + delete reinterpret_cast(user_data); + }; + return gGlobalProcTable.ImpellerTypographyContextRegisterFont( + Get(), // + &c_mapping, // + mapping.release(), // + optional_family_name_alias // + ); + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerParagraphBuilder +/// +class ParagraphBuilder final + : public Object { + public: + explicit ParagraphBuilder(const TypographyContext& context) + : Object(gGlobalProcTable.ImpellerParagraphBuilderNew(context.Get()), + AdoptTag::kAdopt) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphBuilderBuildParagraphNew + /// + Paragraph Build(float width) { + return Paragraph( + gGlobalProcTable.ImpellerParagraphBuilderBuildParagraphNew(Get(), // + width // + ), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphBuilderPushStyle + /// + ParagraphBuilder& PushStyle(const ParagraphStyle& style) { + gGlobalProcTable.ImpellerParagraphBuilderPushStyle(Get(), style.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphBuilderPopStyle + /// + ParagraphBuilder& PopStyle() { + gGlobalProcTable.ImpellerParagraphBuilderPopStyle(Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphBuilderAddText + /// + ParagraphBuilder& AddText(const uint8_t* utf8_data, uint32_t length) { + gGlobalProcTable.ImpellerParagraphBuilderAddText(Get(), utf8_data, length); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphBuilderAddText + /// + ParagraphBuilder& AddText(const std::string& string) { + return AddText(reinterpret_cast(string.data()), + string.size()); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerParagraphBuilderAddText + /// + ParagraphBuilder& AddText(const std::string_view& string) { + return AddText(reinterpret_cast(string.data()), + string.size()); + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerPath +/// +class Path final : public Object { + public: + Path(ImpellerPath path, AdoptTag tag) : Object(path, tag) {} +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerPathBuilder +/// +class PathBuilder final + : public Object { + public: + PathBuilder() + : Object(gGlobalProcTable.ImpellerPathBuilderNew(), AdoptTag::kAdopt) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerPathBuilderCopyPathNew + /// + Path BuildCopy(ImpellerFillType fill = + ImpellerFillType::kImpellerFillTypeNonZero) const { + return Path(gGlobalProcTable.ImpellerPathBuilderCopyPathNew(Get(), fill), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPathBuilderTakePathNew + /// + Path Build( + ImpellerFillType fill = ImpellerFillType::kImpellerFillTypeNonZero) { + return Path(gGlobalProcTable.ImpellerPathBuilderTakePathNew(Get(), fill), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPathBuilderAddArc + /// + PathBuilder& AddArc(const ImpellerRect& oval_bounds, + float start_angle_degrees, + float end_angle_degrees) { + gGlobalProcTable.ImpellerPathBuilderAddArc(Get(), // + &oval_bounds, // + start_angle_degrees, // + end_angle_degrees // + ); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPathBuilderAddOval + /// + PathBuilder& AddOval(const ImpellerRect& oval_bounds) { + gGlobalProcTable.ImpellerPathBuilderAddOval(Get(), &oval_bounds); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPathBuilderAddRect + /// + PathBuilder& AddRect(const ImpellerRect& rect) { + gGlobalProcTable.ImpellerPathBuilderAddRect(Get(), &rect); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPathBuilderAddRoundedRect + /// + PathBuilder& AddRoundedRect(const ImpellerRect& rect, + const ImpellerRoundingRadii& rounding_radii) { + gGlobalProcTable.ImpellerPathBuilderAddRoundedRect(Get(), &rect, + &rounding_radii); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPathBuilderClose + /// + PathBuilder& Close() { + gGlobalProcTable.ImpellerPathBuilderClose(Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPathBuilderCubicCurveTo + /// + PathBuilder& CubicCurveTo(const ImpellerPoint& control_point_1, + const ImpellerPoint& control_point_2, + const ImpellerPoint& end_point) { + gGlobalProcTable.ImpellerPathBuilderCubicCurveTo( + Get(), &control_point_1, &control_point_2, &end_point); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPathBuilderLineTo + /// + PathBuilder& LineTo(const ImpellerPoint& location) { + gGlobalProcTable.ImpellerPathBuilderLineTo(Get(), &location); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPathBuilderMoveTo + /// + PathBuilder& MoveTo(const ImpellerPoint& location) { + gGlobalProcTable.ImpellerPathBuilderMoveTo(Get(), &location); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerPathBuilderQuadraticCurveTo + /// + PathBuilder& QuadraticCurveTo(const ImpellerPoint& control_point, + const ImpellerPoint& end_point) { + gGlobalProcTable.ImpellerPathBuilderQuadraticCurveTo(Get(), &control_point, + &end_point); + return *this; + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerDisplayList +/// +class DisplayList final + : public Object { + public: + DisplayList(ImpellerDisplayList display_list, AdoptTag tag) + : Object(display_list, tag) {} +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerSurface +/// +class Surface final : public Object { + public: + explicit Surface(ImpellerSurface surface) : Object(surface) {} + + Surface(ImpellerSurface surface, AdoptTag tag) : Object(surface, tag) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerSurfaceCreateWrappedFBONew + /// + static Surface WrapFBO(const Context& context, + uint64_t fbo, + ImpellerPixelFormat format, + const ImpellerISize& size) { + return Surface( + gGlobalProcTable.ImpellerSurfaceCreateWrappedFBONew(context.Get(), // + fbo, // + format, // + &size // + ), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerSurfaceDrawDisplayList + /// + bool Draw(const DisplayList& display_list) { + return gGlobalProcTable.ImpellerSurfaceDrawDisplayList(Get(), + display_list.Get()); + } +}; + +//------------------------------------------------------------------------------ +/// @see ImpellerDisplayListBuilder +/// +class DisplayListBuilder final + : public Object { + public: + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderNew + /// + explicit DisplayListBuilder(const ImpellerRect* cull_rect = nullptr) + : Object(gGlobalProcTable.ImpellerDisplayListBuilderNew(cull_rect), + AdoptTag::kAdopt) {} + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderCreateDisplayListNew + /// + DisplayList Build() { + return DisplayList( + gGlobalProcTable.ImpellerDisplayListBuilderCreateDisplayListNew(Get()), + AdoptTag::kAdopt); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderClipOval + /// + DisplayListBuilder& ClipOval(const ImpellerRect& oval_bounds, + ImpellerClipOperation op) { + gGlobalProcTable.ImpellerDisplayListBuilderClipOval(Get(), // + &oval_bounds, // + op); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderClipPath + /// + DisplayListBuilder& ClipPath(const Path& path, ImpellerClipOperation op) { + gGlobalProcTable.ImpellerDisplayListBuilderClipPath(Get(), // + path.Get(), // + op); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderClipRect + /// + DisplayListBuilder& ClipRect(const ImpellerRect& rect, + ImpellerClipOperation op) { + gGlobalProcTable.ImpellerDisplayListBuilderClipRect(Get(), // + &rect, // + op); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderClipRoundedRect + /// + DisplayListBuilder& ClipRoundedRect(const ImpellerRect& rect, + const ImpellerRoundingRadii& radii, + ImpellerClipOperation op) { + gGlobalProcTable.ImpellerDisplayListBuilderClipRoundedRect(Get(), // + &rect, // + &radii, // + op // + ); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawDashedLine + /// + DisplayListBuilder& DrawDashedLine(const ImpellerPoint& from, + const ImpellerPoint& to, + float on_length, + float off_length, + const Paint& paint) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawDashedLine(Get(), // + &from, // + &to, // + on_length, // + off_length, // + paint.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawDisplayList + /// + DisplayListBuilder& DrawDisplayList(const DisplayList& display_list, + float opacity = 1.0f) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawDisplayList( + Get(), display_list.Get(), opacity); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawLine + /// + DisplayListBuilder& DrawLine(const ImpellerPoint& from, + const ImpellerPoint& to, + const Paint& paint) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawLine(Get(), // + &from, // + &to, // + paint.Get() // + ); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawOval + /// + DisplayListBuilder& DrawOval(const ImpellerRect& oval_bounds, + const Paint& paint) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawOval(Get(), // + &oval_bounds, // + paint.Get() // + ); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawPaint + /// + DisplayListBuilder& DrawPaint(const Paint& paint) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawPaint(Get(), paint.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawParagraph + /// + DisplayListBuilder& DrawParagraph(const Paragraph& paragraph, + const ImpellerPoint& point) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawParagraph( + Get(), paragraph.Get(), &point); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawPath + /// + DisplayListBuilder& DrawPath(const Path& path, const Paint& paint) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawPath(Get(), path.Get(), + paint.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawRect + /// + DisplayListBuilder& DrawRect(const ImpellerRect& rect, const Paint& paint) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawRect(Get(), &rect, + paint.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawRoundedRect + /// + DisplayListBuilder& DrawRoundedRect(const ImpellerRect& rect, + const ImpellerRoundingRadii& radii, + const Paint& paint) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawRoundedRect(Get(), // + &rect, // + &radii, // + paint.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawRoundedRectDifference + /// + DisplayListBuilder& DrawRoundedRectDifference( + const ImpellerRect& outer_rect, + const ImpellerRoundingRadii& outer_radii, + const ImpellerRect& inner_rect, + const ImpellerRoundingRadii& inner_radii, + const Paint& paint) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawRoundedRectDifference( + Get(), // + &outer_rect, // + &outer_radii, // + &inner_rect, // + &inner_radii, // + paint.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawTexture + /// + DisplayListBuilder& DrawTexture(const Texture& texture, + const ImpellerPoint& point, + ImpellerTextureSampling sampling, + const Paint& paint) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawTexture( + Get(), texture.Get(), &point, sampling, paint.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderDrawTextureRect + /// + DisplayListBuilder& DrawTextureRect(const Texture& texture, + const ImpellerRect& src_rect, + const ImpellerRect& dst_rect, + ImpellerTextureSampling sampling, + const Paint& paint) { + gGlobalProcTable.ImpellerDisplayListBuilderDrawTextureRect( + Get(), texture.Get(), // + &src_rect, // + &dst_rect, // + sampling, // + paint.Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderGetSaveCount + /// + uint32_t GetSaveCount() { + return gGlobalProcTable.ImpellerDisplayListBuilderGetSaveCount(Get()); + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderGetTransform + /// + ImpellerMatrix GetTransform() { + ImpellerMatrix result; + gGlobalProcTable.ImpellerDisplayListBuilderGetTransform(Get(), &result); + return result; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderResetTransform + /// + DisplayListBuilder& ResetTransform() { + gGlobalProcTable.ImpellerDisplayListBuilderResetTransform(Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderRestore + /// + DisplayListBuilder& Restore() { + gGlobalProcTable.ImpellerDisplayListBuilderRestore(Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderRestoreToCount + /// + DisplayListBuilder& RestoreToCount(uint32_t count) { + gGlobalProcTable.ImpellerDisplayListBuilderRestoreToCount(Get(), // + count); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderRotate + /// + DisplayListBuilder& Rotate(float angle_degrees) { + gGlobalProcTable.ImpellerDisplayListBuilderRotate(Get(), // + angle_degrees); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderSave + /// + DisplayListBuilder& Save() { + gGlobalProcTable.ImpellerDisplayListBuilderSave(Get()); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderSaveLayer + /// + DisplayListBuilder& SaveLayer(const ImpellerRect& bounds, + const Paint* paint = nullptr, + const ImageFilter* backdrop = nullptr) { + gGlobalProcTable.ImpellerDisplayListBuilderSaveLayer( + Get(), // + &bounds, // + paint ? paint->Get() : NULL, // + backdrop ? backdrop->Get() : NULL // + ); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderScale + /// + DisplayListBuilder& Scale(float x_scale, float y_scale) { + gGlobalProcTable.ImpellerDisplayListBuilderScale(Get(), // + x_scale, // + y_scale); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderSetTransform + /// + DisplayListBuilder& SetTransform(const ImpellerMatrix& transform) { + gGlobalProcTable.ImpellerDisplayListBuilderSetTransform(Get(), &transform); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderTransform + /// + DisplayListBuilder& Transform(const ImpellerMatrix& transform) { + gGlobalProcTable.ImpellerDisplayListBuilderTransform(Get(), &transform); + return *this; + } + + //---------------------------------------------------------------------------- + /// @see ImpellerDisplayListBuilderTranslate + /// + DisplayListBuilder& Translate(float x_translation, float y_translation) { + gGlobalProcTable.ImpellerDisplayListBuilderTranslate(Get(), // + x_translation, // + y_translation // + ); + return *this; + } +}; + +} // namespace IMPELLER_HPP_NAMESPACE + +#endif // FLUTTER_IMPELLER_TOOLKIT_INTEROP_IMPELLER_HPP_ diff --git a/fml/platform/darwin/scoped_block.mm b/impeller/toolkit/interop/impeller_cc.cc similarity index 54% rename from fml/platform/darwin/scoped_block.mm rename to impeller/toolkit/interop/impeller_cc.cc index 3969ed48861bd..69323c90fd7fe 100644 --- a/fml/platform/darwin/scoped_block.mm +++ b/impeller/toolkit/interop/impeller_cc.cc @@ -2,10 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/fml/platform/darwin/scoped_block.h" +#include "impeller.hpp" -namespace fml { - -// - -} // namespace fml +// This C++ file is only present to make sure the C++ header can be imported +// cleanly in a C++ translation unit. diff --git a/impeller/toolkit/interop/impeller_unittests.cc b/impeller/toolkit/interop/impeller_unittests.cc index a386aaf956359..19b64cab4fd39 100644 --- a/impeller/toolkit/interop/impeller_unittests.cc +++ b/impeller/toolkit/interop/impeller_unittests.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flutter/fml/native_library.h" #include "flutter/testing/testing.h" #include "impeller/base/allocation.h" #include "impeller/renderer/backend/gles/context_gles.h" @@ -10,6 +11,7 @@ #include "impeller/toolkit/interop/dl_builder.h" #include "impeller/toolkit/interop/formats.h" #include "impeller/toolkit/interop/impeller.h" +#include "impeller/toolkit/interop/impeller.hpp" #include "impeller/toolkit/interop/paint.h" #include "impeller/toolkit/interop/paragraph.h" #include "impeller/toolkit/interop/paragraph_builder.h" @@ -30,18 +32,14 @@ TEST_P(InteropPlaygroundTest, CanCreateContext) { } TEST_P(InteropPlaygroundTest, CanCreateDisplayListBuilder) { - auto builder = ImpellerDisplayListBuilderNew(nullptr); - ASSERT_NE(builder, nullptr); - ImpellerMatrix matrix; - ImpellerDisplayListBuilderGetTransform(builder, &matrix); - ASSERT_TRUE(ToImpellerType(matrix).IsIdentity()); - ASSERT_EQ(ImpellerDisplayListBuilderGetSaveCount(builder), 1u); - ImpellerDisplayListBuilderSave(builder); - ASSERT_EQ(ImpellerDisplayListBuilderGetSaveCount(builder), 2u); - // ImpellerDisplayListBuilderSave(nullptr); <-- Compiler error. - ImpellerDisplayListBuilderRestore(builder); - ASSERT_EQ(ImpellerDisplayListBuilderGetSaveCount(builder), 1u); - ImpellerDisplayListBuilderRelease(builder); + hpp::DisplayListBuilder builder; + ASSERT_TRUE(builder); + ASSERT_TRUE(ToImpellerType(builder.GetTransform()).IsIdentity()); + ASSERT_EQ(builder.GetSaveCount(), 1u); + builder.Save(); + ASSERT_EQ(builder.GetSaveCount(), 2u); + builder.Restore(); + ASSERT_EQ(builder.GetSaveCount(), 1u); } TEST_P(InteropPlaygroundTest, CanCreateSurface) { @@ -69,6 +67,15 @@ TEST_P(InteropPlaygroundTest, CanDrawRect) { color = {1.0, 0.0, 0.0, 1.0}; ImpellerPaintSetColor(paint.GetC(), &color); ImpellerDisplayListBuilderTranslate(builder.GetC(), 110, 210); + ImpellerMatrix scale_transform = { + // clang-format off + 2.0, 0.0, 0.0, 0.0, // + 0.0, 2.0, 0.0, 0.0, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 1.0, // + // clang-format on + }; + ImpellerDisplayListBuilderTransform(builder.GetC(), &scale_transform); ImpellerDisplayListBuilderDrawRect(builder.GetC(), &rect, paint.GetC()); auto dl = Adopt( ImpellerDisplayListBuilderCreateDisplayListNew(builder.GetC())); @@ -183,64 +190,128 @@ TEST_P(InteropPlaygroundTest, CanCreateOpenGLImage) { })); } +TEST_P(InteropPlaygroundTest, ClearsOpenGLStancilStateAfterTransition) { + auto context = GetInteropContext(); + auto impeller_context = context->GetContext(); + if (impeller_context->GetBackendType() != + impeller::Context::BackendType::kOpenGLES) { + GTEST_SKIP() << "This test works with OpenGL handles is only suitable for " + "that backend."; + return; + } + const auto& gl_context = ContextGLES::Cast(*impeller_context); + const auto& gl = gl_context.GetReactor()->GetProcTable(); + auto builder = + Adopt(ImpellerDisplayListBuilderNew(nullptr)); + auto paint = Adopt(ImpellerPaintNew()); + ImpellerColor color = {0.0, 0.0, 1.0, 1.0}; + ImpellerPaintSetColor(paint.GetC(), &color); + ImpellerRect rect = {10, 20, 100, 200}; + ImpellerDisplayListBuilderDrawRect(builder.GetC(), &rect, paint.GetC()); + color = {1.0, 0.0, 0.0, 1.0}; + ImpellerPaintSetColor(paint.GetC(), &color); + ImpellerDisplayListBuilderTranslate(builder.GetC(), 110, 210); + ImpellerDisplayListBuilderClipRect(builder.GetC(), &rect, + kImpellerClipOperationDifference); + ImpellerDisplayListBuilderDrawRect(builder.GetC(), &rect, paint.GetC()); + auto dl = Adopt( + ImpellerDisplayListBuilderCreateDisplayListNew(builder.GetC())); + ASSERT_TRUE(dl); + ASSERT_TRUE( + OpenPlaygroundHere([&](const auto& context, const auto& surface) -> bool { + ImpellerSurfaceDrawDisplayList(surface.GetC(), dl.GetC()); + // OpenGL state is reset even though the operations above enable a + // stencil check. + GLboolean stencil_enabled = true; + gl.GetBooleanv(GL_STENCIL_TEST, &stencil_enabled); + return stencil_enabled == GL_FALSE; + })); +} + TEST_P(InteropPlaygroundTest, CanCreateParagraphs) { // Create a typography context. - auto type_context = Adopt(ImpellerTypographyContextNew()); + hpp::TypographyContext type_context; ASSERT_TRUE(type_context); // Create a builder. - auto builder = - Adopt(ImpellerParagraphBuilderNew(type_context.GetC())); + hpp::ParagraphBuilder builder(type_context); ASSERT_TRUE(builder); // Create a paragraph style with the font size and foreground and background // colors. - auto style = Adopt(ImpellerParagraphStyleNew()); + hpp::ParagraphStyle style; ASSERT_TRUE(style); - ImpellerParagraphStyleSetFontSize(style.GetC(), 150.0f); + style.SetFontSize(150.0f); + style.SetHeight(2.0f); { - auto paint = Adopt(ImpellerPaintNew()); + hpp::Paint paint; ASSERT_TRUE(paint); - ImpellerColor color = {1.0, 0.0, 0.0, 1.0}; - ImpellerPaintSetColor(paint.GetC(), &color); - ImpellerParagraphStyleSetForeground(style.GetC(), paint.GetC()); + paint.SetColor({1.0, 0.0, 0.0, 1.0}); + style.SetForeground(paint); } { - auto paint = Adopt(ImpellerPaintNew()); - ASSERT_TRUE(paint); - ImpellerColor color = {1.0, 1.0, 1.0, 1.0}; - ImpellerPaintSetColor(paint.GetC(), &color); - ImpellerParagraphStyleSetBackground(style.GetC(), paint.GetC()); + hpp::Paint paint; + paint.SetColor({1.0, 1.0, 1.0, 1.0}); + style.SetBackground(paint); } // Push the style onto the style stack. - ImpellerParagraphBuilderPushStyle(builder.GetC(), style.GetC()); + builder.PushStyle(style); std::string text = "the ⚡️ quick ⚡️ brown 🦊 fox jumps over the lazy dog 🐶."; // Add the paragraph text data. - ImpellerParagraphBuilderAddText(builder.GetC(), - reinterpret_cast(text.data()), - text.size()); + builder.AddText(text); // Layout and build the paragraph. - auto paragraph = Adopt( - ImpellerParagraphBuilderBuildParagraphNew(builder.GetC(), 1200.0f)); + auto paragraph = builder.Build(1200.0f); ASSERT_TRUE(paragraph); // Create a display list with just the paragraph drawn into it. - auto dl_builder = - Adopt(ImpellerDisplayListBuilderNew(nullptr)); - ImpellerPoint point = {20, 20}; - ImpellerDisplayListBuilderDrawParagraph(dl_builder.GetC(), paragraph.GetC(), - &point); - auto dl = Adopt( - ImpellerDisplayListBuilderCreateDisplayListNew(dl_builder.GetC())); + hpp::DisplayListBuilder dl_builder; + dl_builder.DrawParagraph(paragraph, {20, 20}); + + // Build the display list. + auto dl = dl_builder.Build(); ASSERT_TRUE( OpenPlaygroundHere([&](const auto& context, const auto& surface) -> bool { - ImpellerSurfaceDrawDisplayList(surface.GetC(), dl.GetC()); + hpp::Surface window(surface.GetC()); + window.Draw(dl); + return true; + })); +} + +TEST_P(InteropPlaygroundTest, CanCreateShapes) { + hpp::DisplayListBuilder builder; + + hpp::Paint red_paint; + red_paint.SetColor({1.0, 0.0, 0.0, 1.0}); + red_paint.SetStrokeWidth(10.0); + + builder.Translate(10, 10); + builder.DrawRect({0, 0, 100, 100}, red_paint); + builder.Translate(100, 100); + builder.DrawOval({0, 0, 100, 100}, red_paint); + builder.Translate(100, 100); + builder.DrawLine({0, 0}, {100, 100}, red_paint); + + builder.Translate(100, 100); + ImpellerRoundingRadii radii = {}; + radii.top_left = {10, 10}; + radii.bottom_right = {10, 10}; + builder.DrawRoundedRect({0, 0, 100, 100}, radii, red_paint); + + builder.Translate(100, 100); + builder.DrawPath(hpp::PathBuilder{}.AddOval({0, 0, 100, 100}).Build(), + red_paint); + + auto dl = builder.Build(); + ASSERT_TRUE( + OpenPlaygroundHere([&](const auto& context, const auto& surface) -> bool { + hpp::Surface window(surface.GetC()); + window.Draw(dl); return true; })); } @@ -319,97 +390,83 @@ TEST_P(InteropPlaygroundTest, CanCreateParagraphsWithCustomFont) { })); } // namespace impeller::interop::testing -static void DrawTextFrame(ImpellerTypographyContext tc, - ImpellerDisplayListBuilder builder, - ImpellerParagraphStyle p_style, - ImpellerPaint bg, +static void DrawTextFrame(const hpp::TypographyContext& tc, + hpp::DisplayListBuilder& builder, + hpp::ParagraphStyle& p_style, + const hpp::Paint& bg, ImpellerColor color, ImpellerTextAlignment align, float x_offset) { const char text[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; - ImpellerPaint fg = ImpellerPaintNew(); + hpp::Paint fg; // Draw a box. - ImpellerPaintSetColor(fg, &color); - ImpellerPaintSetDrawStyle(fg, kImpellerDrawStyleStroke); + fg.SetColor(color); + fg.SetDrawStyle(kImpellerDrawStyleStroke); ImpellerRect box_rect = {10 + x_offset, 10, 200, 200}; - ImpellerDisplayListBuilderDrawRect(builder, &box_rect, fg); + builder.DrawRect(box_rect, fg); // Draw text. - ImpellerPaintSetDrawStyle(fg, kImpellerDrawStyleFill); - ImpellerParagraphStyleSetForeground(p_style, fg); - ImpellerParagraphStyleSetBackground(p_style, bg); - ImpellerParagraphStyleSetTextAlignment(p_style, align); - ImpellerParagraphBuilder p_builder = ImpellerParagraphBuilderNew(tc); - ImpellerParagraphBuilderPushStyle(p_builder, p_style); - ImpellerParagraphBuilderAddText(p_builder, (const uint8_t*)text, - sizeof(text)); - ImpellerParagraph left_p = ImpellerParagraphBuilderBuildParagraphNew( - p_builder, box_rect.width - 20.0); + fg.SetDrawStyle(kImpellerDrawStyleFill); + p_style.SetForeground(fg); + p_style.SetBackground(bg); + p_style.SetTextAlignment(align); + + hpp::ParagraphBuilder p_builder(tc); + p_builder.PushStyle(p_style); + p_builder.AddText((const uint8_t*)text, sizeof(text)); + + auto left_p = p_builder.Build(box_rect.width - 20.0); ImpellerPoint pt = {20.0f + x_offset, 20.0f}; - float w = ImpellerParagraphGetMaxWidth(left_p); - float h = ImpellerParagraphGetHeight(left_p); - ImpellerDisplayListBuilderDrawParagraph(builder, left_p, &pt); - ImpellerPaintSetDrawStyle(fg, kImpellerDrawStyleStroke); + float w = left_p.GetMaxWidth(); + float h = left_p.GetHeight(); + builder.DrawParagraph(left_p, pt); + fg.SetDrawStyle(kImpellerDrawStyleStroke); // Draw an inner box around the paragraph layout. ImpellerRect inner_box_rect = {pt.x, pt.y, w, h}; - ImpellerDisplayListBuilderDrawRect(builder, &inner_box_rect, fg); - - ImpellerParagraphRelease(left_p); - ImpellerParagraphBuilderRelease(p_builder); - ImpellerPaintRelease(fg); + builder.DrawRect(inner_box_rect, fg); } TEST_P(InteropPlaygroundTest, CanRenderTextAlignments) { - ImpellerTypographyContext tc = ImpellerTypographyContextNew(); + hpp::TypographyContext tc; - ImpellerDisplayList dl = NULL; + hpp::DisplayListBuilder builder; + hpp::Paint bg; + hpp::ParagraphStyle p_style; + p_style.SetFontFamily("Roboto"); + p_style.SetFontSize(24.0); + p_style.SetFontWeight(kImpellerFontWeight400); - { - ImpellerDisplayListBuilder builder = ImpellerDisplayListBuilderNew(NULL); - ImpellerPaint bg = ImpellerPaintNew(); - ImpellerParagraphStyle p_style = ImpellerParagraphStyleNew(); - ImpellerParagraphStyleSetFontFamily(p_style, "Roboto"); - ImpellerParagraphStyleSetFontSize(p_style, 24.0); - ImpellerParagraphStyleSetFontWeight(p_style, kImpellerFontWeight400); - - // Clear the background to a white color. - ImpellerColor clear_color = {1.0, 1.0, 1.0, 1.0}; - ImpellerPaintSetColor(bg, &clear_color); - ImpellerDisplayListBuilderDrawPaint(builder, bg); - - // Draw red, left-aligned text. - ImpellerColor red = {1.0, 0.0, 0.0, 1.0}; - DrawTextFrame(tc, builder, p_style, bg, red, kImpellerTextAlignmentLeft, - 0.0); - - // Draw green, centered text. - ImpellerColor green = {0.0, 1.0, 0.0, 1.0}; - DrawTextFrame(tc, builder, p_style, bg, green, kImpellerTextAlignmentCenter, - 220.0); - - // Draw blue, right-aligned text. - ImpellerColor blue = {0.0, 0.0, 1.0, 1.0}; - DrawTextFrame(tc, builder, p_style, bg, blue, kImpellerTextAlignmentRight, - 440.0); - - dl = ImpellerDisplayListBuilderCreateDisplayListNew(builder); - - ImpellerPaintRelease(bg); - ImpellerDisplayListBuilderRelease(builder); - } + // Clear the background to a white color. + ImpellerColor clear_color = {1.0, 1.0, 1.0, 1.0}; + bg.SetColor(clear_color); + builder.DrawPaint(bg); + + // Draw red, left-aligned text. + ImpellerColor red = {1.0, 0.0, 0.0, 1.0}; + DrawTextFrame(tc, builder, p_style, bg, red, kImpellerTextAlignmentLeft, 0.0); + + // Draw green, centered text. + ImpellerColor green = {0.0, 1.0, 0.0, 1.0}; + DrawTextFrame(tc, builder, p_style, bg, green, kImpellerTextAlignmentCenter, + 220.0); + + // Draw blue, right-aligned text. + ImpellerColor blue = {0.0, 0.0, 1.0, 1.0}; + DrawTextFrame(tc, builder, p_style, bg, blue, kImpellerTextAlignmentRight, + 440.0); + + auto dl = builder.Build(); ASSERT_TRUE( OpenPlaygroundHere([&](const auto& context, const auto& surface) -> bool { - ImpellerSurfaceDrawDisplayList(surface.GetC(), dl); + hpp::Surface window(surface.GetC()); + window.Draw(dl); return true; })); - - ImpellerDisplayListRelease(dl); - ImpellerTypographyContextRelease(tc); } } // namespace impeller::interop::testing diff --git a/impeller/toolkit/interop/paragraph_style.cc b/impeller/toolkit/interop/paragraph_style.cc index 55a7fcdfdc8fc..d18943efcbf88 100644 --- a/impeller/toolkit/interop/paragraph_style.cc +++ b/impeller/toolkit/interop/paragraph_style.cc @@ -28,6 +28,7 @@ void ParagraphStyle::SetFontSize(double size) { void ParagraphStyle::SetHeight(double height) { style_.height = height; + style_.has_height_override = (height != 0.0); } void ParagraphStyle::SetTextAlignment(txt::TextAlign alignment) { diff --git a/impeller/toolkit/interop/playground_test.cc b/impeller/toolkit/interop/playground_test.cc index f23a9276878b6..03b100dbe9e6e 100644 --- a/impeller/toolkit/interop/playground_test.cc +++ b/impeller/toolkit/interop/playground_test.cc @@ -4,9 +4,26 @@ #include "impeller/toolkit/interop/playground_test.h" +#include "impeller/toolkit/interop/impeller.hpp" + +namespace IMPELLER_HPP_NAMESPACE { +ProcTable gGlobalProcTable; +} // namespace IMPELLER_HPP_NAMESPACE + namespace impeller::interop::testing { -PlaygroundTest::PlaygroundTest() = default; +PlaygroundTest::PlaygroundTest() { + static std::once_flag sOnceFlag; + std::call_once(sOnceFlag, []() { + std::map proc_map; +#define IMPELLER_HPP_PROC(name) \ + proc_map[#name] = reinterpret_cast(&name); + IMPELLER_HPP_EACH_PROC(IMPELLER_HPP_PROC) +#undef IMPELLER_HPP_PROC + hpp::gGlobalProcTable.Initialize( + [&](auto name) { return proc_map.at(name); }); + }); +} PlaygroundTest::~PlaygroundTest() = default; diff --git a/impeller/toolkit/interop/surface.cc b/impeller/toolkit/interop/surface.cc index 11e6def21ef5d..b5e7f7282439f 100644 --- a/impeller/toolkit/interop/surface.cc +++ b/impeller/toolkit/interop/surface.cc @@ -62,8 +62,10 @@ bool Surface::DrawDisplayList(const DisplayList& dl) const { auto skia_cull_rect = SkIRect::MakeWH(cull_rect.GetWidth(), cull_rect.GetHeight()); - return RenderToOnscreen(content_context, render_target, display_list, - skia_cull_rect, /*reset_host_buffer=*/true); + auto result = RenderToOnscreen(content_context, render_target, display_list, + skia_cull_rect, /*reset_host_buffer=*/true); + context_->GetContext()->ResetThreadLocalState(); + return result; } } // namespace impeller::interop diff --git a/impeller/tools/compiler.gni b/impeller/tools/compiler.gni index 6c2d080865ee1..180600b8ea93d 100644 --- a/impeller/tools/compiler.gni +++ b/impeller/tools/compiler.gni @@ -36,6 +36,7 @@ template("_impellerc") { # --vulkan # --runtime-stage-metal # --runtime-stage-gles +# --runtime-stage-gles3 # --runtime-stage-vulkan # Not required for --shader_bundle mode. # Required: sl_file_extension The file extension to use for output files. diff --git a/impeller/tools/component.gni b/impeller/tools/component.gni index 00df9f2ee5294..d6fdef6b5e914 100644 --- a/impeller/tools/component.gni +++ b/impeller/tools/component.gni @@ -43,7 +43,7 @@ template("impeller_component") { } if (is_ios || is_mac) { - cflags_objc += flutter_cflags_objc_arc + cflags_objc += flutter_cflags_objc } if (!defined(invoker.cflags_objcc)) { @@ -51,7 +51,7 @@ template("impeller_component") { } if (is_ios || is_mac) { - cflags_objcc += flutter_cflags_objcc_arc + cflags_objcc += flutter_cflags_objcc } } } diff --git a/impeller/tools/malioc.json b/impeller/tools/malioc.json index aafabbc2af84b..96e0defe7adcd 100644 --- a/impeller/tools/malioc.json +++ b/impeller/tools/malioc.json @@ -20,8 +20,8 @@ "arith_fma" ], "longest_path_cycles": [ - 0.578125, - 0.578125, + 0.53125, + 0.53125, 0.21875, 0.125, 0.0, @@ -42,8 +42,8 @@ "arith_fma" ], "shortest_path_cycles": [ - 0.53125, - 0.53125, + 0.484375, + 0.484375, 0.203125, 0.0625, 0.0, @@ -55,8 +55,8 @@ "arith_fma" ], "total_cycles": [ - 0.578125, - 0.578125, + 0.53125, + 0.53125, 0.296875, 0.125, 0.0, @@ -586,7 +586,7 @@ "shortest_path_cycles": [ 0.5, 0.109375, - 0.296875, + 0.28125, 0.5, 0.0, 0.25, @@ -596,9 +596,9 @@ "load_store" ], "total_cycles": [ - 1.4249999523162842, + 1.40625, 0.862500011920929, - 1.4249999523162842, + 1.40625, 0.875, 4.0, 0.25, @@ -613,6 +613,76 @@ } } }, + "flutter/impeller/entity/conical_gradient_uniform_fill.frag.vkspv": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/conical_gradient_uniform_fill.frag.vkspv", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 70, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null, + null, + null, + null, + null + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "varying" + ], + "shortest_path_cycles": [ + 0.1875, + 0.0625, + 0.1875, + 0.0, + 0.0, + 0.25, + 0.0 + ], + "total_bound_pipelines": [ + "load_store" + ], + "total_cycles": [ + 1.5499999523162842, + 0.800000011920929, + 1.5499999523162842, + 0.5, + 4.0, + 0.25, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 36, + "work_registers_used": 18 + } + } + } + }, "flutter/impeller/entity/fast_gradient.frag.vkspv": { "Mali-G78": { "core": "Mali-G78", @@ -636,7 +706,7 @@ "longest_path_cycles": [ 0.5, 0.109375, - 0.125, + 0.109375, 0.5, 0.0, 0.25, @@ -658,7 +728,7 @@ "shortest_path_cycles": [ 0.5, 0.109375, - 0.125, + 0.109375, 0.5, 0.0, 0.25, @@ -671,7 +741,7 @@ "total_cycles": [ 0.5, 0.109375, - 0.125, + 0.109375, 0.5, 0.0, 0.25, @@ -1114,8 +1184,8 @@ "arith_fma" ], "longest_path_cycles": [ - 0.578125, - 0.578125, + 0.53125, + 0.53125, 0.265625, 0.125, 0.0, @@ -1136,8 +1206,8 @@ "arith_fma" ], "shortest_path_cycles": [ - 0.53125, - 0.53125, + 0.484375, + 0.484375, 0.21875, 0.0625, 0.0, @@ -1149,8 +1219,8 @@ "arith_fma" ], "total_cycles": [ - 0.578125, - 0.578125, + 0.53125, + 0.53125, 0.34375, 0.125, 0.0, @@ -1178,7 +1248,7 @@ "arithmetic" ], "longest_path_cycles": [ - 4.289999961853027, + 3.9600000381469727, 2.0, 2.0 ], @@ -1951,6 +2021,121 @@ } } }, + "flutter/impeller/entity/gles/conical_gradient_uniform_fill.frag.gles": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/gles/conical_gradient_uniform_fill.frag.gles", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 62, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null, + null, + null, + null, + null + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "varying" + ], + "shortest_path_cycles": [ + 0.1875, + 0.0625, + 0.1875, + 0.0, + 0.0, + 0.25, + 0.0 + ], + "total_bound_pipelines": [ + "load_store" + ], + "total_cycles": [ + 1.40625, + 0.84375, + 1.40625, + 0.4375, + 4.0, + 0.25, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 38, + "work_registers_used": 21 + } + } + }, + "Mali-T880": { + "core": "Mali-T880", + "filename": "flutter/impeller/entity/gles/conical_gradient_uniform_fill.frag.gles", + "has_uniform_computation": true, + "type": "Fragment", + "variants": { + "Main": { + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null + ], + "pipelines": [ + "arithmetic", + "load_store", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arithmetic" + ], + "shortest_path_cycles": [ + 1.649999976158142, + 1.0, + 0.0 + ], + "total_bound_pipelines": [ + "arithmetic" + ], + "total_cycles": [ + 20.66666603088379, + 4.0, + 0.0 + ] + }, + "thread_occupancy": 100, + "uniform_registers_used": 6, + "work_registers_used": 3 + } + } + } + }, "flutter/impeller/entity/gles/fast_gradient.frag.gles": { "Mali-G78": { "core": "Mali-G78", @@ -3210,12 +3395,12 @@ } } }, - "flutter/impeller/entity/gles/linear_to_srgb_filter.frag.gles": { + "flutter/impeller/entity/gles/linear_gradient_uniform_fill.frag.gles": { "Mali-G78": { "core": "Mali-G78", - "filename": "flutter/impeller/entity/gles/linear_to_srgb_filter.frag.gles", + "filename": "flutter/impeller/entity/gles/linear_gradient_uniform_fill.frag.gles", "has_side_effects": false, - "has_uniform_computation": false, + "has_uniform_computation": true, "modifies_coverage": false, "reads_color_buffer": false, "type": "Fragment", @@ -3223,22 +3408,20 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 40, + "fp16_arithmetic": 13, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - "arith_total", - "arith_cvt", - "arith_sfu" + null ], "longest_path_cycles": [ - 0.4375, - 0.328125, - 0.4375, - 0.4375, - 0.0, - 0.25, - 0.25 + null, + null, + null, + null, + null, + null, + null ], "pipelines": [ "arith_total", @@ -3250,43 +3433,40 @@ "texture" ], "shortest_path_bound_pipelines": [ - "arith_total", - "arith_sfu" + "varying" ], "shortest_path_cycles": [ - 0.4375, - 0.328125, - 0.40625, - 0.4375, + 0.203125, + 0.203125, + 0.171875, + 0.0625, 0.0, 0.25, - 0.25 + 0.0 ], "total_bound_pipelines": [ - "arith_total", - "arith_cvt", - "arith_sfu" + "load_store" ], "total_cycles": [ - 0.4375, - 0.328125, - 0.4375, - 0.4375, - 0.0, + 0.8125, + 0.46875, + 0.8125, + 0.1875, + 4.0, 0.25, - 0.25 + 0.0 ] }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 10, - "work_registers_used": 30 + "uniform_registers_used": 12, + "work_registers_used": 21 } } }, "Mali-T880": { "core": "Mali-T880", - "filename": "flutter/impeller/entity/gles/linear_to_srgb_filter.frag.gles", + "filename": "flutter/impeller/entity/gles/linear_gradient_uniform_fill.frag.gles", "has_uniform_computation": false, "type": "Fragment", "variants": { @@ -3294,7 +3474,127 @@ "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - "arithmetic" + null + ], + "longest_path_cycles": [ + null, + null, + null + ], + "pipelines": [ + "arithmetic", + "load_store", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arithmetic" + ], + "shortest_path_cycles": [ + 2.640000104904175, + 1.0, + 0.0 + ], + "total_bound_pipelines": [ + "arithmetic" + ], + "total_cycles": [ + 10.0, + 4.0, + 0.0 + ] + }, + "thread_occupancy": 100, + "uniform_registers_used": 3, + "work_registers_used": 3 + } + } + } + }, + "flutter/impeller/entity/gles/linear_to_srgb_filter.frag.gles": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/gles/linear_to_srgb_filter.frag.gles", + "has_side_effects": false, + "has_uniform_computation": false, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 40, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + "arith_total", + "arith_cvt", + "arith_sfu" + ], + "longest_path_cycles": [ + 0.4375, + 0.328125, + 0.4375, + 0.4375, + 0.0, + 0.25, + 0.25 + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arith_total", + "arith_sfu" + ], + "shortest_path_cycles": [ + 0.4375, + 0.328125, + 0.40625, + 0.4375, + 0.0, + 0.25, + 0.25 + ], + "total_bound_pipelines": [ + "arith_total", + "arith_cvt", + "arith_sfu" + ], + "total_cycles": [ + 0.4375, + 0.328125, + 0.4375, + 0.4375, + 0.0, + 0.25, + 0.25 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 10, + "work_registers_used": 30 + } + } + }, + "Mali-T880": { + "core": "Mali-T880", + "filename": "flutter/impeller/entity/gles/linear_to_srgb_filter.frag.gles", + "has_uniform_computation": false, + "type": "Fragment", + "variants": { + "Main": { + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + "arithmetic" ], "longest_path_cycles": [ 4.949999809265137, @@ -3837,6 +4137,121 @@ } } }, + "flutter/impeller/entity/gles/radial_gradient_uniform_fill.frag.gles": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/gles/radial_gradient_uniform_fill.frag.gles", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 67, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null, + null, + null, + null, + null + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "varying" + ], + "shortest_path_cycles": [ + 0.1875, + 0.171875, + 0.1875, + 0.0625, + 0.0, + 0.25, + 0.0 + ], + "total_bound_pipelines": [ + "load_store" + ], + "total_cycles": [ + 0.762499988079071, + 0.359375, + 0.762499988079071, + 0.1875, + 4.0, + 0.25, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 10, + "work_registers_used": 21 + } + } + }, + "Mali-T880": { + "core": "Mali-T880", + "filename": "flutter/impeller/entity/gles/radial_gradient_uniform_fill.frag.gles", + "has_uniform_computation": false, + "type": "Fragment", + "variants": { + "Main": { + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null + ], + "pipelines": [ + "arithmetic", + "load_store", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arithmetic" + ], + "shortest_path_cycles": [ + 2.640000104904175, + 1.0, + 0.0 + ], + "total_bound_pipelines": [ + "arithmetic" + ], + "total_cycles": [ + 9.666666984558105, + 4.0, + 0.0 + ] + }, + "thread_occupancy": 100, + "uniform_registers_used": 2, + "work_registers_used": 3 + } + } + } + }, "flutter/impeller/entity/gles/rrect_blur.frag.gles": { "Mali-G78": { "core": "Mali-G78", @@ -4730,10 +5145,10 @@ } } }, - "flutter/impeller/entity/gles/texture_downsample.frag.gles": { + "flutter/impeller/entity/gles/sweep_gradient_uniform_fill.frag.gles": { "Mali-G78": { "core": "Mali-G78", - "filename": "flutter/impeller/entity/gles/texture_downsample.frag.gles", + "filename": "flutter/impeller/entity/gles/sweep_gradient_uniform_fill.frag.gles", "has_side_effects": false, "has_uniform_computation": true, "modifies_coverage": false, @@ -4743,7 +5158,7 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 80, + "fp16_arithmetic": 0, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ @@ -4769,27 +5184,143 @@ ], "shortest_path_bound_pipelines": [ "arith_total", - "arith_cvt" + "arith_fma" ], "shortest_path_cycles": [ - 0.0625, - 0.0, - 0.0625, - 0.0, - 0.0, + 0.375, + 0.375, + 0.25, + 0.3125, 0.0, + 0.25, 0.0 ], "total_bound_pipelines": [ - "arith_total", - "arith_cvt", - "varying", - "texture" + "load_store" ], "total_cycles": [ + 0.925000011920929, + 0.625, + 0.925000011920929, + 0.4375, + 4.0, + 0.25, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 20, + "work_registers_used": 25 + } + } + }, + "Mali-T880": { + "core": "Mali-T880", + "filename": "flutter/impeller/entity/gles/sweep_gradient_uniform_fill.frag.gles", + "has_uniform_computation": false, + "type": "Fragment", + "variants": { + "Main": { + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null + ], + "pipelines": [ + "arithmetic", + "load_store", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arithmetic" + ], + "shortest_path_cycles": [ + 2.9700000286102295, + 1.0, + 0.0 + ], + "total_bound_pipelines": [ + "arithmetic" + ], + "total_cycles": [ + 10.333333015441895, + 5.0, + 0.0 + ] + }, + "thread_occupancy": 100, + "uniform_registers_used": 3, + "work_registers_used": 3 + } + } + } + }, + "flutter/impeller/entity/gles/texture_downsample.frag.gles": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/gles/texture_downsample.frag.gles", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 80, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null, + null, + null, + null, + null + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arith_total", + "arith_cvt" + ], + "shortest_path_cycles": [ + 0.0625, + 0.0, + 0.0625, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "total_bound_pipelines": [ + "arith_total", + "arith_cvt", + "varying", + "texture" + ], + "total_cycles": [ + 0.25, + 0.125, 0.25, - 0.125, - 0.25, 0.0, 0.0, 0.25, @@ -4850,6 +5381,124 @@ } } }, + "flutter/impeller/entity/gles/texture_downsample_gles.frag.gles": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/gles/texture_downsample_gles.frag.gles", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 57, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null, + null, + null, + null, + null + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arith_total", + "arith_cvt" + ], + "shortest_path_cycles": [ + 0.0625, + 0.0, + 0.0625, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "total_bound_pipelines": [ + "arith_total", + "arith_cvt" + ], + "total_cycles": [ + 0.359375, + 0.125, + 0.359375, + 0.0, + 0.0, + 0.25, + 0.25 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 6, + "work_registers_used": 19 + } + } + }, + "Mali-T880": { + "core": "Mali-T880", + "filename": "flutter/impeller/entity/gles/texture_downsample_gles.frag.gles", + "has_uniform_computation": false, + "type": "Fragment", + "variants": { + "Main": { + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null + ], + "pipelines": [ + "arithmetic", + "load_store", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arithmetic", + "load_store" + ], + "shortest_path_cycles": [ + 1.0, + 1.0, + 0.0 + ], + "total_bound_pipelines": [ + "arithmetic" + ], + "total_cycles": [ + 5.333333492279053, + 1.0, + 1.0 + ] + }, + "thread_occupancy": 100, + "uniform_registers_used": 1, + "work_registers_used": 4 + } + } + } + }, "flutter/impeller/entity/gles/texture_fill.frag.gles": { "Mali-G78": { "core": "Mali-G78", @@ -6298,7 +6947,7 @@ "shortest_path_cycles": [ 0.5625, 0.203125, - 0.265625, + 0.25, 0.5625, 0.0, 0.25, @@ -6308,9 +6957,9 @@ "load_store" ], "total_cycles": [ - 0.71875, + 0.699999988079071, 0.40625, - 0.71875, + 0.699999988079071, 0.5625, 4.0, 0.25, @@ -6325,6 +6974,76 @@ } } }, + "flutter/impeller/entity/linear_gradient_uniform_fill.frag.vkspv": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/linear_gradient_uniform_fill.frag.vkspv", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 35, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null, + null, + null, + null, + null + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "varying" + ], + "shortest_path_cycles": [ + 0.15625, + 0.15625, + 0.15625, + 0.0625, + 0.0, + 0.25, + 0.0 + ], + "total_bound_pipelines": [ + "load_store" + ], + "total_cycles": [ + 0.84375, + 0.359375, + 0.84375, + 0.25, + 4.0, + 0.25, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 14, + "work_registers_used": 18 + } + } + } + }, "flutter/impeller/entity/linear_to_srgb_filter.frag.vkspv": { "Mali-G78": { "core": "Mali-G78", @@ -6768,7 +7487,7 @@ "shortest_path_cycles": [ 0.5625, 0.21875, - 0.28125, + 0.265625, 0.5625, 0.0, 0.25, @@ -6778,9 +7497,9 @@ "load_store" ], "total_cycles": [ - 0.71875, + 0.699999988079071, 0.421875, - 0.71875, + 0.699999988079071, 0.625, 4.0, 0.25, @@ -6795,6 +7514,76 @@ } } }, + "flutter/impeller/entity/radial_gradient_uniform_fill.frag.vkspv": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/radial_gradient_uniform_fill.frag.vkspv", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 58, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null, + null, + null, + null, + null + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "varying" + ], + "shortest_path_cycles": [ + 0.171875, + 0.171875, + 0.171875, + 0.0625, + 0.0, + 0.25, + 0.0 + ], + "total_bound_pipelines": [ + "load_store" + ], + "total_cycles": [ + 0.84375, + 0.359375, + 0.84375, + 0.25, + 4.0, + 0.25, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 12, + "work_registers_used": 18 + } + } + } + }, "flutter/impeller/entity/rrect_blur.frag.vkspv": { "Mali-G78": { "core": "Mali-G78", @@ -7443,6 +8232,77 @@ } } }, + "flutter/impeller/entity/sweep_gradient_uniform_fill.frag.vkspv": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/sweep_gradient_uniform_fill.frag.vkspv", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 0, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null, + null, + null, + null, + null + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arith_total", + "arith_fma" + ], + "shortest_path_cycles": [ + 0.390625, + 0.390625, + 0.25, + 0.3125, + 0.0, + 0.25, + 0.0 + ], + "total_bound_pipelines": [ + "load_store" + ], + "total_cycles": [ + 0.875, + 0.637499988079071, + 0.875, + 0.5, + 4.0, + 0.25, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 20, + "work_registers_used": 23 + } + } + } + }, "flutter/impeller/entity/texture_downsample.frag.vkspv": { "Mali-G78": { "core": "Mali-G78", @@ -7515,6 +8375,78 @@ } } }, + "flutter/impeller/entity/texture_downsample_gles.frag.vkspv": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/texture_downsample_gles.frag.vkspv", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 28, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null, + null, + null, + null, + null + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arith_total", + "arith_cvt" + ], + "shortest_path_cycles": [ + 0.109375, + 0.0, + 0.109375, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "total_bound_pipelines": [ + "arith_total", + "arith_cvt" + ], + "total_cycles": [ + 0.4375, + 0.09375, + 0.4375, + 0.0, + 0.0, + 0.25, + 0.25 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 8, + "work_registers_used": 17 + } + } + } + }, "flutter/impeller/entity/texture_fill.frag.vkspv": { "Mali-G78": { "core": "Mali-G78", diff --git a/impeller/tools/shaders.gni b/impeller/tools/shaders.gni index e0bd24183dc03..140b886552de4 100644 --- a/impeller/tools/shaders.gni +++ b/impeller/tools/shaders.gni @@ -97,6 +97,7 @@ template("impeller_shaders") { analyze = false } gles_shaders = "gles_$target_name" + impeller_shaders_gles(gles_shaders) { name = invoker.name require_framebuffer_fetch = require_framebuffer_fetch @@ -110,6 +111,22 @@ template("impeller_shaders") { } analyze = analyze } + + gles3_shaders = "gles3_$target_name" + + impeller_shaders_gles(gles3_shaders) { + name = invoker.name + require_framebuffer_fetch = require_framebuffer_fetch + gles_language_version = 300 + is_300 = true + analyze = false + if (defined(invoker.gles_exclusions)) { + shaders = invoker.shaders - invoker.gles_exclusions + } else { + shaders = invoker.shaders + } + analyze = analyze + } } if (impeller_enable_vulkan) { @@ -139,7 +156,10 @@ template("impeller_shaders") { } if (enable_opengles) { - public_deps += [ ":$gles_shaders" ] + public_deps += [ + ":$gles3_shaders", + ":$gles_shaders", + ] } if (impeller_enable_vulkan) { diff --git a/impeller/tools/shaders_gles.gni b/impeller/tools/shaders_gles.gni index 903be3fedb5bd..9839227daa004 100644 --- a/impeller/tools/shaders_gles.gni +++ b/impeller/tools/shaders_gles.gni @@ -19,6 +19,8 @@ template("impeller_shaders_gles") { require_framebuffer_fetch = invoker.require_framebuffer_fetch } + is_300 = defined(invoker.is_300) && invoker.is_300 + shaders_base_name = string_join("", [ invoker.name, @@ -35,11 +37,18 @@ template("impeller_shaders_gles") { # Metal reflectors generate a superset of information. if (impeller_enable_metal || impeller_enable_vulkan) { - intermediates_subdir = "gles" + if (is_300) { + intermediates_subdir = "gles3" + } else { + intermediates_subdir = "gles" + } } shader_target_flags = [ "--opengl-es" ] defines = [ "IMPELLER_TARGET_OPENGLES" ] + if (is_300) { + defines += [ "IMPELLER_TARGET_OPENGLES3" ] + } } gles_shaders = @@ -68,13 +77,24 @@ template("impeller_shaders_gles") { } embed_gles_lib = "embed_$target_name" - embed_blob(embed_gles_lib) { - gles_library_files = get_target_outputs(":$gles_lib") - symbol_name = shaders_base_name - blob = gles_library_files[0] - hdr = "$target_gen_dir/gles/$shaders_base_name.h" - cc = "$target_gen_dir/gles/$shaders_base_name.cc" - deps = [ ":$gles_lib" ] + if (is_300) { + embed_blob(embed_gles_lib) { + gles_library_files = get_target_outputs(":$gles_lib") + symbol_name = shaders_base_name + "3" + blob = gles_library_files[0] + hdr = "$target_gen_dir/gles3/$shaders_base_name.h" + cc = "$target_gen_dir/gles3/$shaders_base_name.cc" + deps = [ ":$gles_lib" ] + } + } else { + embed_blob(embed_gles_lib) { + gles_library_files = get_target_outputs(":$gles_lib") + symbol_name = shaders_base_name + blob = gles_library_files[0] + hdr = "$target_gen_dir/gles/$shaders_base_name.h" + cc = "$target_gen_dir/gles/$shaders_base_name.cc" + deps = [ ":$gles_lib" ] + } } group(target_name) { diff --git a/impeller/typographer/BUILD.gn b/impeller/typographer/BUILD.gn index acdbeb80a8e4d..3470c3d0bead5 100644 --- a/impeller/typographer/BUILD.gn +++ b/impeller/typographer/BUILD.gn @@ -34,6 +34,11 @@ impeller_component("typographer") { "../renderer", ] + if (!is_fuchsia) { + public_deps += + [ "//flutter/third_party/abseil-cpp/absl/container:flat_hash_map" ] + } + deps = [ "//flutter/fml" ] } diff --git a/impeller/typographer/backends/skia/typographer_context_skia.cc b/impeller/typographer/backends/skia/typographer_context_skia.cc index ea8dc36b6c1ac..de7e5b5c29b95 100644 --- a/impeller/typographer/backends/skia/typographer_context_skia.cc +++ b/impeller/typographer/backends/skia/typographer_context_skia.cc @@ -5,6 +5,7 @@ #include "impeller/typographer/backends/skia/typographer_context_skia.h" #include +#include #include #include #include @@ -283,7 +284,8 @@ static bool BulkUpdateAtlasBitmap(const GlyphAtlas& atlas, if (!data.has_value()) { continue; } - auto [pos, bounds] = data.value(); + auto [pos, bounds, placeholder] = data.value(); + FML_DCHECK(!placeholder); Size size = pos.GetSize(); if (size.IsEmpty()) { continue; @@ -326,7 +328,9 @@ static bool UpdateAtlasBitmap(const GlyphAtlas& atlas, if (!data.has_value()) { continue; } - auto [pos, bounds] = data.value(); + auto [pos, bounds, placeholder] = data.value(); + FML_DCHECK(!placeholder); + Size size = pos.GetSize(); if (size.IsEmpty()) { continue; @@ -404,45 +408,77 @@ static Rect ComputeGlyphSize(const SkFont& font, scaled_bounds.fBottom); }; -static void CollectNewGlyphs(const std::shared_ptr& atlas, - const FontGlyphMap& font_glyph_map, - std::vector& new_glyphs, - std::vector& glyph_sizes) { - for (const auto& font_value : font_glyph_map) { - const ScaledFont& scaled_font = font_value.first; - const FontGlyphAtlas* font_glyph_atlas = - atlas->GetFontGlyphAtlas(scaled_font.font, scaled_font.scale); - - auto metrics = scaled_font.font.GetMetrics(); - - SkFont sk_font( - TypefaceSkia::Cast(*scaled_font.font.GetTypeface()).GetSkiaTypeface(), - metrics.point_size, metrics.scaleX, metrics.skewX); - sk_font.setEdging(SkFont::Edging::kAntiAlias); - sk_font.setHinting(SkFontHinting::kSlight); - sk_font.setEmbolden(metrics.embolden); - // Rather than computing the bounds at the requested point size and scaling - // up the bounds, we scale up the font size and request the bounds. This - // seems to give more accurate bounds information. - sk_font.setSize(sk_font.getSize() * scaled_font.scale); - sk_font.setSubpixel(true); - - if (font_glyph_atlas) { - for (const SubpixelGlyph& glyph : font_value.second) { - if (!font_glyph_atlas->FindGlyphBounds(glyph)) { - new_glyphs.emplace_back(scaled_font, glyph); - glyph_sizes.push_back( - ComputeGlyphSize(sk_font, glyph, scaled_font.scale)); +std::pair, std::vector> +TypographerContextSkia::CollectNewGlyphs( + const std::shared_ptr& atlas, + const std::vector>& text_frames) { + std::vector new_glyphs; + std::vector glyph_sizes; + size_t generation_id = atlas->GetAtlasGeneration(); + intptr_t atlas_id = reinterpret_cast(atlas.get()); + for (const auto& frame : text_frames) { + auto [frame_generation_id, frame_atlas_id] = + frame->GetAtlasGenerationAndID(); + if (atlas->IsValid() && frame->IsFrameComplete() && + frame_generation_id == generation_id && frame_atlas_id == atlas_id && + !frame->GetFrameBounds(0).is_placeholder) { + continue; + } + frame->ClearFrameBounds(); + frame->SetAtlasGeneration(generation_id, atlas_id); + + for (const auto& run : frame->GetRuns()) { + auto metrics = run.GetFont().GetMetrics(); + + auto rounded_scale = TextFrame::RoundScaledFontSize(frame->GetScale()); + ScaledFont scaled_font{.font = run.GetFont(), .scale = rounded_scale}; + + FontGlyphAtlas* font_glyph_atlas = + atlas->GetOrCreateFontGlyphAtlas(scaled_font); + FML_DCHECK(!!font_glyph_atlas); + + SkFont sk_font( + TypefaceSkia::Cast(*scaled_font.font.GetTypeface()).GetSkiaTypeface(), + metrics.point_size, metrics.scaleX, metrics.skewX); + sk_font.setEdging(SkFont::Edging::kAntiAlias); + sk_font.setHinting(SkFontHinting::kSlight); + sk_font.setEmbolden(metrics.embolden); + // Rather than computing the bounds at the requested point size and + // scaling up the bounds, we scale up the font size and request the + // bounds. This seems to give more accurate bounds information. + sk_font.setSize(sk_font.getSize() * scaled_font.scale); + sk_font.setSubpixel(true); + + for (const auto& glyph_position : run.GetGlyphPositions()) { + Point subpixel = TextFrame::ComputeSubpixelPosition( + glyph_position, scaled_font.font.GetAxisAlignment(), + frame->GetOffset(), frame->GetScale()); + SubpixelGlyph subpixel_glyph(glyph_position.glyph, subpixel, + frame->GetProperties()); + const auto& font_glyph_bounds = + font_glyph_atlas->FindGlyphBounds(subpixel_glyph); + + if (!font_glyph_bounds.has_value()) { + new_glyphs.push_back(FontGlyphPair{scaled_font, subpixel_glyph}); + auto glyph_bounds = + ComputeGlyphSize(sk_font, subpixel_glyph, scaled_font.scale); + glyph_sizes.push_back(glyph_bounds); + + auto frame_bounds = FrameBounds{ + Rect::MakeLTRB(0, 0, 0, 0), // + glyph_bounds, // + /*placeholder=*/true // + }; + + frame->AppendFrameBounds(frame_bounds); + font_glyph_atlas->AppendGlyph(subpixel_glyph, frame_bounds); + } else { + frame->AppendFrameBounds(font_glyph_bounds.value()); } } - } else { - for (const SubpixelGlyph& glyph : font_value.second) { - new_glyphs.emplace_back(scaled_font, glyph); - glyph_sizes.push_back( - ComputeGlyphSize(sk_font, glyph, scaled_font.scale)); - } } } + return {std::move(new_glyphs), std::move(glyph_sizes)}; } std::shared_ptr TypographerContextSkia::CreateGlyphAtlas( @@ -450,7 +486,7 @@ std::shared_ptr TypographerContextSkia::CreateGlyphAtlas( GlyphAtlas::Type type, HostBuffer& host_buffer, const std::shared_ptr& atlas_context, - const FontGlyphMap& font_glyph_map) const { + const std::vector>& text_frames) const { TRACE_EVENT0("impeller", __FUNCTION__); if (!IsValid()) { return nullptr; @@ -458,7 +494,7 @@ std::shared_ptr TypographerContextSkia::CreateGlyphAtlas( std::shared_ptr last_atlas = atlas_context->GetGlyphAtlas(); FML_DCHECK(last_atlas->GetType() == type); - if (font_glyph_map.empty()) { + if (text_frames.empty()) { return last_atlas; } @@ -467,9 +503,7 @@ std::shared_ptr TypographerContextSkia::CreateGlyphAtlas( // with the current atlas and reuse if possible. For each new font and // glyph pair, compute the glyph size at scale. // --------------------------------------------------------------------------- - std::vector new_glyphs; - std::vector glyph_sizes; - CollectNewGlyphs(last_atlas, font_glyph_map, new_glyphs, glyph_sizes); + auto [new_glyphs, glyph_sizes] = CollectNewGlyphs(last_atlas, text_frames); if (new_glyphs.size() == 0) { return last_atlas; } @@ -538,11 +572,14 @@ std::shared_ptr TypographerContextSkia::CreateGlyphAtlas( if (atlas_context->GetAtlasSize().height >= max_texture_height || context.GetBackendType() == Context::BackendType::kOpenGLES) { blit_old_atlas = false; - new_atlas = std::make_shared(type); + new_atlas = std::make_shared( + type, /*initial_generation=*/last_atlas->GetAtlasGeneration() + 1); + + auto [update_glyphs, update_sizes] = + CollectNewGlyphs(new_atlas, text_frames); + new_glyphs = std::move(update_glyphs); + glyph_sizes = std::move(update_sizes); - new_glyphs.clear(); - glyph_sizes.clear(); - CollectNewGlyphs(new_atlas, font_glyph_map, new_glyphs, glyph_sizes); glyph_positions.clear(); glyph_positions.reserve(new_glyphs.size()); first_missing_index = 0; diff --git a/impeller/typographer/backends/skia/typographer_context_skia.h b/impeller/typographer/backends/skia/typographer_context_skia.h index c25008740614d..181839df9d4f3 100644 --- a/impeller/typographer/backends/skia/typographer_context_skia.h +++ b/impeller/typographer/backends/skia/typographer_context_skia.h @@ -27,9 +27,14 @@ class TypographerContextSkia : public TypographerContext { GlyphAtlas::Type type, HostBuffer& host_buffer, const std::shared_ptr& atlas_context, - const FontGlyphMap& font_glyph_map) const override; + const std::vector>& text_frames) + const override; private: + static std::pair, std::vector> + CollectNewGlyphs(const std::shared_ptr& atlas, + const std::vector>& text_frames); + TypographerContextSkia(const TypographerContextSkia&) = delete; TypographerContextSkia& operator=(const TypographerContextSkia&) = delete; diff --git a/impeller/typographer/font_glyph_pair.h b/impeller/typographer/font_glyph_pair.h index d4c12fa4195ee..d5b8906de8c3f 100644 --- a/impeller/typographer/font_glyph_pair.h +++ b/impeller/typographer/font_glyph_pair.h @@ -5,10 +5,6 @@ #ifndef FLUTTER_IMPELLER_TYPOGRAPHER_FONT_GLYPH_PAIR_H_ #define FLUTTER_IMPELLER_TYPOGRAPHER_FONT_GLYPH_PAIR_H_ -#include -#include - -#include "fml/hash_combine.h" #include "impeller/geometry/color.h" #include "impeller/geometry/path.h" #include "impeller/geometry/point.h" @@ -24,6 +20,17 @@ struct GlyphProperties { Join stroke_join = Join::kMiter; Scalar stroke_miter = 4.0; bool stroke = false; + + struct Equal { + constexpr bool operator()(const impeller::GlyphProperties& lhs, + const impeller::GlyphProperties& rhs) const { + return lhs.color.ToARGB() == rhs.color.ToARGB() && + lhs.stroke == rhs.stroke && lhs.stroke_cap == rhs.stroke_cap && + lhs.stroke_join == rhs.stroke_join && + lhs.stroke_miter == rhs.stroke_miter && + lhs.stroke_width == rhs.stroke_width; + } + }; }; //------------------------------------------------------------------------------ @@ -34,11 +41,10 @@ struct ScaledFont { Font font; Scalar scale; - struct Hash { - constexpr std::size_t operator()(const impeller::ScaledFont& sf) const { - return fml::HashCombine(sf.font.GetHash(), sf.scale); - } - }; + template + friend H AbslHashValue(H h, const ScaledFont& sf) { + return H::combine(std::move(h), sf.font.GetHash(), sf.scale); + } struct Equal { constexpr bool operator()(const impeller::ScaledFont& lhs, @@ -63,50 +69,40 @@ struct SubpixelGlyph { subpixel_offset(p_subpixel_offset), properties(p_properties) {} - struct Hash { - constexpr std::size_t operator()(const impeller::SubpixelGlyph& sg) const { - if (!sg.properties.has_value()) { - return fml::HashCombine(sg.glyph.index, sg.subpixel_offset.x, - sg.subpixel_offset.y); - } - return fml::HashCombine( - sg.glyph.index, sg.subpixel_offset.x, sg.subpixel_offset.y, - sg.properties->color.ToARGB(), sg.properties->stroke, - sg.properties->stroke_cap, sg.properties->stroke_join, - sg.properties->stroke_miter, sg.properties->stroke_width); + template + friend H AbslHashValue(H h, const SubpixelGlyph& sg) { + if (!sg.properties.has_value()) { + return H::combine(std::move(h), sg.glyph.index, sg.subpixel_offset.x, + sg.subpixel_offset.y); } - }; + return H::combine(std::move(h), sg.glyph.index, sg.subpixel_offset.x, + sg.subpixel_offset.y, sg.properties->color.ToARGB(), + sg.properties->stroke, sg.properties->stroke_cap, + sg.properties->stroke_join, sg.properties->stroke_miter, + sg.properties->stroke_width); + } struct Equal { constexpr bool operator()(const impeller::SubpixelGlyph& lhs, const impeller::SubpixelGlyph& rhs) const { - if (!lhs.properties.has_value() && !rhs.properties.has_value()) { - return lhs.glyph.index == rhs.glyph.index && - lhs.glyph.type == rhs.glyph.type && - lhs.subpixel_offset == rhs.subpixel_offset; + // Check simple non-optionals first. + if (lhs.glyph.index != rhs.glyph.index || + lhs.glyph.type != rhs.glyph.type || + lhs.subpixel_offset != rhs.subpixel_offset || + // Mixmatch properties. + lhs.properties.has_value() != rhs.properties.has_value()) { + return false; } - return lhs.glyph.index == rhs.glyph.index && - lhs.glyph.type == rhs.glyph.type && - lhs.subpixel_offset == rhs.subpixel_offset && - lhs.properties.has_value() && rhs.properties.has_value() && - lhs.properties->color.ToARGB() == rhs.properties->color.ToARGB() && - lhs.properties->stroke == rhs.properties->stroke && - lhs.properties->stroke_cap == rhs.properties->stroke_cap && - lhs.properties->stroke_join == rhs.properties->stroke_join && - lhs.properties->stroke_miter == rhs.properties->stroke_miter && - lhs.properties->stroke_width == rhs.properties->stroke_width; + if (lhs.properties.has_value()) { + // Both have properties. + return GlyphProperties::Equal{}(lhs.properties.value(), + rhs.properties.value()); + } + return true; } }; }; -using FontGlyphMap = - std::unordered_map, - ScaledFont::Hash, - ScaledFont::Equal>; - //------------------------------------------------------------------------------ /// @brief A font along with a glyph in that font rendered at a particular /// scale and subpixel position. @@ -114,8 +110,8 @@ using FontGlyphMap = struct FontGlyphPair { FontGlyphPair(const ScaledFont& sf, const SubpixelGlyph& g) : scaled_font(sf), glyph(g) {} - const ScaledFont& scaled_font; - const SubpixelGlyph& glyph; + ScaledFont scaled_font; + SubpixelGlyph glyph; }; } // namespace impeller diff --git a/impeller/typographer/glyph_atlas.cc b/impeller/typographer/glyph_atlas.cc index 77b8dfd923933..9d75d6271065c 100644 --- a/impeller/typographer/glyph_atlas.cc +++ b/impeller/typographer/glyph_atlas.cc @@ -7,10 +7,13 @@ #include #include +#include "impeller/typographer/font_glyph_pair.h" + namespace impeller { GlyphAtlasContext::GlyphAtlasContext(GlyphAtlas::Type type) - : atlas_(std::make_shared(type)), atlas_size_(ISize(0, 0)) {} + : atlas_(std::make_shared(type, /*initial_generation=*/0)), + atlas_size_(ISize(0, 0)) {} GlyphAtlasContext::~GlyphAtlasContext() {} @@ -43,7 +46,8 @@ void GlyphAtlasContext::UpdateRectPacker( rect_packer_ = std::move(rect_packer); } -GlyphAtlas::GlyphAtlas(Type type) : type_(type) {} +GlyphAtlas::GlyphAtlas(Type type, size_t initial_generation) + : type_(type), generation_(initial_generation) {} GlyphAtlas::~GlyphAtlas() = default; @@ -63,14 +67,24 @@ void GlyphAtlas::SetTexture(std::shared_ptr texture) { texture_ = std::move(texture); } +size_t GlyphAtlas::GetAtlasGeneration() const { + return generation_; +} + +void GlyphAtlas::SetAtlasGeneration(size_t generation) { + generation_ = generation; +} + void GlyphAtlas::AddTypefaceGlyphPositionAndBounds(const FontGlyphPair& pair, Rect position, Rect bounds) { - font_atlas_map_[pair.scaled_font].positions_[pair.glyph] = - std::make_pair(position, bounds); + FontAtlasMap::iterator it = font_atlas_map_.find(pair.scaled_font); + FML_DCHECK(it != font_atlas_map_.end()); + it->second.positions_[pair.glyph] = + FrameBounds{position, bounds, /*is_placeholder=*/false}; } -std::optional> GlyphAtlas::FindFontGlyphBounds( +std::optional GlyphAtlas::FindFontGlyphBounds( const FontGlyphPair& pair) const { const auto& found = font_atlas_map_.find(pair.scaled_font); if (found == font_atlas_map_.end()) { @@ -79,13 +93,11 @@ std::optional> GlyphAtlas::FindFontGlyphBounds( return found->second.FindGlyphBounds(pair.glyph); } -const FontGlyphAtlas* GlyphAtlas::GetFontGlyphAtlas(const Font& font, - Scalar scale) const { - const auto& found = font_atlas_map_.find(ScaledFont{font, scale}); - if (found == font_atlas_map_.end()) { - return nullptr; - } - return &found->second; +FontGlyphAtlas* GlyphAtlas::GetOrCreateFontGlyphAtlas( + const ScaledFont& scaled_font) { + auto [iter, inserted] = + font_atlas_map_.try_emplace(scaled_font, FontGlyphAtlas()); + return &iter->second; } size_t GlyphAtlas::GetGlyphCount() const { @@ -108,7 +120,7 @@ size_t GlyphAtlas::IterateGlyphs( for (const auto& glyph_value : font_value.second.positions_) { count++; if (!iterator(font_value.first, glyph_value.first, - glyph_value.second.first)) { + glyph_value.second.atlas_bounds)) { return count; } } @@ -116,7 +128,7 @@ size_t GlyphAtlas::IterateGlyphs( return count; } -std::optional> FontGlyphAtlas::FindGlyphBounds( +std::optional FontGlyphAtlas::FindGlyphBounds( const SubpixelGlyph& glyph) const { const auto& found = positions_.find(glyph); if (found == positions_.end()) { @@ -125,4 +137,9 @@ std::optional> FontGlyphAtlas::FindGlyphBounds( return found->second; } +void FontGlyphAtlas::AppendGlyph(const SubpixelGlyph& glyph, + const FrameBounds& frame_bounds) { + positions_[glyph] = frame_bounds; +} + } // namespace impeller diff --git a/impeller/typographer/glyph_atlas.h b/impeller/typographer/glyph_atlas.h index 587ece46f045e..de158bae31c73 100644 --- a/impeller/typographer/glyph_atlas.h +++ b/impeller/typographer/glyph_atlas.h @@ -8,7 +8,16 @@ #include #include #include -#include + +#include "flutter/fml/build_config.h" + +#if defined(OS_FUCHSIA) +// TODO(gaaclarke): Migrate to use absl. I couldn't get it working since absl +// has special logic in its GN files for Fuchsia that I couldn't sort out. +#define IMPELLER_TYPOGRAPHER_USE_STD_HASH +#else +#include "flutter/third_party/abseil-cpp/absl/container/flat_hash_map.h" +#endif #include "impeller/core/texture.h" #include "impeller/geometry/rect.h" @@ -19,6 +28,40 @@ namespace impeller { class FontGlyphAtlas; +/// Helper for AbslHashAdapter. Tallies a hash value with fml::HashCombine. +template +struct AbslHashAdapterCombiner { + std::size_t value = 0; + + template + static AbslHashAdapterCombiner combine(AbslHashAdapterCombiner combiner, + const Args&... args) { + combiner.value = fml::HashCombine(combiner.value, args...); + return combiner; + } +}; + +/// Adapts AbslHashValue functions to be used with std::unordered_map and the +/// fml hash functions. +template +struct AbslHashAdapter { + constexpr std::size_t operator()(const T& element) const { + AbslHashAdapterCombiner combiner; + combiner = AbslHashValue(std::move(combiner), element); + return combiner.value; + } +}; + +struct FrameBounds { + /// The bounds of the glyph within the glyph atlas. + Rect atlas_bounds; + /// The local glyph bounds. + Rect glyph_bounds; + /// Whether [atlas_bounds] are still a placeholder and have + /// not yet been computed. + bool is_placeholder = true; +}; + //------------------------------------------------------------------------------ /// @brief A texture containing the bitmap representation of glyphs in /// different fonts along with the ability to query the location of @@ -48,8 +91,9 @@ class GlyphAtlas { /// @brief Create an empty glyph atlas. /// /// @param[in] type How the glyphs are represented in the texture. + /// @param[in] initial_generation the atlas generation. /// - explicit GlyphAtlas(Type type); + GlyphAtlas(Type type, size_t initial_generation); ~GlyphAtlas(); @@ -115,7 +159,7 @@ class GlyphAtlas { /// @return The location of the font-glyph pair in the atlas. /// `std::nullopt` if the pair is not in the atlas. /// - std::optional> FindFontGlyphBounds( + std::optional FindFontGlyphBounds( const FontGlyphPair& pair) const; //---------------------------------------------------------------------------- @@ -130,17 +174,38 @@ class GlyphAtlas { /// scale are not available in the atlas. The pointer is only /// valid for the lifetime of the GlyphAtlas. /// - const FontGlyphAtlas* GetFontGlyphAtlas(const Font& font, Scalar scale) const; + FontGlyphAtlas* GetOrCreateFontGlyphAtlas(const ScaledFont& scaled_font); + + //---------------------------------------------------------------------------- + /// @brief Retrieve the generation id for this glyph atlas. + /// + /// The generation id is used to match with a TextFrame to + /// determine if the frame is guaranteed to already be populated + /// in the atlas. + size_t GetAtlasGeneration() const; + + //---------------------------------------------------------------------------- + /// @brief Update the atlas generation. + void SetAtlasGeneration(size_t value); private: const Type type_; std::shared_ptr texture_; - - std::unordered_map - font_atlas_map_; + size_t generation_ = 0; + +#if defined(IMPELLER_TYPOGRAPHER_USE_STD_HASH) + using FontAtlasMap = std::unordered_map, + ScaledFont::Equal>; +#else + using FontAtlasMap = absl::flat_hash_map, + ScaledFont::Equal>; +#endif + + FontAtlasMap font_atlas_map_; GlyphAtlas(const GlyphAtlas&) = delete; @@ -204,6 +269,7 @@ class GlyphAtlasContext { class FontGlyphAtlas { public: FontGlyphAtlas() = default; + FontGlyphAtlas(FontGlyphAtlas&&) = default; //---------------------------------------------------------------------------- /// @brief Find the location of a glyph in the atlas. @@ -213,20 +279,32 @@ class FontGlyphAtlas { /// @return The location of the glyph in the atlas. /// `std::nullopt` if the glyph is not in the atlas. /// - std::optional> FindGlyphBounds( - const SubpixelGlyph& glyph) const; + std::optional FindGlyphBounds(const SubpixelGlyph& glyph) const; + + //---------------------------------------------------------------------------- + /// @brief Append the frame bounds of a glyph to this atlas. + /// + /// This may indicate a placeholder glyph location to be replaced + /// at a later time, as indicated by FrameBounds.placeholder. + void AppendGlyph(const SubpixelGlyph& glyph, const FrameBounds& frame_bounds); private: friend class GlyphAtlas; - std::unordered_map, - SubpixelGlyph::Hash, - SubpixelGlyph::Equal> - positions_; +#if defined(IMPELLER_TYPOGRAPHER_USE_STD_HASH) + using PositionsMap = std::unordered_map, + SubpixelGlyph::Equal>; +#else + using PositionsMap = absl::flat_hash_map, + SubpixelGlyph::Equal>; +#endif + + PositionsMap positions_; FontGlyphAtlas(const FontGlyphAtlas&) = delete; - - FontGlyphAtlas& operator=(const FontGlyphAtlas&) = delete; }; } // namespace impeller diff --git a/impeller/typographer/lazy_glyph_atlas.cc b/impeller/typographer/lazy_glyph_atlas.cc index be5688f6a7fc6..106ca538bf2e8 100644 --- a/impeller/typographer/lazy_glyph_atlas.cc +++ b/impeller/typographer/lazy_glyph_atlas.cc @@ -7,6 +7,7 @@ #include "fml/logging.h" #include "impeller/base/validation.h" #include "impeller/typographer/glyph_atlas.h" +#include "impeller/typographer/text_frame.h" #include "impeller/typographer/typographer_context.h" #include @@ -30,23 +31,22 @@ LazyGlyphAtlas::LazyGlyphAtlas( LazyGlyphAtlas::~LazyGlyphAtlas() = default; -void LazyGlyphAtlas::AddTextFrame(const TextFrame& frame, +void LazyGlyphAtlas::AddTextFrame(const std::shared_ptr& frame, Scalar scale, Point offset, - const GlyphProperties& properties) { + std::optional properties) { + frame->SetPerFrameData(scale, offset, properties); FML_DCHECK(alpha_atlas_ == nullptr && color_atlas_ == nullptr); - if (frame.GetAtlasType() == GlyphAtlas::Type::kAlphaBitmap) { - frame.CollectUniqueFontGlyphPairs(alpha_glyph_map_, scale, offset, - properties); + if (frame->GetAtlasType() == GlyphAtlas::Type::kAlphaBitmap) { + alpha_text_frames_.push_back(frame); } else { - frame.CollectUniqueFontGlyphPairs(color_glyph_map_, scale, offset, - properties); + color_text_frames_.push_back(frame); } } void LazyGlyphAtlas::ResetTextFrames() { - alpha_glyph_map_.clear(); - color_glyph_map_.clear(); + alpha_text_frames_.clear(); + color_text_frames_.clear(); alpha_atlas_.reset(); color_atlas_.reset(); } @@ -75,8 +75,8 @@ const std::shared_ptr& LazyGlyphAtlas::CreateOrGetGlyphAtlas( return kNullGlyphAtlas; } - auto& glyph_map = type == GlyphAtlas::Type::kAlphaBitmap ? alpha_glyph_map_ - : color_glyph_map_; + auto& glyph_map = type == GlyphAtlas::Type::kAlphaBitmap ? alpha_text_frames_ + : color_text_frames_; const std::shared_ptr& atlas_context = type == GlyphAtlas::Type::kAlphaBitmap ? alpha_context_ : color_context_; std::shared_ptr atlas = typographer_context_->CreateGlyphAtlas( diff --git a/impeller/typographer/lazy_glyph_atlas.h b/impeller/typographer/lazy_glyph_atlas.h index bead4bda983eb..47c6d466df140 100644 --- a/impeller/typographer/lazy_glyph_atlas.h +++ b/impeller/typographer/lazy_glyph_atlas.h @@ -19,10 +19,10 @@ class LazyGlyphAtlas { ~LazyGlyphAtlas(); - void AddTextFrame(const TextFrame& frame, + void AddTextFrame(const std::shared_ptr& frame, Scalar scale, Point offset, - const GlyphProperties& properties); + std::optional properties); void ResetTextFrames(); @@ -34,8 +34,8 @@ class LazyGlyphAtlas { private: std::shared_ptr typographer_context_; - FontGlyphMap alpha_glyph_map_; - FontGlyphMap color_glyph_map_; + std::vector> alpha_text_frames_; + std::vector> color_text_frames_; std::shared_ptr alpha_context_; std::shared_ptr color_context_; mutable std::shared_ptr alpha_atlas_; diff --git a/impeller/typographer/rectangle_packer.cc b/impeller/typographer/rectangle_packer.cc index 3ec7464ff7995..f9a7be86c5f59 100644 --- a/impeller/typographer/rectangle_packer.cc +++ b/impeller/typographer/rectangle_packer.cc @@ -5,6 +5,7 @@ #include "impeller/typographer/rectangle_packer.h" #include +#include #include #include "flutter/fml/logging.h" diff --git a/impeller/typographer/rectangle_packer.h b/impeller/typographer/rectangle_packer.h index 817bc6605e070..d8b1e1caf3966 100644 --- a/impeller/typographer/rectangle_packer.h +++ b/impeller/typographer/rectangle_packer.h @@ -9,6 +9,7 @@ #include "impeller/geometry/scalar.h" #include +#include namespace impeller { diff --git a/impeller/typographer/text_frame.cc b/impeller/typographer/text_frame.cc index b715616fb4d87..870ff17821e11 100644 --- a/impeller/typographer/text_frame.cc +++ b/impeller/typographer/text_frame.cc @@ -3,11 +3,25 @@ // found in the LICENSE file. #include "impeller/typographer/text_frame.h" +#include "impeller/geometry/scalar.h" #include "impeller/typographer/font.h" #include "impeller/typographer/font_glyph_pair.h" namespace impeller { +namespace { +static bool TextPropertiesEquals(const std::optional& a, + const std::optional& b) { + if (!a.has_value() && !b.has_value()) { + return true; + } + if (a.has_value() && b.has_value()) { + return GlyphProperties::Equal{}(a.value(), b.value()); + } + return false; +} +} // namespace + TextFrame::TextFrame() = default; TextFrame::TextFrame(std::vector& runs, Rect bounds, bool has_color) @@ -37,7 +51,7 @@ bool TextFrame::HasColor() const { } // static -Scalar TextFrame::RoundScaledFontSize(Scalar scale, Scalar point_size) { +Scalar TextFrame::RoundScaledFontSize(Scalar scale) { // An arbitrarily chosen maximum text scale to ensure that regardless of the // CTM, a glyph will fit in the atlas. If we clamp significantly, this may // reduce fidelity but is preferable to the alternative of failing to render. @@ -84,27 +98,59 @@ Point TextFrame::ComputeSubpixelPosition( } } -void TextFrame::CollectUniqueFontGlyphPairs( - FontGlyphMap& glyph_map, - Scalar scale, - Point offset, - const GlyphProperties& properties) const { - std::optional lookup = - (properties.stroke || HasColor()) - ? std::optional(properties) - : std::nullopt; - for (const TextRun& run : GetRuns()) { - const Font& font = run.GetFont(); - auto rounded_scale = - RoundScaledFontSize(scale, font.GetMetrics().point_size); - auto& set = glyph_map[ScaledFont{font, rounded_scale}]; - for (const TextRun::GlyphPosition& glyph_position : - run.GetGlyphPositions()) { - Point subpixel = ComputeSubpixelPosition( - glyph_position, font.GetAxisAlignment(), offset, scale); - set.emplace(glyph_position.glyph, subpixel, lookup); - } +void TextFrame::SetPerFrameData(Scalar scale, + Point offset, + std::optional properties) { + if (!ScalarNearlyEqual(scale_, scale) || + !ScalarNearlyEqual(offset_.x, offset.x) || + !ScalarNearlyEqual(offset_.y, offset.y) || + !TextPropertiesEquals(properties_, properties)) { + bound_values_.clear(); + } + scale_ = scale; + offset_ = offset; + properties_ = properties; +} + +Scalar TextFrame::GetScale() const { + return scale_; +} + +Point TextFrame::GetOffset() const { + return offset_; +} + +std::optional TextFrame::GetProperties() const { + return properties_; +} + +void TextFrame::AppendFrameBounds(const FrameBounds& frame_bounds) { + bound_values_.push_back(frame_bounds); +} + +void TextFrame::ClearFrameBounds() { + bound_values_.clear(); +} + +bool TextFrame::IsFrameComplete() const { + size_t run_size = 0; + for (const auto& x : runs_) { + run_size += x.GetGlyphCount(); } + return bound_values_.size() == run_size; +} + +const FrameBounds& TextFrame::GetFrameBounds(size_t index) const { + return bound_values_[index]; +} + +std::pair TextFrame::GetAtlasGenerationAndID() const { + return std::make_pair(generation_, atlas_id_); +} + +void TextFrame::SetAtlasGeneration(size_t value, intptr_t atlas_id) { + generation_ = value; + atlas_id_ = atlas_id; } } // namespace impeller diff --git a/impeller/typographer/text_frame.h b/impeller/typographer/text_frame.h index b742e96e2a769..10a942cbeeb5b 100644 --- a/impeller/typographer/text_frame.h +++ b/impeller/typographer/text_frame.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_IMPELLER_TYPOGRAPHER_TEXT_FRAME_H_ #define FLUTTER_IMPELLER_TYPOGRAPHER_TEXT_FRAME_H_ +#include #include "impeller/typographer/glyph_atlas.h" #include "impeller/typographer/text_run.h" @@ -16,6 +17,8 @@ namespace impeller { /// This object is typically the entrypoint in the Impeller type /// rendering subsystem. /// +/// A text frame should not be reused in multiple places within a single frame, +/// as internally it is used as a cache for various glyph properties. class TextFrame { public: TextFrame(); @@ -24,18 +27,13 @@ class TextFrame { ~TextFrame(); - void CollectUniqueFontGlyphPairs(FontGlyphMap& glyph_map, - Scalar scale, - Point offset, - const GlyphProperties& properties) const; - static Point ComputeSubpixelPosition( const TextRun::GlyphPosition& glyph_position, AxisAlignment alignment, Point offset, Scalar scale); - static Scalar RoundScaledFontSize(Scalar scale, Scalar point_size); + static Scalar RoundScaledFontSize(Scalar scale); //---------------------------------------------------------------------------- /// @brief The conservative bounding box for this text frame. @@ -69,17 +67,62 @@ class TextFrame { bool HasColor() const; //---------------------------------------------------------------------------- - /// @brief The type of atlas this run should be emplaced in. + /// @brief The type of atlas this run should be place in. GlyphAtlas::Type GetAtlasType() const; + /// @brief Verifies that all glyphs in this text frame have computed bounds + /// information. + bool IsFrameComplete() const; + + /// @brief Retrieve the frame bounds for the glyph at [index]. + /// + /// This method is only valid if [IsFrameComplete] returns true. + const FrameBounds& GetFrameBounds(size_t index) const; + + /// @brief Store text frame scale, offset, and properties for hashing in th + /// glyph atlas. + void SetPerFrameData(Scalar scale, + Point offset, + std::optional properties); + + // A generation id for the glyph atlas this text run was associated + // with. As long as the frame generation matches the atlas generation, + // the contents are guaranteed to be populated and do not need to be + // processed. + std::pair GetAtlasGenerationAndID() const; + TextFrame& operator=(TextFrame&& other) = default; TextFrame(const TextFrame& other) = default; private: + friend class TypographerContextSkia; + friend class LazyGlyphAtlas; + + Scalar GetScale() const; + + Point GetOffset() const; + + std::optional GetProperties() const; + + void AppendFrameBounds(const FrameBounds& frame_bounds); + + void ClearFrameBounds(); + + void SetAtlasGeneration(size_t value, intptr_t atlas_id); + std::vector runs_; Rect bounds_; bool has_color_; + + // Data that is cached when rendering the text frame and is only + // valid for the current atlas generation. + std::vector bound_values_; + Scalar scale_ = 0; + size_t generation_ = 0; + intptr_t atlas_id_ = 0; + Point offset_; + std::optional properties_; }; } // namespace impeller diff --git a/impeller/typographer/typographer_context.h b/impeller/typographer/typographer_context.h index 2c45c419dc848..138a911d89c10 100644 --- a/impeller/typographer/typographer_context.h +++ b/impeller/typographer/typographer_context.h @@ -9,6 +9,7 @@ #include "impeller/renderer/context.h" #include "impeller/typographer/glyph_atlas.h" +#include "impeller/typographer/text_frame.h" namespace impeller { @@ -33,7 +34,7 @@ class TypographerContext { GlyphAtlas::Type type, HostBuffer& host_buffer, const std::shared_ptr& atlas_context, - const FontGlyphMap& font_glyph_map) const = 0; + const std::vector>& text_frames) const = 0; protected: //---------------------------------------------------------------------------- diff --git a/impeller/typographer/typographer_unittests.cc b/impeller/typographer/typographer_unittests.cc index 7c8e9cd08852c..e677cd7c3c329 100644 --- a/impeller/typographer/typographer_unittests.cc +++ b/impeller/typographer/typographer_unittests.cc @@ -36,11 +36,10 @@ static std::shared_ptr CreateGlyphAtlas( GlyphAtlas::Type type, Scalar scale, const std::shared_ptr& atlas_context, - const TextFrame& frame) { - FontGlyphMap font_glyph_map; - frame.CollectUniqueFontGlyphPairs(font_glyph_map, scale, {0, 0}, {}); + const std::shared_ptr& frame) { + frame->SetPerFrameData(scale, {0, 0}, std::nullopt); return typographer_context->CreateGlyphAtlas(context, type, host_buffer, - atlas_context, font_glyph_map); + atlas_context, {frame}); } static std::shared_ptr CreateGlyphAtlas( @@ -51,15 +50,13 @@ static std::shared_ptr CreateGlyphAtlas( Scalar scale, const std::shared_ptr& atlas_context, const std::vector>& frames, - const std::vector& properties) { - FontGlyphMap font_glyph_map; + const std::vector>& properties) { size_t offset = 0; for (auto& frame : frames) { - frame->CollectUniqueFontGlyphPairs(font_glyph_map, scale, {0, 0}, - properties[offset++]); + frame->SetPerFrameData(scale, {0, 0}, properties[offset++]); } return typographer_context->CreateGlyphAtlas(context, type, host_buffer, - atlas_context, font_glyph_map); + atlas_context, frames); } TEST_P(TypographerTest, CanConvertTextBlob) { @@ -84,7 +81,8 @@ TEST_P(TypographerTest, CanCreateGlyphAtlas) { auto context = TypographerContextSkia::Make(); auto atlas_context = context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap); - auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); ASSERT_TRUE(context && context->IsValid()); SkFont sk_font = flutter::testing::CreateTestFontOfSize(12); auto blob = SkTextBlob::MakeFromString("hello", sk_font); @@ -92,7 +90,8 @@ TEST_P(TypographerTest, CanCreateGlyphAtlas) { auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, GlyphAtlas::Type::kAlphaBitmap, 1.0f, atlas_context, - *MakeTextFrameFromTextBlobSkia(blob)); + MakeTextFrameFromTextBlobSkia(blob)); + ASSERT_NE(atlas, nullptr); ASSERT_NE(atlas->GetTexture(), nullptr); ASSERT_EQ(atlas->GetType(), GlyphAtlas::Type::kAlphaBitmap); @@ -118,7 +117,8 @@ TEST_P(TypographerTest, CanCreateGlyphAtlas) { } TEST_P(TypographerTest, LazyAtlasTracksColor) { - auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); #if FML_OS_MACOSX auto mapping = flutter::testing::OpenFixtureAsSkData("Apple Color Emoji.ttc"); #else @@ -137,14 +137,14 @@ TEST_P(TypographerTest, LazyAtlasTracksColor) { LazyGlyphAtlas lazy_atlas(TypographerContextSkia::Make()); - lazy_atlas.AddTextFrame(*frame, 1.0f, {0, 0}, {}); + lazy_atlas.AddTextFrame(frame, 1.0f, {0, 0}, {}); frame = MakeTextFrameFromTextBlobSkia( SkTextBlob::MakeFromString("😀 ", emoji_font)); ASSERT_TRUE(frame->GetAtlasType() == GlyphAtlas::Type::kColorBitmap); - lazy_atlas.AddTextFrame(*frame, 1.0f, {0, 0}, {}); + lazy_atlas.AddTextFrame(frame, 1.0f, {0, 0}, {}); // Creates different atlases for color and red bitmap. auto color_atlas = lazy_atlas.CreateOrGetGlyphAtlas( @@ -160,7 +160,8 @@ TEST_P(TypographerTest, GlyphAtlasWithOddUniqueGlyphSize) { auto context = TypographerContextSkia::Make(); auto atlas_context = context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap); - auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); ASSERT_TRUE(context && context->IsValid()); SkFont sk_font = flutter::testing::CreateTestFontOfSize(12); auto blob = SkTextBlob::MakeFromString("AGH", sk_font); @@ -168,7 +169,7 @@ TEST_P(TypographerTest, GlyphAtlasWithOddUniqueGlyphSize) { auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, GlyphAtlas::Type::kAlphaBitmap, 1.0f, atlas_context, - *MakeTextFrameFromTextBlobSkia(blob)); + MakeTextFrameFromTextBlobSkia(blob)); ASSERT_NE(atlas, nullptr); ASSERT_NE(atlas->GetTexture(), nullptr); @@ -180,7 +181,8 @@ TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) { auto context = TypographerContextSkia::Make(); auto atlas_context = context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap); - auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); ASSERT_TRUE(context && context->IsValid()); SkFont sk_font = flutter::testing::CreateTestFontOfSize(12); auto blob = SkTextBlob::MakeFromString("spooky skellingtons", sk_font); @@ -188,7 +190,7 @@ TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) { auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, GlyphAtlas::Type::kAlphaBitmap, 1.0f, atlas_context, - *MakeTextFrameFromTextBlobSkia(blob)); + MakeTextFrameFromTextBlobSkia(blob)); ASSERT_NE(atlas, nullptr); ASSERT_NE(atlas->GetTexture(), nullptr); ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas()); @@ -198,13 +200,14 @@ TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) { auto next_atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, GlyphAtlas::Type::kAlphaBitmap, 1.0f, atlas_context, - *MakeTextFrameFromTextBlobSkia(blob)); + MakeTextFrameFromTextBlobSkia(blob)); ASSERT_EQ(atlas, next_atlas); ASSERT_EQ(atlas_context->GetGlyphAtlas(), atlas); } TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) { - auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); auto context = TypographerContextSkia::Make(); auto atlas_context = context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap); @@ -220,15 +223,15 @@ TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) { auto blob = SkTextBlob::MakeFromString(test_string, sk_font); ASSERT_TRUE(blob); - FontGlyphMap font_glyph_map; size_t size_count = 8; + std::vector> frames; for (size_t index = 0; index < size_count; index += 1) { - MakeTextFrameFromTextBlobSkia(blob)->CollectUniqueFontGlyphPairs( - font_glyph_map, 0.6 * index, {0, 0}, {}); + frames.push_back(MakeTextFrameFromTextBlobSkia(blob)); + frames.back()->SetPerFrameData(0.6 * index, {0, 0}, {}); }; auto atlas = context->CreateGlyphAtlas(*GetContext(), GlyphAtlas::Type::kAlphaBitmap, - *host_buffer, atlas_context, font_glyph_map); + *host_buffer, atlas_context, frames); ASSERT_NE(atlas, nullptr); ASSERT_NE(atlas->GetTexture(), nullptr); @@ -251,7 +254,8 @@ TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) { } TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) { - auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); auto context = TypographerContextSkia::Make(); auto atlas_context = context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap); @@ -262,7 +266,7 @@ TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) { auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, GlyphAtlas::Type::kAlphaBitmap, 1.0f, atlas_context, - *MakeTextFrameFromTextBlobSkia(blob)); + MakeTextFrameFromTextBlobSkia(blob)); auto old_packer = atlas_context->GetRectPacker(); ASSERT_NE(atlas, nullptr); @@ -277,7 +281,7 @@ TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) { auto next_atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, GlyphAtlas::Type::kAlphaBitmap, 1.0f, atlas_context, - *MakeTextFrameFromTextBlobSkia(blob2)); + MakeTextFrameFromTextBlobSkia(blob2)); ASSERT_EQ(atlas, next_atlas); auto* second_texture = next_atlas->GetTexture().get(); @@ -288,7 +292,8 @@ TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) { } TEST_P(TypographerTest, GlyphColorIsPartOfCacheKey) { - auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); #if FML_OS_MACOSX auto mapping = flutter::testing::OpenFixtureAsSkData("Apple Color Emoji.ttc"); #else @@ -308,7 +313,7 @@ TEST_P(TypographerTest, GlyphColorIsPartOfCacheKey) { SkTextBlob::MakeFromString("😂", emoji_font)); auto frame_2 = MakeTextFrameFromTextBlobSkia( SkTextBlob::MakeFromString("😂", emoji_font)); - auto properties = { + std::vector> properties = { GlyphProperties{.color = Color::Red()}, GlyphProperties{.color = Color::Blue()}, }; @@ -322,7 +327,8 @@ TEST_P(TypographerTest, GlyphColorIsPartOfCacheKey) { } TEST_P(TypographerTest, GlyphColorIsIgnoredForNonEmojiFonts) { - auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); sk_sp font_mgr = txt::GetDefaultFontManager(); sk_sp typeface = font_mgr->matchFamilyStyle("Arial", SkFontStyle::Normal()); @@ -338,7 +344,7 @@ TEST_P(TypographerTest, GlyphColorIsIgnoredForNonEmojiFonts) { MakeTextFrameFromTextBlobSkia(SkTextBlob::MakeFromString("A", sk_font)); auto frame_2 = MakeTextFrameFromTextBlobSkia(SkTextBlob::MakeFromString("A", sk_font)); - auto properties = { + std::vector> properties = { GlyphProperties{}, GlyphProperties{}, }; @@ -421,7 +427,8 @@ TEST_P(TypographerTest, GlyphAtlasTextureWillGrowTilMaxTextureSize) { GTEST_SKIP() << "Atlas growth isn't supported for OpenGLES currently."; } - auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); auto context = TypographerContextSkia::Make(); auto atlas_context = context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap); @@ -432,7 +439,7 @@ TEST_P(TypographerTest, GlyphAtlasTextureWillGrowTilMaxTextureSize) { auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, GlyphAtlas::Type::kAlphaBitmap, 1.0f, atlas_context, - *MakeTextFrameFromTextBlobSkia(blob)); + MakeTextFrameFromTextBlobSkia(blob)); // Continually append new glyphs until the glyph size grows to the maximum. // Note that the sizes here are more or less experimentally determined, but // the important expectation is that the atlas size will shrink again after @@ -473,7 +480,7 @@ TEST_P(TypographerTest, GlyphAtlasTextureWillGrowTilMaxTextureSize) { atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, GlyphAtlas::Type::kAlphaBitmap, 50 + i, atlas_context, - *MakeTextFrameFromTextBlobSkia(blob)); + MakeTextFrameFromTextBlobSkia(blob)); ASSERT_TRUE(!!atlas); EXPECT_EQ(atlas->GetTexture()->GetTextureDescriptor().size, expected_sizes[i]); @@ -485,6 +492,169 @@ TEST_P(TypographerTest, GlyphAtlasTextureWillGrowTilMaxTextureSize) { ASSERT_EQ(atlas->GetGlyphCount(), 2u); } +TEST_P(TypographerTest, TextFrameInitialBoundsArePlaceholder) { + SkFont font = flutter::testing::CreateTestFontOfSize(12); + auto blob = SkTextBlob::MakeFromString( + "the quick brown fox jumped over the lazy dog.", font); + ASSERT_TRUE(blob); + auto frame = MakeTextFrameFromTextBlobSkia(blob); + + EXPECT_FALSE(frame->IsFrameComplete()); + + auto context = TypographerContextSkia::Make(); + auto atlas_context = + context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); + + auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, + GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f, + atlas_context, frame); + + // The glyph position in the atlas was not known when this value + // was recorded. It is marked as a placeholder. + EXPECT_TRUE(frame->IsFrameComplete()); + EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder); + + atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, + GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f, + atlas_context, frame); + + // The second time the glyph is rendered, the bounds are correcly known. + EXPECT_TRUE(frame->IsFrameComplete()); + EXPECT_FALSE(frame->GetFrameBounds(0).is_placeholder); +} + +TEST_P(TypographerTest, TextFrameInvalidationWithScale) { + SkFont font = flutter::testing::CreateTestFontOfSize(12); + auto blob = SkTextBlob::MakeFromString( + "the quick brown fox jumped over the lazy dog.", font); + ASSERT_TRUE(blob); + auto frame = MakeTextFrameFromTextBlobSkia(blob); + + EXPECT_FALSE(frame->IsFrameComplete()); + + auto context = TypographerContextSkia::Make(); + auto atlas_context = + context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); + + auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, + GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f, + atlas_context, frame); + + // The glyph position in the atlas was not known when this value + // was recorded. It is marked as a placeholder. + EXPECT_TRUE(frame->IsFrameComplete()); + EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder); + + // Change the scale and the glyph data will still be a placeholder, as the + // old data is no longer valid. + atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, + GlyphAtlas::Type::kAlphaBitmap, /*scale=*/2.0f, + atlas_context, frame); + + // The second time the glyph is rendered, the bounds are correcly known. + EXPECT_TRUE(frame->IsFrameComplete()); + EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder); +} + +TEST_P(TypographerTest, TextFrameAtlasGenerationTracksState) { + SkFont font = flutter::testing::CreateTestFontOfSize(12); + auto blob = SkTextBlob::MakeFromString( + "the quick brown fox jumped over the lazy dog.", font); + ASSERT_TRUE(blob); + auto frame = MakeTextFrameFromTextBlobSkia(blob); + + EXPECT_FALSE(frame->IsFrameComplete()); + + auto context = TypographerContextSkia::Make(); + auto atlas_context = + context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); + + auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, + GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f, + atlas_context, frame); + + // The glyph position in the atlas was not known when this value + // was recorded. It is marked as a placeholder. + EXPECT_TRUE(frame->IsFrameComplete()); + EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder); + if (GetBackend() == PlaygroundBackend::kOpenGLES) { + // OpenGLES must always increase the atlas backend if the texture grows. + EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u); + } else { + EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u); + } + + atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, + GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f, + atlas_context, frame); + + // The second time the glyph is rendered, the bounds are correcly known. + EXPECT_TRUE(frame->IsFrameComplete()); + EXPECT_FALSE(frame->GetFrameBounds(0).is_placeholder); + if (GetBackend() == PlaygroundBackend::kOpenGLES) { + EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u); + } else { + EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u); + } + + // Force increase the generation. + atlas_context->GetGlyphAtlas()->SetAtlasGeneration(2u); + atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, + GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f, + atlas_context, frame); + + EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 2u); +} + +TEST_P(TypographerTest, InvalidAtlasForcesRepopulation) { + SkFont font = flutter::testing::CreateTestFontOfSize(12); + auto blob = SkTextBlob::MakeFromString( + "the quick brown fox jumped over the lazy dog.", font); + ASSERT_TRUE(blob); + auto frame = MakeTextFrameFromTextBlobSkia(blob); + + EXPECT_FALSE(frame->IsFrameComplete()); + + auto context = TypographerContextSkia::Make(); + auto atlas_context = + context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap); + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); + + auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer, + GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f, + atlas_context, frame); + + // The glyph position in the atlas was not known when this value + // was recorded. It is marked as a placeholder. + EXPECT_TRUE(frame->IsFrameComplete()); + EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder); + if (GetBackend() == PlaygroundBackend::kOpenGLES) { + // OpenGLES must always increase the atlas backend if the texture grows. + EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u); + } else { + EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u); + } + + auto second_context = TypographerContextSkia::Make(); + auto second_atlas_context = + second_context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap); + + EXPECT_FALSE(second_atlas_context->GetGlyphAtlas()->IsValid()); + + atlas = CreateGlyphAtlas(*GetContext(), second_context.get(), *host_buffer, + GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f, + second_atlas_context, frame); + + EXPECT_TRUE(second_atlas_context->GetGlyphAtlas()->IsValid()); +} + } // namespace testing } // namespace impeller diff --git a/lib/gpu/BUILD.gn b/lib/gpu/BUILD.gn index d70d261df01c5..86105cbb756d6 100644 --- a/lib/gpu/BUILD.gn +++ b/lib/gpu/BUILD.gn @@ -42,8 +42,6 @@ source_set("gpu") { "export.h", "formats.cc", "formats.h", - "host_buffer.cc", - "host_buffer.h", "render_pass.cc", "render_pass.h", "render_pipeline.cc", diff --git a/lib/gpu/context.cc b/lib/gpu/context.cc index c0c3345ce8487..7204ac443c375 100644 --- a/lib/gpu/context.cc +++ b/lib/gpu/context.cc @@ -10,11 +10,18 @@ #include "flutter/lib/ui/ui_dart_state.h" #include "fml/make_copyable.h" #include "impeller/core/platform.h" +#include "impeller/renderer/context.h" #include "tonic/converter/dart_converter.h" namespace flutter { namespace gpu { +bool SupportsNormalOffscreenMSAA(const impeller::Context& context) { + auto& capabilities = context.GetCapabilities(); + return capabilities->SupportsOffscreenMSAA() && + !capabilities->SupportsImplicitResolvingMSAA(); +} + IMPLEMENT_WRAPPERTYPEINFO(flutter_gpu, Context); std::shared_ptr Context::default_context_; @@ -111,3 +118,8 @@ extern int InternalFlutterGpu_Context_GetMinimumUniformByteAlignment( flutter::gpu::Context* wrapper) { return impeller::DefaultUniformAlignment(); } + +extern bool InternalFlutterGpu_Context_GetSupportsOffscreenMSAA( + flutter::gpu::Context* wrapper) { + return flutter::gpu::SupportsNormalOffscreenMSAA(*wrapper->GetContext()); +} diff --git a/lib/gpu/context.h b/lib/gpu/context.h index b482ae479938b..4dbabcb5e432d 100644 --- a/lib/gpu/context.h +++ b/lib/gpu/context.h @@ -13,6 +13,8 @@ namespace flutter { namespace gpu { +bool SupportsNormalOffscreenMSAA(const impeller::Context& context); + class Context : public RefCountedDartWrappable { DEFINE_WRAPPERTYPEINFO(); FML_FRIEND_MAKE_REF_COUNTED(Context); @@ -74,6 +76,10 @@ FLUTTER_GPU_EXPORT extern int InternalFlutterGpu_Context_GetMinimumUniformByteAlignment( flutter::gpu::Context* wrapper); +FLUTTER_GPU_EXPORT +extern bool InternalFlutterGpu_Context_GetSupportsOffscreenMSAA( + flutter::gpu::Context* wrapper); + } // extern "C" #endif // FLUTTER_LIB_GPU_CONTEXT_H_ diff --git a/lib/gpu/device_buffer.cc b/lib/gpu/device_buffer.cc index 7de08bcffc773..6d1f845cb5dc6 100644 --- a/lib/gpu/device_buffer.cc +++ b/lib/gpu/device_buffer.cc @@ -74,12 +74,18 @@ bool InternalFlutterGpu_DeviceBuffer_InitializeWithHostData( Dart_Handle wrapper, flutter::gpu::Context* gpu_context, Dart_Handle byte_data) { - auto data = tonic::DartByteData(byte_data); - auto mapping = fml::NonOwnedMapping(reinterpret_cast(data.data()), - data.length_in_bytes()); - auto device_buffer = - gpu_context->GetContext()->GetResourceAllocator()->CreateBufferWithCopy( - mapping); + std::shared_ptr device_buffer = nullptr; + { + // `DartByteData` gets raw pointers into the Dart heap via + // `Dart_TypedDataAcquireData`. So it must be destructed before + // `AssociateWithDartWrapper` is called, which mutates the heap. + auto data = tonic::DartByteData(byte_data); + auto mapping = fml::NonOwnedMapping(reinterpret_cast(data.data()), + data.length_in_bytes()); + device_buffer = + gpu_context->GetContext()->GetResourceAllocator()->CreateBufferWithCopy( + mapping); + } if (!device_buffer) { FML_LOG(ERROR) << "Failed to create device buffer with copy."; return false; diff --git a/lib/gpu/host_buffer.cc b/lib/gpu/host_buffer.cc deleted file mode 100644 index eb4b6fae088e6..0000000000000 --- a/lib/gpu/host_buffer.cc +++ /dev/null @@ -1,61 +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. - -#include "flutter/lib/gpu/host_buffer.h" - -#include - -#include "dart_api.h" -#include "impeller/core/host_buffer.h" -#include "impeller/core/platform.h" -#include "third_party/tonic/typed_data/dart_byte_data.h" - -namespace flutter { -namespace gpu { - -IMPLEMENT_WRAPPERTYPEINFO(flutter_gpu, HostBuffer); - -HostBuffer::HostBuffer(Context* context) - : host_buffer_(impeller::HostBuffer::Create( - context->GetContext()->GetResourceAllocator())) {} - -HostBuffer::~HostBuffer() = default; - -std::shared_ptr HostBuffer::GetBuffer() { - return host_buffer_; -} - -size_t HostBuffer::EmplaceBytes(const tonic::DartByteData& byte_data) { - auto view = - host_buffer_->Emplace(byte_data.data(), byte_data.length_in_bytes(), - impeller::DefaultUniformAlignment()); - emplacements_[current_offset_] = view; - size_t previous_offset = current_offset_; - current_offset_ += view.range.length; - return previous_offset; -} - -std::optional HostBuffer::GetBufferViewForOffset( - size_t offset) { - return emplacements_[offset]; -} - -} // namespace gpu -} // namespace flutter - -//---------------------------------------------------------------------------- -/// Exports -/// - -void InternalFlutterGpu_HostBuffer_Initialize(Dart_Handle wrapper, - flutter::gpu::Context* context) { - auto res = fml::MakeRefCounted(context); - res->AssociateWithDartWrapper(wrapper); -} - -size_t InternalFlutterGpu_HostBuffer_EmplaceBytes( - flutter::gpu::HostBuffer* wrapper, - Dart_Handle byte_data) { - return wrapper->EmplaceBytes(tonic::DartByteData(byte_data)); -} diff --git a/lib/gpu/host_buffer.h b/lib/gpu/host_buffer.h deleted file mode 100644 index 31a5750303b41..0000000000000 --- a/lib/gpu/host_buffer.h +++ /dev/null @@ -1,62 +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. - -#ifndef FLUTTER_LIB_GPU_HOST_BUFFER_H_ -#define FLUTTER_LIB_GPU_HOST_BUFFER_H_ - -#include "flutter/lib/gpu/export.h" -#include "flutter/lib/ui/dart_wrapper.h" -#include "impeller/core/buffer_view.h" -#include "impeller/core/host_buffer.h" -#include "lib/gpu/context.h" -#include "third_party/tonic/typed_data/dart_byte_data.h" - -namespace flutter { -namespace gpu { - -class HostBuffer : public RefCountedDartWrappable { - DEFINE_WRAPPERTYPEINFO(); - FML_FRIEND_MAKE_REF_COUNTED(HostBuffer); - - public: - explicit HostBuffer(Context* context); - - ~HostBuffer() override; - - std::shared_ptr GetBuffer(); - - size_t EmplaceBytes(const tonic::DartByteData& byte_data); - - std::optional GetBufferViewForOffset(size_t offset); - - private: - size_t current_offset_ = 0; - std::shared_ptr host_buffer_; - std::unordered_map emplacements_; - - FML_DISALLOW_COPY_AND_ASSIGN(HostBuffer); -}; - -} // namespace gpu -} // namespace flutter - -//---------------------------------------------------------------------------- -/// Exports -/// - -extern "C" { - -FLUTTER_GPU_EXPORT -extern void InternalFlutterGpu_HostBuffer_Initialize( - Dart_Handle wrapper, - flutter::gpu::Context* context); - -FLUTTER_GPU_EXPORT -extern size_t InternalFlutterGpu_HostBuffer_EmplaceBytes( - flutter::gpu::HostBuffer* wrapper, - Dart_Handle byte_data); - -} // extern "C" - -#endif // FLUTTER_LIB_GPU_HOST_BUFFER_H_ diff --git a/lib/gpu/lib/src/buffer.dart b/lib/gpu/lib/src/buffer.dart index e836c71662de7..fd3ae464f88be 100644 --- a/lib/gpu/lib/src/buffer.dart +++ b/lib/gpu/lib/src/buffer.dart @@ -8,8 +8,8 @@ part of flutter_gpu; /// A reference to a byte range within a GPU-resident [Buffer]. class BufferView { - /// The buffer of this view. - final Buffer buffer; + /// The device buffer of this view. + final DeviceBuffer buffer; /// The start of the view, in bytes starting from the beginning of the /// [buffer]. @@ -23,21 +23,9 @@ class BufferView { {required this.offsetInBytes, required this.lengthInBytes}); } -/// A buffer that can be referenced by commands on the GPU. -mixin Buffer { - void _bindAsVertexBuffer(RenderPass renderPass, int offsetInBytes, - int lengthInBytes, int vertexCount); - - void _bindAsIndexBuffer(RenderPass renderPass, int offsetInBytes, - int lengthInBytes, IndexType indexType, int indexCount); - - bool _bindAsUniform(RenderPass renderPass, UniformSlot slot, - int offsetInBytes, int lengthInBytes); -} - /// [DeviceBuffer] is a region of memory allocated on the device heap /// (GPU-resident memory). -base class DeviceBuffer extends NativeFieldWrapperClass1 with Buffer { +base class DeviceBuffer extends NativeFieldWrapperClass1 { bool _valid = false; get isValid { return _valid; @@ -61,21 +49,18 @@ base class DeviceBuffer extends NativeFieldWrapperClass1 with Buffer { final StorageMode storageMode; final int sizeInBytes; - @override void _bindAsVertexBuffer(RenderPass renderPass, int offsetInBytes, int lengthInBytes, int vertexCount) { renderPass._bindVertexBufferDevice( this, offsetInBytes, lengthInBytes, vertexCount); } - @override void _bindAsIndexBuffer(RenderPass renderPass, int offsetInBytes, int lengthInBytes, IndexType indexType, int indexCount) { renderPass._bindIndexBufferDevice( this, offsetInBytes, lengthInBytes, indexType.index, indexCount); } - @override bool _bindAsUniform(RenderPass renderPass, UniformSlot slot, int offsetInBytes, int lengthInBytes) { return renderPass._bindUniformDevice( @@ -156,49 +141,98 @@ base class DeviceBuffer extends NativeFieldWrapperClass1 with Buffer { external void _flush(int offsetInBytes, int lengthInBytes); } -/// [HostBuffer] is a [Buffer] which is allocated on the host (native CPU -/// resident memory) and lazily uploaded to the GPU. A [HostBuffer] can be -/// safely mutated or extended at any time on the host, and will be -/// automatically re-uploaded to the GPU the next time a GPU operation needs to -/// access it. +/// [HostBuffer] is a bump allocator that managed a [DeviceBuffer] block list. /// -/// This is useful for efficiently chunking sparse data uploads, especially -/// ephemeral uniform data that needs to change from frame to frame. +/// This is useful for chunking sparse data uploads, especially ephemeral +/// uniform or vertex data that needs to change from frame to frame. /// -/// Different platforms have different data alignment requirements for accessing -/// device buffer data. The [HostBuffer] takes these requirements into account -/// and automatically inserts padding between emplaced data if necessary. -base class HostBuffer extends NativeFieldWrapperClass1 with Buffer { - /// Creates a new HostBuffer. - HostBuffer._initialize(GpuContext gpuContext) { - _initialize(gpuContext); - } +/// Different platforms have different data alignment requirements when reading +/// [DeviceBuffer] data for shader uniforms. [HostBuffer] uses +/// [GpuContext.minimumUniformByteAlignment] to align each emplacement +/// automatically, so that they may be used in uniform bindings. +/// +/// The length of each [DeviceBuffer] block is determined by +/// [blockLengthInBytes] and cannot be changed after creation of the +/// [HostBuffer]. If [HostBuffer.emplace] is given a [ByteData] that is larger +/// than [blockLengthInBytes], a new [DeviceBuffer] block is created that +/// matches the size of the oversized [ByteData]. +base class HostBuffer { + /// The default length to use for each [DeviceBuffer] block. + static const int kDefaultBlockLengthInBytes = 1024000; // 1024 Kb - @override - void _bindAsVertexBuffer(RenderPass renderPass, int offsetInBytes, - int lengthInBytes, int vertexCount) { - renderPass._bindVertexBufferHost( - this, offsetInBytes, lengthInBytes, vertexCount); + /// The length to use for each [DeviceBuffer] block. + final int blockLengthInBytes; + + static const int _kFrameCount = 4; + + /// The number of frames to cycle through before reusing device buffers. + /// Cycling to the next frame happens when [reset] is called. + int get frameCount { + return _kFrameCount; } - @override - void _bindAsIndexBuffer(RenderPass renderPass, int offsetInBytes, - int lengthInBytes, IndexType indexType, int indexCount) { - renderPass._bindIndexBufferHost( - this, offsetInBytes, lengthInBytes, indexType.index, indexCount); + final GpuContext _gpuContext; + + /// The current frame. Rotates through [frameCount] frames when [reset] is + /// called. + int _frameCursor = 0; + + /// The buffer within the current frame to be used for the next emplacement. + int _bufferCursor = 0; + + /// The offset within the current block to be used for the next emplacement. + int _offsetCursor = 0; + + final List> _buffers = []; + + /// Creates a new HostBuffer. + HostBuffer._initialize(this._gpuContext, + {this.blockLengthInBytes = HostBuffer.kDefaultBlockLengthInBytes}) { + for (int i = 0; i < frameCount; i++) { + List frame = []; + _buffers.add(frame); + _buffers[i].add(_allocateNewBlock(blockLengthInBytes)); + } } - @override - bool _bindAsUniform(RenderPass renderPass, UniformSlot slot, - int offsetInBytes, int lengthInBytes) { - return renderPass._bindUniformHost( - slot.shader, slot.uniformName, this, offsetInBytes, lengthInBytes); + DeviceBuffer _allocateNewBlock(length) { + final buffer = + _gpuContext.createDeviceBuffer(StorageMode.hostVisible, length); + if (buffer == null) { + throw Exception('Failed to allocate DeviceBuffer of length $length'); + } + return buffer; } - /// Wrap with native counterpart. - @Native)>( - symbol: 'InternalFlutterGpu_HostBuffer_Initialize') - external void _initialize(GpuContext gpuContext); + /// Prepare a new buffer range to be used for storing the given bytes. + /// Allocates a new block if necessary. + BufferView _allocateEmplacement(ByteData bytes) { + if (bytes.lengthInBytes > blockLengthInBytes) { + return BufferView(_allocateNewBlock(bytes.lengthInBytes), + offsetInBytes: 0, lengthInBytes: bytes.lengthInBytes); + } + + int padding = _gpuContext.minimumUniformByteAlignment - + (_offsetCursor % _gpuContext.minimumUniformByteAlignment); + // If the padding is the full alignment size, then we're already aligned. + // So reset the padding to zero. + padding %= _gpuContext.minimumUniformByteAlignment; + if (_offsetCursor + padding >= blockLengthInBytes) { + DeviceBuffer buffer = _allocateNewBlock(blockLengthInBytes); + _buffers[_frameCursor].add(buffer); + _bufferCursor++; + _offsetCursor = bytes.lengthInBytes; + + return BufferView(buffer, + offsetInBytes: 0, lengthInBytes: blockLengthInBytes); + } + + _offsetCursor += padding; + final view = BufferView(_buffers[_frameCursor][_bufferCursor], + offsetInBytes: _offsetCursor, lengthInBytes: bytes.lengthInBytes); + _offsetCursor += bytes.lengthInBytes; + return view; + } /// Append byte data to the end of the [HostBuffer] and produce a [BufferView] /// that references the new data in the buffer. @@ -207,15 +241,26 @@ base class HostBuffer extends NativeFieldWrapperClass1 with Buffer { /// buffer if necessary to abide by platform-specific uniform alignment /// requirements. /// - /// The updated buffer will be uploaded to the GPU if the returned - /// [BufferView] is used by a rendering command. + /// The [DeviceBuffer] referenced in the [BufferView] has already been + /// flushed, so there is no need to call [DeviceBuffer.flush] before + /// referencing it in a command. BufferView emplace(ByteData bytes) { - int resultOffset = _emplaceBytes(bytes); - return BufferView(this, - offsetInBytes: resultOffset, lengthInBytes: bytes.lengthInBytes); + BufferView view = _allocateEmplacement(bytes); + if (!view.buffer + .overwrite(bytes, destinationOffsetInBytes: view.offsetInBytes)) { + throw Exception( + 'Failed to write range (offset=${view.offsetInBytes}, length=${view.lengthInBytes}) ' + 'to HostBuffer-managed DeviceBuffer (frame=$_frameCursor, buffer=$_bufferCursor, offset=$_offsetCursor).'); + } + + return view; } - @Native, Handle)>( - symbol: 'InternalFlutterGpu_HostBuffer_EmplaceBytes') - external int _emplaceBytes(ByteData bytes); + /// Resets the bump allocator to the beginning of the first [DeviceBuffer] + /// block. + void reset() { + _frameCursor = (_frameCursor + 1) % frameCount; + _bufferCursor = 0; + _offsetCursor = 0; + } } diff --git a/lib/gpu/lib/src/command_buffer.dart b/lib/gpu/lib/src/command_buffer.dart index 09cae10b8fa75..20c0137ce69aa 100644 --- a/lib/gpu/lib/src/command_buffer.dart +++ b/lib/gpu/lib/src/command_buffer.dart @@ -9,13 +9,15 @@ part of flutter_gpu; typedef CompletionCallback = void Function(bool success); base class CommandBuffer extends NativeFieldWrapperClass1 { + final GpuContext _gpuContext; + /// Creates a new CommandBuffer. - CommandBuffer._(GpuContext gpuContext) { - _initialize(gpuContext); + CommandBuffer._(this._gpuContext) { + _initialize(_gpuContext); } RenderPass createRenderPass(RenderTarget renderTarget) { - return RenderPass._(this, renderTarget); + return RenderPass._(_gpuContext, this, renderTarget); } void submit({CompletionCallback? completionCallback}) { diff --git a/lib/gpu/lib/src/context.dart b/lib/gpu/lib/src/context.dart index 217b387314908..5b62f6685ce59 100644 --- a/lib/gpu/lib/src/context.dart +++ b/lib/gpu/lib/src/context.dart @@ -46,6 +46,18 @@ base class GpuContext extends NativeFieldWrapperClass1 { return _getMinimumUniformByteAlignment(); } + /// Whether the backend supports multisample anti-aliasing for offscreen + /// color and stencil attachments. A subset of OpenGLES-only devices do not + /// support this functionality. + /// + /// Any texture created via [createTexture] is an offscreen texture. + /// There is currently no way to render directly against the "onscreen" + /// texture that the framework renders to, so all Flutter GPU textures are + /// "offscreen". + bool get doesSupportOffscreenMSAA { + return _getSupportsOffscreenMSAA(); + } + /// Allocates a new region of GPU-resident memory. /// /// The [storageMode] must be either [StorageMode.hostVisible] or @@ -75,8 +87,12 @@ base class GpuContext extends NativeFieldWrapperClass1 { return result.isValid ? result : null; } - HostBuffer createHostBuffer() { - return HostBuffer._initialize(this); + /// Creates a bump allocator that managed a [DeviceBuffer] block list. + /// + /// See also [HostBuffer]. + HostBuffer createHostBuffer( + {int blockLengthInBytes = HostBuffer.kDefaultBlockLengthInBytes}) { + return HostBuffer._initialize(this, blockLengthInBytes: blockLengthInBytes); } /// Allocates a new texture in GPU-resident memory. @@ -134,6 +150,10 @@ base class GpuContext extends NativeFieldWrapperClass1 { @Native)>( symbol: 'InternalFlutterGpu_Context_GetMinimumUniformByteAlignment') external int _getMinimumUniformByteAlignment(); + + @Native)>( + symbol: 'InternalFlutterGpu_Context_GetSupportsOffscreenMSAA') + external bool _getSupportsOffscreenMSAA(); } /// The default graphics context. diff --git a/lib/gpu/lib/src/render_pass.dart b/lib/gpu/lib/src/render_pass.dart index 95d7b5f317add..6dd60ab548a85 100644 --- a/lib/gpu/lib/src/render_pass.dart +++ b/lib/gpu/lib/src/render_pass.dart @@ -21,6 +21,37 @@ base class ColorAttachment { Texture texture; Texture? resolveTexture; + + void _validate() { + if (resolveTexture != null) { + if (resolveTexture!.format != texture.format) { + throw Exception( + "ColorAttachment MSAA resolve texture must have the same format as the texture"); + } + if (resolveTexture!.width != texture.width || + resolveTexture!.height != texture.height) { + throw Exception( + "ColorAttachment MSAA resolve texture must have the same dimensions as the texture"); + } + if (resolveTexture!.sampleCount != 1) { + throw Exception( + "ColorAttachment MSAA resolve texture must have a sample count of 1"); + } + if (texture.sampleCount <= 1) { + throw Exception( + "ColorAttachment must have a sample count greater than 1 when a MSAA resolve texture is set"); + } + if (storeAction != StoreAction.multisampleResolve && + storeAction != StoreAction.storeAndMultisampleResolve) { + throw Exception( + "ColorAttachment StoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); + } + if (resolveTexture!.storageMode == StorageMode.deviceTransient) { + throw Exception( + "ColorAttachment MSAA resolve texture must not have a storage mode of deviceTransient"); + } + } + } } base class DepthStencilAttachment { @@ -43,6 +74,19 @@ base class DepthStencilAttachment { int stencilClearValue; Texture texture; + + void _validate() { + if (texture.storageMode == StorageMode.deviceTransient) { + if (depthLoadAction == LoadAction.load) { + throw Exception( + "DepthStencilAttachment depthLoadAction must not be load when the texture has a storage mode of deviceTransient"); + } + if (stencilLoadAction == LoadAction.load) { + throw Exception( + "DepthStencilAttachment stencilLoadAction must not be load when the texture has a storage mode of deviceTransient"); + } + } + } } base class StencilConfig { @@ -106,6 +150,18 @@ base class SamplerOptions { SamplerAddressMode heightAddressMode; } +base class Scissor { + Scissor({this.x = 0, this.y = 0, this.width = 0, this.height = 0}); + + int x, y, width, height; + + void _validate() { + if (x < 0 || y < 0 || width < 0 || height < 0) { + throw Exception("Invalid values for scissor. All values should be positive."); + } + } +} + base class RenderTarget { const RenderTarget( {this.colorAttachments = const [], @@ -117,17 +173,33 @@ base class RenderTarget { colorAttachments: [colorAttachment], depthStencilAttachment: depthStencilAttachment); + _validate() { + for (final color in colorAttachments) { + color._validate(); + } + if (depthStencilAttachment != null) { + depthStencilAttachment!._validate(); + } + } + final List colorAttachments; final DepthStencilAttachment? depthStencilAttachment; } base class RenderPass extends NativeFieldWrapperClass1 { /// Creates a new RenderPass. - RenderPass._(CommandBuffer commandBuffer, RenderTarget renderTarget) { + RenderPass._(GpuContext gpuContext, CommandBuffer commandBuffer, + RenderTarget renderTarget) { + assert(() { + renderTarget._validate(); + return true; + }()); + _initialize(); String? error; for (final (index, color) in renderTarget.colorAttachments.indexed) { error = _setColorAttachment( + gpuContext, index, color.loadAction.index, color.storeAction.index, @@ -190,6 +262,14 @@ base class RenderPass extends NativeFieldWrapperClass1 { sampler = SamplerOptions(); } + assert(() { + if (texture.storageMode == StorageMode.deviceTransient) { + throw Exception( + "Textures with StorageMode.deviceTransient cannot be bound to a RenderPass"); + } + return true; + }()); + bool success = _bindTexture( slot.shader, slot.uniformName, @@ -258,6 +338,14 @@ base class RenderPass extends NativeFieldWrapperClass1 { targetFace.index); } + void setScissor(Scissor scissor) { + assert(() { + scissor._validate(); + return true; + }()); + _setScissor(scissor.x, scissor.y, scissor.width, scissor.height); + } + void setCullMode(CullMode cullMode) { _setCullMode(cullMode.index); } @@ -287,6 +375,7 @@ base class RenderPass extends NativeFieldWrapperClass1 { @Native< Handle Function( + Pointer, Pointer, Int, Int, @@ -298,6 +387,7 @@ base class RenderPass extends NativeFieldWrapperClass1 { Pointer, Handle)>(symbol: 'InternalFlutterGpu_RenderPass_SetColorAttachment') external String? _setColorAttachment( + GpuContext context, int colorAttachmentIndex, int loadAction, int storeAction, @@ -334,33 +424,17 @@ base class RenderPass extends NativeFieldWrapperClass1 { external void _bindVertexBufferDevice(DeviceBuffer buffer, int offsetInBytes, int lengthInBytes, int vertexCount); - @Native, Pointer, Int, Int, Int)>( - symbol: 'InternalFlutterGpu_RenderPass_BindVertexBufferHost') - external void _bindVertexBufferHost( - HostBuffer buffer, int offsetInBytes, int lengthInBytes, int vertexCount); - @Native, Pointer, Int, Int, Int, Int)>( symbol: 'InternalFlutterGpu_RenderPass_BindIndexBufferDevice') external void _bindIndexBufferDevice(DeviceBuffer buffer, int offsetInBytes, int lengthInBytes, int indexType, int indexCount); - @Native, Pointer, Int, Int, Int, Int)>( - symbol: 'InternalFlutterGpu_RenderPass_BindIndexBufferHost') - external void _bindIndexBufferHost(HostBuffer buffer, int offsetInBytes, - int lengthInBytes, int indexType, int indexCount); - @Native< Bool Function(Pointer, Pointer, Handle, Pointer, Int, Int)>(symbol: 'InternalFlutterGpu_RenderPass_BindUniformDevice') external bool _bindUniformDevice(Shader shader, String uniformName, DeviceBuffer buffer, int offsetInBytes, int lengthInBytes); - @Native< - Bool Function(Pointer, Pointer, Handle, Pointer, Int, - Int)>(symbol: 'InternalFlutterGpu_RenderPass_BindUniformHost') - external bool _bindUniformHost(Shader shader, String uniformName, - HostBuffer buffer, int offsetInBytes, int lengthInBytes); - @Native< Bool Function( Pointer, @@ -424,6 +498,14 @@ base class RenderPass extends NativeFieldWrapperClass1 { int writeMask, int target_face); + @Native, Int, Int, Int, Int)>( + symbol: 'InternalFlutterGpu_RenderPass_SetScissor') + external void _setScissor( + int x, + int y, + int width, + int height); + @Native, Int)>( symbol: 'InternalFlutterGpu_RenderPass_SetCullMode') external void _setCullMode(int cullMode); @@ -432,7 +514,7 @@ base class RenderPass extends NativeFieldWrapperClass1 { symbol: 'InternalFlutterGpu_RenderPass_SetPrimitiveType') external void _setPrimitiveType(int primitiveType); - @Native, Int)>( + @Native, Int)>( symbol: 'InternalFlutterGpu_RenderPass_SetWindingOrder') external void _setWindingOrder(int windingOrder); diff --git a/lib/gpu/pubspec.yaml b/lib/gpu/pubspec.yaml index 910d8f816abe6..3406760324cbe 100644 --- a/lib/gpu/pubspec.yaml +++ b/lib/gpu/pubspec.yaml @@ -8,7 +8,7 @@ homepage: https://flutter.dev repository: https://github.com/flutter/engine/tree/main/lib/gpu environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 dependencies: # To update these, use "flutter update-packages --force-upgrade". diff --git a/lib/gpu/render_pass.cc b/lib/gpu/render_pass.cc index 70d68d0e78081..b2f43bc2db187 100644 --- a/lib/gpu/render_pass.cc +++ b/lib/gpu/render_pass.cc @@ -20,6 +20,7 @@ #include "impeller/renderer/pipeline.h" #include "impeller/renderer/pipeline_descriptor.h" #include "impeller/renderer/pipeline_library.h" +#include "lib/gpu/context.h" #include "lib/ui/ui_dart_state.h" #include "tonic/converter/dart_converter.h" @@ -101,10 +102,16 @@ std::shared_ptr> RenderPass::GetOrCreatePipeline() { // Infer the pipeline layout based on the shape of the RenderTarget. auto pipeline_desc = pipeline_descriptor_; - for (const auto& it : render_target_.GetColorAttachments()) { - auto& color = GetColorAttachmentDescriptor(it.first); - color.format = render_target_.GetRenderTargetPixelFormat(); - } + + pipeline_desc.SetSampleCount(render_target_.GetSampleCount()); + + render_target_.IterateAllColorAttachments( + [&](size_t index, const impeller::ColorAttachment& attachment) -> bool { + auto& color = GetColorAttachmentDescriptor(index); + color.format = render_target_.GetRenderTargetPixelFormat(); + return true; + }); + pipeline_desc.SetColorAttachmentDescriptors(color_descriptors_); { @@ -173,28 +180,34 @@ bool RenderPass::Draw() { render_pass_->SetPipeline(GetOrCreatePipeline()); for (const auto& [_, buffer] : vertex_uniform_bindings) { - render_pass_->BindResource(impeller::ShaderStage::kVertex, - impeller::DescriptorType::kUniformBuffer, - buffer.slot, *buffer.view.GetMetadata(), - buffer.view.resource); + render_pass_->BindDynamicResource( + impeller::ShaderStage::kVertex, + impeller::DescriptorType::kUniformBuffer, buffer.slot, + std::make_unique(*buffer.view.GetMetadata()), + buffer.view.resource); } for (const auto& [_, texture] : vertex_texture_bindings) { - render_pass_->BindResource(impeller::ShaderStage::kVertex, - impeller::DescriptorType::kSampledImage, - texture.slot, *texture.texture.GetMetadata(), - texture.texture.resource, *texture.sampler); + render_pass_->BindDynamicResource( + impeller::ShaderStage::kVertex, impeller::DescriptorType::kSampledImage, + texture.slot, + std::make_unique( + *texture.texture.GetMetadata()), + texture.texture.resource, *texture.sampler); } for (const auto& [_, buffer] : fragment_uniform_bindings) { - render_pass_->BindResource(impeller::ShaderStage::kFragment, - impeller::DescriptorType::kUniformBuffer, - buffer.slot, *buffer.view.GetMetadata(), - buffer.view.resource); + render_pass_->BindDynamicResource( + impeller::ShaderStage::kFragment, + impeller::DescriptorType::kUniformBuffer, buffer.slot, + std::make_unique(*buffer.view.GetMetadata()), + buffer.view.resource); } for (const auto& [_, texture] : fragment_texture_bindings) { - render_pass_->BindResource(impeller::ShaderStage::kFragment, - impeller::DescriptorType::kSampledImage, - texture.slot, *texture.texture.GetMetadata(), - texture.texture.resource, *texture.sampler); + render_pass_->BindDynamicResource( + impeller::ShaderStage::kFragment, + impeller::DescriptorType::kSampledImage, texture.slot, + std::make_unique( + *texture.texture.GetMetadata()), + texture.texture.resource, *texture.sampler); } render_pass_->SetVertexBuffer(vertex_buffer); @@ -203,6 +216,10 @@ bool RenderPass::Draw() { render_pass_->SetStencilReference(stencil_reference); + if (scissor.has_value()) { + render_pass_->SetScissor(scissor.value()); + } + bool result = render_pass_->Draw().ok(); return result; @@ -222,6 +239,7 @@ void InternalFlutterGpu_RenderPass_Initialize(Dart_Handle wrapper) { Dart_Handle InternalFlutterGpu_RenderPass_SetColorAttachment( flutter::gpu::RenderPass* wrapper, + flutter::gpu::Context* context, int color_attachment_index, int load_action, int store_action, @@ -242,6 +260,14 @@ Dart_Handle InternalFlutterGpu_RenderPass_SetColorAttachment( tonic::DartConverter::FromDart( resolve_texture_wrapper); desc.resolve_texture = resolve_texture->GetTexture(); + + // If the backend doesn't support normal MSAA, gracefully fallback to + // rendering without MSAA. + if (!flutter::gpu::SupportsNormalOffscreenMSAA(*context->GetContext())) { + desc.texture = desc.resolve_texture; + desc.resolve_texture = nullptr; + desc.store_action = impeller::StoreAction::kStore; + } } wrapper->GetRenderTarget().SetColorAttachment(desc, color_attachment_index); return Dart_Null(); @@ -299,10 +325,8 @@ static void BindVertexBuffer( int offset_in_bytes, int length_in_bytes, int vertex_count) { - wrapper->vertex_buffer = impeller::BufferView{ - .buffer = buffer, - .range = impeller::Range(offset_in_bytes, length_in_bytes), - }; + wrapper->vertex_buffer = impeller::BufferView( + buffer, impeller::Range(offset_in_bytes, length_in_bytes)); // If the index type is set, then the `vertex_count` becomes the index // count... So don't overwrite the count if it's already been set when binding @@ -327,24 +351,6 @@ void InternalFlutterGpu_RenderPass_BindVertexBufferDevice( length_in_bytes, vertex_count); } -void InternalFlutterGpu_RenderPass_BindVertexBufferHost( - flutter::gpu::RenderPass* wrapper, - flutter::gpu::HostBuffer* host_buffer, - int offset_in_bytes, - int length_in_bytes, - int vertex_count) { - std::optional view = - host_buffer->GetBufferViewForOffset(offset_in_bytes); - if (!view.has_value()) { - FML_LOG(ERROR) - << "Failed to bind vertex buffer due to invalid HostBuffer offset: " - << offset_in_bytes; - return; - } - BindVertexBuffer(wrapper, view->buffer, view->range.offset, - view->range.length, vertex_count); -} - static void BindIndexBuffer( flutter::gpu::RenderPass* wrapper, const std::shared_ptr& buffer, @@ -353,10 +359,8 @@ static void BindIndexBuffer( int index_type, int index_count) { impeller::IndexType type = flutter::gpu::ToImpellerIndexType(index_type); - wrapper->index_buffer = impeller::BufferView{ - .buffer = buffer, - .range = impeller::Range(offset_in_bytes, length_in_bytes), - }; + wrapper->index_buffer = impeller::BufferView( + buffer, impeller::Range(offset_in_bytes, length_in_bytes)); wrapper->index_buffer_type = type; bool setting_index_buffer = type != impeller::IndexType::kNone; @@ -377,24 +381,6 @@ void InternalFlutterGpu_RenderPass_BindIndexBufferDevice( length_in_bytes, index_type, index_count); } -void InternalFlutterGpu_RenderPass_BindIndexBufferHost( - flutter::gpu::RenderPass* wrapper, - flutter::gpu::HostBuffer* host_buffer, - int offset_in_bytes, - int length_in_bytes, - int index_type, - int index_count) { - auto view = host_buffer->GetBufferViewForOffset(offset_in_bytes); - if (!view.has_value()) { - FML_LOG(ERROR) - << "Failed to bind index buffer due to invalid HostBuffer offset: " - << offset_in_bytes; - return; - } - BindIndexBuffer(wrapper, view->buffer, view->range.offset, view->range.length, - index_type, index_count); -} - static bool BindUniform( flutter::gpu::RenderPass* wrapper, flutter::gpu::Shader* shader, @@ -424,21 +410,19 @@ static bool BindUniform( return false; } - if (!buffer || static_cast(offset_in_bytes + length_in_bytes) >= + if (!buffer || static_cast(offset_in_bytes + length_in_bytes) > buffer->GetDeviceBufferDescriptor().size) { return false; } uniform_map->insert_or_assign( uniform_struct, - impeller::BufferAndUniformSlot{ + flutter::gpu::RenderPass::BufferAndUniformSlot{ .slot = uniform_struct->slot, .view = impeller::BufferResource{ &uniform_struct->metadata, - impeller::BufferView{ - .buffer = buffer, - .range = impeller::Range(offset_in_bytes, length_in_bytes), - }, + impeller::BufferView( + buffer, impeller::Range(offset_in_bytes, length_in_bytes)), }}); return true; } @@ -455,24 +439,6 @@ bool InternalFlutterGpu_RenderPass_BindUniformDevice( length_in_bytes); } -bool InternalFlutterGpu_RenderPass_BindUniformHost( - flutter::gpu::RenderPass* wrapper, - flutter::gpu::Shader* shader, - Dart_Handle uniform_name_handle, - flutter::gpu::HostBuffer* host_buffer, - int offset_in_bytes, - int length_in_bytes) { - auto view = host_buffer->GetBufferViewForOffset(offset_in_bytes); - if (!view.has_value()) { - FML_LOG(ERROR) - << "Failed to bind index buffer due to invalid HostBuffer offset: " - << offset_in_bytes; - return false; - } - return BindUniform(wrapper, shader, uniform_name_handle, view->buffer, - view->range.offset, view->range.length); -} - bool InternalFlutterGpu_RenderPass_BindTexture( flutter::gpu::RenderPass* wrapper, flutter::gpu::Shader* shader, @@ -583,6 +549,14 @@ void InternalFlutterGpu_RenderPass_SetStencilReference( wrapper->stencil_reference = static_cast(stencil_reference); } +void InternalFlutterGpu_RenderPass_SetScissor(flutter::gpu::RenderPass* wrapper, + int x, + int y, + int width, + int height) { + wrapper->scissor = impeller::TRect::MakeXYWH(x, y, width, height); +} + void InternalFlutterGpu_RenderPass_SetStencilConfig( flutter::gpu::RenderPass* wrapper, int stencil_compare_operation, diff --git a/lib/gpu/render_pass.h b/lib/gpu/render_pass.h index da9840d62699f..5c540f761817b 100644 --- a/lib/gpu/render_pass.h +++ b/lib/gpu/render_pass.h @@ -13,11 +13,11 @@ #include "flutter/lib/ui/dart_wrapper.h" #include "fml/memory/ref_ptr.h" #include "impeller/core/formats.h" +#include "impeller/core/shader_types.h" #include "impeller/renderer/command.h" #include "impeller/renderer/render_pass.h" #include "impeller/renderer/render_target.h" #include "lib/gpu/device_buffer.h" -#include "lib/gpu/host_buffer.h" #include "lib/gpu/render_pipeline.h" #include "lib/gpu/texture.h" @@ -57,9 +57,14 @@ class RenderPass : public RefCountedDartWrappable { bool Draw(); + struct BufferAndUniformSlot { + impeller::ShaderUniformSlot slot; + impeller::BufferResource view; + }; + using BufferUniformMap = std::unordered_map; + BufferAndUniformSlot>; using TextureUniformMap = std::unordered_map; @@ -75,6 +80,7 @@ class RenderPass : public RefCountedDartWrappable { size_t element_count = 0; uint32_t stencil_reference = 0; + std::optional> scissor; // Helper flag to determine whether the vertex_count should override the // element count. The index count takes precedent. @@ -118,6 +124,7 @@ extern void InternalFlutterGpu_RenderPass_Initialize(Dart_Handle wrapper); FLUTTER_GPU_EXPORT extern Dart_Handle InternalFlutterGpu_RenderPass_SetColorAttachment( flutter::gpu::RenderPass* wrapper, + flutter::gpu::Context* context, int color_attachment_index, int load_action, int store_action, @@ -157,14 +164,6 @@ extern void InternalFlutterGpu_RenderPass_BindVertexBufferDevice( int length_in_bytes, int vertex_count); -FLUTTER_GPU_EXPORT -extern void InternalFlutterGpu_RenderPass_BindVertexBufferHost( - flutter::gpu::RenderPass* wrapper, - flutter::gpu::HostBuffer* host_buffer, - int offset_in_bytes, - int length_in_bytes, - int vertex_count); - FLUTTER_GPU_EXPORT extern void InternalFlutterGpu_RenderPass_BindIndexBufferDevice( flutter::gpu::RenderPass* wrapper, @@ -174,15 +173,6 @@ extern void InternalFlutterGpu_RenderPass_BindIndexBufferDevice( int index_type, int index_count); -FLUTTER_GPU_EXPORT -extern void InternalFlutterGpu_RenderPass_BindIndexBufferHost( - flutter::gpu::RenderPass* wrapper, - flutter::gpu::HostBuffer* host_buffer, - int offset_in_bytes, - int length_in_bytes, - int index_type, - int index_count); - FLUTTER_GPU_EXPORT extern bool InternalFlutterGpu_RenderPass_BindUniformDevice( flutter::gpu::RenderPass* wrapper, @@ -192,15 +182,6 @@ extern bool InternalFlutterGpu_RenderPass_BindUniformDevice( int offset_in_bytes, int length_in_bytes); -FLUTTER_GPU_EXPORT -extern bool InternalFlutterGpu_RenderPass_BindUniformHost( - flutter::gpu::RenderPass* wrapper, - flutter::gpu::Shader* shader, - Dart_Handle uniform_name_handle, - flutter::gpu::HostBuffer* host_buffer, - int offset_in_bytes, - int length_in_bytes); - FLUTTER_GPU_EXPORT extern bool InternalFlutterGpu_RenderPass_BindTexture( flutter::gpu::RenderPass* wrapper, @@ -260,6 +241,14 @@ extern void InternalFlutterGpu_RenderPass_SetStencilConfig( int write_mask, int target); +FLUTTER_GPU_EXPORT +extern void InternalFlutterGpu_RenderPass_SetScissor( + flutter::gpu::RenderPass* wrapper, + int x, + int y, + int width, + int height); + FLUTTER_GPU_EXPORT extern void InternalFlutterGpu_RenderPass_SetCullMode( flutter::gpu::RenderPass* wrapper, diff --git a/lib/snapshot/BUILD.gn b/lib/snapshot/BUILD.gn index 6c6ce7fce573c..02f7956c1e147 100644 --- a/lib/snapshot/BUILD.gn +++ b/lib/snapshot/BUILD.gn @@ -63,9 +63,6 @@ group("generate_snapshot_bins") { # See: `bin_to_linkable` rules below that build these outputs into linkable form # See: https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode compiled_action("generate_snapshot_bin") { - # TODO(https://github.com/flutter/flutter/issues/154437). - prefix_with_time_cmd = true - if (target_cpu == "x86" && host_os == "linux") { # By default Dart will create a 32-bit gen_snapshot host binary if the target # platform is 32-bit. Override this to create a 64-bit gen_snapshot for x86 @@ -201,12 +198,18 @@ if (host_os == "mac" && (target_os == "mac" || target_os == "ios")) { gen_snapshot_target = "$dart_src/runtime/bin:$gen_snapshot_target_name($build_toolchain)" - copy(target_name) { + action(target_name) { + script = "//flutter/sky/tools/cp.py" + # The toolchain-specific output directory. For cross-compiles, this is a # clang-x64 or clang-arm64 subdirectory of the top-level build directory. output_dir = get_label_info(gen_snapshot_target, "root_out_dir") - sources = [ "${output_dir}/${gen_snapshot_target_name}" ] + args = [ + rebase_path("${output_dir}/${gen_snapshot_target_name}"), + rebase_path( + "${root_out_dir}/artifacts_$host_cpu/gen_snapshot_${target_cpu}"), + ] outputs = [ "${root_out_dir}/artifacts_$host_cpu/gen_snapshot_${target_cpu}" ] deps = [ gen_snapshot_target ] diff --git a/lib/snapshot/pubspec.yaml b/lib/snapshot/pubspec.yaml index cba35438d9020..79c7060370a6a 100644 --- a/lib/snapshot/pubspec.yaml +++ b/lib/snapshot/pubspec.yaml @@ -6,4 +6,4 @@ name: _snapshot_but_this_package_name_is_not_used environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/lib/ui/compositing/scene_builder.cc b/lib/ui/compositing/scene_builder.cc index 4c2659ffa1efe..93651ade387ca 100644 --- a/lib/ui/compositing/scene_builder.cc +++ b/lib/ui/compositing/scene_builder.cc @@ -152,7 +152,8 @@ void SceneBuilder::pushImageFilter(Dart_Handle layer_handle, double dy, const fml::RefPtr& old_layer) { auto layer = std::make_shared( - image_filter->filter(), SkPoint::Make(SafeNarrow(dx), SafeNarrow(dy))); + image_filter->filter(DlTileMode::kDecal), + SkPoint::Make(SafeNarrow(dx), SafeNarrow(dy))); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); @@ -175,7 +176,7 @@ void SceneBuilder::pushBackdropFilter( } auto layer = std::make_shared( - filter->filter(), static_cast(blend_mode), + filter->filter(DlTileMode::kMirror), static_cast(blend_mode), converted_backdrop_id); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 1e2bc7f9a2e9c..d8509e37e3a25 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -134,173 +134,175 @@ typedef CanvasPath Path; // - Resolve the native function pointer associated with an @Native function. // If there is a mismatch between names or parameter count an @Native is // trying to resolve, an exception will be thrown. -#define FFI_METHOD_LIST(V) \ - V(Canvas, clipPath) \ - V(Canvas, clipRect) \ - V(Canvas, clipRRect) \ - V(Canvas, drawArc) \ - V(Canvas, drawAtlas) \ - V(Canvas, drawCircle) \ - V(Canvas, drawColor) \ - V(Canvas, drawDRRect) \ - V(Canvas, drawImage) \ - V(Canvas, drawImageNine) \ - V(Canvas, drawImageRect) \ - V(Canvas, drawLine) \ - V(Canvas, drawOval) \ - V(Canvas, drawPaint) \ - V(Canvas, drawPath) \ - V(Canvas, drawPicture) \ - V(Canvas, drawPoints) \ - V(Canvas, drawRRect) \ - V(Canvas, drawRect) \ - V(Canvas, drawShadow) \ - V(Canvas, drawVertices) \ - V(Canvas, getDestinationClipBounds) \ - V(Canvas, getLocalClipBounds) \ - V(Canvas, getSaveCount) \ - V(Canvas, getTransform) \ - V(Canvas, restore) \ - V(Canvas, restoreToCount) \ - V(Canvas, rotate) \ - V(Canvas, save) \ - V(Canvas, saveLayer) \ - V(Canvas, saveLayerWithoutBounds) \ - V(Canvas, scale) \ - V(Canvas, skew) \ - V(Canvas, transform) \ - V(Canvas, translate) \ - V(Codec, dispose) \ - V(Codec, frameCount) \ - V(Codec, getNextFrame) \ - V(Codec, repetitionCount) \ - V(ColorFilter, initLinearToSrgbGamma) \ - V(ColorFilter, initMatrix) \ - V(ColorFilter, initMode) \ - V(ColorFilter, initSrgbToLinearGamma) \ - V(EngineLayer, dispose) \ - V(FragmentProgram, initFromAsset) \ - V(ReusableFragmentShader, Dispose) \ - V(ReusableFragmentShader, SetImageSampler) \ - V(ReusableFragmentShader, ValidateSamplers) \ - V(Gradient, initLinear) \ - V(Gradient, initRadial) \ - V(Gradient, initSweep) \ - V(Gradient, initTwoPointConical) \ - V(Image, dispose) \ - V(Image, width) \ - V(Image, height) \ - V(Image, toByteData) \ - V(Image, colorSpace) \ - V(ImageDescriptor, bytesPerPixel) \ - V(ImageDescriptor, dispose) \ - V(ImageDescriptor, height) \ - V(ImageDescriptor, instantiateCodec) \ - V(ImageDescriptor, width) \ - V(ImageFilter, initBlur) \ - V(ImageFilter, initDilate) \ - V(ImageFilter, initErode) \ - V(ImageFilter, initColorFilter) \ - V(ImageFilter, initComposeFilter) \ - V(ImageFilter, initMatrix) \ - V(ImageShader, dispose) \ - V(ImageShader, initWithImage) \ - V(ImmutableBuffer, dispose) \ - V(ImmutableBuffer, length) \ - V(ParagraphBuilder, addPlaceholder) \ - V(ParagraphBuilder, addText) \ - V(ParagraphBuilder, build) \ - V(ParagraphBuilder, pop) \ - V(ParagraphBuilder, pushStyle) \ - V(Paragraph, alphabeticBaseline) \ - V(Paragraph, computeLineMetrics) \ - V(Paragraph, didExceedMaxLines) \ - V(Paragraph, dispose) \ - V(Paragraph, getClosestGlyphInfo) \ - V(Paragraph, getGlyphInfoAt) \ - V(Paragraph, getLineBoundary) \ - V(Paragraph, getLineMetricsAt) \ - V(Paragraph, getLineNumberAt) \ - V(Paragraph, getNumberOfLines) \ - V(Paragraph, getPositionForOffset) \ - V(Paragraph, getRectsForPlaceholders) \ - V(Paragraph, getRectsForRange) \ - V(Paragraph, getWordBoundary) \ - V(Paragraph, height) \ - V(Paragraph, ideographicBaseline) \ - V(Paragraph, layout) \ - V(Paragraph, longestLine) \ - V(Paragraph, maxIntrinsicWidth) \ - V(Paragraph, minIntrinsicWidth) \ - V(Paragraph, paint) \ - V(Paragraph, width) \ - V(PathMeasure, setPath) \ - V(PathMeasure, getLength) \ - V(PathMeasure, getPosTan) \ - V(PathMeasure, getSegment) \ - V(PathMeasure, isClosed) \ - V(PathMeasure, nextContour) \ - V(Path, addArc) \ - V(Path, addOval) \ - V(Path, addPath) \ - V(Path, addPathWithMatrix) \ - V(Path, addPolygon) \ - V(Path, addRRect) \ - V(Path, addRect) \ - V(Path, arcTo) \ - V(Path, arcToPoint) \ - V(Path, clone) \ - V(Path, close) \ - V(Path, conicTo) \ - V(Path, contains) \ - V(Path, cubicTo) \ - V(Path, extendWithPath) \ - V(Path, extendWithPathAndMatrix) \ - V(Path, getBounds) \ - V(Path, getFillType) \ - V(Path, lineTo) \ - V(Path, moveTo) \ - V(Path, op) \ - V(Path, quadraticBezierTo) \ - V(Path, relativeArcToPoint) \ - V(Path, relativeConicTo) \ - V(Path, relativeCubicTo) \ - V(Path, relativeLineTo) \ - V(Path, relativeMoveTo) \ - V(Path, relativeQuadraticBezierTo) \ - V(Path, reset) \ - V(Path, setFillType) \ - V(Path, shift) \ - V(Path, transform) \ - V(PictureRecorder, endRecording) \ - V(Picture, GetAllocationSize) \ - V(Picture, dispose) \ - V(Picture, toImage) \ - V(Picture, toImageSync) \ - V(SceneBuilder, addPerformanceOverlay) \ - V(SceneBuilder, addPicture) \ - V(SceneBuilder, addPlatformView) \ - V(SceneBuilder, addRetained) \ - V(SceneBuilder, addTexture) \ - V(SceneBuilder, build) \ - V(SceneBuilder, pop) \ - V(SceneBuilder, pushBackdropFilter) \ - V(SceneBuilder, pushClipPath) \ - V(SceneBuilder, pushClipRRect) \ - V(SceneBuilder, pushClipRect) \ - V(SceneBuilder, pushColorFilter) \ - V(SceneBuilder, pushImageFilter) \ - V(SceneBuilder, pushOffset) \ - V(SceneBuilder, pushOpacity) \ - V(SceneBuilder, pushShaderMask) \ - V(SceneBuilder, pushTransformHandle) \ - V(Scene, dispose) \ - V(Scene, toImage) \ - V(Scene, toImageSync) \ - V(SemanticsUpdateBuilder, build) \ - V(SemanticsUpdateBuilder, updateCustomAction) \ - V(SemanticsUpdateBuilder, updateNode) \ - V(SemanticsUpdate, dispose) \ +#define FFI_METHOD_LIST(V) \ + V(Canvas, clipPath) \ + V(Canvas, clipRect) \ + V(Canvas, clipRRect) \ + V(Canvas, drawArc) \ + V(Canvas, drawAtlas) \ + V(Canvas, drawCircle) \ + V(Canvas, drawColor) \ + V(Canvas, drawDRRect) \ + V(Canvas, drawImage) \ + V(Canvas, drawImageNine) \ + V(Canvas, drawImageRect) \ + V(Canvas, drawLine) \ + V(Canvas, drawOval) \ + V(Canvas, drawPaint) \ + V(Canvas, drawPath) \ + V(Canvas, drawPicture) \ + V(Canvas, drawPoints) \ + V(Canvas, drawRRect) \ + V(Canvas, drawRect) \ + V(Canvas, drawShadow) \ + V(Canvas, drawVertices) \ + V(Canvas, getDestinationClipBounds) \ + V(Canvas, getLocalClipBounds) \ + V(Canvas, getSaveCount) \ + V(Canvas, getTransform) \ + V(Canvas, restore) \ + V(Canvas, restoreToCount) \ + V(Canvas, rotate) \ + V(Canvas, save) \ + V(Canvas, saveLayer) \ + V(Canvas, saveLayerWithoutBounds) \ + V(Canvas, scale) \ + V(Canvas, skew) \ + V(Canvas, transform) \ + V(Canvas, translate) \ + V(Codec, dispose) \ + V(Codec, frameCount) \ + V(Codec, getNextFrame) \ + V(Codec, repetitionCount) \ + V(ColorFilter, initLinearToSrgbGamma) \ + V(ColorFilter, initMatrix) \ + V(ColorFilter, initMode) \ + V(ColorFilter, initSrgbToLinearGamma) \ + V(EngineLayer, dispose) \ + V(FragmentProgram, initFromAsset) \ + V(ReusableFragmentShader, Dispose) \ + V(ReusableFragmentShader, SetImageSampler) \ + V(ReusableFragmentShader, ValidateSamplers) \ + V(ReusableFragmentShader, ValidateImageFilter) \ + V(Gradient, initLinear) \ + V(Gradient, initRadial) \ + V(Gradient, initSweep) \ + V(Gradient, initTwoPointConical) \ + V(Image, dispose) \ + V(Image, width) \ + V(Image, height) \ + V(Image, toByteData) \ + V(Image, colorSpace) \ + V(ImageDescriptor, bytesPerPixel) \ + V(ImageDescriptor, dispose) \ + V(ImageDescriptor, height) \ + V(ImageDescriptor, instantiateCodec) \ + V(ImageDescriptor, width) \ + V(ImageFilter, initBlur) \ + V(ImageFilter, initDilate) \ + V(ImageFilter, initErode) \ + V(ImageFilter, initColorFilter) \ + V(ImageFilter, initComposeFilter) \ + V(ImageFilter, initShader) \ + V(ImageFilter, initMatrix) \ + V(ImageShader, dispose) \ + V(ImageShader, initWithImage) \ + V(ImmutableBuffer, dispose) \ + V(ImmutableBuffer, length) \ + V(ParagraphBuilder, addPlaceholder) \ + V(ParagraphBuilder, addText) \ + V(ParagraphBuilder, build) \ + V(ParagraphBuilder, pop) \ + V(ParagraphBuilder, pushStyle) \ + V(Paragraph, alphabeticBaseline) \ + V(Paragraph, computeLineMetrics) \ + V(Paragraph, didExceedMaxLines) \ + V(Paragraph, dispose) \ + V(Paragraph, getClosestGlyphInfo) \ + V(Paragraph, getGlyphInfoAt) \ + V(Paragraph, getLineBoundary) \ + V(Paragraph, getLineMetricsAt) \ + V(Paragraph, getLineNumberAt) \ + V(Paragraph, getNumberOfLines) \ + V(Paragraph, getPositionForOffset) \ + V(Paragraph, getRectsForPlaceholders) \ + V(Paragraph, getRectsForRange) \ + V(Paragraph, getWordBoundary) \ + V(Paragraph, height) \ + V(Paragraph, ideographicBaseline) \ + V(Paragraph, layout) \ + V(Paragraph, longestLine) \ + V(Paragraph, maxIntrinsicWidth) \ + V(Paragraph, minIntrinsicWidth) \ + V(Paragraph, paint) \ + V(Paragraph, width) \ + V(PathMeasure, setPath) \ + V(PathMeasure, getLength) \ + V(PathMeasure, getPosTan) \ + V(PathMeasure, getSegment) \ + V(PathMeasure, isClosed) \ + V(PathMeasure, nextContour) \ + V(Path, addArc) \ + V(Path, addOval) \ + V(Path, addPath) \ + V(Path, addPathWithMatrix) \ + V(Path, addPolygon) \ + V(Path, addRRect) \ + V(Path, addRect) \ + V(Path, arcTo) \ + V(Path, arcToPoint) \ + V(Path, clone) \ + V(Path, close) \ + V(Path, conicTo) \ + V(Path, contains) \ + V(Path, cubicTo) \ + V(Path, extendWithPath) \ + V(Path, extendWithPathAndMatrix) \ + V(Path, getBounds) \ + V(Path, getFillType) \ + V(Path, lineTo) \ + V(Path, moveTo) \ + V(Path, op) \ + V(Path, quadraticBezierTo) \ + V(Path, relativeArcToPoint) \ + V(Path, relativeConicTo) \ + V(Path, relativeCubicTo) \ + V(Path, relativeLineTo) \ + V(Path, relativeMoveTo) \ + V(Path, relativeQuadraticBezierTo) \ + V(Path, reset) \ + V(Path, setFillType) \ + V(Path, shift) \ + V(Path, transform) \ + V(PictureRecorder, endRecording) \ + V(Picture, GetAllocationSize) \ + V(Picture, dispose) \ + V(Picture, toImage) \ + V(Picture, toImageSync) \ + V(SceneBuilder, addPerformanceOverlay) \ + V(SceneBuilder, addPicture) \ + V(SceneBuilder, addPlatformView) \ + V(SceneBuilder, addRetained) \ + V(SceneBuilder, addTexture) \ + V(SceneBuilder, build) \ + V(SceneBuilder, pop) \ + V(SceneBuilder, pushBackdropFilter) \ + V(SceneBuilder, pushClipPath) \ + V(SceneBuilder, pushClipRRect) \ + V(SceneBuilder, pushClipRect) \ + V(SceneBuilder, pushColorFilter) \ + V(SceneBuilder, pushImageFilter) \ + V(SceneBuilder, pushOffset) \ + V(SceneBuilder, pushOpacity) \ + V(SceneBuilder, pushShaderMask) \ + V(SceneBuilder, pushTransformHandle) \ + V(Scene, dispose) \ + V(Scene, toImage) \ + V(Scene, toImageSync) \ + V(SemanticsUpdateBuilder, build) \ + V(SemanticsUpdateBuilder, updateCustomAction) \ + V(SemanticsUpdateBuilder, updateNode) \ + V(SemanticsUpdate, dispose) \ V(Vertices, dispose) #define FFI_FUNCTION_INSERT(FUNCTION) \ diff --git a/lib/ui/fixtures/out_of_bounds.apng b/lib/ui/fixtures/out_of_bounds.apng new file mode 100644 index 0000000000000..33993c7960e96 Binary files /dev/null and b/lib/ui/fixtures/out_of_bounds.apng differ diff --git a/lib/ui/fixtures/out_of_bounds_wrapping.apng b/lib/ui/fixtures/out_of_bounds_wrapping.apng new file mode 100644 index 0000000000000..e4cc5d50cfe5f Binary files /dev/null and b/lib/ui/fixtures/out_of_bounds_wrapping.apng differ diff --git a/lib/ui/fixtures/shaders/general_shaders/BUILD.gn b/lib/ui/fixtures/shaders/general_shaders/BUILD.gn index 9e9eb0be00812..d14c68b0abdf9 100644 --- a/lib/ui/fixtures/shaders/general_shaders/BUILD.gn +++ b/lib/ui/fixtures/shaders/general_shaders/BUILD.gn @@ -17,6 +17,9 @@ if (enable_unittests) { "uniforms.frag", "uniforms_sorted.frag", "uniform_arrays.frag", + "filter_shader.frag", + "missing_size.frag", + "missing_texture.frag", ] group("general_shaders") { diff --git a/lib/ui/fixtures/shaders/general_shaders/filter_shader.frag b/lib/ui/fixtures/shaders/general_shaders/filter_shader.frag new file mode 100644 index 0000000000000..a7181e09060c9 --- /dev/null +++ b/lib/ui/fixtures/shaders/general_shaders/filter_shader.frag @@ -0,0 +1,15 @@ +// 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. + +#include + +uniform vec2 u_size; +uniform sampler2D u_texture; + +out vec4 frag_color; + +void main() { + // swap color channels. + frag_color = texture(u_texture, FlutterFragCoord().xy / u_size).bgra; +} diff --git a/lib/ui/fixtures/shaders/general_shaders/missing_size.frag b/lib/ui/fixtures/shaders/general_shaders/missing_size.frag new file mode 100644 index 0000000000000..e6cc02dd1f750 --- /dev/null +++ b/lib/ui/fixtures/shaders/general_shaders/missing_size.frag @@ -0,0 +1,13 @@ +// 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. + +#include + +uniform sampler2D u_texture; + +out vec4 frag_color; + +void main() { + frag_color = texture(u_texture, FlutterFragCoord().xy / vec2(100)).bgra; +} diff --git a/third_party/accessibility/base/platform/darwin/scoped_nsobject.mm b/lib/ui/fixtures/shaders/general_shaders/missing_texture.frag similarity index 53% rename from third_party/accessibility/base/platform/darwin/scoped_nsobject.mm rename to lib/ui/fixtures/shaders/general_shaders/missing_texture.frag index 991f6b80d2dca..20f80ae397397 100644 --- a/third_party/accessibility/base/platform/darwin/scoped_nsobject.mm +++ b/lib/ui/fixtures/shaders/general_shaders/missing_texture.frag @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/platform/darwin/scoped_nsobject.h" +#include -namespace base { +uniform vec2 u_size; -// +out vec4 frag_color; -} // namespace base +void main() { + frag_color = vec4(u_size.x, u_size.y, 0, 1); +} diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 3b683dcbc9d71..2cfec8d08af2c 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -197,8 +197,26 @@ class Color { /// * Bits 16-23 are the red value. /// * Bits 8-15 are the green value. /// * Bits 0-7 are the blue value. - @Deprecated('Use component accessors like .r or .g.') - int get value { + @Deprecated('Use component accessors like .r or .g, or toARGB32 for an explicit conversion') + int get value => toARGB32(); + + /// Returns a 32-bit value representing this color. + /// + /// Unlike accessing the floating point equivalent channels individually + /// ([a], [r], [g], [b]), this method is intentionally _lossy_, and scales + /// each channel using `(channel * 255.0).round() & 0xff`. + /// + /// While useful for storing a 32-bit integer value, prefer accessing the + /// individual channels (and storing the double equivalent) where higher + /// precision is required. + /// + /// The bits are assigned as follows: + /// + /// * Bits 24-31 represents the [a] channel as an 8-bit unsigned integer. + /// * Bits 16-23 represents the [r] channel as an 8-bit unsigned integer. + /// * Bits 8-15 represents the [g] channel as an 8-bit unsigned integer. + /// * Bits 0-7 represents the [b] channel as an 8-bit unsigned integer. + int toARGB32() { return _floatToInt8(a) << 24 | _floatToInt8(r) << 16 | _floatToInt8(g) << 8 | @@ -1117,15 +1135,19 @@ enum PaintingStyle { stroke, } -/// Different ways to clip a widget's content. +/// Different ways to clip content. +/// +/// See also: +/// +/// * [Paint.isAntiAlias], the anti-aliasing switch for general draw operations. enum Clip { /// No clip at all. /// /// This is the default option for most widgets: if the content does not /// overflow the widget boundary, don't pay any performance cost for clipping. /// - /// If the content does overflow, please explicitly specify the following - /// [Clip] options: + /// If the content does overflow, consider the following [Clip] options: + /// /// * [hardEdge], which is the fastest clipping, but with lower fidelity. /// * [antiAlias], which is a little slower than [hardEdge], but with smoothed edges. /// * [antiAliasWithSaveLayer], which is much slower than [antiAlias], and should @@ -1144,50 +1166,53 @@ enum Clip { /// /// See also: /// - /// * [antiAlias], which is more reasonable when clipping is needed and the shape is not + /// * [antiAlias], recommended when clipping is needed and the shape is not /// an axis-aligned rectangle. hardEdge, /// Clip with anti-aliasing. /// - /// This mode has anti-aliased clipping edges to achieve a smoother look. + /// This mode has anti-aliased clipping edges, which reduces jagged edges when + /// the clip shape itself has edges that are diagonal, curved, or otherwise + /// not axis-aligned. /// - /// It' s much faster than [antiAliasWithSaveLayer], but slower than [hardEdge]. + /// This is much faster than [antiAliasWithSaveLayer], but slower than [hardEdge]. /// - /// This will be the common case when dealing with circles and arcs. - /// - /// Different from [hardEdge] and [antiAliasWithSaveLayer], this clipping may have - /// bleeding edge artifacts. - /// (See https://fiddle.skia.org/c/21cb4c2b2515996b537f36e7819288ae for an example.) + /// Unlike [hardEdge] and [antiAliasWithSaveLayer], this clipping can have + /// bleeding edge artifacts + /// ([Skia Fiddle example](https://fiddle.skia.org/c/21cb4c2b2515996b537f36e7819288ae)). /// /// See also: /// - /// * [hardEdge], which is a little faster, but with lower fidelity. - /// * [antiAliasWithSaveLayer], which is much slower, but can avoid the - /// bleeding edges if there's no other way. + /// * [hardEdge], which is faster, but with lower fidelity. + /// * [antiAliasWithSaveLayer], which is much slower, but avoids bleeding + /// edge artifacts. /// * [Paint.isAntiAlias], which is the anti-aliasing switch for general draw operations. antiAlias, - /// Clip with anti-aliasing and saveLayer immediately following the clip. + /// Clip with anti-aliasing and `saveLayer` immediately following the clip. /// /// This mode not only clips with anti-aliasing, but also allocates an offscreen /// buffer. All subsequent paints are carried out on that buffer before finally /// being clipped and composited back. /// - /// This is very slow. It has no bleeding edge artifacts (that [antiAlias] has) - /// but it changes the semantics as an offscreen buffer is now introduced. - /// (See https://github.com/flutter/flutter/issues/18057#issuecomment-394197336 - /// for a difference between paint without saveLayer and paint with saveLayer.) + /// This is very slow. It has no bleeding edge artifacts, unlike [antiAlias], + /// but it changes the semantics as it introduces an offscreen buffer. + /// For example, see this + /// [Skia Fiddle without `saveLayer`](https://fiddle.skia.org/c/83ed46ceadaf90f36a4df3b98cbe1c35) + /// and this + /// [Skia Fiddle with `saveLayer`](https://fiddle.skia.org/c/704acfa049a7e99fbe685232c45d1582). /// - /// This will be only rarely needed. One case where you might need this is if - /// you have an image overlaid on a very different background color. In these - /// cases, consider whether you can avoid overlaying multiple colors in one - /// spot (e.g. by having the background color only present where the image is - /// absent). If you can, [antiAlias] would be fine and much faster. + /// Use this mode only if necessary. For example, if you have an + /// image overlaid on a very different background color. In these + /// cases, consider if you can avoid overlaying multiple colors in one + /// location (e.g. by having the background color only present where the image is + /// absent). If possible, prefer [antiAlias] as it is much faster. /// /// See also: /// /// * [antiAlias], which is much faster, and has similar clipping results. + /// * [Canvas.saveLayer]. antiAliasWithSaveLayer, } @@ -4001,7 +4026,7 @@ abstract class ImageFilter { ImageFilter._(); // ignore: unused_element /// Creates an image filter that applies a Gaussian blur. - factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0, TileMode tileMode = TileMode.clamp }) { + factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0, TileMode? tileMode }) { return _GaussianBlurImageFilter(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); } @@ -4038,6 +4063,61 @@ abstract class ImageFilter { return _ComposeImageFilter(innerFilter: inner, outerFilter: outer); } + /// Creates an image filter from a [FragmentShader]. + /// + /// The fragment shader provided here has additional requirements to be used + /// by the engine for filtering. The first uniform value must be a vec2, this + /// will be set by the engine to the size of the bound texture. There must + /// also be at least one sampler2D uniform, the first of which will be set by + /// the engine to contain the filter input. + /// + /// For example, the following is a valid fragment shader that can be used + /// with this API. Note that the uniform names are not required to have any + /// particular value. + /// + /// ```glsl + /// #include + /// + /// uniform vec2 u_size; + /// uniform float u_time; + /// + /// uniform sampler2D u_texture_input; + /// + /// out vec4 frag_color; + /// + /// void main() { + /// frag_color = texture(u_texture_input, FlutterFragCoord().xy / u_size) * u_time; + /// + /// } + /// + /// ``` + /// + /// This API is only supported when using the Impeller rendering engine. On + /// other backends a [UnsupportedError] will be thrown. To check at runtime + /// whether this API is suppored use [isShaderFilterSupported]. + factory ImageFilter.shader(FragmentShader shader) { + if (!_impellerEnabled) { + throw UnsupportedError('ImageFilter.shader only supported with Impeller rendering engine.'); + } + final bool invalidFloats = shader._floats.length < 2; + final bool invalidSampler = !shader._validateImageFilter(); + if (invalidFloats || invalidSampler) { + final StringBuffer buffer = StringBuffer( + 'ImageFilter.shader requires that the first uniform is a vec2 and at ' + 'least one sampler uniform is present.\n'); + if (invalidFloats) { + buffer.write('The shader has fewer than two float uniforms.\n'); + } + if (invalidSampler) { + buffer.write('The shader is missing a sampler uniform.\n'); + } + } + return _FragmentShaderImageFilter(shader); + } + + /// Whether [ImageFilter.shader] is supported on the current backend. + static bool get isShaderFilterSupported => _impellerEnabled; + // Converts this to a native DlImageFilter. See the comments of this method in // subclasses for the exact type of DlImageFilter this method converts to. _ImageFilter _toNativeImageFilter(); @@ -4083,7 +4163,7 @@ class _GaussianBlurImageFilter implements ImageFilter { final double sigmaX; final double sigmaY; - final TileMode tileMode; + final TileMode? tileMode; // MakeBlurFilter late final _ImageFilter nativeFilter = _ImageFilter.blur(this); @@ -4096,6 +4176,7 @@ class _GaussianBlurImageFilter implements ImageFilter { case TileMode.mirror: return 'mirror'; case TileMode.repeated: return 'repeated'; case TileMode.decal: return 'decal'; + case null: return 'unspecified'; } } @@ -4211,6 +4292,35 @@ class _ComposeImageFilter implements ImageFilter { int get hashCode => Object.hash(innerFilter, outerFilter); } +class _FragmentShaderImageFilter implements ImageFilter { + _FragmentShaderImageFilter(this.shader); + + final FragmentShader shader; + + late final _ImageFilter nativeFilter = _ImageFilter.shader(this); + + @override + _ImageFilter _toNativeImageFilter() => nativeFilter; + + @override + String get _shortDescription => 'shader'; + + @override + String toString() => 'ImageFilter.shader(Shader#${shader.hashCode})'; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is _FragmentShaderImageFilter + && other.shader == shader; + } + + @override + int get hashCode => shader.hashCode; +} + /// An [ImageFilter] that is backed by a native DlImageFilter. /// /// This is a private class, rather than being the implementation of the public @@ -4221,7 +4331,7 @@ base class _ImageFilter extends NativeFieldWrapperClass1 { _ImageFilter.blur(_GaussianBlurImageFilter filter) : creator = filter { _constructor(); - _initBlur(filter.sigmaX, filter.sigmaY, filter.tileMode.index); + _initBlur(filter.sigmaX, filter.sigmaY, filter.tileMode?.index ?? -1); } /// Creates an image filter that dilates each input pixel's channel values @@ -4270,6 +4380,12 @@ base class _ImageFilter extends NativeFieldWrapperClass1 { _initComposed(nativeFilterOuter, nativeFilterInner); } + _ImageFilter.shader(_FragmentShaderImageFilter filter) + : creator = filter { + _constructor(); + _initShader(filter.shader); + } + @Native(symbol: 'ImageFilter::Create') external void _constructor(); @@ -4291,6 +4407,9 @@ base class _ImageFilter extends NativeFieldWrapperClass1 { @Native, Pointer, Pointer)>(symbol: 'ImageFilter::initComposeFilter') external void _initComposed(_ImageFilter outerFilter, _ImageFilter innerFilter); + @Native, Pointer)>(symbol: 'ImageFilter::initShader') + external void _initShader(FragmentShader shader); + /// The original Dart object that created the native wrapper, which retains /// the values used for the filter. final ImageFilter creator; @@ -4943,6 +5062,9 @@ base class FragmentShader extends Shader { @Native)>(symbol: 'ReusableFragmentShader::ValidateSamplers') external bool _validateSamplers(); + @Native)>(symbol: 'ReusableFragmentShader::ValidateImageFilter') + external bool _validateImageFilter(); + @Native)>(symbol: 'ReusableFragmentShader::Dispose') external void _dispose(); } diff --git a/lib/ui/painting/canvas.cc b/lib/ui/painting/canvas.cc index 1da304df6583c..217854d93b293 100644 --- a/lib/ui/painting/canvas.cc +++ b/lib/ui/painting/canvas.cc @@ -60,7 +60,8 @@ void Canvas::saveLayerWithoutBounds(Dart_Handle paint_objects, FML_DCHECK(paint.isNotNull()); if (display_list_builder_) { DlPaint dl_paint; - const DlPaint* save_paint = paint.paint(dl_paint, kSaveLayerWithPaintFlags); + const DlPaint* save_paint = + paint.paint(dl_paint, kSaveLayerWithPaintFlags, DlTileMode::kDecal); FML_DCHECK(save_paint); TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)"); builder()->SaveLayer(nullptr, save_paint); @@ -80,7 +81,8 @@ void Canvas::saveLayer(double left, SafeNarrow(right), SafeNarrow(bottom)); if (display_list_builder_) { DlPaint dl_paint; - const DlPaint* save_paint = paint.paint(dl_paint, kSaveLayerWithPaintFlags); + const DlPaint* save_paint = + paint.paint(dl_paint, kSaveLayerWithPaintFlags, DlTileMode::kDecal); FML_DCHECK(save_paint); TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)"); builder()->SaveLayer(&bounds, save_paint); @@ -229,7 +231,7 @@ void Canvas::drawLine(double x1, FML_DCHECK(paint.isNotNull()); if (display_list_builder_) { DlPaint dl_paint; - paint.paint(dl_paint, kDrawLineFlags); + paint.paint(dl_paint, kDrawLineFlags, DlTileMode::kDecal); builder()->DrawLine(SkPoint::Make(SafeNarrow(x1), SafeNarrow(y1)), SkPoint::Make(SafeNarrow(x2), SafeNarrow(y2)), dl_paint); @@ -242,8 +244,8 @@ void Canvas::drawPaint(Dart_Handle paint_objects, Dart_Handle paint_data) { FML_DCHECK(paint.isNotNull()); if (display_list_builder_) { DlPaint dl_paint; - paint.paint(dl_paint, kDrawPaintFlags); - std::shared_ptr filter = dl_paint.getImageFilter(); + paint.paint(dl_paint, kDrawPaintFlags, DlTileMode::kClamp); + std::shared_ptr filter = dl_paint.getImageFilter(); if (filter && !filter->asColorFilter()) { // drawPaint does an implicit saveLayer if an SkImageFilter is // present that cannot be replaced by an SkColorFilter. @@ -264,7 +266,7 @@ void Canvas::drawRect(double left, FML_DCHECK(paint.isNotNull()); if (display_list_builder_) { DlPaint dl_paint; - paint.paint(dl_paint, kDrawRectFlags); + paint.paint(dl_paint, kDrawRectFlags, DlTileMode::kDecal); builder()->DrawRect(SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top), SafeNarrow(right), SafeNarrow(bottom)), dl_paint); @@ -279,7 +281,7 @@ void Canvas::drawRRect(const RRect& rrect, FML_DCHECK(paint.isNotNull()); if (display_list_builder_) { DlPaint dl_paint; - paint.paint(dl_paint, kDrawRRectFlags); + paint.paint(dl_paint, kDrawRRectFlags, DlTileMode::kDecal); builder()->DrawRRect(rrect.sk_rrect, dl_paint); } } @@ -293,7 +295,7 @@ void Canvas::drawDRRect(const RRect& outer, FML_DCHECK(paint.isNotNull()); if (display_list_builder_) { DlPaint dl_paint; - paint.paint(dl_paint, kDrawDRRectFlags); + paint.paint(dl_paint, kDrawDRRectFlags, DlTileMode::kDecal); builder()->DrawDRRect(outer.sk_rrect, inner.sk_rrect, dl_paint); } } @@ -309,7 +311,7 @@ void Canvas::drawOval(double left, FML_DCHECK(paint.isNotNull()); if (display_list_builder_) { DlPaint dl_paint; - paint.paint(dl_paint, kDrawOvalFlags); + paint.paint(dl_paint, kDrawOvalFlags, DlTileMode::kDecal); builder()->DrawOval(SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top), SafeNarrow(right), SafeNarrow(bottom)), dl_paint); @@ -326,7 +328,7 @@ void Canvas::drawCircle(double x, FML_DCHECK(paint.isNotNull()); if (display_list_builder_) { DlPaint dl_paint; - paint.paint(dl_paint, kDrawCircleFlags); + paint.paint(dl_paint, kDrawCircleFlags, DlTileMode::kDecal); builder()->DrawCircle(SkPoint::Make(SafeNarrow(x), SafeNarrow(y)), SafeNarrow(radius), dl_paint); } @@ -346,9 +348,9 @@ void Canvas::drawArc(double left, FML_DCHECK(paint.isNotNull()); if (display_list_builder_) { DlPaint dl_paint; - paint.paint(dl_paint, useCenter // - ? kDrawArcWithCenterFlags - : kDrawArcNoCenterFlags); + paint.paint(dl_paint, + useCenter ? kDrawArcWithCenterFlags : kDrawArcNoCenterFlags, + DlTileMode::kDecal); builder()->DrawArc( SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top), SafeNarrow(right), SafeNarrow(bottom)), @@ -371,7 +373,7 @@ void Canvas::drawPath(const CanvasPath* path, } if (display_list_builder_) { DlPaint dl_paint; - paint.paint(dl_paint, kDrawPathFlags); + paint.paint(dl_paint, kDrawPathFlags, DlTileMode::kDecal); builder()->DrawPath(path->path(), dl_paint); } } @@ -401,7 +403,8 @@ Dart_Handle Canvas::drawImage(const CanvasImage* image, auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex); if (display_list_builder_) { DlPaint dl_paint; - const DlPaint* opt_paint = paint.paint(dl_paint, kDrawImageWithPaintFlags); + const DlPaint* opt_paint = + paint.paint(dl_paint, kDrawImageWithPaintFlags, DlTileMode::kClamp); builder()->DrawImage(dl_image, SkPoint::Make(SafeNarrow(x), SafeNarrow(y)), sampling, opt_paint); } @@ -444,7 +447,7 @@ Dart_Handle Canvas::drawImageRect(const CanvasImage* image, if (display_list_builder_) { DlPaint dl_paint; const DlPaint* opt_paint = - paint.paint(dl_paint, kDrawImageRectWithPaintFlags); + paint.paint(dl_paint, kDrawImageRectWithPaintFlags, DlTileMode::kClamp); builder()->DrawImageRect(dl_image, src, dst, sampling, opt_paint, DlCanvas::SrcRectConstraint::kFast); } @@ -489,7 +492,7 @@ Dart_Handle Canvas::drawImageNine(const CanvasImage* image, if (display_list_builder_) { DlPaint dl_paint; const DlPaint* opt_paint = - paint.paint(dl_paint, kDrawImageNineWithPaintFlags); + paint.paint(dl_paint, kDrawImageNineWithPaintFlags, DlTileMode::kClamp); builder()->DrawImageNine(dl_image, icenter, dst, filter, opt_paint); } return Dart_Null(); @@ -524,13 +527,13 @@ void Canvas::drawPoints(Dart_Handle paint_objects, DlPaint dl_paint; switch (point_mode) { case DlCanvas::PointMode::kPoints: - paint.paint(dl_paint, kDrawPointsAsPointsFlags); + paint.paint(dl_paint, kDrawPointsAsPointsFlags, DlTileMode::kDecal); break; case DlCanvas::PointMode::kLines: - paint.paint(dl_paint, kDrawPointsAsLinesFlags); + paint.paint(dl_paint, kDrawPointsAsLinesFlags, DlTileMode::kDecal); break; case DlCanvas::PointMode::kPolygon: - paint.paint(dl_paint, kDrawPointsAsPolygonFlags); + paint.paint(dl_paint, kDrawPointsAsPolygonFlags, DlTileMode::kDecal); break; } builder()->DrawPoints(point_mode, @@ -554,7 +557,7 @@ void Canvas::drawVertices(const Vertices* vertices, FML_DCHECK(paint.isNotNull()); if (display_list_builder_) { DlPaint dl_paint; - paint.paint(dl_paint, kDrawVerticesFlags); + paint.paint(dl_paint, kDrawVerticesFlags, DlTileMode::kDecal); builder()->DrawVertices(vertices->vertices(), blend_mode, dl_paint); } } @@ -603,7 +606,8 @@ Dart_Handle Canvas::drawAtlas(Dart_Handle paint_objects, } DlPaint dl_paint; - const DlPaint* opt_paint = paint.paint(dl_paint, kDrawAtlasWithPaintFlags); + const DlPaint* opt_paint = + paint.paint(dl_paint, kDrawAtlasWithPaintFlags, DlTileMode::kClamp); builder()->DrawAtlas( dl_image, reinterpret_cast(transforms.data()), reinterpret_cast(rects.data()), dl_color.data(), diff --git a/lib/ui/painting/color_filter.cc b/lib/ui/painting/color_filter.cc index f0d780d27208d..e58cb659209c0 100644 --- a/lib/ui/painting/color_filter.cc +++ b/lib/ui/painting/color_filter.cc @@ -23,7 +23,7 @@ void ColorFilter::Create(Dart_Handle wrapper) { } void ColorFilter::initMode(int color, int blend_mode) { - filter_ = DlBlendColorFilter::Make(static_cast(color), + filter_ = DlColorFilter::MakeBlend(static_cast(color), static_cast(blend_mode)); } @@ -39,15 +39,15 @@ void ColorFilter::initMatrix(const tonic::Float32List& color_matrix) { matrix[9] *= 1.0f / 255; matrix[14] *= 1.0f / 255; matrix[19] *= 1.0f / 255; - filter_ = DlMatrixColorFilter::Make(matrix); + filter_ = DlColorFilter::MakeMatrix(matrix); } void ColorFilter::initLinearToSrgbGamma() { - filter_ = DlLinearToSrgbGammaColorFilter::kInstance; + filter_ = DlColorFilter::MakeLinearToSrgbGamma(); } void ColorFilter::initSrgbToLinearGamma() { - filter_ = DlSrgbToLinearGammaColorFilter::kInstance; + filter_ = DlColorFilter::MakeSrgbToLinearGamma(); } ColorFilter::~ColorFilter() = default; diff --git a/lib/ui/painting/fragment_program.cc b/lib/ui/painting/fragment_program.cc index 6e63cbd6161ae..54b321609c08e 100644 --- a/lib/ui/painting/fragment_program.cc +++ b/lib/ui/painting/fragment_program.cc @@ -33,6 +33,8 @@ static std::string RuntimeStageBackendToString( return "OpenGLES"; case impeller::RuntimeStageBackend::kVulkan: return "Vulkan"; + case impeller::RuntimeStageBackend::kOpenGLES3: + return "OpenGLES3"; } } @@ -144,6 +146,13 @@ std::shared_ptr FragmentProgram::MakeDlColorSource( std::move(float_uniforms)); } +std::shared_ptr FragmentProgram::MakeDlImageFilter( + std::shared_ptr> float_uniforms, + const std::vector>& children) { + return DlImageFilter::MakeRuntimeEffect(runtime_effect_, children, + std::move(float_uniforms)); +} + void FragmentProgram::Create(Dart_Handle wrapper) { auto res = fml::MakeRefCounted(); res->AssociateWithDartWrapper(wrapper); diff --git a/lib/ui/painting/fragment_program.h b/lib/ui/painting/fragment_program.h index 65d4d8809fd9a..65eb460658c51 100644 --- a/lib/ui/painting/fragment_program.h +++ b/lib/ui/painting/fragment_program.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_LIB_UI_PAINTING_FRAGMENT_PROGRAM_H_ #define FLUTTER_LIB_UI_PAINTING_FRAGMENT_PROGRAM_H_ +#include "display_list/effects/dl_image_filter.h" #include "flutter/display_list/effects/dl_runtime_effect.h" #include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/shader.h" @@ -38,6 +39,10 @@ class FragmentProgram : public RefCountedDartWrappable { std::shared_ptr> float_uniforms, const std::vector>& children); + std::shared_ptr MakeDlImageFilter( + std::shared_ptr> float_uniforms, + const std::vector>& children); + private: FragmentProgram(); sk_sp runtime_effect_; diff --git a/lib/ui/painting/fragment_shader.cc b/lib/ui/painting/fragment_shader.cc index 491d312e7e6c0..c9e88170ec4a4 100644 --- a/lib/ui/painting/fragment_shader.cc +++ b/lib/ui/painting/fragment_shader.cc @@ -9,15 +9,8 @@ #include "flutter/display_list/dl_tile_mode.h" #include "flutter/display_list/effects/dl_color_source.h" -#include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/fragment_program.h" -#include "flutter/lib/ui/ui_dart_state.h" -#include "third_party/skia/include/core/SkString.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/typed_data/typed_list.h" namespace flutter { @@ -55,7 +48,7 @@ Dart_Handle ReusableFragmentShader::Create(Dart_Handle wrapper, } bool ReusableFragmentShader::ValidateSamplers() { - for (auto i = 0u; i < samplers_.size(); i += 1) { + for (auto i = 0u; i < samplers_.size(); i++) { if (samplers_[i] == nullptr) { return false; } @@ -80,7 +73,7 @@ void ReusableFragmentShader::SetImageSampler(Dart_Handle index_handle, // TODO(115794): Once the DlImageSampling enum is replaced, expose the // sampling options as a new default parameter for users. - samplers_[index] = std::make_shared( + samplers_[index] = DlColorSource::MakeImage( image->image(), DlTileMode::kClamp, DlTileMode::kClamp, DlImageSampling::kNearestNeighbor, nullptr); // This should be true since we already checked the image above, but @@ -93,6 +86,19 @@ void ReusableFragmentShader::SetImageSampler(Dart_Handle index_handle, uniform_floats[float_count_ + 2 * index + 1] = image->height(); } +std::shared_ptr ReusableFragmentShader::as_image_filter() const { + FML_CHECK(program_); + + // The lifetime of this object is longer than a frame, and the uniforms can be + // continually changed on the UI thread. So we take a copy of the uniforms + // before handing it to the DisplayList for consumption on the render thread. + auto uniform_data = std::make_shared>(); + uniform_data->resize(uniform_data_->size()); + memcpy(uniform_data->data(), uniform_data_->bytes(), uniform_data->size()); + + return program_->MakeDlImageFilter(std::move(uniform_data), samplers_); +} + std::shared_ptr ReusableFragmentShader::shader( DlImageSampling sampling) { FML_CHECK(program_); @@ -111,6 +117,24 @@ std::shared_ptr ReusableFragmentShader::shader( return source; } +// Image filters require at least one uniform sampler input to bind +// the input texture. +bool ReusableFragmentShader::ValidateImageFilter() { + if (samplers_.size() < 1) { + return false; + } + // The first sampler does not need to be set. + for (auto i = 1u; i < samplers_.size(); i++) { + if (samplers_[i] == nullptr) { + return false; + } + // The samplers should have been checked as they were added, this + // is a double-sanity-check. + FML_DCHECK(samplers_[i]->isUIThreadSafe()); + } + return true; +} + void ReusableFragmentShader::Dispose() { uniform_data_.reset(); program_ = nullptr; diff --git a/lib/ui/painting/fragment_shader.h b/lib/ui/painting/fragment_shader.h index e6756b09edd30..e82a22418156a 100644 --- a/lib/ui/painting/fragment_shader.h +++ b/lib/ui/painting/fragment_shader.h @@ -38,11 +38,15 @@ class ReusableFragmentShader : public Shader { bool ValidateSamplers(); + bool ValidateImageFilter(); + void Dispose(); // |Shader| std::shared_ptr shader(DlImageSampling) override; + std::shared_ptr as_image_filter() const; + private: ReusableFragmentShader(fml::RefPtr program, uint64_t float_count, diff --git a/lib/ui/painting/gradient.cc b/lib/ui/painting/gradient.cc index 6f3f8b5d60f3d..1b87943e51162 100644 --- a/lib/ui/painting/gradient.cc +++ b/lib/ui/painting/gradient.cc @@ -33,19 +33,17 @@ void CanvasGradient::initLinear(const tonic::Float32List& end_points, color_stops.data() == nullptr); int num_colors = colors.num_elements() / 4; - static_assert(sizeof(SkPoint) == sizeof(float) * 2, - "SkPoint doesn't use floats."); - static_assert(sizeof(SkColor) == sizeof(int32_t), - "SkColor doesn't use int32_t."); + static_assert(sizeof(DlPoint) == sizeof(float) * 2, + "DlPoint doesn't use floats."); - SkMatrix sk_matrix; + DlMatrix dl_matrix; bool has_matrix = matrix4.data() != nullptr; if (has_matrix) { - sk_matrix = ToSkMatrix(matrix4); + dl_matrix = ToDlMatrix(matrix4); } - SkPoint p0 = SkPoint::Make(end_points[0], end_points[1]); - SkPoint p1 = SkPoint::Make(end_points[2], end_points[3]); + DlPoint p0 = DlPoint(end_points[0], end_points[1]); + DlPoint p1 = DlPoint(end_points[2], end_points[3]); std::vector dl_colors; dl_colors.reserve(num_colors); for (int i = 0; i < colors.num_elements(); i += 4) { @@ -58,7 +56,7 @@ void CanvasGradient::initLinear(const tonic::Float32List& end_points, dl_shader_ = DlColorSource::MakeLinear(p0, p1, num_colors, dl_colors.data(), color_stops.data(), tile_mode, - has_matrix ? &sk_matrix : nullptr); + has_matrix ? &dl_matrix : nullptr); // Just a sanity check, all gradient shaders should be thread-safe FML_DCHECK(dl_shader_->isUIThreadSafe()); } @@ -74,13 +72,10 @@ void CanvasGradient::initRadial(double center_x, color_stops.data() == nullptr); int num_colors = colors.num_elements() / 4; - static_assert(sizeof(SkColor) == sizeof(int32_t), - "SkColor doesn't use int32_t."); - - SkMatrix sk_matrix; + DlMatrix dl_matrix; bool has_matrix = matrix4.data() != nullptr; if (has_matrix) { - sk_matrix = ToSkMatrix(matrix4); + dl_matrix = ToDlMatrix(matrix4); } std::vector dl_colors; @@ -94,9 +89,9 @@ void CanvasGradient::initRadial(double center_x, } dl_shader_ = DlColorSource::MakeRadial( - SkPoint::Make(SafeNarrow(center_x), SafeNarrow(center_y)), - SafeNarrow(radius), num_colors, dl_colors.data(), color_stops.data(), - tile_mode, has_matrix ? &sk_matrix : nullptr); + DlPoint(SafeNarrow(center_x), SafeNarrow(center_y)), SafeNarrow(radius), + num_colors, dl_colors.data(), color_stops.data(), tile_mode, + has_matrix ? &dl_matrix : nullptr); // Just a sanity check, all gradient shaders should be thread-safe FML_DCHECK(dl_shader_->isUIThreadSafe()); } @@ -113,13 +108,10 @@ void CanvasGradient::initSweep(double center_x, color_stops.data() == nullptr); int num_colors = colors.num_elements() / 4; - static_assert(sizeof(SkColor) == sizeof(int32_t), - "SkColor doesn't use int32_t."); - - SkMatrix sk_matrix; + DlMatrix dl_matrix; bool has_matrix = matrix4.data() != nullptr; if (has_matrix) { - sk_matrix = ToSkMatrix(matrix4); + dl_matrix = ToDlMatrix(matrix4); } std::vector dl_colors; @@ -133,11 +125,11 @@ void CanvasGradient::initSweep(double center_x, } dl_shader_ = DlColorSource::MakeSweep( - SkPoint::Make(SafeNarrow(center_x), SafeNarrow(center_y)), + DlPoint(SafeNarrow(center_x), SafeNarrow(center_y)), SafeNarrow(start_angle) * 180.0f / static_cast(M_PI), SafeNarrow(end_angle) * 180.0f / static_cast(M_PI), num_colors, dl_colors.data(), color_stops.data(), tile_mode, - has_matrix ? &sk_matrix : nullptr); + has_matrix ? &dl_matrix : nullptr); // Just a sanity check, all gradient shaders should be thread-safe FML_DCHECK(dl_shader_->isUIThreadSafe()); } @@ -156,13 +148,10 @@ void CanvasGradient::initTwoPointConical(double start_x, color_stops.data() == nullptr); int num_colors = colors.num_elements() / 4; - static_assert(sizeof(SkColor) == sizeof(int32_t), - "SkColor doesn't use int32_t."); - - SkMatrix sk_matrix; + DlMatrix dl_matrix; bool has_matrix = matrix4.data() != nullptr; if (has_matrix) { - sk_matrix = ToSkMatrix(matrix4); + dl_matrix = ToDlMatrix(matrix4); } std::vector dl_colors; @@ -176,11 +165,10 @@ void CanvasGradient::initTwoPointConical(double start_x, } dl_shader_ = DlColorSource::MakeConical( - SkPoint::Make(SafeNarrow(start_x), SafeNarrow(start_y)), - SafeNarrow(start_radius), - SkPoint::Make(SafeNarrow(end_x), SafeNarrow(end_y)), + DlPoint(SafeNarrow(start_x), SafeNarrow(start_y)), + SafeNarrow(start_radius), DlPoint(SafeNarrow(end_x), SafeNarrow(end_y)), SafeNarrow(end_radius), num_colors, dl_colors.data(), color_stops.data(), - tile_mode, has_matrix ? &sk_matrix : nullptr); + tile_mode, has_matrix ? &dl_matrix : nullptr); // Just a sanity check, all gradient shaders should be thread-safe FML_DCHECK(dl_shader_->isUIThreadSafe()); } diff --git a/lib/ui/painting/image_decoder_impeller.cc b/lib/ui/painting/image_decoder_impeller.cc index 6a1118954a9be..d88e0807c5d02 100644 --- a/lib/ui/painting/image_decoder_impeller.cc +++ b/lib/ui/painting/image_decoder_impeller.cc @@ -114,6 +114,7 @@ DecompressResult ImageDecoderImpeller::DecompressTexture( SkISize target_size, impeller::ISize max_texture_size, bool supports_wide_gamut, + const std::shared_ptr& capabilities, const std::shared_ptr& allocator) { TRACE_EVENT0("impeller", __FUNCTION__); if (!descriptor) { @@ -238,10 +239,11 @@ DecompressResult ImageDecoderImpeller::DecompressTexture( : std::optional(image_info.makeDimensions(target_size)); if (source_size.width() > max_texture_size.width || - source_size.height() > max_texture_size.height) { + source_size.height() > max_texture_size.height || + !capabilities->SupportsTextureToTextureBlits()) { //---------------------------------------------------------------------------- /// 2. If the decoded image isn't the requested target size and the src size - /// exceeds the device max texture size, perform a slow CPU reisze. + /// exceeds the device max texture size, perform a slow CPU resize. /// TRACE_EVENT0("impeller", "SlowCPUDecodeScale"); const auto scaled_image_info = image_info.makeDimensions(target_size); @@ -357,7 +359,6 @@ ImageDecoderImpeller::UnsafeUploadTextureToPrivate( resize_desc.usage |= impeller::TextureUsage::kShaderWrite; resize_desc.compression_type = impeller::CompressionType::kLossless; } - auto resize_texture = context->GetResourceAllocator()->CreateTexture(resize_desc); if (!resize_texture) { @@ -382,6 +383,14 @@ ImageDecoderImpeller::UnsafeUploadTextureToPrivate( return std::make_pair(nullptr, decode_error); } + // Flush the pending command buffer to ensure that its output becomes visible + // to the raster thread. + if (context->AddTrackingFence(result_texture)) { + command_buffer->WaitUntilScheduled(); + } else { + command_buffer->WaitUntilCompleted(); + } + context->DisposeThreadLocalCachedResources(); return std::make_pair( @@ -519,6 +528,13 @@ void ImageDecoderImpeller::Decode(fml::RefPtr descriptor, result, supports_wide_gamut = supports_wide_gamut_, // gpu_disabled_switch = gpu_disabled_switch_]() { +#if FML_OS_IOS_SIMULATOR + // No-op backend. + if (!context) { + return; + } +#endif // FML_OS_IOS_SIMULATOR + if (!context) { result(nullptr, "No Impeller context is available"); return; @@ -529,7 +545,8 @@ void ImageDecoderImpeller::Decode(fml::RefPtr descriptor, // Always decompress on the concurrent runner. auto bitmap_result = DecompressTexture( raw_descriptor, target_size, max_size_supported, - supports_wide_gamut, context->GetResourceAllocator()); + /*supports_wide_gamut=*/supports_wide_gamut, + context->GetCapabilities(), context->GetResourceAllocator()); if (!bitmap_result.device_buffer) { result(nullptr, bitmap_result.decode_error); return; diff --git a/lib/ui/painting/image_decoder_impeller.h b/lib/ui/painting/image_decoder_impeller.h index 1d956a30a936c..3a1e5172a9917 100644 --- a/lib/ui/painting/image_decoder_impeller.h +++ b/lib/ui/painting/image_decoder_impeller.h @@ -11,6 +11,7 @@ #include "flutter/lib/ui/painting/image_decoder.h" #include "impeller/core/formats.h" #include "impeller/geometry/size.h" +#include "impeller/renderer/capabilities.h" #include "include/core/SkImageInfo.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -68,6 +69,7 @@ class ImageDecoderImpeller final : public ImageDecoder { SkISize target_size, impeller::ISize max_texture_size, bool supports_wide_gamut, + const std::shared_ptr& capabilities, const std::shared_ptr& allocator); /// @brief Create a device private texture from the provided host buffer. diff --git a/lib/ui/painting/image_decoder_no_gl_unittests.cc b/lib/ui/painting/image_decoder_no_gl_unittests.cc index d935fa2e3e9f9..aad5032952ea2 100644 --- a/lib/ui/painting/image_decoder_no_gl_unittests.cc +++ b/lib/ui/painting/image_decoder_no_gl_unittests.cc @@ -3,8 +3,10 @@ // found in the LICENSE file. #include "flutter/lib/ui/painting/image_decoder_no_gl_unittests.h" +#include #include "flutter/fml/endianness.h" +#include "impeller/renderer/capabilities.h" #include "include/core/SkColorType.h" namespace flutter { @@ -80,6 +82,10 @@ TEST(ImageDecoderNoGLTest, ImpellerWideGamutDisplayP3) { #endif auto data = flutter::testing::OpenFixtureAsSkData("DisplayP3Logo.png"); auto image = SkImages::DeferredFromEncodedData(data); + std::shared_ptr capabilities = + impeller::CapabilitiesBuilder() + .SetSupportsTextureToTextureBlits(true) + .Build(); ASSERT_TRUE(image != nullptr); ASSERT_EQ(SkISize::Make(100, 100), image->dimensions()); @@ -100,7 +106,7 @@ TEST(ImageDecoderNoGLTest, ImpellerWideGamutDisplayP3) { std::optional wide_result = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/true, allocator); + /*supports_wide_gamut=*/true, capabilities, allocator); ASSERT_TRUE(wide_result.has_value()); ASSERT_EQ(wide_result->image_info.colorType(), kRGBA_F16_SkColorType); ASSERT_TRUE(wide_result->image_info.colorSpace()->isSRGB()); @@ -124,7 +130,7 @@ TEST(ImageDecoderNoGLTest, ImpellerWideGamutDisplayP3) { std::optional narrow_result = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/false, allocator); + /*supports_wide_gamut=*/false, capabilities, allocator); ASSERT_TRUE(narrow_result.has_value()); ASSERT_EQ(narrow_result->image_info.colorType(), kRGBA_8888_SkColorType); @@ -137,6 +143,10 @@ TEST(ImageDecoderNoGLTest, ImpellerWideGamutIndexedPng) { #endif auto data = flutter::testing::OpenFixtureAsSkData("WideGamutIndexed.png"); auto image = SkImages::DeferredFromEncodedData(data); + std::shared_ptr capabilities = + impeller::CapabilitiesBuilder() + .SetSupportsTextureToTextureBlits(true) + .Build(); ASSERT_TRUE(image != nullptr); ASSERT_EQ(SkISize::Make(100, 100), image->dimensions()); @@ -157,7 +167,7 @@ TEST(ImageDecoderNoGLTest, ImpellerWideGamutIndexedPng) { std::optional wide_result = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/true, allocator); + /*supports_wide_gamut=*/true, capabilities, allocator); ASSERT_EQ(wide_result->image_info.colorType(), kBGR_101010x_XR_SkColorType); ASSERT_TRUE(wide_result->image_info.colorSpace()->isSRGB()); @@ -180,19 +190,23 @@ TEST(ImageDecoderNoGLTest, ImpellerWideGamutIndexedPng) { std::optional narrow_result = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/false, allocator); + /*supports_wide_gamut=*/false, capabilities, allocator); ASSERT_TRUE(narrow_result.has_value()); ASSERT_EQ(narrow_result->image_info.colorType(), kRGBA_8888_SkColorType); #endif // IMPELLER_SUPPORTS_RENDERING } -TEST(ImageDecoderNoGLTest, ImepllerUnmultipliedAlphaPng) { +TEST(ImageDecoderNoGLTest, ImpellerUnmultipliedAlphaPng) { #if defined(OS_FUCHSIA) GTEST_SKIP() << "Fuchsia can't load the test fixtures."; #endif auto data = flutter::testing::OpenFixtureAsSkData("unmultiplied_alpha.png"); auto image = SkImages::DeferredFromEncodedData(data); + std::shared_ptr capabilities = + impeller::CapabilitiesBuilder() + .SetSupportsTextureToTextureBlits(true) + .Build(); ASSERT_TRUE(image != nullptr); ASSERT_EQ(SkISize::Make(11, 11), image->dimensions()); @@ -210,7 +224,7 @@ TEST(ImageDecoderNoGLTest, ImepllerUnmultipliedAlphaPng) { std::optional result = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(11, 11), {11, 11}, - /*supports_wide_gamut=*/true, allocator); + /*supports_wide_gamut=*/true, capabilities, allocator); ASSERT_EQ(result->image_info.colorType(), kRGBA_8888_SkColorType); const SkPixmap& pixmap = result->sk_bitmap->pixmap(); diff --git a/lib/ui/painting/image_decoder_unittests.cc b/lib/ui/painting/image_decoder_unittests.cc index 69096eeca3866..968c09d3446c7 100644 --- a/lib/ui/painting/image_decoder_unittests.cc +++ b/lib/ui/painting/image_decoder_unittests.cc @@ -24,6 +24,7 @@ #include "flutter/testing/test_gl_surface.h" #include "flutter/testing/testing.h" #include "fml/logging.h" +#include "impeller/core/runtime_types.h" #include "impeller/renderer/command_queue.h" #include "third_party/skia/include/codec/SkCodecAnimation.h" #include "third_party/skia/include/core/SkData.h" @@ -96,6 +97,10 @@ class TestImpellerContext : public impeller::Context { void Shutdown() override {} + RuntimeStageBackend GetRuntimeStageBackend() const override { + return RuntimeStageBackend::kVulkan; + } + bool DidDisposeResources() const { return did_dispose_; } mutable size_t command_buffer_count_ = 0; @@ -441,12 +446,16 @@ TEST_F(ImageDecoderFixtureTest, ImpellerNullColorspace) { std::move(data), image->imageInfo(), 10 * 4); #if IMPELLER_SUPPORTS_RENDERING + std::shared_ptr capabilities = + impeller::CapabilitiesBuilder() + .SetSupportsTextureToTextureBlits(true) + .Build(); std::shared_ptr allocator = std::make_shared(); std::optional decompressed = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/true, allocator); + /*supports_wide_gamut=*/true, capabilities, allocator); ASSERT_TRUE(decompressed.has_value()); ASSERT_EQ(decompressed->image_info.colorType(), kRGBA_8888_SkColorType); ASSERT_EQ(decompressed->image_info.colorSpace(), nullptr); @@ -468,12 +477,16 @@ TEST_F(ImageDecoderFixtureTest, ImpellerPixelConversion32F) { std::move(data), image->imageInfo(), 10 * 16); #if IMPELLER_SUPPORTS_RENDERING + std::shared_ptr capabilities = + impeller::CapabilitiesBuilder() + .SetSupportsTextureToTextureBlits(true) + .Build(); std::shared_ptr allocator = std::make_shared(); std::optional decompressed = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/true, allocator); + /*supports_wide_gamut=*/true, capabilities, allocator); ASSERT_TRUE(decompressed.has_value()); ASSERT_EQ(decompressed->image_info.colorType(), kRGBA_F16_SkColorType); @@ -496,12 +509,16 @@ TEST_F(ImageDecoderFixtureTest, ImpellerWideGamutDisplayP3Opaque) { std::move(generator)); #if IMPELLER_SUPPORTS_RENDERING + std::shared_ptr capabilities = + impeller::CapabilitiesBuilder() + .SetSupportsTextureToTextureBlits(true) + .Build(); std::shared_ptr allocator = std::make_shared(); std::optional wide_result = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/true, allocator); + /*supports_wide_gamut=*/true, capabilities, allocator); ASSERT_TRUE(wide_result.has_value()); ASSERT_EQ(wide_result->image_info.colorType(), kBGR_101010x_XR_SkColorType); @@ -526,7 +543,7 @@ TEST_F(ImageDecoderFixtureTest, ImpellerWideGamutDisplayP3Opaque) { std::optional narrow_result = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/false, allocator); + /*supports_wide_gamut=*/false, capabilities, allocator); ASSERT_TRUE(narrow_result.has_value()); ASSERT_EQ(narrow_result->image_info.colorType(), kRGBA_8888_SkColorType); @@ -548,12 +565,16 @@ TEST_F(ImageDecoderFixtureTest, ImpellerNonWideGamut) { std::move(generator)); #if IMPELLER_SUPPORTS_RENDERING + std::shared_ptr capabilities = + impeller::CapabilitiesBuilder() + .SetSupportsTextureToTextureBlits(true) + .Build(); std::shared_ptr allocator = std::make_shared(); std::optional result = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(600, 200), {600, 200}, - /*supports_wide_gamut=*/true, allocator); + /*supports_wide_gamut=*/true, capabilities, allocator); ASSERT_TRUE(result.has_value()); ASSERT_EQ(result->image_info.colorType(), kRGBA_8888_SkColorType); @@ -802,13 +823,21 @@ TEST(ImageDecoderTest, VerifySimpleDecoding) { EXPECT_EQ(compressed_image->alphaType(), kOpaque_SkAlphaType); #if IMPELLER_SUPPORTS_RENDERING + std::shared_ptr capabilities = + impeller::CapabilitiesBuilder() + .SetSupportsTextureToTextureBlits(true) + .Build(); + std::shared_ptr capabilities_no_blit = + impeller::CapabilitiesBuilder() + .SetSupportsTextureToTextureBlits(false) + .Build(); // Bitmap sizes reflect the original image size as resizing is done on the // GPU if the src size is smaller than the max texture size. std::shared_ptr allocator = std::make_shared(); auto result_1 = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(6, 2), {1000, 1000}, - /*supports_wide_gamut=*/false, allocator); + /*supports_wide_gamut=*/false, capabilities, allocator); EXPECT_EQ(result_1.sk_bitmap->width(), 75); EXPECT_EQ(result_1.sk_bitmap->height(), 25); @@ -816,7 +845,7 @@ TEST(ImageDecoderTest, VerifySimpleDecoding) { // max texture size even if destination size isn't max texture size. auto result_2 = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(6, 2), {10, 10}, - /*supports_wide_gamut=*/false, allocator); + /*supports_wide_gamut=*/false, capabilities, allocator); EXPECT_EQ(result_2.sk_bitmap->width(), 6); EXPECT_EQ(result_2.sk_bitmap->height(), 2); @@ -824,9 +853,16 @@ TEST(ImageDecoderTest, VerifySimpleDecoding) { // is scaled down. auto result_3 = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(60, 20), {10, 10}, - /*supports_wide_gamut=*/false, allocator); + /*supports_wide_gamut=*/false, capabilities, allocator); EXPECT_EQ(result_3.sk_bitmap->width(), 10); EXPECT_EQ(result_3.sk_bitmap->height(), 10); + + // CPU resize is forced. + auto result_4 = ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(6, 2), {1000, 1000}, + /*supports_wide_gamut=*/false, capabilities_no_blit, allocator); + EXPECT_EQ(result_4.sk_bitmap->width(), 6); + EXPECT_EQ(result_4.sk_bitmap->height(), 2); #endif // IMPELLER_SUPPORTS_RENDERING } diff --git a/lib/ui/painting/image_filter.cc b/lib/ui/painting/image_filter.cc index 74f89834fe757..2319008bc4fa2 100644 --- a/lib/ui/painting/image_filter.cc +++ b/lib/ui/painting/image_filter.cc @@ -4,9 +4,13 @@ #include "flutter/lib/ui/painting/image_filter.h" +#include "display_list/dl_sampling_options.h" +#include "display_list/effects/dl_image_filters.h" #include "flutter/lib/ui/floating_point.h" #include "flutter/lib/ui/painting/matrix.h" #include "flutter/lib/ui/ui_dart_state.h" +#include "lib/ui/painting/fragment_program.h" +#include "lib/ui/painting/fragment_shader.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" #include "third_party/tonic/dart_binding_macros.h" @@ -51,37 +55,74 @@ ImageFilter::ImageFilter() {} ImageFilter::~ImageFilter() {} +const std::shared_ptr ImageFilter::filter( + DlTileMode mode) const { + if (is_dynamic_tile_mode_) { + FML_DCHECK(filter_.get() != nullptr); + const DlBlurImageFilter* blur_filter = filter_->asBlur(); + FML_DCHECK(blur_filter != nullptr); + if (blur_filter->tile_mode() != mode) { + return DlBlurImageFilter::Make(blur_filter->sigma_x(), + blur_filter->sigma_y(), mode); + } + } + return filter_; +} + void ImageFilter::initBlur(double sigma_x, double sigma_y, - DlTileMode tile_mode) { + int tile_mode_index) { + DlTileMode tile_mode; + bool is_dynamic; + if (tile_mode_index < 0) { + is_dynamic = true; + tile_mode = DlTileMode::kClamp; + } else { + is_dynamic = false; + tile_mode = static_cast(tile_mode_index); + } filter_ = DlBlurImageFilter::Make(SafeNarrow(sigma_x), SafeNarrow(sigma_y), tile_mode); + // If it was a NOP filter, don't bother processing dynamic substitutions + // (They'd fail the FML_DCHECK anyway) + is_dynamic_tile_mode_ = is_dynamic && filter_; } void ImageFilter::initDilate(double radius_x, double radius_y) { + is_dynamic_tile_mode_ = false; filter_ = DlDilateImageFilter::Make(SafeNarrow(radius_x), SafeNarrow(radius_y)); } void ImageFilter::initErode(double radius_x, double radius_y) { + is_dynamic_tile_mode_ = false; filter_ = DlErodeImageFilter::Make(SafeNarrow(radius_x), SafeNarrow(radius_y)); } void ImageFilter::initMatrix(const tonic::Float64List& matrix4, int filterQualityIndex) { + is_dynamic_tile_mode_ = false; auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex); - filter_ = DlMatrixImageFilter::Make(ToSkMatrix(matrix4), sampling); + filter_ = DlMatrixImageFilter::Make(ToDlMatrix(matrix4), sampling); } void ImageFilter::initColorFilter(ColorFilter* colorFilter) { FML_DCHECK(colorFilter); + is_dynamic_tile_mode_ = false; filter_ = DlColorFilterImageFilter::Make(colorFilter->filter()); } void ImageFilter::initComposeFilter(ImageFilter* outer, ImageFilter* inner) { FML_DCHECK(outer && inner); - filter_ = DlComposeImageFilter::Make(outer->filter(), inner->filter()); + is_dynamic_tile_mode_ = false; + filter_ = DlComposeImageFilter::Make(outer->filter(DlTileMode::kClamp), + inner->filter(DlTileMode::kClamp)); +} + +void ImageFilter::initShader(ReusableFragmentShader* shader) { + FML_DCHECK(shader); + filter_ = shader->as_image_filter(); } } // namespace flutter diff --git a/lib/ui/painting/image_filter.h b/lib/ui/painting/image_filter.h index 68d70b3f150c5..c7bb6369bdb3a 100644 --- a/lib/ui/painting/image_filter.h +++ b/lib/ui/painting/image_filter.h @@ -9,6 +9,7 @@ #include "flutter/display_list/effects/dl_image_filter.h" #include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/color_filter.h" +#include "lib/ui/painting/fragment_shader.h" #include "third_party/tonic/typed_data/typed_list.h" namespace tonic { @@ -28,21 +29,23 @@ class ImageFilter : public RefCountedDartWrappable { static DlImageSampling SamplingFromIndex(int filterQualityIndex); static DlFilterMode FilterModeFromIndex(int index); - void initBlur(double sigma_x, double sigma_y, DlTileMode tile_mode); + void initBlur(double sigma_x, double sigma_y, int tile_mode_index); void initDilate(double radius_x, double radius_y); void initErode(double radius_x, double radius_y); void initMatrix(const tonic::Float64List& matrix4, int filter_quality_index); void initColorFilter(ColorFilter* colorFilter); void initComposeFilter(ImageFilter* outer, ImageFilter* inner); + void initShader(ReusableFragmentShader* shader); - const std::shared_ptr filter() const { return filter_; } + const std::shared_ptr filter(DlTileMode mode) const; static void RegisterNatives(tonic::DartLibraryNatives* natives); private: ImageFilter(); - std::shared_ptr filter_; + std::shared_ptr filter_; + bool is_dynamic_tile_mode_ = false; }; } // namespace flutter diff --git a/lib/ui/painting/image_generator_apng.cc b/lib/ui/painting/image_generator_apng.cc index 159d87638aeb2..08182c9ff2bd4 100644 --- a/lib/ui/painting/image_generator_apng.cc +++ b/lib/ui/painting/image_generator_apng.cc @@ -110,6 +110,28 @@ bool APNGImageGenerator::GetPixels(const SkImageInfo& info, << ") of APNG due to the frame missing data (frame_info)."; return false; } + if ( + // Check for unsigned integer wrapping for + // frame.{x|y}_offset + frame_info.{width|height}(). + frame.x_offset > + std::numeric_limits::max() - frame_info.width() || + frame.y_offset > + std::numeric_limits::max() - frame_info.height() || + + frame.x_offset + frame_info.width() > + static_cast(info.width()) || + frame.y_offset + frame_info.height() > + static_cast(info.height())) { + FML_DLOG(ERROR) + << "Decoded image at index " << image_index + << " (frame index: " << frame_index + << ") rejected because the destination region (x: " << frame.x_offset + << ", y: " << frame.y_offset << ", width: " << frame_info.width() + << ", height: " << frame_info.height() + << ") is not entirely within the destination surface (width: " + << info.width() << ", height: " << info.height() << ")."; + return false; + } //---------------------------------------------------------------------------- /// 3. Composite the frame onto the canvas. @@ -630,7 +652,19 @@ uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() { bool APNGImageGenerator::RenderDefaultImage(const SkImageInfo& info, void* pixels, size_t row_bytes) { - SkCodec::Result result = images_[0].codec->getPixels(info, pixels, row_bytes); + APNGImage& frame = images_[0]; + SkImageInfo frame_info = frame.codec->getInfo(); + if (frame_info.width() > info.width() || + frame_info.height() > info.height()) { + FML_DLOG(ERROR) + << "Default image rejected because the destination region (width: " + << frame_info.width() << ", height: " << frame_info.height() + << ") is not entirely within the destination surface (width: " + << info.width() << ", height: " << info.height() << ")."; + return false; + } + + SkCodec::Result result = frame.codec->getPixels(info, pixels, row_bytes); if (result != SkCodec::kSuccess) { FML_DLOG(ERROR) << "Failed to decode the APNG's default/fallback image. " "SkCodec::Result: " diff --git a/lib/ui/painting/image_shader.cc b/lib/ui/painting/image_shader.cc index 5093f51433c0e..a55c885d16eeb 100644 --- a/lib/ui/painting/image_shader.cc +++ b/lib/ui/painting/image_shader.cc @@ -5,6 +5,7 @@ #include "flutter/lib/ui/painting/image_shader.h" #include "flutter/lib/ui/painting/image_filter.h" +#include "flutter/display_list/effects/color_sources/dl_image_color_source.h" #include "flutter/lib/ui/painting/display_list_image_gpu.h" #include "flutter/lib/ui/ui_dart_state.h" #include "third_party/tonic/converter/dart_converter.h" @@ -35,23 +36,25 @@ Dart_Handle ImageShader::initWithImage(CanvasImage* image, image_ = image->image(); tonic::Float64List matrix4(matrix_handle); - SkMatrix local_matrix = ToSkMatrix(matrix4); + DlMatrix local_matrix = ToDlMatrix(matrix4); matrix4.Release(); sampling_is_locked_ = filter_quality_index >= 0; DlImageSampling sampling = sampling_is_locked_ ? ImageFilter::SamplingFromIndex(filter_quality_index) : DlImageSampling::kLinear; - cached_shader_ = std::make_shared( - image_, tmx, tmy, sampling, &local_matrix); + cached_shader_ = + DlColorSource::MakeImage(image_, tmx, tmy, sampling, &local_matrix); FML_DCHECK(cached_shader_->isUIThreadSafe()); return Dart_Null(); } std::shared_ptr ImageShader::shader(DlImageSampling sampling) { - if (sampling_is_locked_ || sampling == cached_shader_->sampling()) { + const DlImageColorSource* image_shader = cached_shader_->asImage(); + FML_DCHECK(image_shader); + if (sampling_is_locked_ || sampling == image_shader->sampling()) { return cached_shader_; } - return cached_shader_->with_sampling(sampling); + return image_shader->WithSampling(sampling); } int ImageShader::width() { diff --git a/lib/ui/painting/image_shader.h b/lib/ui/painting/image_shader.h index 61b4a851ebb15..07042abd1ef8c 100644 --- a/lib/ui/painting/image_shader.h +++ b/lib/ui/painting/image_shader.h @@ -43,7 +43,7 @@ class ImageShader : public Shader { sk_sp image_; bool sampling_is_locked_; - std::shared_ptr cached_shader_; + std::shared_ptr cached_shader_; }; } // namespace flutter diff --git a/lib/ui/painting/matrix.cc b/lib/ui/painting/matrix.cc index 7b8e5ed98106b..02b4b101e092d 100644 --- a/lib/ui/painting/matrix.cc +++ b/lib/ui/painting/matrix.cc @@ -43,6 +43,18 @@ SkMatrix ToSkMatrix(const tonic::Float64List& matrix4) { return sk_matrix; } +DlMatrix ToDlMatrix(const tonic::Float64List& matrix4) { + FML_DCHECK(matrix4.data()); + // clang-format off + return DlMatrix::MakeColumn( + SafeNarrow(matrix4[ 0]), SafeNarrow(matrix4[ 1]), SafeNarrow(matrix4[ 2]), SafeNarrow(matrix4[ 3]), + SafeNarrow(matrix4[ 4]), SafeNarrow(matrix4[ 5]), SafeNarrow(matrix4[ 6]), SafeNarrow(matrix4[ 7]), + SafeNarrow(matrix4[ 8]), SafeNarrow(matrix4[ 9]), SafeNarrow(matrix4[10]), SafeNarrow(matrix4[11]), + SafeNarrow(matrix4[12]), SafeNarrow(matrix4[13]), SafeNarrow(matrix4[14]), SafeNarrow(matrix4[15]) + ); + // clang-format on +} + tonic::Float64List ToMatrix4(const SkMatrix& sk_matrix) { tonic::Float64List matrix4(Dart_NewTypedData(Dart_TypedData_kFloat64, 16)); for (int i = 0; i < 9; ++i) { diff --git a/lib/ui/painting/matrix.h b/lib/ui/painting/matrix.h index 94b618cd6ff73..2312a64ec920f 100644 --- a/lib/ui/painting/matrix.h +++ b/lib/ui/painting/matrix.h @@ -5,6 +5,8 @@ #ifndef FLUTTER_LIB_UI_PAINTING_MATRIX_H_ #define FLUTTER_LIB_UI_PAINTING_MATRIX_H_ +#include "flutter/display_list/geometry/dl_geometry_types.h" + #include "third_party/skia/include/core/SkM44.h" #include "third_party/skia/include/core/SkMatrix.h" #include "third_party/tonic/typed_data/typed_list.h" @@ -13,6 +15,7 @@ namespace flutter { SkM44 ToSkM44(const tonic::Float64List& matrix4); SkMatrix ToSkMatrix(const tonic::Float64List& matrix4); +DlMatrix ToDlMatrix(const tonic::Float64List& matrix4); tonic::Float64List ToMatrix4(const SkMatrix& sk_matrix); } // namespace flutter diff --git a/lib/ui/painting/multi_frame_codec.cc b/lib/ui/painting/multi_frame_codec.cc index 1521c535fd97c..f05ff111236ce 100644 --- a/lib/ui/painting/multi_frame_codec.cc +++ b/lib/ui/painting/multi_frame_codec.cc @@ -190,6 +190,17 @@ void MultiFrameCodec::State::GetNextFrameAndInvokeCallback( const std::shared_ptr& gpu_disable_sync_switch, size_t trace_id, const std::shared_ptr& impeller_context) { +#if FML_OS_IOS_SIMULATOR + // Noop backend. + if (!resourceContext && !impeller_context) { + ui_task_runner->PostTask( + fml::MakeCopyable([callback = std::move(callback)]() { + // must be destroyed on UI thread. + })); + return; + } +#endif // FML_OS_IOS_SIMULATOR + fml::RefPtr image = nullptr; int duration = 0; sk_sp dlImage; diff --git a/lib/ui/painting/paint.cc b/lib/ui/painting/paint.cc index 19a535520c1d3..ea0ceebe9c167 100644 --- a/lib/ui/painting/paint.cc +++ b/lib/ui/painting/paint.cc @@ -83,7 +83,8 @@ Paint::Paint(Dart_Handle paint_objects, Dart_Handle paint_data) : paint_objects_(paint_objects), paint_data_(paint_data) {} const DlPaint* Paint::paint(DlPaint& paint, - const DisplayListAttributeFlags& flags) const { + const DisplayListAttributeFlags& flags, + DlTileMode tile_mode) const { if (isNull()) { return nullptr; } @@ -148,7 +149,7 @@ const DlPaint* Paint::paint(DlPaint& paint, } else { ImageFilter* decoded = tonic::DartConverter::FromDart(image_filter); - paint.setImageFilter(decoded->filter()); + paint.setImageFilter(decoded->filter(tile_mode)); } } } @@ -208,7 +209,7 @@ const DlPaint* Paint::paint(DlPaint& paint, return &paint; } -void Paint::toDlPaint(DlPaint& paint) const { +void Paint::toDlPaint(DlPaint& paint, DlTileMode tile_mode) const { if (isNull()) { return; } @@ -252,7 +253,7 @@ void Paint::toDlPaint(DlPaint& paint) const { if (!Dart_IsNull(image_filter)) { ImageFilter* decoded = tonic::DartConverter::FromDart(image_filter); - paint.setImageFilter(decoded->filter()); + paint.setImageFilter(decoded->filter(tile_mode)); } } diff --git a/lib/ui/painting/paint.h b/lib/ui/painting/paint.h index e87313a489dcb..2fdf1fae8f89d 100644 --- a/lib/ui/painting/paint.h +++ b/lib/ui/painting/paint.h @@ -18,9 +18,10 @@ class Paint { Paint(Dart_Handle paint_objects, Dart_Handle paint_data); const DlPaint* paint(DlPaint& paint, - const DisplayListAttributeFlags& flags) const; + const DisplayListAttributeFlags& flags, + DlTileMode tile_mode) const; - void toDlPaint(DlPaint& paint) const; + void toDlPaint(DlPaint& paint, DlTileMode tile_mode) const; bool isNull() const { return Dart_IsNull(paint_data_); } bool isNotNull() const { return !Dart_IsNull(paint_data_); } diff --git a/lib/ui/painting/paint_unittests.cc b/lib/ui/painting/paint_unittests.cc index 87447fa548124..9d18dbed43bde 100644 --- a/lib/ui/painting/paint_unittests.cc +++ b/lib/ui/painting/paint_unittests.cc @@ -21,7 +21,7 @@ TEST_F(ShellTest, ConvertPaintToDlPaint) { Dart_GetField(dart_paint, tonic::ToDart("_objects")); Dart_Handle paint_data = Dart_GetField(dart_paint, tonic::ToDart("_data")); Paint ui_paint(paint_objects, paint_data); - ui_paint.toDlPaint(dl_paint); + ui_paint.toDlPaint(dl_paint, DlTileMode::kClamp); message_latch->Signal(); }; @@ -52,7 +52,7 @@ TEST_F(ShellTest, ConvertPaintToDlPaint) { ASSERT_EQ(dl_paint.getBlendMode(), DlBlendMode::kModulate); ASSERT_EQ(static_cast(dl_paint.getColor().argb()), 0x11223344u); ASSERT_EQ(*dl_paint.getColorFilter(), - DlBlendColorFilter(DlColor(0x55667788), DlBlendMode::kXor)); + *DlColorFilter::MakeBlend(DlColor(0x55667788), DlBlendMode::kXor)); ASSERT_EQ(*dl_paint.getMaskFilter(), DlBlurMaskFilter(DlBlurStyle::kInner, 0.75)); ASSERT_EQ(dl_paint.getDrawStyle(), DlDrawStyle::kStroke); diff --git a/lib/ui/semantics.dart b/lib/ui/semantics.dart index bb9d586e41e54..59052b157b687 100644 --- a/lib/ui/semantics.dart +++ b/lib/ui/semantics.dart @@ -45,6 +45,7 @@ class SemanticsAction { static const int _kMoveCursorBackwardByWordIndex = 1 << 20; static const int _kSetTextIndex = 1 << 21; static const int _kFocusIndex = 1 << 22; + static const int _kScrollToOffsetIndex = 1 << 23; // READ THIS: if you add an action here, you MUST update the // numSemanticsActions value in testing/dart/semantics_test.dart and // lib/web_ui/test/engine/semantics/semantics_api_test.dart, or tests @@ -86,6 +87,17 @@ class SemanticsAction { /// scrollable. static const SemanticsAction scrollDown = SemanticsAction._(_kScrollDownIndex, 'scrollDown'); + /// A request to scroll the scrollable container to a given scroll offset. + /// + /// The payload of this [SemanticsAction] is a flutter-standard-encoded + /// [Float64List] of length 2 containing the target horizontal and vertical + /// offsets (in logical pixels) the receiving scrollable container should + /// scroll to. + /// + /// This action is used by iOS Full Keyboard Access to reveal contents that + /// are currently not visible in the viewport. + static const SemanticsAction scrollToOffset = SemanticsAction._(_kScrollToOffsetIndex, 'scrollToOffset'); + /// A request to increase the value represented by the semantics node. /// /// For example, this action might be recognized by a slider control. @@ -265,6 +277,7 @@ class SemanticsAction { _kScrollRightIndex: scrollRight, _kScrollUpIndex: scrollUp, _kScrollDownIndex: scrollDown, + _kScrollToOffsetIndex: scrollToOffset, _kIncreaseIndex: increase, _kDecreaseIndex: decrease, _kShowOnScreenIndex: showOnScreen, @@ -764,7 +777,7 @@ base class LocaleStringAttribute extends StringAttribute { _initLocaleStringAttribute(this, range.start, range.end, locale.toLanguageTag()); } - /// The lanuage of this attribute. + /// The language of this attribute. final Locale locale; @Native(symbol: 'NativeStringAttribute::initLocaleStringAttribute') diff --git a/lib/ui/semantics/semantics_node.h b/lib/ui/semantics/semantics_node.h index 98d84c6c6aebb..0f02ff3202f18 100644 --- a/lib/ui/semantics/semantics_node.h +++ b/lib/ui/semantics/semantics_node.h @@ -43,6 +43,7 @@ enum class SemanticsAction : int32_t { kMoveCursorBackwardByWord = 1 << 20, kSetText = 1 << 21, kFocus = 1 << 22, + kScrollToOffset = 1 << 23, }; const int kVerticalScrollSemanticsActions = diff --git a/lib/ui/text/paragraph_builder.cc b/lib/ui/text/paragraph_builder.cc index bb3c7416bce13..d5ea7cc2fc12f 100644 --- a/lib/ui/text/paragraph_builder.cc +++ b/lib/ui/text/paragraph_builder.cc @@ -458,7 +458,7 @@ void ParagraphBuilder::pushStyle(const tonic::Int32List& encoded, Paint background(background_objects, background_data); if (background.isNotNull()) { DlPaint dl_paint; - background.toDlPaint(dl_paint); + background.toDlPaint(dl_paint, DlTileMode::kDecal); style.background = dl_paint; } } @@ -467,7 +467,7 @@ void ParagraphBuilder::pushStyle(const tonic::Int32List& encoded, Paint foreground(foreground_objects, foreground_data); if (foreground.isNotNull()) { DlPaint dl_paint; - foreground.toDlPaint(dl_paint); + foreground.toDlPaint(dl_paint, DlTileMode::kDecal); style.foreground = dl_paint; } } diff --git a/lib/ui/ui_dart_state.cc b/lib/ui/ui_dart_state.cc index 7440a88b24d93..f4022b04afb4e 100644 --- a/lib/ui/ui_dart_state.cc +++ b/lib/ui/ui_dart_state.cc @@ -169,6 +169,10 @@ void UIDartState::FlushMicrotasksNow() { microtask_queue_.RunMicrotasks(); } +bool UIDartState::HasPendingMicrotasks() { + return microtask_queue_.HasMicrotasks(); +} + void UIDartState::AddOrRemoveTaskObserver(bool add) { auto task_runner = context_.task_runners.GetUITaskRunner(); if (!task_runner) { diff --git a/lib/ui/ui_dart_state.h b/lib/ui/ui_dart_state.h index 3ce014f44fa75..514d1df3e4d87 100644 --- a/lib/ui/ui_dart_state.h +++ b/lib/ui/ui_dart_state.h @@ -130,6 +130,8 @@ class UIDartState : public tonic::DartState { void FlushMicrotasksNow(); + bool HasPendingMicrotasks(); + fml::WeakPtr GetIOManager() const; fml::RefPtr GetSkiaUnrefQueue() const; diff --git a/lib/web_ui/README.md b/lib/web_ui/README.md index 21bde30a6ffa4..e05830879d754 100644 --- a/lib/web_ui/README.md +++ b/lib/web_ui/README.md @@ -180,7 +180,7 @@ The available versions of Chrome for Testing available can be found [here](https - Edit `dev/package_lock.yaml` and update the following values under `chrome`: - Set `version` to the full four part version number of the build of Chrome for Testing you want to roll (for example, `118.0.5993.70`) -- Run `dart dev/browser_roller.dart` and make sure it completes successfully. +- Run `dart dev/package_roller.dart` and make sure it completes successfully. The script uploads the specified versions of Chromium (and Chromedriver) to the right locations in CIPD: [Chrome](https://chrome-infra-packages.appspot.com/p/flutter_internal/browsers/chrome), [Chromedriver](https://chrome-infra-packages.appspot.com/p/flutter_internal/browser-drivers/chrome). @@ -194,7 +194,7 @@ If you have questions, contact the Flutter Web team on Flutter Discord on the We test with Firefox on LUCI in the Linux Web Engine builder. The process for rolling Firefox is even easier than Chromium. Simply update `package_lock.yaml` -with the latest version of Firefox, and run `browser_roller.dart`. +with the latest version of Firefox, and run `package_roller.dart`. #### .ci.yaml @@ -210,7 +210,7 @@ After rolling Chrome and/or Firefox, also update the CI dependencies in ] ``` -##### **browser_roller.dart** +##### **package_roller.dart** The script has the following command-line options: @@ -220,7 +220,7 @@ The script has the following command-line options: > Try the following! > > ```bash -> dart ./dev/browser_roller.dart --dry-run --verbose +> dart dev/package_roller.dart --dry-run --verbose > ``` #### **Other browsers / manual upload** diff --git a/lib/web_ui/dev/build.dart b/lib/web_ui/dev/build.dart index 1b6fcf3cbe5f6..58ca394dc9bb3 100644 --- a/lib/web_ui/dev/build.dart +++ b/lib/web_ui/dev/build.dart @@ -19,6 +19,7 @@ const Map targetAliases = { 'canvaskit': 'flutter/third_party/canvaskit:canvaskit_group', 'canvaskit_chromium': 'flutter/third_party/canvaskit:canvaskit_chromium_group', 'skwasm': 'flutter/third_party/canvaskit:skwasm_group', + 'skwasm_st': 'flutter/third_party/canvaskit:skwasm_st_group', 'archive': 'flutter/web_sdk:flutter_web_sdk_archive', }; diff --git a/lib/web_ui/dev/felt.dart b/lib/web_ui/dev/felt.dart index 0db0771ee2ba3..1e89cdb5fe165 100644 --- a/lib/web_ui/dev/felt.dart +++ b/lib/web_ui/dev/felt.dart @@ -10,6 +10,7 @@ import 'analyze.dart'; import 'build.dart'; import 'clean.dart'; import 'exceptions.dart'; +import 'generate_builder_json.dart'; import 'licenses.dart'; import 'roll_fallback_fonts.dart'; import 'test_runner.dart'; @@ -23,6 +24,7 @@ CommandRunner runner = CommandRunner( ..addCommand(BuildCommand()) ..addCommand(CleanCommand()) ..addCommand(RollFallbackFontsCommand()) + ..addCommand(GenerateBuilderJsonCommand()) ..addCommand(LicensesCommand()) ..addCommand(TestCommand()); diff --git a/lib/web_ui/dev/felt_config.dart b/lib/web_ui/dev/felt_config.dart index 31f873938a830..047c6612c1dd8 100644 --- a/lib/web_ui/dev/felt_config.dart +++ b/lib/web_ui/dev/felt_config.dart @@ -52,11 +52,19 @@ enum BrowserName { } class RunConfiguration { - RunConfiguration(this.name, this.browser, this.variant); + RunConfiguration( + this.name, + this.browser, + this.variant, + this.crossOriginIsolated, + this.forceSingleThreadedSkwasm + ); final String name; final BrowserName browser; final CanvasKitVariant? variant; + final bool crossOriginIsolated; + final bool forceSingleThreadedSkwasm; } class ArtifactDependencies { @@ -184,7 +192,15 @@ class FeltConfig { final CanvasKitVariant? variant = variantNode == null ? null : CanvasKitVariant.values.byName(variantNode as String); - final RunConfiguration runConfig = RunConfiguration(name, browser, variant); + final bool crossOriginIsolated = runConfigYaml['cross-origin-isolated'] as bool? ?? false; + final bool forceSingleThreadedSkwasm = runConfigYaml['force-single-threaded-skwasm'] as bool? ?? false; + final RunConfiguration runConfig = RunConfiguration( + name, + browser, + variant, + crossOriginIsolated, + forceSingleThreadedSkwasm + ); runConfigs.add(runConfig); if (runConfigsByName.containsKey(name)) { throw AssertionError('Duplicate run config name: $name'); diff --git a/lib/web_ui/dev/generate_builder_json.dart b/lib/web_ui/dev/generate_builder_json.dart index dd0c271ff951c..d4263e423b357 100644 --- a/lib/web_ui/dev/generate_builder_json.dart +++ b/lib/web_ui/dev/generate_builder_json.dart @@ -2,179 +2,205 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:convert'; +import 'dart:io' as io; +import 'package:args/command_runner.dart'; +import 'package:path/path.dart' as path; + +import 'environment.dart'; import 'felt_config.dart'; +import 'package_lock.dart'; -String generateBuilderJson(FeltConfig config) { - final Map outputJson = { - '_comment': 'THIS IS A GENERATED FILE. Do not edit this file directly.', - '_comment2': 'See `generate_builder_json.dart` for the generator code', - 'builds': [ - _getArtifactBuildStep(), - for (final TestBundle bundle in config.testBundles) - _getBundleBuildStep(bundle), - ], - 'tests': _getAllTestSteps(config.testSuites) - }; - return const JsonEncoder.withIndent(' ').convert(outputJson); -} +class GenerateBuilderJsonCommand extends Command { + @override + String get description => + 'Generates JSON for the engine_v2 builders to build and copy all artifacts, ' + 'compile all test bundles, and run all test suites on all platforms.'; -Map _getArtifactBuildStep() { - return { - 'name': 'web_tests/artifacts', - 'drone_dimensions': [ - 'device_type=none', - 'os=Linux', - 'cores=32' - ], - 'gclient_variables': { - 'download_android_deps': false, - 'download_jdk': false, - 'download_emsdk': true, - }, - 'gn': [ - '--web', - '--runtime-mode=release', - '--no-goma', - ], - 'ninja': { - 'config': 'wasm_release', - 'targets': [ - 'flutter/web_sdk:flutter_web_sdk_archive' - ] - }, - 'archives': [ - { - 'name': 'wasm_release', - 'base_path': 'out/wasm_release/zip_archives/', - 'type': 'gcs', - 'include_paths': [ - 'out/wasm_release/zip_archives/flutter-web-sdk.zip' - ], - 'realm': 'production', - } - ], - 'generators': { - 'tasks': [ - { - 'name': 'check licenses', - 'parameters': [ - 'check-licenses' - ], - 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], + @override + String get name => 'generate-builder-json'; - }, - { - 'name': 'web engine analysis', - 'parameters': [ - 'analyze' - ], - 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], - }, - { - 'name': 'copy artifacts for web tests', - 'parameters': [ - 'test', - '--copy-artifacts', - ], - 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], - }, - ] - }, - }; -} + @override + FutureOr? run() { + final PackageLock packageLock = PackageLock(); + final FeltConfig config = FeltConfig.fromFile( + path.join(environment.webUiTestDir.path, 'felt_config.yaml') + ); + final String configString = generate(config, packageLock); + final io.File configFile = io.File(path.join( + environment.flutterDirectory.path, + 'ci', + 'builders', + 'linux_web_engine.json', + )); + configFile.writeAsStringSync('$configString\n'); + return true; + } + + String generate(FeltConfig config, PackageLock packageLock) { + final Map outputJson = { + '_comment': 'THIS IS A GENERATED FILE. Do not edit this file directly.', + '_comment2': 'See `generate_builder_json.dart` for the generator code', + 'builds': [ + _getArtifactBuildStep(), + for (final TestBundle bundle in config.testBundles) + _getBundleBuildStep(bundle), + ], + 'tests': _getAllTestSteps(config.testSuites, packageLock) + }; + return const JsonEncoder.withIndent(' ').convert(outputJson); + } -Map _getBundleBuildStep(TestBundle bundle) { - return { - 'name': 'web_tests/test_bundles/${bundle.name}', - 'drone_dimensions': [ - 'device_type=none', - 'os=Linux', - ], - 'generators': { - 'tasks': [ + Map _getArtifactBuildStep() { + return { + 'name': 'web_tests/artifacts', + 'drone_dimensions': [ + 'device_type=none', + 'os=Linux', + 'cores=32' + ], + 'gclient_variables': { + 'download_android_deps': false, + 'download_jdk': false, + 'download_emsdk': true, + }, + 'gn': [ + '--web', + '--runtime-mode=release', + '--no-goma', + ], + 'ninja': { + 'config': 'wasm_release', + 'targets': [ + 'flutter/web_sdk:flutter_web_sdk_archive' + ] + }, + 'archives': [ { - 'name': 'compile bundle ${bundle.name}', - 'parameters': [ - 'test', - '--compile', - '--bundle=${bundle.name}', + 'name': 'wasm_release', + 'base_path': 'out/wasm_release/zip_archives/', + 'type': 'gcs', + 'include_paths': [ + 'out/wasm_release/zip_archives/flutter-web-sdk.zip' ], - 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], + 'realm': 'production', } - ] - }, - }; -} - -Iterable _getAllTestSteps(List suites) { - return [ - ..._getTestStepsForPlatform(suites, 'Linux', (TestSuite suite) => - suite.runConfig.browser == BrowserName.chrome || - suite.runConfig.browser == BrowserName.firefox - ), - ..._getTestStepsForPlatform(suites, 'Mac', specificOS: 'Mac-13', cpu: 'arm64', (TestSuite suite) => - suite.runConfig.browser == BrowserName.safari - ), - ..._getTestStepsForPlatform(suites, 'Windows', (TestSuite suite) => - suite.runConfig.browser == BrowserName.chrome - ), - ]; -} + ], + 'generators': { + 'tasks': [ + { + 'name': 'check licenses', + 'parameters': [ + 'check-licenses' + ], + 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], -Iterable _getTestStepsForPlatform( - List suites, - String platform, - bool Function(TestSuite suite) filter, { - String? specificOS, - String? cpu, -}) { - return suites - .where(filter) - .map((TestSuite suite) => { - 'name': '$platform run ${suite.name} suite', - 'recipe': 'engine_v2/tester_engine', - 'drone_dimensions': [ - 'device_type=none', - 'os=${specificOS ?? platform}', - if (cpu != null) 'cpu=$cpu', - ], - 'gclient_variables': { - 'download_android_deps': false, - 'download_jdk': false, - }, - 'dependencies': [ - 'web_tests/artifacts', - 'web_tests/test_bundles/${suite.testBundle.name}', - ], - 'test_dependencies': [ + }, { - 'dependency': 'goldctl', - 'version': 'git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd', + 'name': 'web engine analysis', + 'parameters': [ + 'analyze' + ], + 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], }, - if (suite.runConfig.browser == BrowserName.chrome) - { - 'dependency': 'chrome_and_driver', - 'version': '125.0.6422.141', - }, - if (suite.runConfig.browser == BrowserName.firefox) - { - 'dependency': 'firefox', - 'version': 'version:106.0', - } - ], + { + 'name': 'copy artifacts for web tests', + 'parameters': [ + 'test', + '--copy-artifacts', + ], + 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], + }, + ] + }, + }; + } + + Map _getBundleBuildStep(TestBundle bundle) { + return { + 'name': 'web_tests/test_bundles/${bundle.name}', + 'drone_dimensions': [ + 'device_type=none', + 'os=Linux', + ], + 'generators': { 'tasks': [ { - 'name': 'run suite ${suite.name}', + 'name': 'compile bundle ${bundle.name}', 'parameters': [ 'test', - '--run', - '--suite=${suite.name}' + '--compile', + '--bundle=${bundle.name}', ], - 'script': 'flutter/lib/web_ui/dev/felt', + 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], } ] - } - ); + }, + }; + } + + Iterable _getAllTestSteps(List suites, PackageLock packageLock) { + return [ + _getTestStepForPlatformAndBrowser(suites, packageLock, 'Linux', BrowserName.chrome), + _getTestStepForPlatformAndBrowser(suites, packageLock, 'Linux', BrowserName.firefox), + _getTestStepForPlatformAndBrowser(suites, packageLock, 'Mac', BrowserName.safari, specificOS: 'Mac-13', cpu: 'arm64'), + _getTestStepForPlatformAndBrowser(suites, packageLock, 'Windows', BrowserName.chrome) + ]; + } + + Map _getTestStepForPlatformAndBrowser( + List suites, + PackageLock packageLock, + String platform, + BrowserName browser, { + String? specificOS, + String? cpu, + }) { + final filteredSuites = suites.where((suite) => suite.runConfig.browser == browser); + final bundles = filteredSuites.map((suite) => suite.testBundle).toSet(); + return { + 'name': '$platform run ${browser.name} suites', + 'recipe': 'engine_v2/tester_engine', + 'drone_dimensions': [ + 'device_type=none', + 'os=${specificOS ?? platform}', + if (cpu != null) 'cpu=$cpu', + ], + 'gclient_variables': { + 'download_android_deps': false, + 'download_jdk': false, + }, + 'dependencies': [ + 'web_tests/artifacts', + ...bundles.map((bundle) => 'web_tests/test_bundles/${bundle.name}'), + ], + 'test_dependencies': [ + { + 'dependency': 'goldctl', + 'version': 'git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd', + }, + if (browser == BrowserName.chrome) + { + 'dependency': 'chrome_and_driver', + 'version': packageLock.chromeLock.version, + }, + if (browser == BrowserName.firefox) + { + 'dependency': 'firefox', + 'version': 'version:${packageLock.firefoxLock.version}', + } + ], + 'tasks': filteredSuites.map((suite) => { + 'name': 'run suite ${suite.name}', + 'parameters': [ + 'test', + '--run', + '--suite=${suite.name}', + ], + 'script': 'flutter/lib/web_ui/dev/felt', + }).toList(), + }; + } } diff --git a/lib/web_ui/dev/package_lock.yaml b/lib/web_ui/dev/package_lock.yaml index e00f0610ced1f..4e0408b8a5df6 100644 --- a/lib/web_ui/dev/package_lock.yaml +++ b/lib/web_ui/dev/package_lock.yaml @@ -4,7 +4,7 @@ chrome: version: '125.0.6422.141' firefox: - version: '106.0' + version: '132.0' edge: launcher_version: '1.2.0.0' diff --git a/lib/web_ui/dev/roll_fallback_fonts.dart b/lib/web_ui/dev/roll_fallback_fonts.dart index 3cc7af2f795cf..d4d0d321ee3e3 100644 --- a/lib/web_ui/dev/roll_fallback_fonts.dart +++ b/lib/web_ui/dev/roll_fallback_fonts.dart @@ -9,8 +9,6 @@ import 'dart:typed_data'; import 'package:args/command_runner.dart'; import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart' as crypto; -import 'package:csslib/parser.dart' as csslib; -import 'package:csslib/visitor.dart' show StyleSheet, UriTerm, Visitor; import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; @@ -61,40 +59,35 @@ class RollFallbackFontsCommand extends Command Future _generateFallbackFontData() async { final http.Client client = http.Client(); - final List fallbackFonts = []; - final Map urlForFamily = {}; - await _addApiFallbackFonts(client, fallbackFonts, urlForFamily); - await _addSplitFallbackFonts(client, fallbackFonts, urlForFamily); + final List<_FontInfo> fallbackFontInfo = <_FontInfo>[ + ...await _processSplitFallbackFonts(client, splitFallbackFonts), + ...await _processFallbackFonts(client, apiFallbackFonts), + ]; + + final List failedUrls = await _checkForLicenseAttributions(client, fallbackFontInfo); + if (failedUrls.isNotEmpty) { + throw ToolExit('Could not find license attribution at:\n - ${failedUrls.join('\n - ')}'); + } + + final List<_Font> fallbackFontData = <_Font>[]; final Map charsetForFamily = {}; final io.Directory fontDir = await io.Directory.systemTemp.createTemp('flutter_fallback_fonts'); print('Downloading fonts into temp directory: ${fontDir.path}'); final AccumulatorSink hashSink = AccumulatorSink(); final ByteConversionSink hasher = crypto.sha256.startChunkedConversion(hashSink); - for (final String family in fallbackFonts) { + + for (final (:family, :uri) in fallbackFontInfo) { print('Downloading $family...'); - final Uri? uri = urlForFamily[family]; - if (uri == null) { - throw ToolExit('Unable to determine URL to download $family. ' - 'Check if it is still hosted on Google Fonts.'); - } final http.Response fontResponse = await client.get(uri); if (fontResponse.statusCode != 200) { throw ToolExit('Failed to download font for $family'); } - final String urlString = uri.toString(); - if (!urlString.startsWith(expectedUrlPrefix)) { - throw ToolExit('Unexpected url format received from Google Fonts API: $urlString.'); - } - final String urlSuffix = urlString.substring(expectedUrlPrefix.length); + final String urlSuffix = getUrlSuffix(uri); final io.File fontFile = io.File(path.join(fontDir.path, urlSuffix)); final Uint8List bodyBytes = fontResponse.bodyBytes; - if (!await _checkForLicenseAttribution(client, urlSuffix, bodyBytes)) { - throw ToolExit( - 'Expected license attribution not found in file: $urlString'); - } hasher.add(utf8.encode(urlSuffix)); hasher.add(bodyBytes); @@ -112,12 +105,11 @@ class RollFallbackFontsCommand extends Command final StringBuffer sb = StringBuffer(); - final List<_Font> fonts = <_Font>[]; - - for (final String family in fallbackFonts) { + int index = 0; + for (final _FontInfo fontInfo in fallbackFontInfo) { final List starts = []; final List ends = []; - final String charset = charsetForFamily[family]!; + final String charset = charsetForFamily[fontInfo.family]!; for (final String range in charset.split(' ')) { // Range is one hexadecimal number or two, separated by `-`. final List parts = range.split('-'); @@ -130,10 +122,10 @@ class RollFallbackFontsCommand extends Command ends.add(last); } - fonts.add(_Font(family, fonts.length, starts, ends)); + fallbackFontData.add(_Font(fontInfo, index++, starts, ends)); } - final String fontSetsCode = _computeEncodedFontSets(fonts); + final String fontSetsCode = _computeEncodedFontSets(fallbackFontData); sb.writeln('// Copyright 2013 The Flutter Authors. All rights reserved.'); sb.writeln('// Use of this source code is governed by a BSD-style license ' @@ -146,14 +138,9 @@ class RollFallbackFontsCommand extends Command sb.writeln(); sb.writeln('List getFallbackFontList() => ['); - for (final _Font font in fonts) { - final String family = font.family; - final String urlString = urlForFamily[family]!.toString(); - if (!urlString.startsWith(expectedUrlPrefix)) { - throw ToolExit( - 'Unexpected url format received from Google Fonts API: $urlString.'); - } - final String urlSuffix = urlString.substring(expectedUrlPrefix.length); + for (final _Font font in fallbackFontData) { + final String family = font.info.family; + final String urlSuffix = getUrlSuffix(font.info.uri); sb.writeln(" NotoFont('$family', '$urlSuffix'),"); } sb.writeln('];'); @@ -307,16 +294,16 @@ OTHER DEALINGS IN THE FONT SOFTWARE. ]); } - Future _addApiFallbackFonts( + Future> _processFallbackFonts( http.Client client, - List fallbackFonts, - Map urlForFamily, + List requestedFonts, ) async { if (apiKey.isEmpty) { throw UsageException('No Google Fonts API key provided', argParser.usage); } + final List<_FontInfo> processedFonts = <_FontInfo>[]; final http.Response response = await client.get(Uri.parse( - 'https://www.googleapis.com/webfonts/v1/webfonts?key=$apiKey')); + 'https://www.googleapis.com/webfonts/v1/webfonts?capability=WOFF2&key=$apiKey')); if (response.statusCode != 200) { throw ToolExit('Failed to download Google Fonts list.'); } @@ -327,22 +314,25 @@ OTHER DEALINGS IN THE FONT SOFTWARE. .cast>(); for (final Map fontData in fontDatas) { final String family = fontData['family']! as String; - if (apiFallbackFonts.contains(family)) { + if (requestedFonts.contains(family)) { final files = fontData['files']! as Map; final Uri uri = Uri.parse(files['regular']! as String) .replace(scheme: 'https'); - urlForFamily[family] = uri; - fallbackFonts.add(family); + processedFonts.add(( + family: family, + uri: uri, + )); } } + return processedFonts; } - Future _addSplitFallbackFonts( + Future> _processSplitFallbackFonts( http.Client client, - List fallbackFonts, - Map urlForFamily, + List requestedFonts, ) async { - for (final String font in splitFallbackFonts) { + final List<_FontInfo> processedFonts = <_FontInfo>[]; + for (final String font in requestedFonts) { final String modifiedFontName = font.replaceAll(' ', '+'); final Uri cssUri = Uri.parse( 'https://fonts.googleapis.com/css2?family=$modifiedFontName'); @@ -354,26 +344,22 @@ OTHER DEALINGS IN THE FONT SOFTWARE. 'Chrome/112.0.0.0 Safari/537.36' }); final String cssString = response.body; - final StyleSheet stylesheet = csslib.parse(cssString); - final UriCollector uriCollector = UriCollector(); - stylesheet.visit(uriCollector); + // Match the patterns that look like: + // `src: url(...some url...)` + final r = RegExp(r'src:\s*url\((https?://[^)]+?\.woff2)\)'); int familyCount = 0; - for (final Uri uri in uriCollector.uris) { - final String fontName = '$font $familyCount'; - fallbackFonts.add(fontName); - urlForFamily[fontName] = uri; + // Give each font shard a unique family name. + for (final match in r.allMatches(cssString)) { + final String family = '$font $familyCount'; + final Uri uri = Uri.parse(match.group(1)!); + processedFonts.add(( + family: family, + uri: uri, + )); familyCount += 1; } } - } -} - -class UriCollector extends Visitor { - final List uris = []; - - @override - void visitUriTerm(UriTerm uriTerm) { - uris.add(Uri.parse(uriTerm.value as String)); + return processedFonts; } } @@ -382,7 +368,6 @@ const List apiFallbackFonts = [ 'Noto Sans', 'Noto Music', 'Noto Sans Symbols', - 'Noto Sans Symbols 2', 'Noto Sans Adlam', 'Noto Sans Anatolian Hieroglyphs', 'Noto Sans Arabic', @@ -404,12 +389,9 @@ const List apiFallbackFonts = [ 'Noto Sans Cham', 'Noto Sans Cherokee', 'Noto Sans Coptic', - 'Noto Sans Cuneiform', 'Noto Sans Cypriot', 'Noto Sans Deseret', 'Noto Sans Devanagari', - 'Noto Sans Duployan', - 'Noto Sans Egyptian Hieroglyphs', 'Noto Sans Elbasan', 'Noto Sans Elymaic', 'Noto Sans Ethiopic', @@ -420,7 +402,6 @@ const List apiFallbackFonts = [ 'Noto Sans Gujarati', 'Noto Sans Gunjala Gondi', 'Noto Sans Gurmukhi', - 'Noto Sans HK', 'Noto Sans Hanunoo', 'Noto Sans Hatran', 'Noto Sans Hebrew', @@ -428,9 +409,7 @@ const List apiFallbackFonts = [ 'Noto Sans Indic Siyaq Numbers', 'Noto Sans Inscriptional Pahlavi', 'Noto Sans Inscriptional Parthian', - 'Noto Sans JP', 'Noto Sans Javanese', - 'Noto Sans KR', 'Noto Sans Kaithi', 'Noto Sans Kannada', 'Noto Sans Kayah Li', @@ -489,7 +468,6 @@ const List apiFallbackFonts = [ 'Noto Sans Psalter Pahlavi', 'Noto Sans Rejang', 'Noto Sans Runic', - 'Noto Sans SC', 'Noto Sans Saurashtra', 'Noto Sans Sharada', 'Noto Sans Shavian', @@ -501,7 +479,6 @@ const List apiFallbackFonts = [ 'Noto Sans Sundanese', 'Noto Sans Syloti Nagri', 'Noto Sans Syriac', - 'Noto Sans TC', 'Noto Sans Tagalog', 'Noto Sans Tagbanwa', 'Noto Sans Tai Le', @@ -528,33 +505,70 @@ const List apiFallbackFonts = [ /// handling. const List splitFallbackFonts = [ 'Noto Color Emoji', + 'Noto Sans Symbols 2', + 'Noto Sans Cuneiform', + 'Noto Sans Duployan', + 'Noto Sans Egyptian Hieroglyphs', + 'Noto Sans HK', + 'Noto Sans JP', + 'Noto Sans KR', + 'Noto Sans SC', + 'Noto Sans TC', ]; -Future _checkForLicenseAttribution( +String getUrlSuffix(Uri fontUri) { + final String urlString = fontUri.toString(); + if (!urlString.startsWith(expectedUrlPrefix)) { + throw ToolExit('Unexpected url format received from Google Fonts API: $urlString.'); + } + return urlString.substring(expectedUrlPrefix.length); +} + +/// Checks the license attribution for unique fonts in the [fallbackFontInfo] +/// list. +/// +/// Returns a list of URLs that failed to contain the license attribution. +Future> _checkForLicenseAttributions( http.Client client, - String urlSuffix, - Uint8List fontBytes, + List<_FontInfo> fallbackFontInfo, ) async { const String googleFontsUpstream = 'https://github.com/google/fonts/tree/main/ofl'; const String attributionString = 'This Font Software is licensed under the SIL Open Font License, Version 1.1.'; - final String fontPackageName = urlSuffix.split('/').first; - final String fontLicenseUrl = '$googleFontsUpstream/$fontPackageName/OFL.txt'; - final http.Response response = await client.get(Uri.parse(fontLicenseUrl)); - if (response.statusCode != 200) { - throw ToolExit('Failed to download `$fontPackageName` license at $fontLicenseUrl'); + final failedUrls = []; + + final uniqueFontPackageNames = {}; + for (final (family: _, :uri) in fallbackFontInfo) { + final String urlSuffix = getUrlSuffix(uri); + final String fontPackageName = urlSuffix.split('/').first; + uniqueFontPackageNames.add(fontPackageName); + } + + for (final String fontPackageName in uniqueFontPackageNames) { + final String fontLicenseUrl = '$googleFontsUpstream/$fontPackageName/OFL.txt'; + final http.Response response = await client.get(Uri.parse(fontLicenseUrl)); + if (response.statusCode != 200) { + failedUrls.add(fontLicenseUrl); + continue; + } + final String licenseString = response.body; + if (!licenseString.contains(attributionString)) { + failedUrls.add(fontLicenseUrl); + } } - final String licenseString = response.body; - return licenseString.contains(attributionString); + return failedUrls; } +// The basic information of a font: its [family] (name) and [uri]. +typedef _FontInfo = ({String family, Uri uri}); + class _Font { - _Font(this.family, this.index, this.starts, this.ends); + _Font(this.info, this.index, this.starts, this.ends); - final String family; + final _FontInfo info; final int index; final List starts; final List ends; // inclusive ends @@ -566,9 +580,9 @@ class _Font { String.fromCharCodes( '$index'.codeUnits.map((int ch) => ch - 48 + 0x2080)); - String get _shortName => family.startsWith('Noto Sans ') - ? family.substring('Noto Sans '.length) - : family; + String get _shortName => info.family.startsWith('Noto Sans ') + ? info.family.substring('Noto Sans '.length) + : info.family; } /// The boundary of a range of a font. diff --git a/lib/web_ui/dev/steps/copy_artifacts_step.dart b/lib/web_ui/dev/steps/copy_artifacts_step.dart index 7c7293bbd5fc6..930b30ac9f894 100644 --- a/lib/web_ui/dev/steps/copy_artifacts_step.dart +++ b/lib/web_ui/dev/steps/copy_artifacts_step.dart @@ -137,6 +137,10 @@ class CopyArtifactsStep implements PipelineStep { )); for (final io.File imageFile in testImagesDir.listSync(recursive: true).whereType()) { + // Skip files that are used by Skia to test handling of invalid input. + if (pathlib.basename(imageFile.path).contains('invalid')) { + continue; + } final io.File destination = io.File(pathlib.join( environment.webTestsArtifactsDir.path, 'test_images', @@ -230,7 +234,9 @@ class CopyArtifactsStep implements PipelineStep { 'skwasm.wasm', 'skwasm.wasm.map', 'skwasm.js', - 'skwasm.worker.js', + 'skwasm_st.wasm', + 'skwasm_st.wasm.map', + 'skwasm_st.js', ]) { final io.File sourceFile = io.File(pathlib.join( outBuildPath, diff --git a/lib/web_ui/dev/steps/run_suite_step.dart b/lib/web_ui/dev/steps/run_suite_step.dart index e2992e23989a5..50f8336571edf 100644 --- a/lib/web_ui/dev/steps/run_suite_step.dart +++ b/lib/web_ui/dev/steps/run_suite_step.dart @@ -176,11 +176,13 @@ class RunSuiteStep implements PipelineStep { Future _createSkiaClient() async { if (suite.testBundle.compileConfigs.length > 1) { + print('Not creating skia client due to multiple compile configs'); // Multiple compile configs are only used for our fallback tests, which // do not collect goldens. return null; } if (suite.runConfig.browser == BrowserName.safari) { + print('Not creating skia client for Safari'); // Goldens from Safari produce too many diffs, disabled for now. // See https://github.com/flutter/flutter/issues/143591 return null; @@ -192,17 +194,26 @@ class RunSuiteStep implements PipelineStep { workDirectory.deleteSync(recursive: true); } final bool isWasm = suite.testBundle.compileConfigs.first.compiler == Compiler.dart2wasm; + final bool singleThreaded = suite.runConfig.forceSingleThreadedSkwasm || !suite.runConfig.crossOriginIsolated; + final String rendererName = switch (renderer) { + Renderer.skwasm => singleThreaded ? 'skwasm_st' : 'skwasm', + _ => renderer.name, + }; + + final dimensions = { + 'Browser': suite.runConfig.browser.name, + if (isWasm) 'Wasm': 'true', + 'Renderer': rendererName, + if (variant != null) 'CanvasKitVariant': variant.name, + }; + print('Created Skia Gold Client. dimensions: $dimensions'); final SkiaGoldClient skiaClient = SkiaGoldClient( workDirectory, - dimensions: { - 'Browser': suite.runConfig.browser.name, - if (isWasm) 'Wasm': 'true', - 'Renderer': renderer.name, - if (variant != null) 'CanvasKitVariant': variant.name, - }, + dimensions: dimensions, ); if (await _checkSkiaClient(skiaClient)) { + print('Successfully checked Skia Gold Client'); return skiaClient; } diff --git a/lib/web_ui/dev/test_platform.dart b/lib/web_ui/dev/test_platform.dart index 88a633f497588..adde6032593c1 100644 --- a/lib/web_ui/dev/test_platform.dart +++ b/lib/web_ui/dev/test_platform.dart @@ -153,10 +153,6 @@ class BrowserPlatform extends PlatformPlugin { /// The URL for this server. Uri get url => server.url.resolve('/'); - bool get needsCrossOriginIsolated => suite.testBundle.compileConfigs.any( - (CompileConfiguration config) => config.renderer == Renderer.skwasm - ); - /// A [OneOffHandler] for servicing WebSocket connections for /// [BrowserManager]s. /// @@ -507,7 +503,7 @@ class BrowserPlatform extends PlatformPlugin { fileInDirectory.readAsBytesSync(), headers: { HttpHeaders.contentTypeHeader: contentType, - if (isScript && needsCrossOriginIsolated) + if (isScript && suite.runConfig.crossOriginIsolated) ...coopCoepHeaders, }, ); @@ -574,6 +570,7 @@ class BrowserPlatform extends PlatformPlugin { config: { canvasKitVariant: "${getCanvasKitVariant()}", canvasKitBaseUrl: "/canvaskit", + forceSingleThreadedSkwasm: ${suite.runConfig.forceSingleThreadedSkwasm}, }, }); @@ -589,7 +586,7 @@ class BrowserPlatform extends PlatformPlugin { ''', headers: { 'Content-Type': 'text/html', - if (needsCrossOriginIsolated) + if (suite.runConfig.crossOriginIsolated) ...coopCoepHeaders }); } diff --git a/lib/web_ui/dev/test_runner.dart b/lib/web_ui/dev/test_runner.dart index cc10ca0df85e7..672c70f837f72 100644 --- a/lib/web_ui/dev/test_runner.dart +++ b/lib/web_ui/dev/test_runner.dart @@ -13,7 +13,6 @@ import 'package:watcher/src/watch_event.dart'; import 'environment.dart'; import 'exceptions.dart'; import 'felt_config.dart'; -import 'generate_builder_json.dart'; import 'pipeline.dart'; import 'steps/compile_bundle_step.dart'; import 'steps/copy_artifacts_step.dart'; @@ -49,13 +48,6 @@ class TestCommand extends Command with ArgUtils { 'will be run as part of this invocation, without actually ' 'compiling or running them.' ) - ..addFlag( - 'generate-builder-json', - help: - 'Generates JSON for the engine_v2 builders to build and copy all' - 'artifacts, compile all test bundles, and run all test suites on' - 'all platforms.' - ) ..addFlag( 'compile', help: @@ -331,17 +323,6 @@ class TestCommand extends Command with ArgUtils { final List filteredSuites = _filterTestSuites(); final List bundles = _filterBundlesForSuites(filteredSuites); final ArtifactDependencies artifacts = _artifactsForSuites(filteredSuites); - if (boolArg('generate-builder-json')) { - final String configString = generateBuilderJson(config); - final io.File configFile = io.File(path.join( - environment.flutterDirectory.path, - 'ci', - 'builders', - 'linux_web_engine.json', - )); - configFile.writeAsStringSync('$configString\n'); - return true; - } if (isList || isVerbose) { print('Suites:'); for (final TestSuite suite in filteredSuites) { diff --git a/lib/web_ui/flutter_js/src/loader.js b/lib/web_ui/flutter_js/src/loader.js index c24f3765a57ca..2194a9e2be85e 100644 --- a/lib/web_ui/flutter_js/src/loader.js +++ b/lib/web_ui/flutter_js/src/loader.js @@ -79,8 +79,7 @@ export class FlutterLoader { const rendererIsCompatible = (renderer) => { switch (renderer) { case "skwasm": - return browserEnvironment.crossOriginIsolated - && browserEnvironment.hasChromiumBreakIterators + return browserEnvironment.hasChromiumBreakIterators && browserEnvironment.hasImageCodecs && browserEnvironment.supportsWasmGC; default: diff --git a/lib/web_ui/flutter_js/src/skwasm_loader.js b/lib/web_ui/flutter_js/src/skwasm_loader.js index 181c65bd7c4f8..8a72f1cd77c9e 100644 --- a/lib/web_ui/flutter_js/src/skwasm_loader.js +++ b/lib/web_ui/flutter_js/src/skwasm_loader.js @@ -6,32 +6,24 @@ import { createWasmInstantiator } from "./instantiate_wasm.js"; import { resolveUrlWithSegments } from "./utils.js"; export const loadSkwasm = async (deps, config, browserEnvironment, baseUrl) => { - const rawSkwasmUrl = resolveUrlWithSegments(baseUrl, "skwasm.js") + const fileStem = (browserEnvironment.crossOriginIsolated && !config.forceSingleThreadedSkwasm) ? "skwasm" : "skwasm_st"; + const rawSkwasmUrl = resolveUrlWithSegments(baseUrl, `${fileStem}.js`) let skwasmUrl = rawSkwasmUrl; if (deps.flutterTT.policy) { skwasmUrl = deps.flutterTT.policy.createScriptURL(skwasmUrl); } - const wasmInstantiator = createWasmInstantiator(resolveUrlWithSegments(baseUrl, "skwasm.wasm")); + const wasmInstantiator = createWasmInstantiator(resolveUrlWithSegments(baseUrl, `${fileStem}.wasm`)); const skwasm = await import(skwasmUrl); return await skwasm.default({ instantiateWasm: wasmInstantiator, - locateFile: (fileName, scriptDirectory) => { - // When hosted via a CDN or some other url that is not the same - // origin as the main script of the page, we will fail to create - // a web worker with the .worker.js script. This workaround will - // make sure that the worker JS can be loaded regardless of where - // it is hosted. - const url = scriptDirectory + fileName; - if (url.endsWith('.worker.js')) { - return URL.createObjectURL(new Blob( - [`importScripts('${url}');`], - { 'type': 'application/javascript' })); - } - return url; - }, - // Because of the above workaround, the worker is just a blob and - // can't locate the main script using a relative path to itself, - // so we pass the main script location in. - mainScriptUrlOrBlob: rawSkwasmUrl, + // When hosted via a CDN or some other url that is not the same + // origin as the main script of the page, we will fail to create + // a web worker with the bootstrapping script. This workaround will + // make sure that the worker JS can be loaded regardless of where + // it is hosted. + mainScriptUrlOrBlob: new Blob( + [`import '${skwasmUrl}'`], + { 'type': 'application/javascript' }, + ), }); } diff --git a/lib/web_ui/flutter_js/src/types.d.ts b/lib/web_ui/flutter_js/src/types.d.ts index e3cdee0515928..259e904162920 100644 --- a/lib/web_ui/flutter_js/src/types.d.ts +++ b/lib/web_ui/flutter_js/src/types.d.ts @@ -57,6 +57,7 @@ export interface FlutterConfiguration { hostElement: HtmlElement?; fontFallbackBaseUrl: string?; entryPointBaseUrl: string?; + forceSingleThreadedSkwasm: boolean?; } export interface ServiceWorkerSettings { diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index 7bdd746633328..bb007afc84f02 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -68,14 +68,15 @@ class Color { return (x * 255.0).round() & 0xff; } - int get value { + int get value => toARGB32(); + + int toARGB32() { return _floatToInt8(a) << 24 | _floatToInt8(r) << 16 | _floatToInt8(g) << 8 | _floatToInt8(b) << 0; } - int get alpha => (0xff000000 & value) >> 24; double get opacity => alpha / 0xFF; @@ -591,7 +592,7 @@ class ImageFilter { factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0, - TileMode tileMode = TileMode.clamp + TileMode? tileMode }) => engine.renderer.createBlurImageFilter( sigmaX: sigmaX, sigmaY: sigmaY, @@ -613,6 +614,13 @@ class ImageFilter { factory ImageFilter.compose({required ImageFilter outer, required ImageFilter inner}) => engine.renderer.composeImageFilters(outer: outer, inner: inner); + + // ignore: avoid_unused_constructor_parameters + factory ImageFilter.shader(FragmentShader shader) { + throw UnsupportedError('ImageFilter.shader only supported with Impeller rendering engine.'); + } + + static bool get isShaderFilterSupported => false; } enum ColorSpace { diff --git a/lib/web_ui/lib/semantics.dart b/lib/web_ui/lib/semantics.dart index be36411520314..786eaacc5454f 100644 --- a/lib/web_ui/lib/semantics.dart +++ b/lib/web_ui/lib/semantics.dart @@ -33,6 +33,7 @@ class SemanticsAction { static const int _kMoveCursorBackwardByWordIndex = 1 << 20; static const int _kSetTextIndex = 1 << 21; static const int _kFocusIndex = 1 << 22; + static const int _kScrollToOffsetIndex = 1 << 23; static const SemanticsAction tap = SemanticsAction._(_kTapIndex, 'tap'); static const SemanticsAction longPress = SemanticsAction._(_kLongPressIndex, 'longPress'); @@ -40,6 +41,7 @@ class SemanticsAction { static const SemanticsAction scrollRight = SemanticsAction._(_kScrollRightIndex, 'scrollRight'); static const SemanticsAction scrollUp = SemanticsAction._(_kScrollUpIndex, 'scrollUp'); static const SemanticsAction scrollDown = SemanticsAction._(_kScrollDownIndex, 'scrollDown'); + static const SemanticsAction scrollToOffset = SemanticsAction._(_kScrollToOffsetIndex, 'scrollToOffset'); static const SemanticsAction increase = SemanticsAction._(_kIncreaseIndex, 'increase'); static const SemanticsAction decrease = SemanticsAction._(_kDecreaseIndex, 'decrease'); static const SemanticsAction showOnScreen = SemanticsAction._(_kShowOnScreenIndex, 'showOnScreen'); @@ -65,6 +67,7 @@ class SemanticsAction { _kScrollRightIndex: scrollRight, _kScrollUpIndex: scrollUp, _kScrollDownIndex: scrollDown, + _kScrollToOffsetIndex: scrollToOffset, _kIncreaseIndex: increase, _kDecreaseIndex: decrease, _kShowOnScreenIndex: showOnScreen, diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index f50b7cf78c73e..d2491003efc86 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -147,6 +147,7 @@ export 'engine/scene_view.dart'; export 'engine/semantics/accessibility.dart'; export 'engine/semantics/checkable.dart'; export 'engine/semantics/focusable.dart'; +export 'engine/semantics/header.dart'; export 'engine/semantics/heading.dart'; export 'engine/semantics/image.dart'; export 'engine/semantics/incrementable.dart'; diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/canvas.dart index 03eaee4466cc8..dd7cb93f39853 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvas.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvas.dart @@ -101,7 +101,7 @@ class CkCanvas { Uint32List? colors, ui.BlendMode blendMode, ) { - final skPaint = paint.toSkPaint(); + final skPaint = paint.toSkPaint(defaultBlurTileMode: ui.TileMode.clamp); skCanvas.drawAtlas( atlas.skImage, rects, @@ -143,7 +143,7 @@ class CkCanvas { void drawImage(CkImage image, ui.Offset offset, CkPaint paint) { final ui.FilterQuality filterQuality = paint.filterQuality; - final skPaint = paint.toSkPaint(); + final skPaint = paint.toSkPaint(defaultBlurTileMode: ui.TileMode.clamp); if (filterQuality == ui.FilterQuality.high) { skCanvas.drawImageCubic( image.skImage, @@ -168,7 +168,7 @@ class CkCanvas { void drawImageRect(CkImage image, ui.Rect src, ui.Rect dst, CkPaint paint) { final ui.FilterQuality filterQuality = paint.filterQuality; - final skPaint = paint.toSkPaint(); + final skPaint = paint.toSkPaint(defaultBlurTileMode: ui.TileMode.clamp); if (filterQuality == ui.FilterQuality.high) { skCanvas.drawImageRectCubic( image.skImage, @@ -193,7 +193,7 @@ class CkCanvas { void drawImageNine( CkImage image, ui.Rect center, ui.Rect dst, CkPaint paint) { - final skPaint = paint.toSkPaint(); + final skPaint = paint.toSkPaint(defaultBlurTileMode: ui.TileMode.clamp); skCanvas.drawImageNine( image.skImage, toSkRect(center), @@ -315,13 +315,14 @@ class CkCanvas { toSkRect(bounds), null, null, + canvasKit.TileMode.Clamp, ); skPaint?.delete(); } void saveLayerWithoutBounds(CkPaint? paint) { final skPaint = paint?.toSkPaint(); - skCanvas.saveLayer(skPaint, null, null, null); + skCanvas.saveLayer(skPaint, null, null, null, canvasKit.TileMode.Clamp); skPaint?.delete(); } @@ -333,16 +334,26 @@ class CkCanvas { } else { convertible = filter as CkManagedSkImageFilterConvertible; } + // There are 2 ImageFilter objects applied here. The filter in the paint + // object is applied to the contents and its default tile mode is decal + // (automatically applied by toSkPaint). + // The filter supplied as an argument to this function [convertible] will + // be applied to the backdrop and its default tile mode will be mirror. + // We also pass in the blur tile mode as an argument to saveLayer because + // that operation will not adopt the tile mode from the backdrop filter + // and instead needs it supplied to the saveLayer call itself as a + // separate argument. convertible.withSkImageFilter((SkImageFilter filter) { - final skPaint = paint?.toSkPaint(); + final skPaint = paint?.toSkPaint(/*ui.TileMode.decal*/); skCanvas.saveLayer( skPaint, toSkRect(bounds), filter, 0, + toSkTileMode(convertible.backdropTileMode ?? ui.TileMode.mirror), ); skPaint?.delete(); - }); + }, defaultBlurTileMode: ui.TileMode.mirror); } void scale(double sx, double sy) { 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 18181388d9da8..b9fdcfce7bf52 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -987,8 +987,8 @@ final List _skTileModes = [ canvasKit.TileMode.Decal, ]; -SkTileMode toSkTileMode(ui.TileMode mode) { - return _skTileModes[mode.index]; +SkTileMode toSkTileMode(ui.TileMode? mode) { + return mode == null ? canvasKit.TileMode.Clamp : _skTileModes[mode.index]; } @JS() @@ -2579,13 +2579,15 @@ extension SkCanvasExtension on SkCanvas { JSFloat32Array? bounds, SkImageFilter? backdrop, JSNumber? flags, + SkTileMode backdropTileMode, ); void saveLayer( SkPaint? paint, Float32List? bounds, SkImageFilter? backdrop, int? flags, - ) => _saveLayer(paint, bounds?.toJS, backdrop, flags?.toJS); + SkTileMode backdropTileMode, + ) => _saveLayer(paint, bounds?.toJS, backdrop, flags?.toJS, backdropTileMode); external JSVoid restore(); diff --git a/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart b/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart index 71c4123b4581a..51e5fcbcd205f 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart @@ -71,7 +71,9 @@ abstract class CkColorFilter implements CkManagedSkImageFilterConvertible { SkColorFilter _initRawColorFilter(); @override - void withSkImageFilter(SkImageFilterBorrow borrow) { + void withSkImageFilter(SkImageFilterBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { // Since ColorFilter has a const constructor it cannot store dynamically // created Skia objects. Therefore a new SkImageFilter is created every time // it's used. However, once used it's no longer needed, so it's deleted @@ -81,6 +83,13 @@ abstract class CkColorFilter implements CkManagedSkImageFilterConvertible { skImageFilter.delete(); } + /// The blur ImageFilter will override this and return the necessary + /// value to hand to the saveLayer call. It is the only filter type that + /// needs to pass along a tile mode so we just return a default value of + /// clamp for color filters. + @override + ui.TileMode? get backdropTileMode => ui.TileMode.clamp; + @override Matrix4 get transform => Matrix4.identity(); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart index 57b88acb878a6..66588299fd9fe 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart @@ -14,7 +14,7 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web; // Roboto font. The API reference is here: // https://developers.google.com/fonts/docs/developer_api String _robotoUrl = - '${configuration.fontFallbackBaseUrl}roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf'; + '${configuration.fontFallbackBaseUrl}roboto/v32/KFOmCnqEu92Fr1Me4GZLCzYlKw.woff2'; /// Manages the fonts used in the Skia-based backend. class SkiaFontCollection implements FlutterFontCollection { diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart index 6da70c1c6e881..e97327a43ebfd 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart @@ -24,9 +24,16 @@ abstract class CkManagedSkImageFilterConvertible implements ui.ImageFilter { /// Creates a temporary [SkImageFilter], passes it to [borrow], and then /// immediately deletes it. /// + /// If (and only if) the filter is a blur ImageFilter, then the indicated + /// [defaultBlurTileMode] is used in place of a missing (null) tile mode. + /// /// [SkImageFilter] objects are not kept around so that their memory is /// reclaimed immediately, rather than waiting for the GC cycle. - void withSkImageFilter(SkImageFilterBorrow borrow); + void withSkImageFilter(SkImageFilterBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }); + + ui.TileMode? get backdropTileMode; Matrix4 get transform; } @@ -38,7 +45,7 @@ abstract class CkImageFilter implements CkManagedSkImageFilterConvertible { factory CkImageFilter.blur( {required double sigmaX, required double sigmaY, - required ui.TileMode tileMode}) = _CkBlurImageFilter; + required ui.TileMode? tileMode}) = _CkBlurImageFilter; factory CkImageFilter.color({required CkColorFilter colorFilter}) = CkColorFilterImageFilter; factory CkImageFilter.matrix( @@ -55,6 +62,13 @@ abstract class CkImageFilter implements CkManagedSkImageFilterConvertible { CkImageFilter._(); + // The blur ImageFilter will override this and return the necessary + // value to hand to the saveLayer call. It is the only filter type that + // needs to pass along a tile mode so we just return a default value of + // clamp for all other image filters. + @override + ui.TileMode? get backdropTileMode => ui.TileMode.clamp; + @override Matrix4 get transform => Matrix4.identity(); } @@ -65,7 +79,9 @@ class CkColorFilterImageFilter extends CkImageFilter { final CkColorFilter colorFilter; @override - void withSkImageFilter(SkImageFilterBorrow borrow) { + void withSkImageFilter(SkImageFilterBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { final skImageFilter = colorFilter.initRawImageFilter(); borrow(skImageFilter); skImageFilter.delete(); @@ -94,10 +110,15 @@ class _CkBlurImageFilter extends CkImageFilter { final double sigmaX; final double sigmaY; - final ui.TileMode tileMode; + final ui.TileMode? tileMode; + + @override + ui.TileMode? get backdropTileMode => tileMode; @override - void withSkImageFilter(SkImageFilterBorrow borrow) { + void withSkImageFilter(SkImageFilterBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { /// Return the identity matrix when both sigmaX and sigmaY are 0. Replicates /// effect of applying no filter final SkImageFilter skImageFilter; @@ -110,7 +131,7 @@ class _CkBlurImageFilter extends CkImageFilter { skImageFilter = canvasKit.ImageFilter.MakeBlur( sigmaX, sigmaY, - toSkTileMode(tileMode), + toSkTileMode(tileMode ?? defaultBlurTileMode), null, ); } @@ -151,7 +172,9 @@ class _CkMatrixImageFilter extends CkImageFilter { final Matrix4 _transform; @override - void withSkImageFilter(SkImageFilterBorrow borrow) { + void withSkImageFilter(SkImageFilterBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { final skImageFilter = canvasKit.ImageFilter.MakeMatrixTransform( toSkMatrixFromFloat64(matrix), @@ -190,7 +213,9 @@ class _CkDilateImageFilter extends CkImageFilter { final double radiusY; @override - void withSkImageFilter(SkImageFilterBorrow borrow) { + void withSkImageFilter(SkImageFilterBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { final skImageFilter = canvasKit.ImageFilter.MakeDilate( radiusX, radiusY, @@ -227,7 +252,9 @@ class _CkErodeImageFilter extends CkImageFilter { final double radiusY; @override - void withSkImageFilter(SkImageFilterBorrow borrow) { + void withSkImageFilter(SkImageFilterBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { final skImageFilter = canvasKit.ImageFilter.MakeErode( radiusX, radiusY, @@ -264,7 +291,9 @@ class _CkComposeImageFilter extends CkImageFilter { final CkImageFilter inner; @override - void withSkImageFilter(SkImageFilterBorrow borrow) { + void withSkImageFilter(SkImageFilterBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { outer.withSkImageFilter((skOuter) { inner.withSkImageFilter((skInner) { final skImageFilter = canvasKit.ImageFilter.MakeCompose( @@ -273,8 +302,8 @@ class _CkComposeImageFilter extends CkImageFilter { ); borrow(skImageFilter); skImageFilter.delete(); - }); - }); + }, defaultBlurTileMode: defaultBlurTileMode); + }, defaultBlurTileMode: defaultBlurTileMode); } @override diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart index 68b08dc5fe14c..9850c75c99f39 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart @@ -402,7 +402,7 @@ class MeasureVisitor extends LayerVisitor { transformedBounds = rectFromSkIRect( skFilter.getOutputBounds(toSkRect(transformedBounds)), ); - }); + }, defaultBlurTileMode: ui.TileMode.decal); } picture.sceneBounds = transformedBounds; diff --git a/lib/web_ui/lib/src/engine/canvaskit/painting.dart b/lib/web_ui/lib/src/engine/canvaskit/painting.dart index 8d791e94e59ec..c56ddce6c8fcd 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/painting.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/painting.dart @@ -29,7 +29,7 @@ class CkPaint implements ui.Paint { /// /// The caller is responsible for deleting the returned object when it's no /// longer needed. - SkPaint toSkPaint() { + SkPaint toSkPaint({ui.TileMode defaultBlurTileMode = ui.TileMode.decal}) { final skPaint = SkPaint(); skPaint.setAntiAlias(isAntiAlias); skPaint.setBlendMode(toSkBlendMode(blendMode)); @@ -65,7 +65,7 @@ class CkPaint implements ui.Paint { if (localImageFilter != null) { localImageFilter.withSkImageFilter((skImageFilter) { skPaint.setImageFilter(skImageFilter); - }); + }, defaultBlurTileMode: defaultBlurTileMode); } return skPaint; diff --git a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart index d78e0b1754669..6676ab4544693 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -184,7 +184,7 @@ class CanvasKitRenderer implements Renderer { ui.ImageFilter createBlurImageFilter( {double sigmaX = 0.0, double sigmaY = 0.0, - ui.TileMode tileMode = ui.TileMode.clamp}) => + ui.TileMode? tileMode}) => CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); @override diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index 8c7df43e53c5e..169fc4cc14369 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -71,6 +71,10 @@ class Surface extends DisplayCanvas { bool _contextLost = false; bool get debugContextLost => _contextLost; + /// Forces AssertionError when attempting to create a CPU-based surface. + /// Only for tests. + bool debugThrowOnSoftwareSurfaceCreation = false; + /// A cached copy of the most recently created `webglcontextlost` listener. /// /// We must cache this function because each time we access the tear-off it @@ -182,7 +186,6 @@ class Surface extends DisplayCanvas { } BitmapSize? _currentCanvasPhysicalSize; - BitmapSize? _currentSurfaceSize; /// Sets the CSS size of the canvas so that canvas pixels are 1:1 with device /// pixels. @@ -250,7 +253,7 @@ class Surface extends DisplayCanvas { if (!_forceNewContext) { // Check if the window is the same size as before, and if so, don't allocate // a new canvas as the previous canvas is big enough to fit everything. - final BitmapSize? previousSurfaceSize = _currentSurfaceSize; + final BitmapSize? previousSurfaceSize = _surface?._size; if (previousSurfaceSize != null && size.width == previousSurfaceSize.width && size.height == previousSurfaceSize.height) { @@ -299,10 +302,8 @@ class Surface extends DisplayCanvas { _currentCanvasPhysicalSize = size; } - _currentSurfaceSize = size; _surface?.dispose(); - _surface = _createNewSurface(size); - return _surface!; + return _surface = _createNewSurface(size); } JSVoid _contextRestoredListener(DomEvent event) { @@ -462,11 +463,17 @@ class Surface extends DisplayCanvas { CkSurface _createNewSurface(BitmapSize size) { assert(_offscreenCanvas != null || _canvasElement != null); if (webGLVersion == -1) { - return _makeSoftwareCanvasSurface('WebGL support not detected'); + return _makeSoftwareCanvasSurface('WebGL support not detected', size); } else if (configuration.canvasKitForceCpuOnly) { - return _makeSoftwareCanvasSurface('CPU rendering forced by application'); + return _makeSoftwareCanvasSurface( + 'CPU rendering forced by application', + size, + ); } else if (_glContext == 0) { - return _makeSoftwareCanvasSurface('Failed to initialize WebGL context'); + return _makeSoftwareCanvasSurface( + 'Failed to initialize WebGL context', + size, + ); } else { final SkSurface? skSurface = canvasKit.MakeOnScreenGLSurface( _grContext!, @@ -477,31 +484,37 @@ class Surface extends DisplayCanvas { _stencilBits); if (skSurface == null) { - return _makeSoftwareCanvasSurface('Failed to initialize WebGL surface'); + return _makeSoftwareCanvasSurface( + 'Failed to initialize WebGL surface', + size, + ); } - return CkSurface(skSurface, _glContext); + return CkSurface(skSurface, _glContext, size); } } static bool _didWarnAboutWebGlInitializationFailure = false; - CkSurface _makeSoftwareCanvasSurface(String reason) { + CkSurface _makeSoftwareCanvasSurface(String reason, BitmapSize size) { if (!_didWarnAboutWebGlInitializationFailure) { printWarning('WARNING: Falling back to CPU-only rendering. $reason.'); _didWarnAboutWebGlInitializationFailure = true; } - SkSurface surface; - if (useOffscreenCanvas) { - surface = canvasKit.MakeOffscreenSWCanvasSurface(_offscreenCanvas!); - } else { - surface = canvasKit.MakeSWCanvasSurface(_canvasElement!); + try { + assert(!debugThrowOnSoftwareSurfaceCreation); + + SkSurface surface; + if (useOffscreenCanvas) { + surface = canvasKit.MakeOffscreenSWCanvasSurface(_offscreenCanvas!); + } else { + surface = canvasKit.MakeSWCanvasSurface(_canvasElement!); + } + return CkSurface(surface, null, size); + } catch (error) { + throw CanvasKitError('Failed to create CPU-based surface: $error.'); } - return CkSurface( - surface, - null, - ); } bool _presentSurface() { @@ -534,9 +547,9 @@ class Surface extends DisplayCanvas { browserSupportsOffscreenCanvas && !isSafari; } -/// A Dart wrapper around Skia's CkSurface. +/// A Dart wrapper around Skia's SkSurface. class CkSurface { - CkSurface(this.surface, this._glContext); + CkSurface(this.surface, this._glContext, this._size); CkCanvas getCanvas() { assert(!_isDisposed, 'Attempting to use the canvas of a disposed surface'); @@ -549,6 +562,8 @@ class CkSurface { /// at any moment. Storing it may lead to dangling pointer bugs. final SkSurface surface; + final BitmapSize _size; + final int? _glContext; /// Flushes the graphics to be rendered on screen. diff --git a/lib/web_ui/lib/src/engine/color_filter.dart b/lib/web_ui/lib/src/engine/color_filter.dart index 83a7ca27e2cc0..a74145c08bd78 100644 --- a/lib/web_ui/lib/src/engine/color_filter.dart +++ b/lib/web_ui/lib/src/engine/color_filter.dart @@ -135,4 +135,7 @@ class EngineColorFilter implements SceneImageFilter, ui.ColorFilter { return 'ColorFilter.srgbToLinearGamma()'; } } + + @override + Matrix4? get transform => null; } diff --git a/lib/web_ui/lib/src/engine/configuration.dart b/lib/web_ui/lib/src/engine/configuration.dart index 14beead5d41af..50695b991875f 100644 --- a/lib/web_ui/lib/src/engine/configuration.dart +++ b/lib/web_ui/lib/src/engine/configuration.dart @@ -337,6 +337,8 @@ class FlutterConfiguration { /// Defaults to 'https://fonts.gstatic.com/s/'. String get fontFallbackBaseUrl => _configuration?.fontFallbackBaseUrl ?? 'https://fonts.gstatic.com/s/'; + + bool get forceSingleThreadedSkwasm => _configuration?.forceSingleThreadedSkwasm ?? false; } @JS('window.flutterConfiguration') @@ -393,6 +395,10 @@ extension JsFlutterConfigurationExtension on JsFlutterConfiguration { @JS('fontFallbackBaseUrl') external JSString? get _fontFallbackBaseUrl; String? get fontFallbackBaseUrl => _fontFallbackBaseUrl?.toDart; + + @JS('forceSingleThreadedSkwasm') + external JSBoolean? get _forceSingleThreadedSkwasm; + bool? get forceSingleThreadedSkwasm => _forceSingleThreadedSkwasm?.toDart; } /// A JavaScript entrypoint that allows developer to set rendering backend diff --git a/lib/web_ui/lib/src/engine/font_fallback_data.dart b/lib/web_ui/lib/src/engine/font_fallback_data.dart index aa9d58b8dad9c..a6dcfb334ddc0 100644 --- a/lib/web_ui/lib/src/engine/font_fallback_data.dart +++ b/lib/web_ui/lib/src/engine/font_fallback_data.dart @@ -7,149 +7,6 @@ import 'noto_font.dart'; List getFallbackFontList() => [ - NotoFont('Noto Music', 'notomusic/v20/pe0rMIiSN5pO63htf1sxIteQB9Zra1U.ttf'), - NotoFont('Noto Sans', 'notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A99d41P6zHtY.ttf'), - NotoFont('Noto Sans Adlam', 'notosansadlam/v22/neIczCCpqp0s5pPusPamd81eMfjPonvqdbYxxpgufnv0TGnBZLwhuvk.ttf'), - NotoFont('Noto Sans Anatolian Hieroglyphs', 'notosansanatolianhieroglyphs/v16/ijw9s4roRME5LLRxjsRb8A0gKPSWq4BbDmHHu6j2pEtUJzZWXybIymc5QYo.ttf'), - NotoFont('Noto Sans Arabic', 'notosansarabic/v18/nwpxtLGrOAZMl5nJ_wfgRg3DrWFZWsnVBJ_sS6tlqHHFlhQ5l3sQWIHPqzCfyGyvu3CBFQLaig.ttf'), - NotoFont('Noto Sans Armenian', 'notosansarmenian/v43/ZgN0jOZKPa7CHqq0h37c7ReDUubm2SEdFXp7ig73qtTY5idb74R9UdM3y2nZLorxb60iYy6zF3Eg.ttf'), - NotoFont('Noto Sans Avestan', 'notosansavestan/v21/bWti7ejKfBziStx7lIzKOLQZKhIJkyu9SASLji8U.ttf'), - NotoFont('Noto Sans Balinese', 'notosansbalinese/v24/NaPwcYvSBuhTirw6IaFn6UrRDaqje-lpbbRtYf-Fwu2Ov7fdhE5Vd222PPY.ttf'), - NotoFont('Noto Sans Bamum', 'notosansbamum/v27/uk-0EGK3o6EruUbnwovcbBTkkklK_Ya_PBHfNGTPEddO-_gLykxEkxA.ttf'), - NotoFont('Noto Sans Bassa Vah', 'notosansbassavah/v17/PN_bRee-r3f7LnqsD5sax12gjZn7mBpL5YwUpA2MBdcFn4MaAc6p34gH-GD7.ttf'), - NotoFont('Noto Sans Batak', 'notosansbatak/v20/gok2H6TwAEdtF9N8-mdTCQvT-Zdgo4_PHuk74A.ttf'), - NotoFont('Noto Sans Bengali', 'notosansbengali/v20/Cn-SJsCGWQxOjaGwMQ6fIiMywrNJIky6nvd8BjzVMvJx2mcSPVFpVEqE-6KmsolLudCk8izI0lc.ttf'), - NotoFont('Noto Sans Bhaiksuki', 'notosansbhaiksuki/v17/UcC63EosKniBH4iELXATsSBWdvUHXxhj8rLUdU4wh9U.ttf'), - NotoFont('Noto Sans Brahmi', 'notosansbrahmi/v19/vEFK2-VODB8RrNDvZSUmQQIIByV18tK1W77HtMo.ttf'), - NotoFont('Noto Sans Buginese', 'notosansbuginese/v18/esDM30ldNv-KYGGJpKGk18phe_7Da6_gtfuEXLmNtw.ttf'), - NotoFont('Noto Sans Buhid', 'notosansbuhid/v22/Dxxy8jiXMW75w3OmoDXVWJD7YwzAe6tgnaFoGA.ttf'), - NotoFont('Noto Sans Canadian Aboriginal', 'notosanscanadianaboriginal/v26/4C_TLjTuEqPj-8J01CwaGkiZ9os0iGVkezM1mUT-j_Lmlzda6uH_nnX1bzigWLn_yAsg0q0uhQ.ttf'), - NotoFont('Noto Sans Carian', 'notosanscarian/v16/LDIpaoiONgYwA9Yc6f0gUILeMIOgs7ob9yGLmfI.ttf'), - NotoFont('Noto Sans Caucasian Albanian', 'notosanscaucasianalbanian/v18/nKKA-HM_FYFRJvXzVXaANsU0VzsAc46QGOkWytlTs-TXrYDmoVmRSZo.ttf'), - NotoFont('Noto Sans Chakma', 'notosanschakma/v17/Y4GQYbJ8VTEp4t3MKJSMjg5OIzhi4JjTQhYBeYo.ttf'), - NotoFont('Noto Sans Cham', 'notosanscham/v31/pe06MIySN5pO62Z5YkFyQb_bbuRhe6D4yip43qfcERwcv7GykboaLg.ttf'), - NotoFont('Noto Sans Cherokee', 'notosanscherokee/v20/KFOPCm6Yu8uF-29fiz9vQF9YWK6Z8O10cHNA0cSkZCHYWi5PDkm5rAffjl0.ttf'), - NotoFont('Noto Sans Coptic', 'notosanscoptic/v21/iJWfBWmUZi_OHPqn4wq6kgqumOEd78u_VG0xR4Y.ttf'), - NotoFont('Noto Sans Cuneiform', 'notosanscuneiform/v17/bMrrmTWK7YY-MF22aHGGd7H8PhJtvBDWgb9JlRQueeQ.ttf'), - NotoFont('Noto Sans Cypriot', 'notosanscypriot/v19/8AtzGta9PYqQDjyp79a6f8Cj-3a3cxIsK5MPpahF.ttf'), - NotoFont('Noto Sans Deseret', 'notosansdeseret/v17/MwQsbgPp1eKH6QsAVuFb9AZM6MMr2Vq9ZnJSZtQG.ttf'), - NotoFont('Noto Sans Devanagari', 'notosansdevanagari/v26/TuGoUUFzXI5FBtUq5a8bjKYTZjtRU6Sgv3NaV_SNmI0b8QQCQmHn6B2OHjbL_08AlXQly-AzoFoW4Ow.ttf'), - NotoFont('Noto Sans Duployan', 'notosansduployan/v18/gokzH7nwAEdtF9N8-mdTDx_X9JM5wsvrFsIn6WYDvA.ttf'), - NotoFont('Noto Sans Egyptian Hieroglyphs', 'notosansegyptianhieroglyphs/v29/vEF42-tODB8RrNDvZSUmRhcQHzx1s7y_F9-j3qSzEcbEYindSVK8xRg7iw.ttf'), - NotoFont('Noto Sans Elbasan', 'notosanselbasan/v16/-F6rfiZqLzI2JPCgQBnw400qp1trvHdlre4dFcFh.ttf'), - NotoFont('Noto Sans Elymaic', 'notosanselymaic/v17/UqyKK9YTJW5liNMhTMqe9vUFP65ZD4AjWOT0zi2V.ttf'), - NotoFont('Noto Sans Ethiopic', 'notosansethiopic/v47/7cHPv50vjIepfJVOZZgcpQ5B9FBTH9KGNfhSTgtoow1KVnIvyBoMSzUMacb-T35OK6DjwmfeaY9u.ttf'), - NotoFont('Noto Sans Georgian', 'notosansgeorgian/v44/PlIaFke5O6RzLfvNNVSitxkr76PRHBC4Ytyq-Gof7PUs4S7zWn-8YDB09HFNdpvnzFj-f5WK0OQV.ttf'), - NotoFont('Noto Sans Glagolitic', 'notosansglagolitic/v18/1q2ZY4-BBFBst88SU_tOj4J-4yuNF_HI4ERK4Amu7nM1.ttf'), - NotoFont('Noto Sans Gothic', 'notosansgothic/v16/TuGKUUVzXI5FBtUq5a8bj6wRbzxTFMX40kFQRx0.ttf'), - NotoFont('Noto Sans Grantha', 'notosansgrantha/v19/3y976akwcCjmsU8NDyrKo3IQfQ4o-r8cFeulHc6N.ttf'), - NotoFont('Noto Sans Gujarati', 'notosansgujarati/v25/wlpWgx_HC1ti5ViekvcxnhMlCVo3f5pv17ivlzsUB14gg1TMR2Gw4VceEl7MA_ypFwPM_OdiEH0s.ttf'), - NotoFont('Noto Sans Gunjala Gondi', 'notosansgunjalagondi/v19/bWtX7e7KfBziStx7lIzKPrcSMwcEnCv6DW7n5g0ef3PLtymzNxYL4YDE4J4vCTxEJQ.ttf'), - NotoFont('Noto Sans Gurmukhi', 'notosansgurmukhi/v26/w8g9H3EvQP81sInb43inmyN9zZ7hb7ATbSWo4q8dJ74a3cVrYFQ_bogT0-gPeG1OenbxZ_trdp7h.ttf'), - NotoFont('Noto Sans HK', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oWTiYjNvVA.ttf'), - NotoFont('Noto Sans Hanunoo', 'notosanshanunoo/v21/f0Xs0fCv8dxkDWlZSoXOj6CphMloFsEsEpgL_ix2.ttf'), - NotoFont('Noto Sans Hatran', 'notosanshatran/v16/A2BBn4Ne0RgnVF3Lnko-0sOBIfL_mM83r1nwzDs.ttf'), - NotoFont('Noto Sans Hebrew', 'notosanshebrew/v46/or3HQ7v33eiDljA1IufXTtVf7V6RvEEdhQlk0LlGxCyaeNKYZC0sqk3xXGiXd4qtoiJltutR2g.ttf'), - NotoFont('Noto Sans Imperial Aramaic', 'notosansimperialaramaic/v17/a8IMNpjwKmHXpgXbMIsbTc_kvks91LlLetBr5itQrtdml3YfPNno.ttf'), - NotoFont('Noto Sans Indic Siyaq Numbers', 'notosansindicsiyaqnumbers/v16/6xK5dTJFKcWIu4bpRBjRZRpsIYHabOeZ8UZLubTzpXNHKx2WPOpVd5Iu.ttf'), - NotoFont('Noto Sans Inscriptional Pahlavi', 'notosansinscriptionalpahlavi/v17/ll8UK3GaVDuxR-TEqFPIbsR79Xxz9WEKbwsjpz7VklYlC7FCVtqVOAYK0QA.ttf'), - NotoFont('Noto Sans Inscriptional Parthian', 'notosansinscriptionalparthian/v17/k3k7o-IMPvpLmixcA63oYi-yStDkgXuXncL7dzfW3P4TAJ2yklBJ2jNkLlLr.ttf'), - NotoFont('Noto Sans JP', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj75vY0rw-oME.ttf'), - NotoFont('Noto Sans Javanese', 'notosansjavanese/v23/2V01KJkDAIA6Hp4zoSScDjV0Y-eoHAHT-Z3MngEefiidxJnkFFliZYWj4O8.ttf'), - NotoFont('Noto Sans KR', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLTq8H4hfeE.ttf'), - NotoFont('Noto Sans Kaithi', 'notosanskaithi/v22/buEtppS9f8_vkXadMBJJu0tWjLwjQi0KdoZIKlo.ttf'), - NotoFont('Noto Sans Kannada', 'notosanskannada/v27/8vIs7xs32H97qzQKnzfeXycxXZyUmySvZWItmf1fe6TVmgop9ndpS-BqHEyGrDvNzSIMLsPKrkY.ttf'), - NotoFont('Noto Sans Kayah Li', 'notosanskayahli/v21/B50nF61OpWTRcGrhOVJJwOMXdca6Yecki3E06x2jVTX3WCc3CZH4EXLuKVM.ttf'), - NotoFont('Noto Sans Kharoshthi', 'notosanskharoshthi/v16/Fh4qPiLjKS30-P4-pGMMXCCfvkc5Vd7KE5z4rFyx5mR1.ttf'), - NotoFont('Noto Sans Khmer', 'notosanskhmer/v24/ijw3s5roRME5LLRxjsRb-gssOenAyendxrgV2c-Zw-9vbVUti_Z_dWgtWYuNAJz4kAbrddiA.ttf'), - NotoFont('Noto Sans Khojki', 'notosanskhojki/v19/-nFnOHM29Oofr2wohFbTuPPKVWpmK_d709jy92k.ttf'), - NotoFont('Noto Sans Khudawadi', 'notosanskhudawadi/v22/fdNi9t6ZsWBZ2k5ltHN73zZ5hc8HANlHIjRnVVXz9MY.ttf'), - NotoFont('Noto Sans Lao', 'notosanslao/v30/bx6lNx2Ol_ixgdYWLm9BwxM3NW6BOkuf763Clj73CiQ_J1Djx9pidOt4ccbdf5MK3riB2w.ttf'), - NotoFont('Noto Sans Lepcha', 'notosanslepcha/v19/0QI7MWlB_JWgA166SKhu05TekNS32AJstqBXgd4.ttf'), - NotoFont('Noto Sans Limbu', 'notosanslimbu/v24/3JnlSDv90Gmq2mrzckOBBRRoNJVj0MF3OHRDnA.ttf'), - NotoFont('Noto Sans Linear A', 'notosanslineara/v18/oPWS_l16kP4jCuhpgEGmwJOiA18FZj22zmHQAGQicw.ttf'), - NotoFont('Noto Sans Linear B', 'notosanslinearb/v17/HhyJU4wt9vSgfHoORYOiXOckKNB737IV3BkFTq4EPw.ttf'), - NotoFont('Noto Sans Lisu', 'notosanslisu/v25/uk-3EGO3o6EruUbnwovcYhz6kh57_nqbcTdjJnHP2Vwt29IlxkVdig.ttf'), - NotoFont('Noto Sans Lycian', 'notosanslycian/v15/QldVNSNMqAsHtsJ7UmqxBQA9r8wA5_naCJwn00E.ttf'), - NotoFont('Noto Sans Lydian', 'notosanslydian/v18/c4m71mVzGN7s8FmIukZJ1v4ZlcPReUPXMoIjEQI.ttf'), - NotoFont('Noto Sans Mahajani', 'notosansmahajani/v19/-F6sfiVqLzI2JPCgQBnw60Agp0JrvD5Fh8ARHNh4zg.ttf'), - NotoFont('Noto Sans Malayalam', 'notosansmalayalam/v26/sJoi3K5XjsSdcnzn071rL37lpAOsUThnDZIfPdbeSNzVakglNM-Qw8EaeB8Nss-_RuD9BFzEr6HxEA.ttf'), - NotoFont('Noto Sans Mandaic', 'notosansmandaic/v17/cIfnMbdWt1w_HgCcilqhKQBo_OsMI5_A_gMk0izH.ttf'), - NotoFont('Noto Sans Manichaean', 'notosansmanichaean/v18/taiVGntiC4--qtsfi4Jp9-_GkPZZCcrfekqCNTtFCtdX.ttf'), - NotoFont('Noto Sans Marchen', 'notosansmarchen/v20/aFTO7OZ_Y282EP-WyG6QTOX_C8WZMHhPk652ZaHk.ttf'), - NotoFont('Noto Sans Masaram Gondi', 'notosansmasaramgondi/v17/6xK_dThFKcWIu4bpRBjRYRV7KZCbUq6n_1kPnuGe7RI9WSWX.ttf'), - NotoFont('Noto Sans Math', 'notosansmath/v15/7Aump_cpkSecTWaHRlH2hyV5UHkG-V048PW0.ttf'), - NotoFont('Noto Sans Mayan Numerals', 'notosansmayannumerals/v16/PlIuFk25O6RzLfvNNVSivR09_KqYMwvvDKYjfIiE68oo6eepYQ.ttf'), - NotoFont('Noto Sans Medefaidrin', 'notosansmedefaidrin/v23/WwkzxOq6Dk-wranENynkfeVsNbRZtbOIdLb1exeM4ZeuabBfmErWlT318e5A3rw.ttf'), - NotoFont('Noto Sans Meetei Mayek', 'notosansmeeteimayek/v15/HTxAL3QyKieByqY9eZPFweO0be7M21uSphSdhqILnmrRfJ8t_1TJ_vTW5PgeFYVa.ttf'), - NotoFont('Noto Sans Meroitic', 'notosansmeroitic/v18/IFS5HfRJndhE3P4b5jnZ3ITPvC6i00UDgDhTiKY9KQ.ttf'), - NotoFont('Noto Sans Miao', 'notosansmiao/v17/Dxxz8jmXMW75w3OmoDXVV4zyZUjgUYVslLhx.ttf'), - NotoFont('Noto Sans Modi', 'notosansmodi/v23/pe03MIySN5pO62Z5YkFyT7jeav5qWVAgVol-.ttf'), - NotoFont('Noto Sans Mongolian', 'notosansmongolian/v22/VdGCAYADGIwE0EopZx8xQfHlgEAMsrToxLsg6-av1x0.ttf'), - NotoFont('Noto Sans Mro', 'notosansmro/v18/qWcsB6--pZv9TqnUQMhe9b39WDzRtjkho4M.ttf'), - NotoFont('Noto Sans Multani', 'notosansmultani/v20/9Bty3ClF38_RfOpe1gCaZ8p30BOFO1A0pfCs5Kos.ttf'), - NotoFont('Noto Sans Myanmar', 'notosansmyanmar/v20/AlZq_y1ZtY3ymOryg38hOCSdOnFq0En23OU4o1AC.ttf'), - NotoFont('Noto Sans NKo', 'notosansnko/v6/esDX31ZdNv-KYGGJpKGk2_RpMpCMHMLBrdA.ttf'), - NotoFont('Noto Sans Nabataean', 'notosansnabataean/v16/IFS4HfVJndhE3P4b5jnZ34DfsjO330dNoBJ9hK8kMK4.ttf'), - NotoFont('Noto Sans New Tai Lue', 'notosansnewtailue/v22/H4cKBW-Pl9DZ0Xe_nHUapt7PovLXAhAnY7wqaLy-OJgU3p_pdeXAYUbghFPKzeY.ttf'), - NotoFont('Noto Sans Newa', 'notosansnewa/v16/7r3fqXp6utEsO9pI4f8ok8sWg8n_qN4R5lNU.ttf'), - NotoFont('Noto Sans Nushu', 'notosansnushu/v19/rnCw-xRQ3B7652emAbAe_Ai1IYaFWFAMArZKqQ.ttf'), - NotoFont('Noto Sans Ogham', 'notosansogham/v17/kmKlZqk1GBDGN0mY6k5lmEmww4hrt5laQxcoCA.ttf'), - NotoFont('Noto Sans Ol Chiki', 'notosansolchiki/v29/N0b92TJNOPt-eHmFZCdQbrL32r-4CvhzDzRwlxOQYuVALWk267I6gVrz5gQ.ttf'), - NotoFont('Noto Sans Old Hungarian', 'notosansoldhungarian/v18/E213_cD6hP3GwCJPEUssHEM0KqLaHJXg2PiIgRfjbg5nCYXt.ttf'), - NotoFont('Noto Sans Old Italic', 'notosansolditalic/v17/TuGOUUFzXI5FBtUq5a8bh68BJxxEVam7tWlRdRhtCC4d.ttf'), - NotoFont('Noto Sans Old North Arabian', 'notosansoldnortharabian/v16/esDF30BdNv-KYGGJpKGk2tNiMt7Jar6olZDyNdr81zBQmUo_xw4ABw.ttf'), - NotoFont('Noto Sans Old Permic', 'notosansoldpermic/v17/snf1s1q1-dF8pli1TesqcbUY4Mr-ElrwKLdXgv_dKYB5.ttf'), - NotoFont('Noto Sans Old Persian', 'notosansoldpersian/v16/wEOjEAbNnc5caQTFG18FHrZr9Bp6-8CmIJ_tqOlQfx9CjA.ttf'), - NotoFont('Noto Sans Old Sogdian', 'notosansoldsogdian/v17/3JnjSCH90Gmq2mrzckOBBhFhdrMst48aURt7neIqM-9uyg.ttf'), - NotoFont('Noto Sans Old South Arabian', 'notosansoldsoutharabian/v16/3qT5oiOhnSyU8TNFIdhZTice3hB_HWKsEnF--0XCHiKx1OtDT9HwTA.ttf'), - NotoFont('Noto Sans Old Turkic', 'notosansoldturkic/v18/yMJNMJVya43H0SUF_WmcGEQVqoEMKDKbsE2RjEw-Vyws.ttf'), - NotoFont('Noto Sans Oriya', 'notosansoriya/v31/AYCppXfzfccDCstK_hrjDyADv5e9748vhj3CJBLHIARtgD6TJQS0dJT5Ivj0f6_c6LhHBRe-.ttf'), - NotoFont('Noto Sans Osage', 'notosansosage/v18/oPWX_kB6kP4jCuhpgEGmw4mtAVtXRlaSxkrMCQ.ttf'), - NotoFont('Noto Sans Osmanya', 'notosansosmanya/v18/8vIS7xs32H97qzQKnzfeWzUyUpOJmz6kR47NCV5Z.ttf'), - NotoFont('Noto Sans Pahawh Hmong', 'notosanspahawhhmong/v18/bWtp7e_KfBziStx7lIzKKaMUOBEA3UPQDW7krzc_c48aMpM.ttf'), - NotoFont('Noto Sans Palmyrene', 'notosanspalmyrene/v16/ZgNPjOdKPa7CHqq0h37c_ASCWvH93SFCPnK5ZpdNtcA.ttf'), - NotoFont('Noto Sans Pau Cin Hau', 'notosanspaucinhau/v20/x3d-cl3IZKmUqiMg_9wBLLtzl22EayN7ehIdjEWqKMxsKw.ttf'), - NotoFont('Noto Sans Phags Pa', 'notosansphagspa/v15/pxiZyoo6v8ZYyWh5WuPeJzMkd4SrGChkqkSsrvNXiA.ttf'), - NotoFont('Noto Sans Phoenician', 'notosansphoenician/v17/jizFRF9Ksm4Bt9PvcTaEkIHiTVtxmFtS5X7Jot-p5561.ttf'), - NotoFont('Noto Sans Psalter Pahlavi', 'notosanspsalterpahlavi/v17/rP2Vp3K65FkAtHfwd-eISGznYihzggmsicPfud3w1G3KsUQBct4.ttf'), - NotoFont('Noto Sans Rejang', 'notosansrejang/v21/Ktk2AKuMeZjqPnXgyqrib7DIogqwN4O3WYZB_sU.ttf'), - NotoFont('Noto Sans Runic', 'notosansrunic/v17/H4c_BXWPl9DZ0Xe_nHUaus7W68WWaxpvHtgIYg.ttf'), - NotoFont('Noto Sans SC', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYxNbPzS5HE.ttf'), - NotoFont('Noto Sans Saurashtra', 'notosanssaurashtra/v23/ea8GacQ0Wfz_XKWXe6OtoA8w8zvmYwTef9ndjhPTSIx9.ttf'), - NotoFont('Noto Sans Sharada', 'notosanssharada/v16/gok0H7rwAEdtF9N8-mdTGALG6p0kwoXLPOwr4H8a.ttf'), - NotoFont('Noto Sans Shavian', 'notosansshavian/v17/CHy5V_HZE0jxJBQlqAeCKjJvQBNF4EFQSplv2Cwg.ttf'), - NotoFont('Noto Sans Siddham', 'notosanssiddham/v20/OZpZg-FwqiNLe9PELUikxTWDoCCeGqndk3Ic92ZH.ttf'), - NotoFont('Noto Sans Sinhala', 'notosanssinhala/v26/yMJ2MJBya43H0SUF_WmcBEEf4rQVO2P524V5N_MxQzQtb-tf5dJbC30Fu9zUwg2a5lgLpJwbQRM.ttf'), - NotoFont('Noto Sans Sogdian', 'notosanssogdian/v16/taiQGn5iC4--qtsfi4Jp6eHPnfxQBo--Pm6KHidM.ttf'), - NotoFont('Noto Sans Sora Sompeng', 'notosanssorasompeng/v24/PlIRFkO5O6RzLfvNNVSioxM2_OTrEhPyDLolKvCsHzCxWuGkYHR818DpZXJQd4Mu.ttf'), - NotoFont('Noto Sans Soyombo', 'notosanssoyombo/v17/RWmSoL-Y6-8q5LTtXs6MF6q7xsxgY0FrIFOcK25W.ttf'), - NotoFont('Noto Sans Sundanese', 'notosanssundanese/v26/FwZw7_84xUkosG2xJo2gm7nFwSLQkdymq2mkz3Gz1_b6ctxpNNHCizv7fQES.ttf'), - NotoFont('Noto Sans Syloti Nagri', 'notosanssylotinagri/v23/uU9eCAQZ75uhfF9UoWDRiY3q7Sf_VFV3m4dGFVfxN87gsj0.ttf'), - NotoFont('Noto Sans Symbols', 'notosanssymbols/v43/rP2up3q65FkAtHfwd-eIS2brbDN6gxP34F9jRRCe4W3gfQ8gavVFRkzrbQ.ttf'), - NotoFont('Noto Sans Symbols 2', 'notosanssymbols2/v24/I_uyMoGduATTei9eI8daxVHDyfisHr71ypPqfX71-AI.ttf'), - NotoFont('Noto Sans Syriac', 'notosanssyriac/v16/Ktk7AKuMeZjqPnXgyqribqzQqgW0LYiVqV7dXcP0C-VD9MaJyZfUL_FC.ttf'), - NotoFont('Noto Sans TC', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_CpOtma3uNQ.ttf'), - NotoFont('Noto Sans Tagalog', 'notosanstagalog/v22/J7aFnoNzCnFcV9ZI-sUYuvote1R0wwEAA8jHexnL.ttf'), - NotoFont('Noto Sans Tagbanwa', 'notosanstagbanwa/v18/Y4GWYbB8VTEp4t3MKJSMmQdIKjRtt_nZRjQEaYpGoQ.ttf'), - NotoFont('Noto Sans Tai Le', 'notosanstaile/v17/vEFK2-VODB8RrNDvZSUmVxEATwR58tK1W77HtMo.ttf'), - NotoFont('Noto Sans Tai Tham', 'notosanstaitham/v20/kJEbBv0U4hgtwxDUw2x9q7tbjLIfbPGHBoaVSAZ3MdLJBCUbPgquyaRGKMw.ttf'), - NotoFont('Noto Sans Tai Viet', 'notosanstaiviet/v19/8QIUdj3HhN_lv4jf9vsE-9GMOLsaSPZr644fWsRO9w.ttf'), - NotoFont('Noto Sans Takri', 'notosanstakri/v24/TuGJUVpzXI5FBtUq5a8bnKIOdTwQNO_W3khJXg.ttf'), - NotoFont('Noto Sans Tamil', 'notosanstamil/v27/ieVc2YdFI3GCY6SyQy1KfStzYKZgzN1z4LKDbeZce-0429tBManUktuex7vGo70RqKDt_EvT.ttf'), - NotoFont('Noto Sans Tamil Supplement', 'notosanstamilsupplement/v21/DdTz78kEtnooLS5rXF1DaruiCd_bFp_Ph4sGcn7ax_vsAeMkeq1x.ttf'), - NotoFont('Noto Sans Telugu', 'notosanstelugu/v26/0FlxVOGZlE2Rrtr-HmgkMWJNjJ5_RyT8o8c7fHkeg-esVC5dzHkHIJQqrEntezbqQUbf-3v37w.ttf'), - NotoFont('Noto Sans Thaana', 'notosansthaana/v24/C8c14dM-vnz-s-3jaEsxlxHkBH-WZOETXfoQrfQ9Y4XrbhLhnu4-tbNu.ttf'), - NotoFont('Noto Sans Thai', 'notosansthai/v25/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU5RtpzF-QRvzzXg.ttf'), - NotoFont('Noto Sans Tifinagh', 'notosanstifinagh/v20/I_uzMoCduATTei9eI8dawkHIwvmhCvbn6rnEcXfs4Q.ttf'), - NotoFont('Noto Sans Tirhuta', 'notosanstirhuta/v16/t5t6IQYRNJ6TWjahPR6X-M-apUyby7uGUBsTrn5P.ttf'), - NotoFont('Noto Sans Ugaritic', 'notosansugaritic/v16/3qTwoiqhnSyU8TNFIdhZVCwbjCpkAXXkMhoIkiazfg.ttf'), - NotoFont('Noto Sans Vai', 'notosansvai/v17/NaPecZTSBuhTirw6IaFn_UrURMTsDIRSfr0.ttf'), - NotoFont('Noto Sans Wancho', 'notosanswancho/v17/zrf-0GXXyfn6Fs0lH9P4cUubP0GBqAPopiRfKp8.ttf'), - NotoFont('Noto Sans Warang Citi', 'notosanswarangciti/v17/EYqtmb9SzL1YtsZSScyKDXIeOv3w-zgsNvKRpeVCCXzdgA.ttf'), - NotoFont('Noto Sans Yi', 'notosansyi/v19/sJoD3LFXjsSdcnzn071rO3apxVDJNVgSNg.ttf'), - NotoFont('Noto Sans Zanabazar Square', 'notosanszanabazarsquare/v19/Cn-jJsuGWQxOjaGwMQ6fOicyxLBEMRfDtkzl4uagQtJxOCEgN0Gc.ttf'), - NotoFont('Noto Serif Tibetan', 'notoseriftibetan/v22/gokGH7nwAEdtF9N45n0Vaz7O-pk0wsvxHeDXMfqguoCmIrYcPS7rdSy_32c.ttf'), NotoFont('Noto Color Emoji 0', 'notocoloremoji/v32/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabsE4tq3luCC7p-aXxcn.0.woff2'), NotoFont('Noto Color Emoji 1', 'notocoloremoji/v32/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabsE4tq3luCC7p-aXxcn.1.woff2'), NotoFont('Noto Color Emoji 2', 'notocoloremoji/v32/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabsE4tq3luCC7p-aXxcn.2.woff2'), @@ -162,23095 +19,44026 @@ List getFallbackFontList() => [ NotoFont('Noto Color Emoji 9', 'notocoloremoji/v32/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabsE4tq3luCC7p-aXxcn.9.woff2'), NotoFont('Noto Color Emoji 10', 'notocoloremoji/v32/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabsE4tq3luCC7p-aXxcn.10.woff2'), NotoFont('Noto Color Emoji 11', 'notocoloremoji/v32/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabsE4tq3luCC7p-aXxcn.11.woff2'), + NotoFont('Noto Sans Symbols 2 0', 'notosanssymbols2/v24/I_uyMoGduATTei9eI8daxVHDyfisHr71-jrBWXPM4Q.woff2'), + NotoFont('Noto Sans Symbols 2 1', 'notosanssymbols2/v24/I_uyMoGduATTei9eI8daxVHDyfisHr71-ujgfE71.woff2'), + NotoFont('Noto Sans Symbols 2 2', 'notosanssymbols2/v24/I_uyMoGduATTei9eI8daxVHDyfisHr71-gTBWXPM4Q.woff2'), + NotoFont('Noto Sans Symbols 2 3', 'notosanssymbols2/v24/I_uyMoGduATTei9eI8daxVHDyfisHr71-vrgfE71.woff2'), + NotoFont('Noto Sans Symbols 2 4', 'notosanssymbols2/v24/I_uyMoGduATTei9eI8daxVHDyfisHr71-prgfE71.woff2'), + NotoFont('Noto Sans Symbols 2 5', 'notosanssymbols2/v24/I_uyMoGduATTei9eI8daxVHDyfisHr71-pTgfA.woff2'), + NotoFont('Noto Sans Cuneiform 0', 'notosanscuneiform/v17/bMrrmTWK7YY-MF22aHGGd7H8PhJtvBDWse5DlCQu.woff2'), + NotoFont('Noto Sans Cuneiform 1', 'notosanscuneiform/v17/bMrrmTWK7YY-MF22aHGGd7H8PhJtvBDWsbZDlCQu.woff2'), + NotoFont('Noto Sans Cuneiform 2', 'notosanscuneiform/v17/bMrrmTWK7YY-MF22aHGGd7H8PhJtvBDWsbhDlA.woff2'), + NotoFont('Noto Sans Duployan 0', 'notosansduployan/v18/gokzH7nwAEdtF9N8-mdTDx_X9JM5wsvbi-kD5F8a.woff2'), + NotoFont('Noto Sans Duployan 1', 'notosansduployan/v18/gokzH7nwAEdtF9N8-mdTDx_X9JM5wsvbH8gm2WY.woff2'), + NotoFont('Noto Sans Duployan 2', 'notosansduployan/v18/gokzH7nwAEdtF9N8-mdTDx_X9JM5wsvbEcgm.woff2'), + NotoFont('Noto Sans Egyptian Hieroglyphs 0', 'notosansegyptianhieroglyphs/v29/vEF42-tODB8RrNDvZSUmRhcQHzx1s7y_F9-j3qSzEcbEYintdVi99Rg.woff2'), + NotoFont('Noto Sans Egyptian Hieroglyphs 1', 'notosansegyptianhieroglyphs/v29/vEF42-tODB8RrNDvZSUmRhcQHzx1s7y_F9-j3qSzEcbEYintQFi99Rg.woff2'), + NotoFont('Noto Sans Egyptian Hieroglyphs 2', 'notosansegyptianhieroglyphs/v29/vEF42-tODB8RrNDvZSUmRhcQHzx1s7y_F9-j3qSzEcbEYintTli9.woff2'), + NotoFont('Noto Sans HK 0', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.0.woff2'), + NotoFont('Noto Sans HK 1', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.1.woff2'), + NotoFont('Noto Sans HK 2', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.2.woff2'), + NotoFont('Noto Sans HK 3', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.3.woff2'), + NotoFont('Noto Sans HK 4', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.4.woff2'), + NotoFont('Noto Sans HK 5', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.5.woff2'), + NotoFont('Noto Sans HK 6', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.6.woff2'), + NotoFont('Noto Sans HK 7', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.7.woff2'), + NotoFont('Noto Sans HK 8', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.8.woff2'), + NotoFont('Noto Sans HK 9', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.9.woff2'), + NotoFont('Noto Sans HK 10', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.10.woff2'), + NotoFont('Noto Sans HK 11', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.15.woff2'), + NotoFont('Noto Sans HK 12', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.16.woff2'), + NotoFont('Noto Sans HK 13', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.17.woff2'), + NotoFont('Noto Sans HK 14', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.25.woff2'), + NotoFont('Noto Sans HK 15', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.26.woff2'), + NotoFont('Noto Sans HK 16', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.27.woff2'), + NotoFont('Noto Sans HK 17', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.28.woff2'), + NotoFont('Noto Sans HK 18', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.29.woff2'), + NotoFont('Noto Sans HK 19', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.30.woff2'), + NotoFont('Noto Sans HK 20', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.31.woff2'), + NotoFont('Noto Sans HK 21', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.32.woff2'), + NotoFont('Noto Sans HK 22', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.33.woff2'), + NotoFont('Noto Sans HK 23', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.34.woff2'), + NotoFont('Noto Sans HK 24', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.35.woff2'), + NotoFont('Noto Sans HK 25', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.36.woff2'), + NotoFont('Noto Sans HK 26', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.37.woff2'), + NotoFont('Noto Sans HK 27', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.38.woff2'), + NotoFont('Noto Sans HK 28', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.39.woff2'), + NotoFont('Noto Sans HK 29', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.40.woff2'), + NotoFont('Noto Sans HK 30', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.41.woff2'), + NotoFont('Noto Sans HK 31', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.42.woff2'), + NotoFont('Noto Sans HK 32', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.43.woff2'), + NotoFont('Noto Sans HK 33', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.44.woff2'), + NotoFont('Noto Sans HK 34', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.45.woff2'), + NotoFont('Noto Sans HK 35', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.46.woff2'), + NotoFont('Noto Sans HK 36', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.47.woff2'), + NotoFont('Noto Sans HK 37', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.48.woff2'), + NotoFont('Noto Sans HK 38', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.49.woff2'), + NotoFont('Noto Sans HK 39', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.50.woff2'), + NotoFont('Noto Sans HK 40', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.51.woff2'), + NotoFont('Noto Sans HK 41', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.52.woff2'), + NotoFont('Noto Sans HK 42', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.53.woff2'), + NotoFont('Noto Sans HK 43', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.54.woff2'), + NotoFont('Noto Sans HK 44', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.55.woff2'), + NotoFont('Noto Sans HK 45', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.56.woff2'), + NotoFont('Noto Sans HK 46', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.57.woff2'), + NotoFont('Noto Sans HK 47', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.58.woff2'), + NotoFont('Noto Sans HK 48', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.59.woff2'), + NotoFont('Noto Sans HK 49', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.60.woff2'), + NotoFont('Noto Sans HK 50', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.61.woff2'), + NotoFont('Noto Sans HK 51', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.62.woff2'), + NotoFont('Noto Sans HK 52', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.63.woff2'), + NotoFont('Noto Sans HK 53', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.64.woff2'), + NotoFont('Noto Sans HK 54', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.65.woff2'), + NotoFont('Noto Sans HK 55', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.66.woff2'), + NotoFont('Noto Sans HK 56', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.67.woff2'), + NotoFont('Noto Sans HK 57', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.68.woff2'), + NotoFont('Noto Sans HK 58', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.69.woff2'), + NotoFont('Noto Sans HK 59', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.70.woff2'), + NotoFont('Noto Sans HK 60', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.71.woff2'), + NotoFont('Noto Sans HK 61', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.72.woff2'), + NotoFont('Noto Sans HK 62', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.73.woff2'), + NotoFont('Noto Sans HK 63', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.74.woff2'), + NotoFont('Noto Sans HK 64', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.75.woff2'), + NotoFont('Noto Sans HK 65', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.76.woff2'), + NotoFont('Noto Sans HK 66', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.77.woff2'), + NotoFont('Noto Sans HK 67', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.78.woff2'), + NotoFont('Noto Sans HK 68', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.79.woff2'), + NotoFont('Noto Sans HK 69', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.80.woff2'), + NotoFont('Noto Sans HK 70', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.81.woff2'), + NotoFont('Noto Sans HK 71', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.82.woff2'), + NotoFont('Noto Sans HK 72', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.83.woff2'), + NotoFont('Noto Sans HK 73', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.84.woff2'), + NotoFont('Noto Sans HK 74', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.85.woff2'), + NotoFont('Noto Sans HK 75', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.86.woff2'), + NotoFont('Noto Sans HK 76', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.87.woff2'), + NotoFont('Noto Sans HK 77', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.88.woff2'), + NotoFont('Noto Sans HK 78', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.89.woff2'), + NotoFont('Noto Sans HK 79', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.90.woff2'), + NotoFont('Noto Sans HK 80', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.91.woff2'), + NotoFont('Noto Sans HK 81', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.92.woff2'), + NotoFont('Noto Sans HK 82', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.93.woff2'), + NotoFont('Noto Sans HK 83', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.98.woff2'), + NotoFont('Noto Sans HK 84', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.99.woff2'), + NotoFont('Noto Sans HK 85', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.100.woff2'), + NotoFont('Noto Sans HK 86', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.101.woff2'), + NotoFont('Noto Sans HK 87', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.102.woff2'), + NotoFont('Noto Sans HK 88', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.103.woff2'), + NotoFont('Noto Sans HK 89', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.104.woff2'), + NotoFont('Noto Sans HK 90', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.105.woff2'), + NotoFont('Noto Sans HK 91', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.106.woff2'), + NotoFont('Noto Sans HK 92', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.107.woff2'), + NotoFont('Noto Sans HK 93', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.108.woff2'), + NotoFont('Noto Sans HK 94', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.109.woff2'), + NotoFont('Noto Sans HK 95', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.110.woff2'), + NotoFont('Noto Sans HK 96', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.111.woff2'), + NotoFont('Noto Sans HK 97', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.112.woff2'), + NotoFont('Noto Sans HK 98', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.113.woff2'), + NotoFont('Noto Sans HK 99', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.114.woff2'), + NotoFont('Noto Sans HK 100', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.115.woff2'), + NotoFont('Noto Sans HK 101', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.116.woff2'), + NotoFont('Noto Sans HK 102', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.117.woff2'), + NotoFont('Noto Sans HK 103', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.118.woff2'), + NotoFont('Noto Sans HK 104', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oD7kYrUzT7-NvA3pTohjc3XVtNXX8A7gG1LO2KAPAw.119.woff2'), + NotoFont('Noto Sans HK 105', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB-yoaZiLjN.woff2'), + NotoFont('Noto Sans HK 106', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB-yo2ZiLjN.woff2'), + NotoFont('Noto Sans HK 107', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB-yoyZiLjN.woff2'), + NotoFont('Noto Sans HK 108', 'notosanshk/v32/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB-yoKZiA.woff2'), + NotoFont('Noto Sans JP 0', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.0.woff2'), + NotoFont('Noto Sans JP 1', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.1.woff2'), + NotoFont('Noto Sans JP 2', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.2.woff2'), + NotoFont('Noto Sans JP 3', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.3.woff2'), + NotoFont('Noto Sans JP 4', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.4.woff2'), + NotoFont('Noto Sans JP 5', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.5.woff2'), + NotoFont('Noto Sans JP 6', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.6.woff2'), + NotoFont('Noto Sans JP 7', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.7.woff2'), + NotoFont('Noto Sans JP 8', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.8.woff2'), + NotoFont('Noto Sans JP 9', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.9.woff2'), + NotoFont('Noto Sans JP 10', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.10.woff2'), + NotoFont('Noto Sans JP 11', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.11.woff2'), + NotoFont('Noto Sans JP 12', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.12.woff2'), + NotoFont('Noto Sans JP 13', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.13.woff2'), + NotoFont('Noto Sans JP 14', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.14.woff2'), + NotoFont('Noto Sans JP 15', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.15.woff2'), + NotoFont('Noto Sans JP 16', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.16.woff2'), + NotoFont('Noto Sans JP 17', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.17.woff2'), + NotoFont('Noto Sans JP 18', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.18.woff2'), + NotoFont('Noto Sans JP 19', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.19.woff2'), + NotoFont('Noto Sans JP 20', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.20.woff2'), + NotoFont('Noto Sans JP 21', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.21.woff2'), + NotoFont('Noto Sans JP 22', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.22.woff2'), + NotoFont('Noto Sans JP 23', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.23.woff2'), + NotoFont('Noto Sans JP 24', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.24.woff2'), + NotoFont('Noto Sans JP 25', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.25.woff2'), + NotoFont('Noto Sans JP 26', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.26.woff2'), + NotoFont('Noto Sans JP 27', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.27.woff2'), + NotoFont('Noto Sans JP 28', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.28.woff2'), + NotoFont('Noto Sans JP 29', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.29.woff2'), + NotoFont('Noto Sans JP 30', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.30.woff2'), + NotoFont('Noto Sans JP 31', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.31.woff2'), + NotoFont('Noto Sans JP 32', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.32.woff2'), + NotoFont('Noto Sans JP 33', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.33.woff2'), + NotoFont('Noto Sans JP 34', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.34.woff2'), + NotoFont('Noto Sans JP 35', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.35.woff2'), + NotoFont('Noto Sans JP 36', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.36.woff2'), + NotoFont('Noto Sans JP 37', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.37.woff2'), + NotoFont('Noto Sans JP 38', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.38.woff2'), + NotoFont('Noto Sans JP 39', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.39.woff2'), + NotoFont('Noto Sans JP 40', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.40.woff2'), + NotoFont('Noto Sans JP 41', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.41.woff2'), + NotoFont('Noto Sans JP 42', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.42.woff2'), + NotoFont('Noto Sans JP 43', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.43.woff2'), + NotoFont('Noto Sans JP 44', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.44.woff2'), + NotoFont('Noto Sans JP 45', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.45.woff2'), + NotoFont('Noto Sans JP 46', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.46.woff2'), + NotoFont('Noto Sans JP 47', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.47.woff2'), + NotoFont('Noto Sans JP 48', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.48.woff2'), + NotoFont('Noto Sans JP 49', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.49.woff2'), + NotoFont('Noto Sans JP 50', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.50.woff2'), + NotoFont('Noto Sans JP 51', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.51.woff2'), + NotoFont('Noto Sans JP 52', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.52.woff2'), + NotoFont('Noto Sans JP 53', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.53.woff2'), + NotoFont('Noto Sans JP 54', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.54.woff2'), + NotoFont('Noto Sans JP 55', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.55.woff2'), + NotoFont('Noto Sans JP 56', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.56.woff2'), + NotoFont('Noto Sans JP 57', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.57.woff2'), + NotoFont('Noto Sans JP 58', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.58.woff2'), + NotoFont('Noto Sans JP 59', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.59.woff2'), + NotoFont('Noto Sans JP 60', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.60.woff2'), + NotoFont('Noto Sans JP 61', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.61.woff2'), + NotoFont('Noto Sans JP 62', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.62.woff2'), + NotoFont('Noto Sans JP 63', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.63.woff2'), + NotoFont('Noto Sans JP 64', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.64.woff2'), + NotoFont('Noto Sans JP 65', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.65.woff2'), + NotoFont('Noto Sans JP 66', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.66.woff2'), + NotoFont('Noto Sans JP 67', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.67.woff2'), + NotoFont('Noto Sans JP 68', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.68.woff2'), + NotoFont('Noto Sans JP 69', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.69.woff2'), + NotoFont('Noto Sans JP 70', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.70.woff2'), + NotoFont('Noto Sans JP 71', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.71.woff2'), + NotoFont('Noto Sans JP 72', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.72.woff2'), + NotoFont('Noto Sans JP 73', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.73.woff2'), + NotoFont('Noto Sans JP 74', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.74.woff2'), + NotoFont('Noto Sans JP 75', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.75.woff2'), + NotoFont('Noto Sans JP 76', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.76.woff2'), + NotoFont('Noto Sans JP 77', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.77.woff2'), + NotoFont('Noto Sans JP 78', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.78.woff2'), + NotoFont('Noto Sans JP 79', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.79.woff2'), + NotoFont('Noto Sans JP 80', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.80.woff2'), + NotoFont('Noto Sans JP 81', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.81.woff2'), + NotoFont('Noto Sans JP 82', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.82.woff2'), + NotoFont('Noto Sans JP 83', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.83.woff2'), + NotoFont('Noto Sans JP 84', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.84.woff2'), + NotoFont('Noto Sans JP 85', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.85.woff2'), + NotoFont('Noto Sans JP 86', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.86.woff2'), + NotoFont('Noto Sans JP 87', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.87.woff2'), + NotoFont('Noto Sans JP 88', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.88.woff2'), + NotoFont('Noto Sans JP 89', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.89.woff2'), + NotoFont('Noto Sans JP 90', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.90.woff2'), + NotoFont('Noto Sans JP 91', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.91.woff2'), + NotoFont('Noto Sans JP 92', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.92.woff2'), + NotoFont('Noto Sans JP 93', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.93.woff2'), + NotoFont('Noto Sans JP 94', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.94.woff2'), + NotoFont('Noto Sans JP 95', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.95.woff2'), + NotoFont('Noto Sans JP 96', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.96.woff2'), + NotoFont('Noto Sans JP 97', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.97.woff2'), + NotoFont('Noto Sans JP 98', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.98.woff2'), + NotoFont('Noto Sans JP 99', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.99.woff2'), + NotoFont('Noto Sans JP 100', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.100.woff2'), + NotoFont('Noto Sans JP 101', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.101.woff2'), + NotoFont('Noto Sans JP 102', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.102.woff2'), + NotoFont('Noto Sans JP 103', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.103.woff2'), + NotoFont('Noto Sans JP 104', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.104.woff2'), + NotoFont('Noto Sans JP 105', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.105.woff2'), + NotoFont('Noto Sans JP 106', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.106.woff2'), + NotoFont('Noto Sans JP 107', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.107.woff2'), + NotoFont('Noto Sans JP 108', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.108.woff2'), + NotoFont('Noto Sans JP 109', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.109.woff2'), + NotoFont('Noto Sans JP 110', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.110.woff2'), + NotoFont('Noto Sans JP 111', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.111.woff2'), + NotoFont('Noto Sans JP 112', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.112.woff2'), + NotoFont('Noto Sans JP 113', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.113.woff2'), + NotoFont('Noto Sans JP 114', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.114.woff2'), + NotoFont('Noto Sans JP 115', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.115.woff2'), + NotoFont('Noto Sans JP 116', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.116.woff2'), + NotoFont('Noto Sans JP 117', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.117.woff2'), + NotoFont('Noto Sans JP 118', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.118.woff2'), + NotoFont('Noto Sans JP 119', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj756wwr4v0qHnANADNsISRDl2PRkiiWsg.119.woff2'), + NotoFont('Noto Sans JP 120', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj35jS04w-.woff2'), + NotoFont('Noto Sans JP 121', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj35PS04w-.woff2'), + NotoFont('Noto Sans JP 122', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj35LS04w-.woff2'), + NotoFont('Noto Sans JP 123', 'notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj35zS0w.woff2'), + NotoFont('Noto Sans KR 0', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.0.woff2'), + NotoFont('Noto Sans KR 1', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.1.woff2'), + NotoFont('Noto Sans KR 2', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.2.woff2'), + NotoFont('Noto Sans KR 3', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.3.woff2'), + NotoFont('Noto Sans KR 4', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.4.woff2'), + NotoFont('Noto Sans KR 5', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.5.woff2'), + NotoFont('Noto Sans KR 6', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.6.woff2'), + NotoFont('Noto Sans KR 7', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.7.woff2'), + NotoFont('Noto Sans KR 8', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.8.woff2'), + NotoFont('Noto Sans KR 9', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.9.woff2'), + NotoFont('Noto Sans KR 10', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.10.woff2'), + NotoFont('Noto Sans KR 11', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.11.woff2'), + NotoFont('Noto Sans KR 12', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.12.woff2'), + NotoFont('Noto Sans KR 13', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.13.woff2'), + NotoFont('Noto Sans KR 14', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.14.woff2'), + NotoFont('Noto Sans KR 15', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.15.woff2'), + NotoFont('Noto Sans KR 16', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.16.woff2'), + NotoFont('Noto Sans KR 17', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.17.woff2'), + NotoFont('Noto Sans KR 18', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.18.woff2'), + NotoFont('Noto Sans KR 19', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.19.woff2'), + NotoFont('Noto Sans KR 20', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.20.woff2'), + NotoFont('Noto Sans KR 21', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.21.woff2'), + NotoFont('Noto Sans KR 22', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.22.woff2'), + NotoFont('Noto Sans KR 23', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.23.woff2'), + NotoFont('Noto Sans KR 24', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.24.woff2'), + NotoFont('Noto Sans KR 25', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.25.woff2'), + NotoFont('Noto Sans KR 26', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.26.woff2'), + NotoFont('Noto Sans KR 27', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.27.woff2'), + NotoFont('Noto Sans KR 28', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.28.woff2'), + NotoFont('Noto Sans KR 29', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.29.woff2'), + NotoFont('Noto Sans KR 30', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.30.woff2'), + NotoFont('Noto Sans KR 31', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.31.woff2'), + NotoFont('Noto Sans KR 32', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.32.woff2'), + NotoFont('Noto Sans KR 33', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.33.woff2'), + NotoFont('Noto Sans KR 34', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.34.woff2'), + NotoFont('Noto Sans KR 35', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.35.woff2'), + NotoFont('Noto Sans KR 36', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.36.woff2'), + NotoFont('Noto Sans KR 37', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.37.woff2'), + NotoFont('Noto Sans KR 38', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.38.woff2'), + NotoFont('Noto Sans KR 39', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.39.woff2'), + NotoFont('Noto Sans KR 40', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.40.woff2'), + NotoFont('Noto Sans KR 41', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.41.woff2'), + NotoFont('Noto Sans KR 42', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.42.woff2'), + NotoFont('Noto Sans KR 43', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.43.woff2'), + NotoFont('Noto Sans KR 44', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.44.woff2'), + NotoFont('Noto Sans KR 45', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.45.woff2'), + NotoFont('Noto Sans KR 46', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.46.woff2'), + NotoFont('Noto Sans KR 47', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.47.woff2'), + NotoFont('Noto Sans KR 48', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.48.woff2'), + NotoFont('Noto Sans KR 49', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.49.woff2'), + NotoFont('Noto Sans KR 50', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.50.woff2'), + NotoFont('Noto Sans KR 51', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.51.woff2'), + NotoFont('Noto Sans KR 52', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.52.woff2'), + NotoFont('Noto Sans KR 53', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.53.woff2'), + NotoFont('Noto Sans KR 54', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.54.woff2'), + NotoFont('Noto Sans KR 55', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.55.woff2'), + NotoFont('Noto Sans KR 56', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.56.woff2'), + NotoFont('Noto Sans KR 57', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.57.woff2'), + NotoFont('Noto Sans KR 58', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.58.woff2'), + NotoFont('Noto Sans KR 59', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.59.woff2'), + NotoFont('Noto Sans KR 60', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.60.woff2'), + NotoFont('Noto Sans KR 61', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.61.woff2'), + NotoFont('Noto Sans KR 62', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.62.woff2'), + NotoFont('Noto Sans KR 63', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.63.woff2'), + NotoFont('Noto Sans KR 64', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.64.woff2'), + NotoFont('Noto Sans KR 65', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.65.woff2'), + NotoFont('Noto Sans KR 66', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.66.woff2'), + NotoFont('Noto Sans KR 67', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.67.woff2'), + NotoFont('Noto Sans KR 68', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.68.woff2'), + NotoFont('Noto Sans KR 69', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.69.woff2'), + NotoFont('Noto Sans KR 70', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.70.woff2'), + NotoFont('Noto Sans KR 71', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.71.woff2'), + NotoFont('Noto Sans KR 72', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.72.woff2'), + NotoFont('Noto Sans KR 73', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.73.woff2'), + NotoFont('Noto Sans KR 74', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.74.woff2'), + NotoFont('Noto Sans KR 75', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.75.woff2'), + NotoFont('Noto Sans KR 76', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.76.woff2'), + NotoFont('Noto Sans KR 77', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.77.woff2'), + NotoFont('Noto Sans KR 78', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.78.woff2'), + NotoFont('Noto Sans KR 79', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.79.woff2'), + NotoFont('Noto Sans KR 80', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.80.woff2'), + NotoFont('Noto Sans KR 81', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.81.woff2'), + NotoFont('Noto Sans KR 82', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.82.woff2'), + NotoFont('Noto Sans KR 83', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.83.woff2'), + NotoFont('Noto Sans KR 84', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.84.woff2'), + NotoFont('Noto Sans KR 85', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.85.woff2'), + NotoFont('Noto Sans KR 86', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.86.woff2'), + NotoFont('Noto Sans KR 87', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.87.woff2'), + NotoFont('Noto Sans KR 88', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.88.woff2'), + NotoFont('Noto Sans KR 89', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.89.woff2'), + NotoFont('Noto Sans KR 90', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.90.woff2'), + NotoFont('Noto Sans KR 91', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.91.woff2'), + NotoFont('Noto Sans KR 92', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.92.woff2'), + NotoFont('Noto Sans KR 93', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.93.woff2'), + NotoFont('Noto Sans KR 94', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.94.woff2'), + NotoFont('Noto Sans KR 95', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.95.woff2'), + NotoFont('Noto Sans KR 96', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.96.woff2'), + NotoFont('Noto Sans KR 97', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.97.woff2'), + NotoFont('Noto Sans KR 98', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.98.woff2'), + NotoFont('Noto Sans KR 99', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.99.woff2'), + NotoFont('Noto Sans KR 100', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.100.woff2'), + NotoFont('Noto Sans KR 101', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.101.woff2'), + NotoFont('Noto Sans KR 102', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.102.woff2'), + NotoFont('Noto Sans KR 103', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.103.woff2'), + NotoFont('Noto Sans KR 104', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.104.woff2'), + NotoFont('Noto Sans KR 105', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.105.woff2'), + NotoFont('Noto Sans KR 106', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.106.woff2'), + NotoFont('Noto Sans KR 107', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.107.woff2'), + NotoFont('Noto Sans KR 108', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.108.woff2'), + NotoFont('Noto Sans KR 109', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.109.woff2'), + NotoFont('Noto Sans KR 110', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.110.woff2'), + NotoFont('Noto Sans KR 111', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.111.woff2'), + NotoFont('Noto Sans KR 112', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.112.woff2'), + NotoFont('Noto Sans KR 113', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.113.woff2'), + NotoFont('Noto Sans KR 114', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.114.woff2'), + NotoFont('Noto Sans KR 115', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.115.woff2'), + NotoFont('Noto Sans KR 116', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.116.woff2'), + NotoFont('Noto Sans KR 117', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.117.woff2'), + NotoFont('Noto Sans KR 118', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.118.woff2'), + NotoFont('Noto Sans KR 119', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.119.woff2'), + NotoFont('Noto Sans KR 120', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoySLfg8U4h.woff2'), + NotoFont('Noto Sans KR 121', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoySLzg8U4h.woff2'), + NotoFont('Noto Sans KR 122', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoySL3g8U4h.woff2'), + NotoFont('Noto Sans KR 123', 'notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoySLPg8Q.woff2'), + NotoFont('Noto Sans SC 0', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.4.woff2'), + NotoFont('Noto Sans SC 1', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.5.woff2'), + NotoFont('Noto Sans SC 2', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.6.woff2'), + NotoFont('Noto Sans SC 3', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.21.woff2'), + NotoFont('Noto Sans SC 4', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.22.woff2'), + NotoFont('Noto Sans SC 5', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.23.woff2'), + NotoFont('Noto Sans SC 6', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.24.woff2'), + NotoFont('Noto Sans SC 7', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.25.woff2'), + NotoFont('Noto Sans SC 8', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.26.woff2'), + NotoFont('Noto Sans SC 9', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.27.woff2'), + NotoFont('Noto Sans SC 10', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.28.woff2'), + NotoFont('Noto Sans SC 11', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.29.woff2'), + NotoFont('Noto Sans SC 12', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.30.woff2'), + NotoFont('Noto Sans SC 13', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.31.woff2'), + NotoFont('Noto Sans SC 14', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.32.woff2'), + NotoFont('Noto Sans SC 15', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.33.woff2'), + NotoFont('Noto Sans SC 16', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.34.woff2'), + NotoFont('Noto Sans SC 17', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.35.woff2'), + NotoFont('Noto Sans SC 18', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.36.woff2'), + NotoFont('Noto Sans SC 19', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.37.woff2'), + NotoFont('Noto Sans SC 20', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.38.woff2'), + NotoFont('Noto Sans SC 21', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.39.woff2'), + NotoFont('Noto Sans SC 22', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.40.woff2'), + NotoFont('Noto Sans SC 23', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.41.woff2'), + NotoFont('Noto Sans SC 24', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.42.woff2'), + NotoFont('Noto Sans SC 25', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.43.woff2'), + NotoFont('Noto Sans SC 26', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.44.woff2'), + NotoFont('Noto Sans SC 27', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.45.woff2'), + NotoFont('Noto Sans SC 28', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.46.woff2'), + NotoFont('Noto Sans SC 29', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.47.woff2'), + NotoFont('Noto Sans SC 30', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.48.woff2'), + NotoFont('Noto Sans SC 31', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.49.woff2'), + NotoFont('Noto Sans SC 32', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.50.woff2'), + NotoFont('Noto Sans SC 33', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.51.woff2'), + NotoFont('Noto Sans SC 34', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.52.woff2'), + NotoFont('Noto Sans SC 35', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.53.woff2'), + NotoFont('Noto Sans SC 36', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.54.woff2'), + NotoFont('Noto Sans SC 37', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.55.woff2'), + NotoFont('Noto Sans SC 38', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.56.woff2'), + NotoFont('Noto Sans SC 39', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.57.woff2'), + NotoFont('Noto Sans SC 40', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.58.woff2'), + NotoFont('Noto Sans SC 41', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.59.woff2'), + NotoFont('Noto Sans SC 42', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.60.woff2'), + NotoFont('Noto Sans SC 43', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.61.woff2'), + NotoFont('Noto Sans SC 44', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.62.woff2'), + NotoFont('Noto Sans SC 45', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.63.woff2'), + NotoFont('Noto Sans SC 46', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.64.woff2'), + NotoFont('Noto Sans SC 47', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.65.woff2'), + NotoFont('Noto Sans SC 48', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.66.woff2'), + NotoFont('Noto Sans SC 49', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.67.woff2'), + NotoFont('Noto Sans SC 50', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.68.woff2'), + NotoFont('Noto Sans SC 51', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.69.woff2'), + NotoFont('Noto Sans SC 52', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.70.woff2'), + NotoFont('Noto Sans SC 53', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.71.woff2'), + NotoFont('Noto Sans SC 54', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.72.woff2'), + NotoFont('Noto Sans SC 55', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.73.woff2'), + NotoFont('Noto Sans SC 56', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.74.woff2'), + NotoFont('Noto Sans SC 57', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.75.woff2'), + NotoFont('Noto Sans SC 58', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.76.woff2'), + NotoFont('Noto Sans SC 59', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.77.woff2'), + NotoFont('Noto Sans SC 60', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.78.woff2'), + NotoFont('Noto Sans SC 61', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.79.woff2'), + NotoFont('Noto Sans SC 62', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.80.woff2'), + NotoFont('Noto Sans SC 63', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.81.woff2'), + NotoFont('Noto Sans SC 64', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.82.woff2'), + NotoFont('Noto Sans SC 65', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.83.woff2'), + NotoFont('Noto Sans SC 66', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.84.woff2'), + NotoFont('Noto Sans SC 67', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.85.woff2'), + NotoFont('Noto Sans SC 68', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.86.woff2'), + NotoFont('Noto Sans SC 69', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.87.woff2'), + NotoFont('Noto Sans SC 70', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.88.woff2'), + NotoFont('Noto Sans SC 71', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.89.woff2'), + NotoFont('Noto Sans SC 72', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.90.woff2'), + NotoFont('Noto Sans SC 73', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.91.woff2'), + NotoFont('Noto Sans SC 74', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.97.woff2'), + NotoFont('Noto Sans SC 75', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.98.woff2'), + NotoFont('Noto Sans SC 76', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.99.woff2'), + NotoFont('Noto Sans SC 77', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.100.woff2'), + NotoFont('Noto Sans SC 78', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.101.woff2'), + NotoFont('Noto Sans SC 79', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.102.woff2'), + NotoFont('Noto Sans SC 80', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.103.woff2'), + NotoFont('Noto Sans SC 81', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.104.woff2'), + NotoFont('Noto Sans SC 82', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.105.woff2'), + NotoFont('Noto Sans SC 83', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.106.woff2'), + NotoFont('Noto Sans SC 84', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.107.woff2'), + NotoFont('Noto Sans SC 85', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.108.woff2'), + NotoFont('Noto Sans SC 86', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.109.woff2'), + NotoFont('Noto Sans SC 87', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.110.woff2'), + NotoFont('Noto Sans SC 88', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.111.woff2'), + NotoFont('Noto Sans SC 89', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.112.woff2'), + NotoFont('Noto Sans SC 90', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.113.woff2'), + NotoFont('Noto Sans SC 91', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.114.woff2'), + NotoFont('Noto Sans SC 92', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.115.woff2'), + NotoFont('Noto Sans SC 93', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.116.woff2'), + NotoFont('Noto Sans SC 94', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.117.woff2'), + NotoFont('Noto Sans SC 95', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.118.woff2'), + NotoFont('Noto Sans SC 96', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYkldv7JjxkkgFsFSSOPMOkySAZ73y9ViAt3acb8NexQ2w.119.woff2'), + NotoFont('Noto Sans SC 97', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FrY9HbczS.woff2'), + NotoFont('Noto Sans SC 98', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FrYRHbczS.woff2'), + NotoFont('Noto Sans SC 99', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FrYVHbczS.woff2'), + NotoFont('Noto Sans SC 100', 'notosanssc/v37/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FrYtHbQ.woff2'), + NotoFont('Noto Sans TC 0', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.0.woff2'), + NotoFont('Noto Sans TC 1', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.6.woff2'), + NotoFont('Noto Sans TC 2', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.7.woff2'), + NotoFont('Noto Sans TC 3', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.8.woff2'), + NotoFont('Noto Sans TC 4', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.19.woff2'), + NotoFont('Noto Sans TC 5', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.20.woff2'), + NotoFont('Noto Sans TC 6', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.21.woff2'), + NotoFont('Noto Sans TC 7', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.22.woff2'), + NotoFont('Noto Sans TC 8', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.23.woff2'), + NotoFont('Noto Sans TC 9', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.24.woff2'), + NotoFont('Noto Sans TC 10', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.25.woff2'), + NotoFont('Noto Sans TC 11', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.26.woff2'), + NotoFont('Noto Sans TC 12', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.27.woff2'), + NotoFont('Noto Sans TC 13', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.28.woff2'), + NotoFont('Noto Sans TC 14', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.29.woff2'), + NotoFont('Noto Sans TC 15', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.30.woff2'), + NotoFont('Noto Sans TC 16', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.31.woff2'), + NotoFont('Noto Sans TC 17', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.32.woff2'), + NotoFont('Noto Sans TC 18', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.33.woff2'), + NotoFont('Noto Sans TC 19', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.34.woff2'), + NotoFont('Noto Sans TC 20', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.35.woff2'), + NotoFont('Noto Sans TC 21', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.36.woff2'), + NotoFont('Noto Sans TC 22', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.37.woff2'), + NotoFont('Noto Sans TC 23', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.38.woff2'), + NotoFont('Noto Sans TC 24', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.39.woff2'), + NotoFont('Noto Sans TC 25', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.40.woff2'), + NotoFont('Noto Sans TC 26', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.41.woff2'), + NotoFont('Noto Sans TC 27', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.42.woff2'), + NotoFont('Noto Sans TC 28', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.43.woff2'), + NotoFont('Noto Sans TC 29', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.44.woff2'), + NotoFont('Noto Sans TC 30', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.45.woff2'), + NotoFont('Noto Sans TC 31', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.46.woff2'), + NotoFont('Noto Sans TC 32', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.47.woff2'), + NotoFont('Noto Sans TC 33', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.48.woff2'), + NotoFont('Noto Sans TC 34', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.49.woff2'), + NotoFont('Noto Sans TC 35', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.50.woff2'), + NotoFont('Noto Sans TC 36', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.51.woff2'), + NotoFont('Noto Sans TC 37', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.52.woff2'), + NotoFont('Noto Sans TC 38', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.53.woff2'), + NotoFont('Noto Sans TC 39', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.54.woff2'), + NotoFont('Noto Sans TC 40', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.55.woff2'), + NotoFont('Noto Sans TC 41', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.56.woff2'), + NotoFont('Noto Sans TC 42', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.57.woff2'), + NotoFont('Noto Sans TC 43', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.58.woff2'), + NotoFont('Noto Sans TC 44', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.59.woff2'), + NotoFont('Noto Sans TC 45', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.60.woff2'), + NotoFont('Noto Sans TC 46', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.61.woff2'), + NotoFont('Noto Sans TC 47', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.62.woff2'), + NotoFont('Noto Sans TC 48', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.63.woff2'), + NotoFont('Noto Sans TC 49', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.64.woff2'), + NotoFont('Noto Sans TC 50', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.65.woff2'), + NotoFont('Noto Sans TC 51', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.66.woff2'), + NotoFont('Noto Sans TC 52', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.67.woff2'), + NotoFont('Noto Sans TC 53', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.68.woff2'), + NotoFont('Noto Sans TC 54', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.69.woff2'), + NotoFont('Noto Sans TC 55', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.70.woff2'), + NotoFont('Noto Sans TC 56', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.71.woff2'), + NotoFont('Noto Sans TC 57', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.72.woff2'), + NotoFont('Noto Sans TC 58', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.73.woff2'), + NotoFont('Noto Sans TC 59', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.74.woff2'), + NotoFont('Noto Sans TC 60', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.75.woff2'), + NotoFont('Noto Sans TC 61', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.76.woff2'), + NotoFont('Noto Sans TC 62', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.77.woff2'), + NotoFont('Noto Sans TC 63', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.78.woff2'), + NotoFont('Noto Sans TC 64', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.79.woff2'), + NotoFont('Noto Sans TC 65', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.80.woff2'), + NotoFont('Noto Sans TC 66', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.81.woff2'), + NotoFont('Noto Sans TC 67', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.82.woff2'), + NotoFont('Noto Sans TC 68', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.83.woff2'), + NotoFont('Noto Sans TC 69', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.84.woff2'), + NotoFont('Noto Sans TC 70', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.85.woff2'), + NotoFont('Noto Sans TC 71', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.86.woff2'), + NotoFont('Noto Sans TC 72', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.87.woff2'), + NotoFont('Noto Sans TC 73', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.88.woff2'), + NotoFont('Noto Sans TC 74', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.89.woff2'), + NotoFont('Noto Sans TC 75', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.90.woff2'), + NotoFont('Noto Sans TC 76', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.91.woff2'), + NotoFont('Noto Sans TC 77', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.92.woff2'), + NotoFont('Noto Sans TC 78', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.97.woff2'), + NotoFont('Noto Sans TC 79', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.98.woff2'), + NotoFont('Noto Sans TC 80', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.99.woff2'), + NotoFont('Noto Sans TC 81', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.100.woff2'), + NotoFont('Noto Sans TC 82', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.101.woff2'), + NotoFont('Noto Sans TC 83', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.102.woff2'), + NotoFont('Noto Sans TC 84', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.103.woff2'), + NotoFont('Noto Sans TC 85', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.104.woff2'), + NotoFont('Noto Sans TC 86', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.105.woff2'), + NotoFont('Noto Sans TC 87', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.106.woff2'), + NotoFont('Noto Sans TC 88', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.107.woff2'), + NotoFont('Noto Sans TC 89', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.108.woff2'), + NotoFont('Noto Sans TC 90', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.109.woff2'), + NotoFont('Noto Sans TC 91', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.110.woff2'), + NotoFont('Noto Sans TC 92', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.111.woff2'), + NotoFont('Noto Sans TC 93', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.112.woff2'), + NotoFont('Noto Sans TC 94', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.113.woff2'), + NotoFont('Noto Sans TC 95', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.114.woff2'), + NotoFont('Noto Sans TC 96', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.115.woff2'), + NotoFont('Noto Sans TC 97', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.116.woff2'), + NotoFont('Noto Sans TC 98', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.117.woff2'), + NotoFont('Noto Sans TC 99', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.118.woff2'), + NotoFont('Noto Sans TC 100', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_C8mrWSt1KeqzFVoizG-KdWhyhvKuGOf8EUcrq3YKp7nxxk.119.woff2'), + NotoFont('Noto Sans TC 101', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76CyzClEt1a3.woff2'), + NotoFont('Noto Sans TC 102', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76CyzCJEt1a3.woff2'), + NotoFont('Noto Sans TC 103', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76CyzCNEt1a3.woff2'), + NotoFont('Noto Sans TC 104', 'notosanstc/v36/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76CyzC1Etw.woff2'), + NotoFont('Noto Music', 'notomusic/v20/pe0rMIiSN5pO63htf1sxItKQB9Zra1U.woff2'), + NotoFont('Noto Sans', 'notosans/v37/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A99Y41P6zHtY.woff2'), + NotoFont('Noto Sans Adlam', 'notosansadlam/v22/neIczCCpqp0s5pPusPamd81eMfjPonvqdbYxxpgufnv0TGzBZLwhuvk.woff2'), + NotoFont('Noto Sans Anatolian Hieroglyphs', 'notosansanatolianhieroglyphs/v16/ijw9s4roRME5LLRxjsRb8A0gKPSWq4BbDmHHu6j2pEtUJzZWXyPIymc5QYo.woff2'), + NotoFont('Noto Sans Arabic', 'notosansarabic/v28/nwpxtLGrOAZMl5nJ_wfgRg3DrWFZWsnVBJ_sS6tlqHHFlhQ5l3sQWIHPqzCfyGyvvnCBFQLaig.woff2'), + NotoFont('Noto Sans Armenian', 'notosansarmenian/v43/ZgN0jOZKPa7CHqq0h37c7ReDUubm2SEdFXp7ig73qtTY5idb74R9UdM3y2nZLorxb60nYy6zF3Eg.woff2'), + NotoFont('Noto Sans Avestan', 'notosansavestan/v21/bWti7ejKfBziStx7lIzKOLQZKhIJkyu4SASLji8U.woff2'), + NotoFont('Noto Sans Balinese', 'notosansbalinese/v24/NaPwcYvSBuhTirw6IaFn6UrRDaqje-lpbbRtYf-Fwu2Ov7fdhEtVd222PPY.woff2'), + NotoFont('Noto Sans Bamum', 'notosansbamum/v27/uk-0EGK3o6EruUbnwovcbBTkkklK_Ya_PBHfNGTPEddO-_0LykxEkxA.woff2'), + NotoFont('Noto Sans Bassa Vah', 'notosansbassavah/v17/PN_bRee-r3f7LnqsD5sax12gjZn7mBpL5YwUpA2MBdcFn4MaAc6s34gH-GD7.woff2'), + NotoFont('Noto Sans Batak', 'notosansbatak/v20/gok2H6TwAEdtF9N8-mdTCQvT-Zdgpo_PHuk74A.woff2'), + NotoFont('Noto Sans Bengali', 'notosansbengali/v26/Cn-SJsCGWQxOjaGwMQ6fIiMywrNJIky6nvd8BjzVMvJx2mcSPVFpVEqE-6KmsolLudWk8izI0lc.woff2'), + NotoFont('Noto Sans Bhaiksuki', 'notosansbhaiksuki/v17/UcC63EosKniBH4iELXATsSBWdvUHXxhj8rfUdU4wh9U.woff2'), + NotoFont('Noto Sans Brahmi', 'notosansbrahmi/v19/vEFK2-VODB8RrNDvZSUmQQIIByV18te1W77HtMo.woff2'), + NotoFont('Noto Sans Buginese', 'notosansbuginese/v18/esDM30ldNv-KYGGJpKGk18phe_7Da6_gsPuEXLmNtw.woff2'), + NotoFont('Noto Sans Buhid', 'notosansbuhid/v22/Dxxy8jiXMW75w3OmoDXVWJD7YwzAfqtgnaFoGA.woff2'), + NotoFont('Noto Sans Canadian Aboriginal', 'notosanscanadianaboriginal/v26/4C_TLjTuEqPj-8J01CwaGkiZ9os0iGVkezM1mUT-j_Lmlzda6uH_nnX1bzigWLn_zQsg0q0uhQ.woff2'), + NotoFont('Noto Sans Carian', 'notosanscarian/v16/LDIpaoiONgYwA9Yc6f0gUILeMIOgs78b9yGLmfI.woff2'), + NotoFont('Noto Sans Caucasian Albanian', 'notosanscaucasianalbanian/v18/nKKA-HM_FYFRJvXzVXaANsU0VzsAc46QGOkWytlTs-TXrYXmoVmRSZo.woff2'), + NotoFont('Noto Sans Chakma', 'notosanschakma/v17/Y4GQYbJ8VTEp4t3MKJSMjg5OIzhi4J3TQhYBeYo.woff2'), + NotoFont('Noto Sans Cham', 'notosanscham/v31/pe06MIySN5pO62Z5YkFyQb_bbuRhe6D4yip43qfcERwcurGykboaLg.woff2'), + NotoFont('Noto Sans Cherokee', 'notosanscherokee/v20/KFOPCm6Yu8uF-29fiz9vQF9YWK6Z8O10cHNA0cSkZCHYWi5PDky5rAffjl0.woff2'), + NotoFont('Noto Sans Coptic', 'notosanscoptic/v21/iJWfBWmUZi_OHPqn4wq6kgqumOEd786_VG0xR4Y.woff2'), + NotoFont('Noto Sans Cypriot', 'notosanscypriot/v19/8AtzGta9PYqQDjyp79a6f8Cj-3a3cxIpK5MPpahF.woff2'), + NotoFont('Noto Sans Deseret', 'notosansdeseret/v17/MwQsbgPp1eKH6QsAVuFb9AZM6MMr2Vq4ZnJSZtQG.woff2'), + NotoFont('Noto Sans Devanagari', 'notosansdevanagari/v26/TuGoUUFzXI5FBtUq5a8bjKYTZjtRU6Sgv3NaV_SNmI0b8QQCQmHn6B2OHjbL_08AlXQly-UzoFoW4Ow.woff2'), + NotoFont('Noto Sans Elbasan', 'notosanselbasan/v16/-F6rfiZqLzI2JPCgQBnw400qp1trvHdgre4dFcFh.woff2'), + NotoFont('Noto Sans Elymaic', 'notosanselymaic/v17/UqyKK9YTJW5liNMhTMqe9vUFP65ZD4AmWOT0zi2V.woff2'), + NotoFont('Noto Sans Ethiopic', 'notosansethiopic/v47/7cHPv50vjIepfJVOZZgcpQ5B9FBTH9KGNfhSTgtoow1KVnIvyBoMSzUMacb-T35OK6DmwmfeaY9u.woff2'), + NotoFont('Noto Sans Georgian', 'notosansgeorgian/v44/PlIaFke5O6RzLfvNNVSitxkr76PRHBC4Ytyq-Gof7PUs4S7zWn-8YDB09HFNdpvnzFj7f5WK0OQV.woff2'), + NotoFont('Noto Sans Glagolitic', 'notosansglagolitic/v18/1q2ZY4-BBFBst88SU_tOj4J-4yuNF_HI4ERP4Amu7nM1.woff2'), + NotoFont('Noto Sans Gothic', 'notosansgothic/v16/TuGKUUVzXI5FBtUq5a8bj6wRbzxTFMD40kFQRx0.woff2'), + NotoFont('Noto Sans Grantha', 'notosansgrantha/v19/3y976akwcCjmsU8NDyrKo3IQfQ4o-r8ZFeulHc6N.woff2'), + NotoFont('Noto Sans Gujarati', 'notosansgujarati/v25/wlpWgx_HC1ti5ViekvcxnhMlCVo3f5pv17ivlzsUB14gg1TMR2Gw4VceEl7MA_ypFwPJ_OdiEH0s.woff2'), + NotoFont('Noto Sans Gunjala Gondi', 'notosansgunjalagondi/v19/bWtX7e7KfBziStx7lIzKPrcSMwcEnCv6DW7n5g0ef3PLtymzNxYL4YDE5Z4vCTxEJQ.woff2'), + NotoFont('Noto Sans Gurmukhi', 'notosansgurmukhi/v26/w8g9H3EvQP81sInb43inmyN9zZ7hb7ATbSWo4q8dJ74a3cVrYFQ_bogT0-gPeG1Oenb0Z_trdp7h.woff2'), + NotoFont('Noto Sans Hanunoo', 'notosanshanunoo/v21/f0Xs0fCv8dxkDWlZSoXOj6CphMloFsEpEpgL_ix2.woff2'), + NotoFont('Noto Sans Hatran', 'notosanshatran/v16/A2BBn4Ne0RgnVF3Lnko-0sOBIfL_mMo3r1nwzDs.woff2'), + NotoFont('Noto Sans Hebrew', 'notosanshebrew/v46/or3HQ7v33eiDljA1IufXTtVf7V6RvEEdhQlk0LlGxCyaeNKYZC0sqk3xXGiXd4qtpyJltutR2g.woff2'), + NotoFont('Noto Sans Imperial Aramaic', 'notosansimperialaramaic/v17/a8IMNpjwKmHXpgXbMIsbTc_kvks91LlLetBr5itQrtdjl3YfPNno.woff2'), + NotoFont('Noto Sans Indic Siyaq Numbers', 'notosansindicsiyaqnumbers/v16/6xK5dTJFKcWIu4bpRBjRZRpsIYHabOeZ8UZLubTzpXNHKx2TPOpVd5Iu.woff2'), + NotoFont('Noto Sans Inscriptional Pahlavi', 'notosansinscriptionalpahlavi/v17/ll8UK3GaVDuxR-TEqFPIbsR79Xxz9WEKbwsjpz7VklYlC7FCVt-VOAYK0QA.woff2'), + NotoFont('Noto Sans Inscriptional Parthian', 'notosansinscriptionalparthian/v17/k3k7o-IMPvpLmixcA63oYi-yStDkgXuXncL7dzfW3P4TAJ2yklBM2jNkLlLr.woff2'), + NotoFont('Noto Sans Javanese', 'notosansjavanese/v23/2V01KJkDAIA6Hp4zoSScDjV0Y-eoHAHT-Z3MngEefiidxJnkFFxiZYWj4O8.woff2'), + NotoFont('Noto Sans Kaithi', 'notosanskaithi/v22/buEtppS9f8_vkXadMBJJu0tWjLwjQigKdoZIKlo.woff2'), + NotoFont('Noto Sans Kannada', 'notosanskannada/v27/8vIs7xs32H97qzQKnzfeXycxXZyUmySvZWItmf1fe6TVmgop9ndpS-BqHEyGrDvNzScMLsPKrkY.woff2'), + NotoFont('Noto Sans Kayah Li', 'notosanskayahli/v21/B50nF61OpWTRcGrhOVJJwOMXdca6Yecki3E06x2jVTX3WCc3CZT4EXLuKVM.woff2'), + NotoFont('Noto Sans Kharoshthi', 'notosanskharoshthi/v16/Fh4qPiLjKS30-P4-pGMMXCCfvkc5Vd7KE5z9rFyx5mR1.woff2'), + NotoFont('Noto Sans Khmer', 'notosanskhmer/v24/ijw3s5roRME5LLRxjsRb-gssOenAyendxrgV2c-Zw-9vbVUti_Z_dWgtWYuNAJz9kAbrddiA.woff2'), + NotoFont('Noto Sans Khojki', 'notosanskhojki/v19/-nFnOHM29Oofr2wohFbTuPPKVWpmK_J709jy92k.woff2'), + NotoFont('Noto Sans Khudawadi', 'notosanskhudawadi/v22/fdNi9t6ZsWBZ2k5ltHN73zZ5hc8HANlHIjFnVVXz9MY.woff2'), + NotoFont('Noto Sans Lao', 'notosanslao/v30/bx6lNx2Ol_ixgdYWLm9BwxM3NW6BOkuf763Clj73CiQ_J1Djx9pidOt4ccbdepMK3riB2w.woff2'), + NotoFont('Noto Sans Lepcha', 'notosanslepcha/v19/0QI7MWlB_JWgA166SKhu05TekNS32AdstqBXgd4.woff2'), + NotoFont('Noto Sans Limbu', 'notosanslimbu/v24/3JnlSDv90Gmq2mrzckOBBRRoNJVj1cF3OHRDnA.woff2'), + NotoFont('Noto Sans Linear A', 'notosanslineara/v18/oPWS_l16kP4jCuhpgEGmwJOiA18FZj22y2HQAGQicw.woff2'), + NotoFont('Noto Sans Linear B', 'notosanslinearb/v17/HhyJU4wt9vSgfHoORYOiXOckKNB737IV2RkFTq4EPw.woff2'), + NotoFont('Noto Sans Lisu', 'notosanslisu/v25/uk-3EGO3o6EruUbnwovcYhz6kh57_nqbcTdjJnHP2Vwt3tIlxkVdig.woff2'), + NotoFont('Noto Sans Lycian', 'notosanslycian/v15/QldVNSNMqAsHtsJ7UmqxBQA9r8wA5_zaCJwn00E.woff2'), + NotoFont('Noto Sans Lydian', 'notosanslydian/v18/c4m71mVzGN7s8FmIukZJ1v4ZlcPReUbXMoIjEQI.woff2'), + NotoFont('Noto Sans Mahajani', 'notosansmahajani/v19/-F6sfiVqLzI2JPCgQBnw60Agp0JrvD5FgsARHNh4zg.woff2'), + NotoFont('Noto Sans Malayalam', 'notosansmalayalam/v26/sJoi3K5XjsSdcnzn071rL37lpAOsUThnDZIfPdbeSNzVakglNM-Qw8EaeB8Nss-_RuD9AVzEr6HxEA.woff2'), + NotoFont('Noto Sans Mandaic', 'notosansmandaic/v17/cIfnMbdWt1w_HgCcilqhKQBo_OsMI5_F_gMk0izH.woff2'), + NotoFont('Noto Sans Manichaean', 'notosansmanichaean/v18/taiVGntiC4--qtsfi4Jp9-_GkPZZCcrfekqHNTtFCtdX.woff2'), + NotoFont('Noto Sans Marchen', 'notosansmarchen/v20/aFTO7OZ_Y282EP-WyG6QTOX_C8WZMHhKk652ZaHk.woff2'), + NotoFont('Noto Sans Masaram Gondi', 'notosansmasaramgondi/v17/6xK_dThFKcWIu4bpRBjRYRV7KZCbUq6n_1kPnuGb7RI9WSWX.woff2'), + NotoFont('Noto Sans Math', 'notosansmath/v15/7Aump_cpkSecTWaHRlH2hyV5UHkD-V048PW0.woff2'), + NotoFont('Noto Sans Mayan Numerals', 'notosansmayannumerals/v16/PlIuFk25O6RzLfvNNVSivR09_KqYMwvvDKYjfIiE7soo6eepYQ.woff2'), + NotoFont('Noto Sans Medefaidrin', 'notosansmedefaidrin/v23/WwkzxOq6Dk-wranENynkfeVsNbRZtbOIdLb1exeM4ZeuabBfmErWlTj18e5A3rw.woff2'), + NotoFont('Noto Sans Meetei Mayek', 'notosansmeeteimayek/v15/HTxAL3QyKieByqY9eZPFweO0be7M21uSphSdhqILnmrRfJ8t_1TJ_vTT5PgeFYVa.woff2'), + NotoFont('Noto Sans Meroitic', 'notosansmeroitic/v18/IFS5HfRJndhE3P4b5jnZ3ITPvC6i00UDhThTiKY9KQ.woff2'), + NotoFont('Noto Sans Miao', 'notosansmiao/v17/Dxxz8jmXMW75w3OmoDXVV4zyZUjlUYVslLhx.woff2'), + NotoFont('Noto Sans Modi', 'notosansmodi/v23/pe03MIySN5pO62Z5YkFyT7jeav5vWVAgVol-.woff2'), + NotoFont('Noto Sans Mongolian', 'notosansmongolian/v22/VdGCAYADGIwE0EopZx8xQfHlgEAMsrToxL4g6-av1x0.woff2'), + NotoFont('Noto Sans Mro', 'notosansmro/v18/qWcsB6--pZv9TqnUQMhe9b39WDnRtjkho4M.woff2'), + NotoFont('Noto Sans Multani', 'notosansmultani/v20/9Bty3ClF38_RfOpe1gCaZ8p30BOFO1AxpfCs5Kos.woff2'), + NotoFont('Noto Sans Myanmar', 'notosansmyanmar/v20/AlZq_y1ZtY3ymOryg38hOCSdOnFq0Enz3OU4o1AC.woff2'), + NotoFont('Noto Sans NKo', 'notosansnko/v6/esDX31ZdNv-KYGGJpKGk2_RpMpWMHMLBrdA.woff2'), + NotoFont('Noto Sans Nabataean', 'notosansnabataean/v16/IFS4HfVJndhE3P4b5jnZ34DfsjO330dNoBd9hK8kMK4.woff2'), + NotoFont('Noto Sans New Tai Lue', 'notosansnewtailue/v22/H4cKBW-Pl9DZ0Xe_nHUapt7PovLXAhAnY7wqaLy-OJgU3p_pdeXAYUPghFPKzeY.woff2'), + NotoFont('Noto Sans Newa', 'notosansnewa/v16/7r3fqXp6utEsO9pI4f8ok8sWg8n6qN4R5lNU.woff2'), + NotoFont('Noto Sans Nushu', 'notosansnushu/v19/rnCw-xRQ3B7652emAbAe_Ai1IYaFXVAMArZKqQ.woff2'), + NotoFont('Noto Sans Ogham', 'notosansogham/v17/kmKlZqk1GBDGN0mY6k5lmEmww4hrsplaQxcoCA.woff2'), + NotoFont('Noto Sans Ol Chiki', 'notosansolchiki/v29/N0b92TJNOPt-eHmFZCdQbrL32r-4CvhzDzRwlxOQYuVALWk267c6gVrz5gQ.woff2'), + NotoFont('Noto Sans Old Hungarian', 'notosansoldhungarian/v18/E213_cD6hP3GwCJPEUssHEM0KqLaHJXg2PiIgRfmbg5nCYXt.woff2'), + NotoFont('Noto Sans Old Italic', 'notosansolditalic/v17/TuGOUUFzXI5FBtUq5a8bh68BJxxEVam7tWlUdRhtCC4d.woff2'), + NotoFont('Noto Sans Old North Arabian', 'notosansoldnortharabian/v16/esDF30BdNv-KYGGJpKGk2tNiMt7Jar6olZDyNdr81zBQnEo_xw4ABw.woff2'), + NotoFont('Noto Sans Old Permic', 'notosansoldpermic/v17/snf1s1q1-dF8pli1TesqcbUY4Mr-ElrwKLdSgv_dKYB5.woff2'), + NotoFont('Noto Sans Old Persian', 'notosansoldpersian/v16/wEOjEAbNnc5caQTFG18FHrZr9Bp6-8CmIJ_trelQfx9CjA.woff2'), + NotoFont('Noto Sans Old Sogdian', 'notosansoldsogdian/v17/3JnjSCH90Gmq2mrzckOBBhFhdrMst48aURt7mOIqM-9uyg.woff2'), + NotoFont('Noto Sans Old South Arabian', 'notosansoldsoutharabian/v16/3qT5oiOhnSyU8TNFIdhZTice3hB_HWKsEnF--0XCHiKx0etDT9HwTA.woff2'), + NotoFont('Noto Sans Old Turkic', 'notosansoldturkic/v18/yMJNMJVya43H0SUF_WmcGEQVqoEMKDKbsE2UjEw-Vyws.woff2'), + NotoFont('Noto Sans Oriya', 'notosansoriya/v31/AYCppXfzfccDCstK_hrjDyADv5e9748vhj3CJBLHIARtgD6TJQS0dJT5Ivj0f6_Z6LhHBRe-.woff2'), + NotoFont('Noto Sans Osage', 'notosansosage/v18/oPWX_kB6kP4jCuhpgEGmw4mtAVtXQ1aSxkrMCQ.woff2'), + NotoFont('Noto Sans Osmanya', 'notosansosmanya/v18/8vIS7xs32H97qzQKnzfeWzUyUpOJmz6hR47NCV5Z.woff2'), + NotoFont('Noto Sans Pahawh Hmong', 'notosanspahawhhmong/v18/bWtp7e_KfBziStx7lIzKKaMUOBEA3UPQDW7krzI_c48aMpM.woff2'), + NotoFont('Noto Sans Palmyrene', 'notosanspalmyrene/v16/ZgNPjOdKPa7CHqq0h37c_ASCWvH93SFCPne5ZpdNtcA.woff2'), + NotoFont('Noto Sans Pau Cin Hau', 'notosanspaucinhau/v20/x3d-cl3IZKmUqiMg_9wBLLtzl22EayN7ehIdiUWqKMxsKw.woff2'), + NotoFont('Noto Sans Phags Pa', 'notosansphagspa/v15/pxiZyoo6v8ZYyWh5WuPeJzMkd4SrGChkr0SsrvNXiA.woff2'), + NotoFont('Noto Sans Phoenician', 'notosansphoenician/v17/jizFRF9Ksm4Bt9PvcTaEkIHiTVtxmFtS5X7Mot-p5561.woff2'), + NotoFont('Noto Sans Psalter Pahlavi', 'notosanspsalterpahlavi/v17/rP2Vp3K65FkAtHfwd-eISGznYihzggmsicPfud3w1GjKsUQBct4.woff2'), + NotoFont('Noto Sans Rejang', 'notosansrejang/v21/Ktk2AKuMeZjqPnXgyqrib7DIogqwN4a3WYZB_sU.woff2'), + NotoFont('Noto Sans Runic', 'notosansrunic/v17/H4c_BXWPl9DZ0Xe_nHUaus7W68WWbhpvHtgIYg.woff2'), + NotoFont('Noto Sans Saurashtra', 'notosanssaurashtra/v23/ea8GacQ0Wfz_XKWXe6OtoA8w8zvmYwTef9nYjhPTSIx9.woff2'), + NotoFont('Noto Sans Sharada', 'notosanssharada/v16/gok0H7rwAEdtF9N8-mdTGALG6p0kwoXOPOwr4H8a.woff2'), + NotoFont('Noto Sans Shavian', 'notosansshavian/v17/CHy5V_HZE0jxJBQlqAeCKjJvQBNF4EFVSplv2Cwg.woff2'), + NotoFont('Noto Sans Siddham', 'notosanssiddham/v20/OZpZg-FwqiNLe9PELUikxTWDoCCeGqnYk3Ic92ZH.woff2'), + NotoFont('Noto Sans Sinhala', 'notosanssinhala/v32/yMJ2MJBya43H0SUF_WmcBEEf4rQVO2P524V5N_MxQzQtb-tf5dJbC30Fu9zUwg2a5l0LpJwbQRM.woff2'), + NotoFont('Noto Sans Sogdian', 'notosanssogdian/v16/taiQGn5iC4--qtsfi4Jp6eHPnfxQBo-7Pm6KHidM.woff2'), + NotoFont('Noto Sans Sora Sompeng', 'notosanssorasompeng/v24/PlIRFkO5O6RzLfvNNVSioxM2_OTrEhPyDLolKvCsHzCxWuGkYHR818DsZXJQd4Mu.woff2'), + NotoFont('Noto Sans Soyombo', 'notosanssoyombo/v17/RWmSoL-Y6-8q5LTtXs6MF6q7xsxgY0FuIFOcK25W.woff2'), + NotoFont('Noto Sans Sundanese', 'notosanssundanese/v26/FwZw7_84xUkosG2xJo2gm7nFwSLQkdymq2mkz3Gz1_b6ctxpNNHHizv7fQES.woff2'), + NotoFont('Noto Sans Syloti Nagri', 'notosanssylotinagri/v23/uU9eCAQZ75uhfF9UoWDRiY3q7Sf_VFV3m4dGFVLxN87gsj0.woff2'), + NotoFont('Noto Sans Symbols', 'notosanssymbols/v43/rP2up3q65FkAtHfwd-eIS2brbDN6gxP34F9jRRCe4W3gfQ8gb_VFRkzrbQ.woff2'), + NotoFont('Noto Sans Syriac', 'notosanssyriac/v16/Ktk7AKuMeZjqPnXgyqribqzQqgW0LYiVqV7dXcP0C-VD9MaMyZfUL_FC.woff2'), + NotoFont('Noto Sans Tagalog', 'notosanstagalog/v22/J7aFnoNzCnFcV9ZI-sUYuvote1R0wwEFA8jHexnL.woff2'), + NotoFont('Noto Sans Tagbanwa', 'notosanstagbanwa/v18/Y4GWYbB8VTEp4t3MKJSMmQdIKjRtt_nZQzQEaYpGoQ.woff2'), + NotoFont('Noto Sans Tai Le', 'notosanstaile/v17/vEFK2-VODB8RrNDvZSUmVxEATwR58te1W77HtMo.woff2'), + NotoFont('Noto Sans Tai Tham', 'notosanstaitham/v20/kJEbBv0U4hgtwxDUw2x9q7tbjLIfbPGHBoaVSAZ3MdLJBCUbPg-uyaRGKMw.woff2'), + NotoFont('Noto Sans Tai Viet', 'notosanstaiviet/v19/8QIUdj3HhN_lv4jf9vsE-9GMOLsaSPZr7o4fWsRO9w.woff2'), + NotoFont('Noto Sans Takri', 'notosanstakri/v24/TuGJUVpzXI5FBtUq5a8bnKIOdTwQMe_W3khJXg.woff2'), + NotoFont('Noto Sans Tamil', 'notosanstamil/v27/ieVc2YdFI3GCY6SyQy1KfStzYKZgzN1z4LKDbeZce-0429tBManUktuex7vGo70UqKDt_EvT.woff2'), + NotoFont('Noto Sans Tamil Supplement', 'notosanstamilsupplement/v21/DdTz78kEtnooLS5rXF1DaruiCd_bFp_Ph4sGcn7ax_vpAeMkeq1x.woff2'), + NotoFont('Noto Sans Telugu', 'notosanstelugu/v26/0FlxVOGZlE2Rrtr-HmgkMWJNjJ5_RyT8o8c7fHkeg-esVC5dzHkHIJQqrEntezbqREbf-3v37w.woff2'), + NotoFont('Noto Sans Thaana', 'notosansthaana/v24/C8c14dM-vnz-s-3jaEsxlxHkBH-WZOETXfoQrfQ9Y4XrbhLknu4-tbNu.woff2'), + NotoFont('Noto Sans Thai', 'notosansthai/v25/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU5RtpzR-QRvzzXg.woff2'), + NotoFont('Noto Sans Tifinagh', 'notosanstifinagh/v20/I_uzMoCduATTei9eI8dawkHIwvmhCvbn77nEcXfs4Q.woff2'), + NotoFont('Noto Sans Tirhuta', 'notosanstirhuta/v16/t5t6IQYRNJ6TWjahPR6X-M-apUyby7uDUBsTrn5P.woff2'), + NotoFont('Noto Sans Ugaritic', 'notosansugaritic/v16/3qTwoiqhnSyU8TNFIdhZVCwbjCpkAXXkNxoIkiazfg.woff2'), + NotoFont('Noto Sans Vai', 'notosansvai/v17/NaPecZTSBuhTirw6IaFn_UrURMHsDIRSfr0.woff2'), + NotoFont('Noto Sans Wancho', 'notosanswancho/v17/zrf-0GXXyfn6Fs0lH9P4cUubP0GBqAbopiRfKp8.woff2'), + NotoFont('Noto Sans Warang Citi', 'notosanswarangciti/v17/EYqtmb9SzL1YtsZSScyKDXIeOv3w-zgsNvKRoOVCCXzdgA.woff2'), + NotoFont('Noto Sans Yi', 'notosansyi/v19/sJoD3LFXjsSdcnzn071rO3apwFDJNVgSNg.woff2'), + NotoFont('Noto Sans Zanabazar Square', 'notosanszanabazarsquare/v19/Cn-jJsuGWQxOjaGwMQ6fOicyxLBEMRfDtkzl4uagQtJ0OCEgN0Gc.woff2'), + NotoFont('Noto Serif Tibetan', 'notoseriftibetan/v22/gokGH7nwAEdtF9N45n0Vaz7O-pk0wsvxHeDXMfqguoCmIrYcPSvrdSy_32c.woff2'), ]; -// 398 unique sets of fonts containing 4074 font references encoded in 4909 characters +// 6892 unique sets of fonts containing 44697 font references encoded in 80682 characters const String encodedFontSets = - // #0: 5 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, TC₁₂₂. - '1nhb2gn,' - // #1: 3 fonts: HK₃₉, SC₁₀₈, TC₁₂₂. - '1n2qn,' - // #2: 4 fonts: HK₃₉, JP₄₇, SC₁₀₈, TC₁₂₂. - '1nh2in,' - // #3: 1 font: SC₁₀₈. - '4e,' - // #4: 0 fonts. + // #0: 0 fonts. ',' - // #5: 2 fonts: JP₄₇, SC₁₀₈. - '1v2i,' - // #6: 2 fonts: HK₃₉, TC₁₂₂. - '1n3e,' - // #7: 1 font: JP₄₇. - '1v,' - // #8: 4 fonts: HK₃₉, KR₄₉, SC₁₀₈, TC₁₂₂. - '1nj2gn,' - // #9: 3 fonts: JP₄₇, KR₄₉, SC₁₀₈. - '1vb2g,' - // #10: 2 fonts: KR₄₉, SC₁₀₈. - '1x2g,' - // #11: 1 font: Noto Sans₁. - 'b,' - // #12: 1 font: Symbols 2₁₂₀. - '4q,' - // #13: 1 font: Math₇₁. - '2t,' - // #14: 2 fonts: JP₄₇, KR₄₉. - '1vb,' - // #15: 1 font: KR₄₉. - '1x,' - // #16: 6 fonts: Noto Sans₁, HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, TC₁₂₂. - 'b1lhb2gn,' - // #17: 2 fonts: Symbols 2₁₂₀, Noto Color Emoji 3₁₄₆. - '4qz,' - // #18: 1 font: Symbols₁₁₉. - '4p,' - // #19: 6 fonts: HK₃₉, JP₄₇, KR₄₉, Math₇₁, SC₁₀₈, TC₁₂₂. - '1nhbv1kn,' - // #20: 1 font: Noto Color Emoji 3₁₄₆. - '5q,' - // #21: 1 font: Ethiopic₃₁. + // #1: 1 font: JP 0₁₃₆. + '5g,' + // #2: 1 font: JP 1₁₃₇. + '5h,' + // #3: 1 font: HK 7₃₄. + '1i,' + // #4: 1 font: Noto Sans₅₉₁. + '22t,' + // #5: 1 font: Symbols 2 3₁₅. + 'p,' + // #6: 1 font: HK 74₁₀₁. + '3x,' + // #7: 1 font: HK 0₂₇. + '1b,' + // #8: 1 font: HK 76₁₀₃. + '3z,' + // #9: 1 font: HK 2₂₉. + '1d,' + // #10: 1 font: HK 3₃₀. + '1e,' + // #11: 1 font: HK 75₁₀₂. + '3y,' + // #12: 1 font: Math₆₅₅. + '25f,' + // #13: 1 font: HK 1₂₈. + '1c,' + // #14: 1 font: HK 9₃₆. + '1k,' + // #15: 1 font: HK 4₃₁. '1f,' - // #22: 1 font: Noto Color Emoji 9₁₅₂. - '5w,' - // #23: 1 font: Noto Color Emoji 2₁₄₅. - '5p,' - // #24: 138 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #25: 6 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols 2₁₂₀, TC₁₂₂. - '1nhb2glb,' - // #26: 2 fonts: Symbols 2₁₂₀, Noto Color Emoji 2₁₄₅. - '4qy,' - // #27: 3 fonts: HK₃₉, JP₄₇, TC₁₂₂. - '1nh2w,' - // #28: 1 font: Noto Color Emoji 8₁₅₁. - '5v,' - // #29: 1 font: Noto Color Emoji 5₁₄₈. - '5s,' - // #30: 133 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, Javanese₄₈, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabbaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaabaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaa,' - // #31: 1 font: Arabic₄. - 'e,' - // #32: 1 font: Noto Color Emoji 7₁₅₀. - '5u,' - // #33: 1 font: Noto Color Emoji 4₁₄₇. - '5r,' - // #34: 2 fonts: Symbols 2₁₂₀, Noto Color Emoji 7₁₅₀. - '4q1d,' - // #35: 2 fonts: Symbols 2₁₂₀, Noto Color Emoji 4₁₄₇. - '4q1a,' - // #36: 6 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols₁₁₉, TC₁₂₂. - '1nhb2gkc,' - // #37: 2 fonts: Noto Sans₁, Math₇₁. - 'b2r,' - // #38: 2 fonts: Symbols 2₁₂₀, Noto Color Emoji 5₁₄₈. - '4q1b,' - // #39: 1 font: Tamil₁₂₉. - '4z,' - // #40: 1 font: Bengali₁₁. - 'l,' - // #41: 1 font: Grantha₃₅. + // #16: 1 font: HK 6₃₃. + '1h,' + // #17: 1 font: HK 5₃₂. + '1g,' + // #18: 1 font: HK 8₃₅. '1j,' - // #42: 1 font: Gurmukhi₃₈. - '1m,' - // #43: 139 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Math₇₁, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #44: 1 font: Gujarati₃₆. - '1k,' - // #45: 1 font: Oriya₉₇. - '3t,' - // #46: 1 font: Kannada₅₁. - '1z,' - // #47: 1 font: Sinhala₁₁₃. - '4j,' - // #48: 1 font: Telugu₁₃₁. - '5b,' - // #49: 1 font: Noto Color Emoji 1₁₄₄. + // #19: 1 font: JP 51₁₈₇. + '7f,' + // #20: 1 font: KR 112₃₇₂. + '14i,' + // #21: 1 font: KR 114₃₇₄. + '14k,' + // #22: 1 font: KR 115₃₇₅. + '14l,' + // #23: 1 font: KR 118₃₇₈. + '14o,' + // #24: 1 font: KR 113₃₇₃. + '14j,' + // #25: 1 font: KR 117₃₇₇. + '14n,' + // #26: 1 font: KR 116₃₇₆. + '14m,' + // #27: 1 font: JP 50₁₈₆. + '7e,' + // #28: 1 font: KR 110₃₇₀. + '14g,' + // #29: 1 font: KR 111₃₇₁. + '14h,' + // #30: 1 font: KR 107₃₆₇. + '14d,' + // #31: 1 font: KR 108₃₆₈. + '14e,' + // #32: 1 font: KR 109₃₆₉. + '14f,' + // #33: 1 font: JP 7₁₄₃. + '5n,' + // #34: 1 font: JP 6₁₄₂. + '5m,' + // #35: 1 font: KR 106₃₆₆. + '14c,' + // #36: 1 font: JP 15₁₅₁. + '5v,' + // #37: 1 font: JP 17₁₅₃. + '5x,' + // #38: 1 font: JP 8₁₄₄. '5o,' - // #50: 1 font: Noto Color Emoji 6₁₄₉. - '5t,' - // #51: 2 fonts: Noto Sans₁, Coptic₂₂. - 'bu,' - // #52: 1 font: Devanagari₂₆. - '1a,' - // #53: 1 font: Lao₅₇. - '2f,' - // #54: 1 font: Georgian₃₂. - '1g,' - // #55: 6 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, TC₁₂₂, Noto Color Emoji 2₁₄₅. - '1nhb2gnw,' - // #56: 2 fonts: Symbols 2₁₂₀, Noto Color Emoji 9₁₅₂. - '4q1f,' - // #57: 7 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols 2₁₂₀, TC₁₂₂, Noto Color Emoji 2₁₄₅. - '1nhb2glbw,' - // #58: 4 fonts: HK₃₉, JP₄₇, KR₄₉, TC₁₂₂. - '1nhb2u,' - // #59: 1 font: Hebrew₄₂. - '1q,' - // #60: 2 fonts: Symbols₁₁₉, Noto Color Emoji 2₁₄₅. - '4pz,' - // #61: 4 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈. - '1nhb2g,' - // #62: 1 font: Kharoshthi₅₃. - '2b,' - // #63: 1 font: Linear B₆₁. - '2j,' - // #64: 7 fonts: Noto Sans₁, HK₃₉, JP₄₇, KR₄₉, Math₇₁, SC₁₀₈, TC₁₂₂. - 'b1lhbv1kn,' - // #65: 2 fonts: Bengali₁₁, Devanagari₂₆. - 'lo,' - // #66: 1 font: Glagolitic₃₃. - '1h,' - // #67: 7 fonts: HK₃₉, JP₄₇, KR₄₉, Math₇₁, SC₁₀₈, Symbols 2₁₂₀, TC₁₂₂. - '1nhbv1klb,' - // #68: 3 fonts: HK₃₉, KR₄₉, TC₁₂₂. - '1nj2u,' - // #69: 1 font: Malayalam₆₆. - '2o,' - // #70: 1 font: Masaram Gondi₇₀. - '2s,' - // #71: 1 font: Noto Color Emoji 11₁₅₄. + // #39: 1 font: JP 14₁₅₀. + '5u,' + // #40: 1 font: JP 18₁₅₄. '5y,' - // #72: 1 font: Cypriot₂₄. - 'y,' - // #73: 2 fonts: Grantha₃₅, Tamil₁₂₉. - '1j3p,' - // #74: 1 font: Gunjala Gondi₃₇. + // #41: 1 font: KR 105₃₆₅. + '14b,' + // #42: 1 font: JP 34₁₇₀. + '6o,' + // #43: 1 font: SC 87₄₇₁. + '18d,' + // #44: 1 font: JP 42₁₇₈. + '6w,' + // #45: 1 font: JP 16₁₅₂. + '5w,' + // #46: 1 font: JP 9₁₄₅. + '5p,' + // #47: 1 font: JP 22₁₅₈. + '6c,' + // #48: 1 font: KR 104₃₆₄. + '14a,' + // #49: 1 font: JP 41₁₇₇. + '6v,' + // #50: 1 font: HK 73₁₀₀. + '3w,' + // #51: 1 font: SC 93₄₇₇. + '18j,' + // #52: 1 font: HK 10₃₇. '1l,' - // #75: 7 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols₁₁₉, TC₁₂₂, Noto Color Emoji 2₁₄₅. - '1nhb2gkcw,' - // #76: 1 font: Mongolian₇₈. - '3a,' - // #77: 2 fonts: Symbols₁₁₉, Noto Color Emoji 5₁₄₈. - '4p1c,' - // #78: 2 fonts: Symbols 2₁₂₀, Noto Color Emoji 8₁₅₁. - '4q1e,' - // #79: 1 font: Noto Serif Tibetan₁₄₂. - '5m,' - // #80: 2 fonts: Noto Color Emoji 2₁₄₅, Noto Color Emoji 9₁₅₂. - '5pg,' - // #81: 2 fonts: Noto Color Emoji 7₁₅₀, Noto Color Emoji 9₁₅₂. - '5ub,' - // #82: 2 fonts: Noto Sans₁, Devanagari₂₆. - 'by,' - // #83: 2 fonts: Noto Sans₁, Duployan₂₇. - 'bz,' - // #84: 1 font: Armenian₅. + // #53: 1 font: KR 102₃₆₂. + '13y,' + // #54: 1 font: SC 91₄₇₅. + '18h,' + // #55: 1 font: SC 92₄₇₆. + '18i,' + // #56: 2 fonts: Noto Color Emoji 3₃, Symbols 2 3₁₅. + 'dl,' + // #57: 1 font: JP 5₁₄₁. + '5l,' + // #58: 1 font: JP 13₁₄₉. + '5t,' + // #59: 1 font: JP 19₁₅₅. + '5z,' + // #60: 1 font: KR 103₃₆₃. + '13z,' + // #61: 1 font: JP 21₁₅₇. + '6b,' + // #62: 1 font: SC 81₄₆₅. + '17x,' + // #63: 1 font: SC 85₄₆₉. + '18b,' + // #64: 1 font: Symbols₇₀₂. + '27a,' + // #65: 1 font: JP 10₁₄₆. + '5q,' + // #66: 1 font: JP 25₁₆₁. + '6f,' + // #67: 1 font: JP 44₁₈₀. + '6y,' + // #68: 1 font: JP 48₁₈₄. + '7c,' + // #69: 1 font: SC 83₄₆₇. + '17z,' + // #70: 1 font: Noto Color Emoji 3₃. + 'd,' + // #71: 1 font: JP 12₁₄₈. + '5s,' + // #72: 1 font: JP 35₁₇₁. + '6p,' + // #73: 1 font: KR 28₂₈₈. + '11c,' + // #74: 1 font: SC 77₄₆₁. + '17t,' + // #75: 1 font: JP 11₁₄₇. + '5r,' + // #76: 1 font: SC 84₄₆₈. + '18a,' + // #77: 1 font: JP 30₁₆₆. + '6k,' + // #78: 1 font: JP 20₁₅₆. + '6a,' + // #79: 1 font: JP 24₁₆₀. + '6e,' + // #80: 1 font: JP 40₁₇₆. + '6u,' + // #81: 1 font: SC 17₄₀₁. + '15l,' + // #82: 1 font: JP 39₁₇₅. + '6t,' + // #83: 1 font: JP 49₁₈₅. + '7d,' + // #84: 1 font: SC 79₄₆₃. + '17v,' + // #85: 1 font: SC 82₄₆₆. + '17y,' + // #86: 1 font: SC 90₄₇₄. + '18g,' + // #87: 1 font: JP 27₁₆₃. + '6h,' + // #88: 1 font: JP 37₁₇₃. + '6r,' + // #89: 1 font: JP 47₁₈₃. + '7b,' + // #90: 1 font: KR 100₃₆₀. + '13w,' + // #91: 1 font: SC 88₄₇₂. + '18e,' + // #92: 1 font: Noto Color Emoji 9₉. + 'j,' + // #93: 1 font: JP 28₁₆₄. + '6i,' + // #94: 1 font: SC 94₄₇₈. + '18k,' + // #95: 1 font: Ethiopic₆₁₈. + '23u,' + // #96: 1 font: Noto Color Emoji 2₂. + 'c,' + // #97: 1 font: JP 26₁₆₂. + '6g,' + // #98: 1 font: JP 36₁₇₂. + '6q,' + // #99: 1 font: KR 101₃₆₁. + '13x,' + // #100: 1 font: SC 78₄₆₂. + '17u,' + // #101: 1 font: SC 80₄₆₄. + '17w,' + // #102: 1 font: SC 89₄₇₃. + '18f,' + // #103: 4 fonts: HK 33₆₀, JP 20₁₅₆, SC 22₄₀₆, TC 25₅₁₀. + '2i3r9p3z,' + // #104: 1 font: JP 38₁₇₄. + '6s,' + // #105: 1 font: JP 45₁₈₁. + '6z,' + // #106: 1 font: SC 86₄₇₀. + '18c,' + // #107: 4 fonts: HK 64₉₁, JP 43₁₇₉, SC 56₄₄₀, TC 59₅₄₄. + '3n3j10a3z,' + // #108: 1 font: JP 46₁₈₂. + '7a,' + // #109: 1 font: KR 27₂₈₇. + '11b,' + // #110: 3 fonts: HK 29₅₆, JP 16₁₅₂, TC 20₅₀₅. + '2e3r13o,' + // #111: 1 font: JP 23₁₅₉. + '6d,' + // #112: 1 font: JP 55₁₉₁. + '7j,' + // #113: 1 font: KR 32₂₉₂. + '11g,' + // #114: 3 fonts: HK 18₄₅, JP 8₁₄₄, TC 8₄₉₃. + '1t3u13k,' + // #115: 1 font: KR 25₂₈₅. + '10z,' + // #116: 2 fonts: Noto Color Emoji 2₂, Symbols 2 3₁₅. + 'cm,' + // #117: 3 fonts: HK 16₄₃, JP 6₁₄₂, TC 6₄₉₁. + '1r3u13k,' + // #118: 4 fonts: HK 35₆₂, JP 21₁₅₇, SC 24₄₀₈, TC 27₅₁₂. + '2k3q9q3z,' + // #119: 4 fonts: HK 61₈₈, JP 41₁₇₇, SC 53₄₃₇, TC 56₅₄₁. + '3k3k9z3z,' + // #120: 1 font: KR 26₂₈₆. + '11a,' + // #121: 1 font: SC 16₄₀₀. + '15k,' + // #122: 1 font: Noto Color Emoji 8₈. + 'i,' + // #123: 1 font: KR 4₂₆₄. + '10e,' + // #124: 1 font: KR 8₂₆₈. + '10i,' + // #125: 1 font: KR 55₃₁₅. + '12d,' + // #126: 1 font: Arabic₅₉₄. + '22w,' + // #127: 1 font: Noto Color Emoji 5₅. 'f,' - // #85: 1 font: Duployan₂₇. - '1b,' - // #86: 1 font: Limbu₅₉. - '2h,' - // #87: 2 fonts: Math₇₁, Symbols₁₁₉. - '2t1v,' - // #88: 2 fonts: Math₇₁, Symbols 2₁₂₀. - '2t1w,' - // #89: 1 font: Multani₈₀. + // #128: 4 fonts: HK 25₅₂, JP 13₁₄₉, SC 14₃₉₈, TC 16₅₀₁. + '2a3s9o3y,' + // #129: 4 fonts: HK 36₆₃, JP 22₁₅₈, SC 25₄₀₉, TC 28₅₁₃. + '2l3q9q3z,' + // #130: 4 fonts: HK 44₇₁, JP 28₁₆₄, SC 35₄₁₉, TC 37₅₂₂. + '2t3o9u3y,' + // #131: 1 font: SC 23₄₀₇. + '15r,' + // #132: 1 font: SC 27₄₁₁. + '15v,' + // #133: 1 font: SC 42₄₂₆. + '16k,' + // #134: 1 font: SC 54₄₃₈. + '16w,' + // #135: 1 font: Noto Color Emoji 7₇. + 'h,' + // #136: 4 fonts: HK 39₆₆, JP 24₁₆₀, SC 29₄₁₃, TC 31₅₁₆. + '2o3p9s3y,' + // #137: 4 fonts: HK 40₆₇, JP 25₁₆₁, SC 30₄₁₄, TC 32₅₁₇. + '2p3p9s3y,' + // #138: 4 fonts: HK 50₇₇, JP 32₁₆₈, SC 41₄₂₅, TC 43₅₂₈. + '2z3m9w3y,' + // #139: 4 fonts: HK 60₈₇, JP 40₁₇₆, SC 52₄₃₆, TC 55₅₄₀. + '3j3k9z3z,' + // #140: 1 font: JP 31₁₆₇. + '6l,' + // #141: 1 font: JP 33₁₆₉. + '6n,' + // #142: 1 font: KR 42₃₀₂. + '11q,' + // #143: 1 font: KR 45₃₀₅. + '11t,' + // #144: 1 font: KR 62₃₂₂. + '12k,' + // #145: 1 font: Noto Color Emoji 4₄. + 'e,' + // #146: 3 fonts: HK 26₅₃, JP 14₁₅₀, TC 17₅₀₂. + '2b3s13n,' + // #147: 4 fonts: HK 41₆₈, JP 26₁₆₂, SC 32₄₁₆, TC 34₅₁₉. + '2q3p9t3y,' + // #148: 1 font: JP 43₁₇₉. + '6x,' + // #149: 1 font: KR 58₃₁₈. + '12g,' + // #150: 1 font: KR 64₃₂₄. + '12m,' + // #151: 1 font: SC 10₃₉₄. + '15e,' + // #152: 1 font: SC 11₃₉₅. + '15f,' + // #153: 1 font: SC 26₄₁₀. + '15u,' + // #154: 1 font: SC 29₄₁₃. + '15x,' + // #155: 2 fonts: Noto Color Emoji 7₇, Symbols 2 3₁₅. + 'hh,' + // #156: 3 fonts: HK 17₄₄, JP 7₁₄₃, TC 7₄₉₂. + '1s3u13k,' + // #157: 4 fonts: HK 32₅₉, JP 19₁₅₅, SC 21₄₀₅, TC 24₅₀₉. + '2h3r9p3z,' + // #158: 1 font: KR 34₂₉₄. + '11i,' + // #159: 1 font: KR 52₃₁₂. + '12a,' + // #160: 1 font: KR 63₃₂₃. + '12l,' + // #161: 1 font: SC 5₃₈₉. + '14z,' + // #162: 1 font: SC 25₄₀₉. + '15t,' + // #163: 1 font: SC 32₄₁₆. + '16a,' + // #164: 1 font: SC 34₄₁₈. + '16c,' + // #165: 2 fonts: Noto Color Emoji 4₄, Symbols 2 3₁₅. + 'ek,' + // #166: 4 fonts: HK 17₄₄, JP 7₁₄₃, SC 6₃₉₀, TC 7₄₉₂. + '1s3u9m3x,' + // #167: 4 fonts: HK 30₅₇, JP 17₁₅₃, SC 19₄₀₃, TC 21₅₀₆. + '2f3r9p3y,' + // #168: 3 fonts: HK 32₅₉, JP 19₁₅₅, TC 24₅₀₉. + '2h3r13p,' + // #169: 1 font: HK 47₇₄. + '2w,' + // #170: 2 fonts: JP 55₁₉₁, KR 96₃₅₆. + '7j6i,' + // #171: 1 font: KR 12₂₇₂. + '10m,' + // #172: 1 font: KR 35₂₉₅. + '11j,' + // #173: 1 font: KR 44₃₀₄. + '11s,' + // #174: 1 font: SC 4₃₈₈. + '14y,' + // #175: 1 font: SC 39₄₂₃. + '16h,' + // #176: 1 font: SC 52₄₃₆. + '16u,' + // #177: 3 fonts: HK 19₄₆, JP 9₁₄₅, TC 9₄₉₄. + '1u3u13k,' + // #178: 4 fonts: HK 42₆₉, JP 27₁₆₃, SC 33₄₁₇, TC 35₅₂₀. + '2r3p9t3y,' + // #179: 4 fonts: HK 43₇₀, JP 28₁₆₄, SC 34₄₁₈, TC 36₅₂₁. + '2s3p9t3y,' + // #180: 4 fonts: HK 51₇₈, JP 33₁₆₉, SC 42₄₂₆, TC 44₅₂₉. + '3a3m9w3y,' + // #181: 4 fonts: HK 56₈₃, JP 37₁₇₃, SC 48₄₃₂, TC 50₅₃₅. + '3f3l9y3y,' + // #182: 1 font: HK 77₁₀₄. + '4a,' + // #183: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 95₃₅₅, SC 96₄₈₀, TC 95₅₈₀. + '4w4y3v4u3v,' + // #184: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 104₃₆₄, SC 96₄₈₀, TC 95₅₈₀. + '4w4y4e4l3v,' + // #185: 1 font: JP 32₁₆₈. + '6m,' + // #186: 1 font: KR 5₂₆₅. + '10f,' + // #187: 1 font: KR 54₃₁₄. + '12c,' + // #188: 1 font: SC 37₄₂₁. + '16f,' + // #189: 1 font: SC 46₄₃₀. + '16o,' + // #190: 1 font: SC 59₄₄₃. + '17b,' + // #191: 1 font: SC 67₄₅₁. + '17j,' + // #192: 1 font: SC 95₄₇₉. + '18l,' + // #193: 3 fonts: HK 24₅₁, JP 12₁₄₈, TC 15₅₀₀. + '1z3s13n,' + // #194: 4 fonts: HK 45₇₂, JP 29₁₆₅, SC 36₄₂₀, TC 38₅₂₃. + '2u3o9u3y,' + // #195: 4 fonts: HK 47₇₄, JP 30₁₆₆, SC 38₄₂₂, TC 40₅₂₅. + '2w3n9v3y,' + // #196: 4 fonts: HK 48₇₅, JP 31₁₆₇, SC 39₄₂₃, TC 41₅₂₆. + '2x3n9v3y,' + // #197: 4 fonts: HK 55₈₂, JP 36₁₇₂, SC 47₄₃₁, TC 49₅₃₄. + '3e3l9y3y,' + // #198: 2 fonts: JP 4₁₄₀, KR 1₂₆₁. + '5k4q,' + // #199: 2 fonts: JP 48₁₈₄, SC 64₄₄₈. + '7c10d,' + // #200: 1 font: KR 15₂₇₅. + '10p,' + // #201: 1 font: KR 48₃₀₈. + '11w,' + // #202: 1 font: KR 59₃₁₉. + '12h,' + // #203: 1 font: SC 7₃₉₁. + '15b,' + // #204: 1 font: SC 30₄₁₄. + '15y,' + // #205: 1 font: SC 55₄₃₉. + '16x,' + // #206: 1 font: SC 57₄₄₁. + '16z,' + // #207: 1 font: SC 62₄₄₆. + '17e,' + // #208: 1 font: SC 66₄₅₀. + '17i,' + // #209: 2 fonts: Noto Sans₅₉₁, Math₆₅₅. + '22t2l,' + // #210: 134 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc21saaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #211: 5 fonts: HK 48₇₅, JP 31₁₆₇, KR 79₃₃₉, SC 39₄₂₃, TC 41₅₂₆. + '2x3n6p3f3y,' + // #212: 4 fonts: HK 52₇₉, JP 34₁₇₀, SC 44₄₂₈, TC 46₅₃₁. + '3b3m9x3y,' + // #213: 4 fonts: HK 53₈₀, JP 35₁₇₁, SC 45₄₂₉, TC 47₅₃₂. + '3c3m9x3y,' + // #214: 4 fonts: HK 63₉₀, JP 42₁₇₈, SC 55₄₃₉, TC 58₅₄₃. + '3m3j10a3z,' + // #215: 4 fonts: HK 68₉₅, JP 46₁₈₂, SC 61₄₄₅, TC 63₅₄₈. + '3r3i10c3y,' + // #216: 6 fonts: HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e3h6h3t3z3q,' + // #217: 1 font: KR 14₂₇₄. + '10o,' + // #218: 1 font: KR 41₃₀₁. + '11p,' + // #219: 1 font: KR 57₃₁₇. + '12f,' + // #220: 1 font: SC 6₃₉₀. + '15a,' + // #221: 1 font: SC 8₃₉₂. + '15c,' + // #222: 1 font: SC 9₃₉₃. + '15d,' + // #223: 1 font: SC 15₃₉₉. + '15j,' + // #224: 1 font: SC 33₄₁₇. + '16b,' + // #225: 1 font: SC 43₄₂₇. + '16l,' + // #226: 1 font: SC 48₄₃₂. + '16q,' + // #227: 1 font: SC 56₄₄₀. + '16y,' + // #228: 5 fonts: HK 29₅₆, JP 16₁₅₂, KR 70₃₃₀, SC 18₄₀₂, TC 20₅₀₅. + '2e3r6v2t3y,' + // #229: 4 fonts: HK 31₅₈, JP 18₁₅₄, SC 20₄₀₄, TC 23₅₀₈. + '2g3r9p3z,' + // #230: 4 fonts: HK 34₆₁, JP 20₁₅₆, SC 23₄₀₇, TC 26₅₁₁. + '2j3q9q3z,' + // #231: 4 fonts: HK 58₈₅, JP 38₁₇₄, SC 50₄₃₄, TC 52₅₃₇. + '3h3k9z3y,' + // #232: 4 fonts: HK 65₉₂, JP 43₁₇₉, SC 57₄₄₁, TC 60₅₄₅. + '3o3i10b3z,' + // #233: 1 font: KR 22₂₈₂. + '10w,' + // #234: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 84₁₁₁, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3gx2h2l3vx2yx3cxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #235: 3 fonts: HK 23₅₀, JP 11₁₄₇, TC 14₄₉₉. + '1y3s13n,' + // #236: 4 fonts: HK 31₅₈, JP 18₁₅₄, SC 20₄₀₄, TC 22₅₀₇. + '2g3r9p3y,' + // #237: 3 fonts: HK 38₆₅, JP 23₁₅₉, TC 30₅₁₅. + '2n3p13r,' + // #238: 4 fonts: HK 41₆₈, JP 26₁₆₂, SC 31₄₁₅, TC 33₅₁₈. + '2q3p9s3y,' + // #239: 5 fonts: HK 44₇₁, JP 28₁₆₄, KR 77₃₃₇, SC 35₄₁₉, TC 37₅₂₂. + '2t3o6q3d3y,' + // #240: 1 font: HK 64₉₁. + '3n,' + // #241: 4 fonts: HK 72₉₉, JP 49₁₈₅, SC 66₄₅₀, TC 68₅₅₃. + '3v3h10e3y,' + // #242: 1 font: JP 29₁₆₅. + '6j,' + // #243: 2 fonts: JP 47₁₈₃, SC 63₄₄₇. + '7b10d,' + // #244: 1 font: KR 2₂₆₂. + '10c,' + // #245: 1 font: KR 18₂₇₈. + '10s,' + // #246: 1 font: SC 31₄₁₅. + '15z,' + // #247: 1 font: SC 36₄₂₀. + '16e,' + // #248: 2 fonts: Noto Color Emoji 5₅, Symbols 2 3₁₅. + 'fj,' + // #249: 1 font: HK 23₅₀. + '1y,' + // #250: 4 fonts: HK 24₅₁, JP 12₁₄₈, SC 13₃₉₇, TC 15₅₀₀. + '1z3s9o3y,' + // #251: 4 fonts: HK 43₇₀, JP 27₁₆₃, SC 33₄₁₇, TC 35₅₂₀. + '2s3o9t3y,' + // #252: 2 fonts: HK 48₇₅, TC 41₅₂₆. + '2x17i,' + // #253: 2 fonts: HK 49₇₆, TC 42₅₂₇. + '2y17i,' + // #254: 4 fonts: HK 52₇₉, JP 33₁₆₉, SC 43₄₂₇, TC 45₅₃₀. + '3b3l9x3y,' + // #255: 4 fonts: HK 54₈₁, JP 35₁₇₁, SC 46₄₃₀, TC 48₅₃₃. + '3d3l9y3y,' + // #256: 3 fonts: HK 55₈₂, JP 36₁₇₂, TC 49₅₃₄. + '3e3l13x,' + // #257: 3 fonts: HK 64₉₁, SC 56₄₄₀, TC 59₅₄₄. + '3n13k3z,' + // #258: 3 fonts: HK 65₉₂, SC 57₄₄₁, TC 60₅₄₅. + '3o13k3z,' + // #259: 4 fonts: HK 66₉₃, JP 44₁₈₀, SC 58₄₄₂, TC 61₅₄₆. + '3p3i10b3z,' + // #260: 4 fonts: HK 66₉₃, JP 44₁₈₀, SC 59₄₄₃, TC 61₅₄₆. + '3p3i10c3y,' + // #261: 4 fonts: HK 67₉₄, JP 45₁₈₁, SC 60₄₄₄, TC 62₅₄₇. + '3q3i10c3y,' + // #262: 4 fonts: HK 69₉₆, JP 47₁₈₃, SC 63₄₄₇, TC 65₅₅₀. + '3s3i10d3y,' + // #263: 4 fonts: HK 70₉₇, JP 48₁₈₄, SC 64₄₄₈, TC 66₅₅₁. + '3t3i10d3y,' + // #264: 4 fonts: HK 71₉₈, JP 48₁₈₄, SC 64₄₄₈, TC 67₅₅₂. + '3u3h10d3z,' + // #265: 2 fonts: JP 24₁₆₀, SC 29₄₁₃. + '6e9s,' + // #266: 1 font: KR 33₂₉₃. + '11h,' + // #267: 1 font: KR 46₃₀₆. + '11u,' + // #268: 1 font: KR 47₃₀₇. + '11v,' + // #269: 1 font: SC 19₄₀₃. + '15n,' + // #270: 1 font: SC 47₄₃₁. + '16p,' + // #271: 1 font: SC 51₄₃₅. + '16t,' + // #272: 1 font: SC 58₄₄₂. + '17a,' + // #273: 1 font: SC 61₄₄₅. + '17d,' + // #274: 1 font: Tamil₇₁₀. + '27i,' + // #275: 3 fonts: HK 15₄₂, JP 6₁₄₂, TC 5₄₉₀. + '1q3v13j,' + // #276: 4 fonts: HK 25₅₂, JP 13₁₄₉, SC 14₃₉₈, TC 15₅₀₀. + '2a3s9o3x,' + // #277: 4 fonts: HK 29₅₆, JP 16₁₅₂, SC 18₄₀₂, TC 20₅₀₅. + '2e3r9p3y,' + // #278: 4 fonts: HK 30₅₇, JP 17₁₅₃, SC 20₄₀₄, TC 22₅₀₇. + '2f3r9q3y,' + // #279: 1 font: HK 33₆₀. + '2i,' + // #280: 4 fonts: HK 46₇₃, JP 30₁₆₆, SC 38₄₂₂, TC 39₅₂₄. + '2v3o9v3x,' + // #281: 5 fonts: HK 50₇₇, JP 32₁₆₈, KR 80₃₄₀, SC 41₄₂₅, TC 43₅₂₈. + '2z3m6p3g3y,' + // #282: 4 fonts: HK 57₈₄, JP 38₁₇₄, SC 49₄₃₃, TC 51₅₃₆. + '3g3l9y3y,' + // #283: 3 fonts: HK 57₈₄, SC 49₄₃₃, TC 51₅₃₆. + '3g13k3y,' + // #284: 3 fonts: HK 68₉₅, SC 61₄₄₅, TC 63₅₄₈. + '3r13l3y,' + // #285: 5 fonts: HK 80₁₀₇, JP 57₁₉₃, KR 97₃₅₇, SC 71₄₅₅, TC 74₅₅₉. + '4d3h6h3t3z,' + // #286: 10 fonts: HK 82₁₀₉, HK 106₁₃₃, JP 59₁₉₅, JP 121₂₅₇, KR 121₃₈₁, SC 73₄₅₇, SC 98₄₈₂, TC 76₅₆₁, TC 102₅₈₇, Noto Sans₅₉₁. + '4fx2j2j4t2xy3azd,' + // #287: 7 fonts: HK 106₁₃₃, JP 59₁₉₅, JP 121₂₅₇, KR 121₃₈₁, SC 98₄₈₂, TC 102₅₈₇, Noto Sans₅₉₁. + '5d2j2j4t3w4ad,' + // #288: 2 fonts: JP 28₁₆₄, SC 35₄₁₉. + '6i9u,' + // #289: 1 font: KR 11₂₇₁. + '10l,' + // #290: 1 font: KR 23₂₈₃. + '10x,' + // #291: 1 font: KR 38₂₉₈. + '11m,' + // #292: 1 font: SC 22₄₀₆. + '15q,' + // #293: 1 font: SC 28₄₁₂. + '15w,' + // #294: 1 font: SC 38₄₂₂. + '16g,' + // #295: 1 font: SC 40₄₂₄. + '16i,' + // #296: 1 font: SC 41₄₂₅. + '16j,' + // #297: 1 font: SC 44₄₂₈. + '16m,' + // #298: 1 font: SC 50₄₃₄. + '16s,' + // #299: 1 font: Bengali₆₀₁. + '23d,' + // #300: 1 font: Grantha₆₂₂. + '23y,' + // #301: 1 font: Gurmukhi₆₂₅. + '24b,' + // #302: 4 fonts: HK 15₄₂, JP 5₁₄₁, SC 4₃₈₈, TC 4₄₈₉. + '1q3u9m3w,' + // #303: 4 fonts: HK 22₄₉, JP 11₁₄₇, SC 12₃₉₆, TC 13₄₉₈. + '1x3t9o3x,' + // #304: 3 fonts: HK 23₅₀, JP 11₁₄₇, TC 13₄₉₈. + '1y3s13m,' + // #305: 3 fonts: HK 24₅₁, JP 12₁₄₈, TC 14₄₉₉. + '1z3s13m,' + // #306: 4 fonts: HK 27₅₄, JP 14₁₅₀, SC 16₄₀₀, TC 18₅₀₃. + '2c3r9p3y,' + // #307: 4 fonts: HK 28₅₅, JP 15₁₅₁, SC 17₄₀₁, TC 19₅₀₄. + '2d3r9p3y,' + // #308: 3 fonts: HK 28₅₅, JP 15₁₅₁, TC 19₅₀₄. + '2d3r13o,' + // #309: 3 fonts: HK 35₆₂, JP 21₁₅₇, TC 27₅₁₂. + '2k3q13q,' + // #310: 4 fonts: HK 36₆₃, JP 22₁₅₈, SC 26₄₁₀, TC 28₅₁₃. + '2l3q9r3y,' + // #311: 4 fonts: HK 40₆₇, JP 25₁₆₁, SC 31₄₁₅, TC 33₅₁₈. + '2p3p9t3y,' + // #312: 3 fonts: HK 42₆₉, SC 33₄₁₇, TC 35₅₂₀. + '2r13j3y,' + // #313: 1 font: HK 45₇₂. + '2u,' + // #314: 5 fonts: HK 45₇₂, JP 29₁₆₅, KR 78₃₃₈, SC 36₄₂₀, TC 38₅₂₃. + '2u3o6q3d3y,' + // #315: 2 fonts: HK 45₇₂, TC 38₅₂₃. + '2u17i,' + // #316: 4 fonts: HK 46₇₃, JP 30₁₆₆, SC 37₄₂₁, TC 39₅₂₄. + '2v3o9u3y,' + // #317: 2 fonts: HK 47₇₄, TC 40₅₂₅. + '2w17i,' + // #318: 1 font: HK 53₈₀. '3c,' - // #90: 1 font: Pahawh Hmong₁₀₀. - '3w,' - // #91: 2 fonts: Symbols₁₁₉, Noto Color Emoji 3₁₄₆. - '4p1a,' - // #92: 2 fonts: Symbols 2₁₂₀, Noto Color Emoji 6₁₄₉. - '4q1c,' - // #93: 1 font: Tai Tham₁₂₆. - '4w,' - // #94: 2 fonts: Noto Color Emoji 8₁₅₁, Noto Color Emoji 10₁₅₃. - '5vb,' - // #95: 1 font: Noto Music₀. - 'a,' - // #96: 140 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Arabic₄, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Math₇₁, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #97: 138 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Math₇₁, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #98: 2 fonts: Noto Sans₁, Elbasan₂₉. - 'b1b,' - // #99: 2 fonts: Noto Sans₁, Glagolitic₃₃. - 'b1f,' - // #100: 2 fonts: Arabic₄, Thaana₁₃₂. - 'e4x,' - // #101: 1 font: Bhaiksuki₁₂. - 'm,' - // #102: 1 font: Cham₂₀. - 'u,' - // #103: 1 font: Cuneiform₂₃. - 'x,' - // #104: 2 fonts: Devanagari₂₆, Sharada₁₁₀. - '1a3f,' - // #105: 3 fonts: HK₃₉, JP₄₇, KR₄₉. - '1nhb,' - // #106: 8 fonts: HK₃₉, JP₄₇, KR₄₉, Mongolian₇₈, New Tai Lue₈₄, SC₁₀₈, TC₁₂₂, Yi₁₄₀. - '1nhb1cfxnr,' - // #107: 1 font: Khmer₅₄. + // #319: 3 fonts: HK 63₉₀, SC 55₄₃₉, TC 58₅₄₃. + '3m13k3z,' + // #320: 4 fonts: HK 71₉₈, JP 49₁₈₅, SC 65₄₄₉, TC 67₅₅₂. + '3u3i10d3y,' + // #321: 2 fonts: JP 2₁₃₈, Symbols₇₀₂. + '5i21r,' + // #322: 2 fonts: JP 34₁₇₀, SC 44₄₂₈. + '6o9x,' + // #323: 2 fonts: JP 44₁₈₀, SC 58₄₄₂. + '6y10b,' + // #324: 1 font: JP 53₁₈₉. + '7h,' + // #325: 3 fonts: JP 58₁₉₄, KR 98₃₅₈, Math₆₅₅. + '7m6h11k,' + // #326: 1 font: KR 3₂₆₃. + '10d,' + // #327: 1 font: KR 40₃₀₀. + '11o,' + // #328: 1 font: KR 53₃₁₃. + '12b,' + // #329: 1 font: KR 61₃₂₁. + '12j,' + // #330: 1 font: SC 60₄₄₄. + '17c,' + // #331: 1 font: SC 63₄₄₇. + '17f,' + // #332: 1 font: HK 18₄₅. + '1t,' + // #333: 2 fonts: HK 19₄₆, JP 9₁₄₅. + '1u3u,' + // #334: 4 fonts: HK 20₄₇, JP 10₁₄₆, SC 9₃₉₃, TC 10₄₉₅. + '1v3u9m3x,' + // #335: 4 fonts: HK 24₅₁, JP 12₁₄₈, SC 13₃₉₇, TC 14₄₉₉. + '1z3s9o3x,' + // #336: 5 fonts: HK 25₅₂, JP 13₁₄₉, KR 68₃₂₈, SC 14₃₉₈, TC 16₅₀₁. + '2a3s6w2r3y,' + // #337: 5 fonts: HK 26₅₃, JP 14₁₅₀, KR 69₃₂₉, SC 15₃₉₉, TC 17₅₀₂. + '2b3s6w2r3y,' + // #338: 3 fonts: HK 27₅₄, JP 15₁₅₁, TC 18₅₀₃. + '2c3s13n,' + // #339: 3 fonts: HK 39₆₆, JP 24₁₆₀, TC 31₅₁₆. + '2o3p13r,' + // #340: 1 font: HK 48₇₅. + '2x,' + // #341: 2 fonts: HK 57₈₄, TC 51₅₃₆. + '3g17j,' + // #342: 4 fonts: HK 67₉₄, JP 45₁₈₁, SC 60₄₄₄, TC 63₅₄₈. + '3q3i10c3z,' + // #343: 4 fonts: HK 73₁₀₀, JP 50₁₈₆, SC 66₄₅₀, TC 69₅₅₄. + '3w3h10d3z,' + // #344: 1 font: JP 3₁₃₉. + '5j,' + // #345: 2 fonts: JP 4₁₄₀, KR 0₂₆₀. + '5k4p,' + // #346: 2 fonts: JP 49₁₈₅, SC 65₄₄₉. + '7d10d,' + // #347: 1 font: JP 54₁₉₀. + '7i,' + // #348: 2 fonts: JP 54₁₉₀, KR 94₃₅₄. + '7i6h,' + // #349: 1 font: KR 19₂₇₉. + '10t,' + // #350: 1 font: KR 21₂₈₁. + '10v,' + // #351: 1 font: SC 35₄₁₉. + '16d,' + // #352: 1 font: SC 45₄₂₉. + '16n,' + // #353: 1 font: Gujarati₆₂₃. + '23z,' + // #354: 1 font: Oriya₆₈₁. + '26f,' + // #355: 2 fonts: HK 18₄₅, TC 8₄₉₃. + '1t17f,' + // #356: 3 fonts: HK 21₄₈, JP 10₁₄₆, TC 11₄₉₆. + '1w3t13l,' + // #357: 3 fonts: HK 22₄₉, JP 11₁₄₇, TC 13₄₉₈. + '1x3t13m,' + // #358: 4 fonts: HK 23₅₀, JP 11₁₄₇, SC 12₃₉₆, TC 13₄₉₈. + '1y3s9o3x,' + // #359: 2 fonts: HK 24₅₁, JP 12₁₄₈. + '1z3s,' + // #360: 4 fonts: HK 26₅₃, JP 14₁₅₀, SC 15₃₉₉, TC 17₅₀₂. + '2b3s9o3y,' + // #361: 3 fonts: HK 36₆₃, JP 22₁₅₈, TC 28₅₁₃. + '2l3q13q,' + // #362: 4 fonts: HK 38₆₅, JP 23₁₅₉, SC 27₄₁₁, TC 30₅₁₅. + '2n3p9r3z,' + // #363: 4 fonts: HK 39₆₆, JP 24₁₆₀, SC 29₄₁₃, TC 32₅₁₇. + '2o3p9s3z,' + // #364: 3 fonts: HK 40₆₇, SC 30₄₁₄, TC 32₅₁₇. + '2p13i3y,' + // #365: 4 fonts: HK 43₇₀, JP 27₁₆₃, SC 34₄₁₈, TC 36₅₂₁. + '2s3o9u3y,' + // #366: 3 fonts: HK 46₇₃, SC 37₄₂₁, TC 39₅₂₄. + '2v13j3y,' + // #367: 3 fonts: HK 48₇₅, JP 31₁₆₇, TC 41₅₂₆. + '2x3n13u,' + // #368: 3 fonts: HK 48₇₅, SC 39₄₂₃, TC 41₅₂₆. + '2x13j3y,' + // #369: 3 fonts: HK 49₇₆, SC 40₄₂₄, TC 42₅₂₇. + '2y13j3y,' + // #370: 2 fonts: HK 50₇₇, TC 43₅₂₈. + '2z17i,' + // #371: 3 fonts: HK 52₇₉, JP 34₁₇₀, TC 46₅₃₁. + '3b3m13w,' + // #372: 3 fonts: HK 52₇₉, SC 43₄₂₇, TC 45₅₃₀. + '3b13j3y,' + // #373: 3 fonts: HK 54₈₁, JP 35₁₇₁, TC 48₅₃₃. + '3d3l13x,' + // #374: 5 fonts: HK 59₈₆, JP 39₁₇₅, KR 85₃₄₅, SC 51₄₃₅, TC 54₅₃₉. + '3i3k6n3l3z,' + // #375: 4 fonts: HK 62₈₉, JP 42₁₇₈, SC 54₄₃₈, TC 57₅₄₂. + '3l3k9z3z,' + // #376: 3 fonts: HK 67₉₄, SC 60₄₄₄, TC 62₅₄₇. + '3q13l3y,' + // #377: 3 fonts: HK 71₉₈, JP 48₁₈₄, TC 67₅₅₂. + '3u3h14d,' + // #378: 4 fonts: HK 73₁₀₀, JP 50₁₈₆, SC 67₄₅₁, TC 69₅₅₄. + '3w3h10e3y,' + // #379: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 106₃₆₆, SC 96₄₈₀, TC 95₅₈₀. + '4w4y4g4j3v,' + // #380: 2 fonts: JP 25₁₆₁, SC 30₄₁₄. + '6f9s,' + // #381: 2 fonts: JP 37₁₇₃, SC 48₄₃₂. + '6r9y,' + // #382: 2 fonts: JP 55₁₉₁, KR 95₃₅₅. + '7j6h,' + // #383: 1 font: KR 13₂₇₃. + '10n,' + // #384: 1 font: KR 16₂₇₆. + '10q,' + // #385: 1 font: KR 43₃₀₃. + '11r,' + // #386: 1 font: KR 119₃₇₉. + '14p,' + // #387: 1 font: SC 14₃₉₈. + '15i,' + // #388: 1 font: SC 49₄₃₃. + '16r,' + // #389: 1 font: SC 53₄₃₇. + '16v,' + // #390: 1 font: SC 64₄₄₈. + '17g,' + // #391: 1 font: Kannada₆₃₅. + '24l,' + // #392: 1 font: Sinhala₆₉₆. + '26u,' + // #393: 1 font: Telugu₇₁₂. + '27k,' + // #394: 1 font: Noto Color Emoji 1₁. + 'b,' + // #395: 1 font: Noto Color Emoji 6₆. + 'g,' + // #396: 3 fonts: HK 16₄₃, JP 6₁₄₂, TC 5₄₉₀. + '1r3u13j,' + // #397: 4 fonts: HK 19₄₆, JP 9₁₄₅, SC 8₃₉₂, TC 9₄₉₄. + '1u3u9m3x,' + // #398: 2 fonts: HK 23₅₀, TC 14₄₉₉. + '1y17g,' + // #399: 4 fonts: HK 33₆₀, JP 19₁₅₅, SC 22₄₀₆, TC 24₅₀₉. + '2i3q9q3y,' + // #400: 2 fonts: HK 33₆₀, TC 25₅₁₀. + '2i17h,' + // #401: 4 fonts: HK 42₆₉, JP 26₁₆₂, SC 32₄₁₆, TC 34₅₁₉. + '2r3o9t3y,' + // #402: 4 fonts: HK 49₇₆, JP 31₁₆₇, SC 40₄₂₄, TC 42₅₂₇. + '2y3m9w3y,' + // #403: 4 fonts: HK 55₈₂, JP 36₁₇₂, SC 46₄₃₀, TC 49₅₃₄. + '3e3l9x3z,' + // #404: 5 fonts: HK 56₈₃, JP 37₁₇₃, KR 83₃₄₃, SC 47₄₃₁, TC 50₅₃₅. + '3f3l6n3j3z,' + // #405: 4 fonts: HK 59₈₆, JP 39₁₇₅, SC 51₄₃₅, TC 54₅₃₉. + '3i3k9z3z,' + // #406: 3 fonts: HK 59₈₆, JP 39₁₇₅, TC 53₅₃₈. + '3i3k13y,' + // #407: 2 fonts: HK 62₈₉, TC 57₅₄₂. + '3l17k,' + // #408: 2 fonts: HK 63₉₀, TC 58₅₄₃. + '3m17k,' + // #409: 4 fonts: HK 66₉₃, JP 45₁₈₁, SC 59₄₄₃, TC 62₅₄₇. + '3p3j10b3z,' + // #410: 4 fonts: HK 69₉₆, JP 46₁₈₂, SC 62₄₄₆, TC 64₅₄₉. + '3s3h10d3y,' + // #411: 4 fonts: HK 70₉₇, JP 47₁₈₃, SC 63₄₄₇, TC 65₅₅₀. + '3t3h10d3y,' + // #412: 3 fonts: HK 70₉₇, JP 48₁₈₄, TC 66₅₅₁. + '3t3i14c,' + // #413: 4 fonts: HK 72₉₉, JP 49₁₈₅, SC 65₄₄₉, TC 68₅₅₃. + '3v3h10d3z,' + // #414: 5 fonts: HK 77₁₀₄, JP 119₂₅₅, KR 95₃₅₅, SC 68₄₅₂, TC 70₅₅₅. + '4a5u3v3s3y,' + // #415: 10 fonts: HK 82₁₀₉, HK 106₁₃₃, JP 59₁₉₅, JP 121₂₅₇, KR 121₃₈₁, SC 73₄₅₇, SC 98₄₈₂, TC 77₅₆₂, TC 102₅₈₇, Noto Sans₅₉₁. + '4fx2j2j4t2xy3byd,' + // #416: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 103₃₆₃, SC 96₄₈₀, TC 95₅₈₀. + '4w4y4d4m3v,' + // #417: 2 fonts: JP 26₁₆₂, SC 32₄₁₆. + '6g9t,' + // #418: 2 fonts: JP 50₁₈₆, SC 66₄₅₀. + '7e10d,' + // #419: 3 fonts: JP 57₁₉₃, KR 97₃₅₇, SC 71₄₅₅. + '7l6h3t,' + // #420: 1 font: KR 9₂₆₉. + '10j,' + // #421: 1 font: SC 21₄₀₅. + '15p,' + // #422: 1 font: SC 24₄₀₈. + '15s,' + // #423: 1 font: SC 65₄₄₉. + '17h,' + // #424: 4 fonts: HK 20₄₇, JP 9₁₄₅, SC 8₃₉₂, TC 10₄₉₅. + '1v3t9m3y,' + // #425: 1 font: HK 22₄₉. + '1x,' + // #426: 4 fonts: HK 27₅₄, JP 15₁₅₁, SC 16₄₀₀, TC 18₅₀₃. + '2c3s9o3y,' + // #427: 2 fonts: HK 31₅₈, TC 22₅₀₇. + '2g17g,' + // #428: 1 font: HK 32₅₉. + '2h,' + // #429: 5 fonts: HK 33₆₀, JP 20₁₅₆, KR 72₃₃₂, SC 22₄₀₆, TC 25₅₁₀. + '2i3r6t2v3z,' + // #430: 5 fonts: HK 35₆₂, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 27₅₁₂. + '2k3q6t2w3z,' + // #431: 4 fonts: HK 37₆₄, JP 23₁₅₉, SC 26₄₁₀, TC 29₅₁₄. + '2m3q9q3z,' + // #432: 3 fonts: HK 47₇₄, JP 30₁₆₆, TC 40₅₂₅. + '2w3n13u,' + // #433: 4 fonts: HK 53₈₀, JP 34₁₇₀, SC 44₄₂₈, TC 46₅₃₁. + '3c3l9x3y,' + // #434: 4 fonts: HK 54₈₁, JP 35₁₇₁, SC 45₄₂₉, TC 47₅₃₂. + '3d3l9x3y,' + // #435: 3 fonts: HK 56₈₃, JP 37₁₇₃, TC 50₅₃₅. + '3f3l13x,' + // #436: 4 fonts: HK 59₈₆, JP 39₁₇₅, SC 50₄₃₄, TC 53₅₃₈. + '3i3k9y3z,' + // #437: 5 fonts: HK 60₈₇, JP 40₁₇₆, KR 86₃₄₆, SC 52₄₃₆, TC 55₅₄₀. + '3j3k6n3l3z,' + // #438: 4 fonts: HK 60₈₇, JP 40₁₇₆, SC 52₄₃₆, TC 54₅₃₉. + '3j3k9z3y,' + // #439: 4 fonts: HK 61₈₈, JP 40₁₇₆, SC 52₄₃₆, TC 55₅₄₀. + '3k3j9z3z,' + // #440: 3 fonts: HK 62₈₉, SC 54₄₃₈, TC 57₅₄₂. + '3l13k3z,' + // #441: 4 fonts: HK 64₉₁, JP 43₁₇₉, SC 57₄₄₁, TC 59₅₄₄. + '3n3j10b3y,' + // #442: 3 fonts: HK 64₉₁, JP 43₁₇₉, TC 59₅₄₄. + '3n3j14a,' + // #443: 3 fonts: HK 66₉₃, SC 58₄₄₂, TC 61₅₄₆. + '3p13k3z,' + // #444: 3 fonts: HK 67₉₄, JP 45₁₈₁, TC 62₅₄₇. + '3q3i14b,' + // #445: 4 fonts: HK 68₉₅, JP 46₁₈₂, SC 61₄₄₅, TC 64₅₄₉. + '3r3i10c3z,' + // #446: 3 fonts: HK 69₉₆, SC 62₄₄₆, TC 64₅₄₉. + '3s13l3y,' + // #447: 5 fonts: HK 72₉₉, JP 49₁₈₅, KR 92₃₅₂, SC 66₄₅₀, TC 68₅₅₃. + '3v3h6k3t3y,' + // #448: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 95₃₅₅, SC 68₄₅₂, TC 95₅₈₀. + '4w4y3v3s4x,' + // #449: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 105₃₆₅, SC 96₄₈₀, TC 95₅₈₀. + '4w4y4f4k3v,' + // #450: 2 fonts: JP 40₁₇₆, SC 52₄₃₆. + '6u9z,' + // #451: 2 fonts: JP 53₁₈₉, KR 94₃₅₄. + '7h6i,' + // #452: 2 fonts: Noto Sans₅₉₁, Coptic₆₁₂. + '22tu,' + // #453: 1 font: Devanagari₆₁₅. + '23r,' + // #454: 1 font: Lao₆₄₁. + '24r,' + // #455: 2 fonts: Noto Color Emoji 2₂, Symbols₇₀₂. + 'c26x,' + // #456: 2 fonts: Noto Color Emoji 9₉, Symbols 2 3₁₅. + 'jf,' + // #457: 4 fonts: HK 11₃₈, JP 2₁₃₈, TC 1₄₈₆, Symbols₇₀₂. + '1m3v13j8h,' + // #458: 2 fonts: HK 17₄₄, TC 7₄₉₂. + '1s17f,' + // #459: 4 fonts: HK 18₄₅, JP 8₁₄₄, SC 7₃₉₁, TC 8₄₉₃. + '1t3u9m3x,' + // #460: 1 font: HK 19₄₆. + '1u,' + // #461: 2 fonts: HK 20₄₇, JP 9₁₄₅. + '1v3t,' + // #462: 2 fonts: HK 20₄₇, TC 10₄₉₅. + '1v17f,' + // #463: 3 fonts: HK 23₅₀, JP 12₁₄₈, TC 14₄₉₉. + '1y3t13m,' + // #464: 4 fonts: HK 29₅₆, JP 16₁₅₂, KR 70₃₃₀, TC 20₅₀₅. + '2e3r6v6s,' + // #465: 4 fonts: HK 29₅₆, JP 16₁₅₂, SC 19₄₀₃, TC 21₅₀₆. + '2e3r9q3y,' + // #466: 3 fonts: HK 30₅₇, SC 19₄₀₃, TC 21₅₀₆. + '2f13h3y,' + // #467: 2 fonts: HK 30₅₇, TC 21₅₀₆. + '2f17g,' + // #468: 4 fonts: HK 31₅₈, JP 18₁₅₄, SC 21₄₀₅, TC 23₅₀₈. + '2g3r9q3y,' + // #469: 3 fonts: HK 33₆₀, JP 20₁₅₆, TC 25₅₁₀. + '2i3r13p,' + // #470: 5 fonts: HK 34₆₁, JP 20₁₅₆, KR 72₃₃₂, SC 23₄₀₇, TC 26₅₁₁. + '2j3q6t2w3z,' + // #471: 3 fonts: HK 34₆₁, SC 23₄₀₇, TC 26₅₁₁. + '2j13h3z,' + // #472: 5 fonts: HK 36₆₃, JP 22₁₅₈, KR 73₃₃₃, SC 25₄₀₉, TC 28₅₁₃. + '2l3q6s2x3z,' + // #473: 4 fonts: HK 38₆₅, JP 23₁₅₉, SC 28₄₁₂, TC 30₅₁₅. + '2n3p9s3y,' + // #474: 2 fonts: HK 39₆₆, JP 24₁₆₀. + '2o3p,' + // #475: 2 fonts: HK 40₆₇, JP 25₁₆₁. + '2p3p,' + // #476: 5 fonts: HK 40₆₇, JP 25₁₆₁, KR 75₃₃₅, SC 30₄₁₄, TC 32₅₁₇. + '2p3p6r3a3y,' + // #477: 3 fonts: HK 40₆₇, JP 25₁₆₁, TC 33₅₁₈. + '2p3p13s,' + // #478: 3 fonts: HK 41₆₈, JP 26₁₆₂, TC 34₅₁₉. + '2q3p13s,' + // #479: 3 fonts: HK 45₇₂, SC 36₄₂₀, TC 38₅₂₃. + '2u13j3y,' + // #480: 5 fonts: HK 47₇₄, JP 30₁₆₆, KR 79₃₃₉, SC 38₄₂₂, TC 40₅₂₅. + '2w3n6q3e3y,' + // #481: 3 fonts: HK 47₇₄, SC 38₄₂₂, TC 40₅₂₅. + '2w13j3y,' + // #482: 5 fonts: HK 49₇₆, JP 31₁₆₇, KR 79₃₃₉, SC 40₄₂₄, TC 42₅₂₇. + '2y3m6p3g3y,' + // #483: 4 fonts: HK 49₇₆, JP 32₁₆₈, SC 40₄₂₄, TC 42₅₂₇. + '2y3n9v3y,' + // #484: 3 fonts: HK 49₇₆, JP 32₁₆₈, TC 42₅₂₇. + '2y3n13u,' + // #485: 3 fonts: HK 50₇₇, JP 32₁₆₈, TC 43₅₂₈. + '2z3m13v,' + // #486: 3 fonts: HK 50₇₇, SC 41₄₂₅, TC 43₅₂₈. + '2z13j3y,' + // #487: 4 fonts: HK 51₇₈, JP 33₁₆₉, SC 43₄₂₇, TC 45₅₃₀. + '3a3m9x3y,' + // #488: 5 fonts: HK 55₈₂, JP 36₁₇₂, KR 83₃₄₃, SC 47₄₃₁, TC 49₅₃₄. + '3e3l6o3j3y,' + // #489: 3 fonts: HK 57₈₄, JP 37₁₇₃, TC 51₅₃₆. + '3g3k13y,' + // #490: 4 fonts: HK 58₈₅, JP 38₁₇₄, SC 49₄₃₃, TC 52₅₃₇. + '3h3k9y3z,' + // #491: 3 fonts: HK 58₈₅, JP 38₁₇₄, TC 52₅₃₇. + '3h3k13y,' + // #492: 3 fonts: HK 59₈₆, JP 39₁₇₅, TC 54₅₃₉. + '3i3k13z,' + // #493: 3 fonts: HK 60₈₇, JP 40₁₇₆, TC 55₅₄₀. + '3j3k13z,' + // #494: 5 fonts: HK 64₉₁, JP 43₁₇₉, KR 88₃₄₈, SC 56₄₄₀, TC 59₅₄₄. + '3n3j6m3n3z,' + // #495: 2 fonts: HK 65₉₂, TC 60₅₄₅. + '3o17k,' + // #496: 2 fonts: HK 71₉₈, TC 67₅₅₂. + '3u17l,' + // #497: 5 fonts: HK 72₉₉, JP 49₁₈₅, KR 92₃₅₂, SC 65₄₄₉, TC 68₅₅₃. + '3v3h6k3s3z,' + // #498: 2 fonts: HK 74₁₀₁, JP 50₁₈₆. + '3x3g,' + // #499: 1 font: JP 2₁₃₈. + '5i,' + // #500: 2 fonts: JP 23₁₅₉, SC 27₄₁₁. + '6d9r,' + // #501: 2 fonts: JP 25₁₆₁, SC 31₄₁₅. + '6f9t,' + // #502: 2 fonts: JP 35₁₇₁, SC 45₄₂₉. + '6p9x,' + // #503: 2 fonts: JP 50₁₈₆, SC 67₄₅₁. + '7e10e,' + // #504: 1 font: KR 24₂₈₄. + '10y,' + // #505: 1 font: KR 49₃₀₉. + '11x,' + // #506: 1 font: KR 56₃₁₆. + '12e,' + // #507: 1 font: KR 60₃₂₀. + '12i,' + // #508: 1 font: SC 12₃₉₆. + '15g,' + // #509: 1 font: Georgian₆₁₉. + '23v,' + // #510: 4 fonts: HK 12₃₉, JP 3₁₃₉, SC 2₃₈₆, TC 2₄₈₇. + '1n3v9m3w,' + // #511: 2 fonts: HK 15₄₂, JP 5₁₄₁. + '1q3u,' + // #512: 2 fonts: HK 15₄₂, TC 5₄₉₀. + '1q17f,' + // #513: 3 fonts: HK 16₄₃, JP 7₁₄₃, TC 6₄₉₁. + '1r3v13j,' + // #514: 2 fonts: HK 17₄₄, JP 7₁₄₃. + '1s3u,' + // #515: 3 fonts: HK 19₄₆, JP 9₁₄₅, TC 10₄₉₅. + '1u3u13l,' + // #516: 4 fonts: HK 21₄₈, JP 10₁₄₆, SC 10₃₉₄, TC 11₄₉₆. + '1w3t9n3x,' + // #517: 2 fonts: HK 23₅₀, TC 13₄₉₈. + '1y17f,' + // #518: 2 fonts: HK 26₅₃, TC 17₅₀₂. + '2b17g,' + // #519: 2 fonts: HK 27₅₄, TC 18₅₀₃. + '2c17g,' + // #520: 3 fonts: HK 28₅₅, JP 15₁₅₁, TC 20₅₀₅. + '2d3r13p,' + // #521: 3 fonts: HK 29₅₆, JP 16₁₅₂, TC 21₅₀₆. + '2e3r13p,' + // #522: 3 fonts: HK 30₅₇, JP 17₁₅₃, TC 21₅₀₆. + '2f3r13o,' + // #523: 4 fonts: HK 32₅₉, JP 18₁₅₄, SC 21₄₀₅, TC 23₅₀₈. + '2h3q9q3y,' + // #524: 5 fonts: HK 32₅₉, JP 19₁₅₅, KR 71₃₃₁, SC 21₄₀₅, TC 24₅₀₉. + '2h3r6t2v3z,' + // #525: 2 fonts: HK 35₆₂, JP 21₁₅₇. + '2k3q,' + // #526: 3 fonts: HK 36₆₃, SC 25₄₀₉, TC 28₅₁₃. + '2l13h3z,' + // #527: 1 font: HK 41₆₈. + '2q,' + // #528: 4 fonts: HK 44₇₁, JP 28₁₆₄, SC 35₄₁₉, TC 36₅₂₁. + '2t3o9u3x,' + // #529: 4 fonts: HK 46₇₃, JP 29₁₆₅, SC 37₄₂₁, TC 39₅₂₄. + '2v3n9v3y,' + // #530: 5 fonts: HK 46₇₃, JP 30₁₆₆, KR 78₃₃₈, SC 37₄₂₁, TC 39₅₂₄. + '2v3o6p3e3y,' + // #531: 2 fonts: HK 46₇₃, TC 39₅₂₄. + '2v17i,' + // #532: 4 fonts: HK 49₇₆, JP 32₁₆₈, SC 41₄₂₅, TC 43₅₂₈. + '2y3n9w3y,' + // #533: 4 fonts: HK 50₇₇, JP 32₁₆₈, SC 42₄₂₆, TC 44₅₂₉. + '2z3m9x3y,' + // #534: 5 fonts: HK 53₈₀, JP 35₁₇₁, KR 82₃₄₂, SC 45₄₂₉, TC 47₅₃₂. + '3c3m6o3i3y,' + // #535: 3 fonts: HK 53₈₀, SC 45₄₂₉, TC 47₅₃₂. + '3c13k3y,' + // #536: 5 fonts: HK 56₈₃, JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂, TC 50₅₃₅. + '3f3l6o3j3y,' + // #537: 3 fonts: HK 57₈₄, JP 38₁₇₄, TC 51₅₃₆. + '3g3l13x,' + // #538: 2 fonts: HK 59₈₆, JP 39₁₇₅. + '3i3k,' + // #539: 4 fonts: HK 62₈₉, JP 41₁₇₇, SC 54₄₃₈, TC 57₅₄₂. + '3l3j10a3z,' + // #540: 4 fonts: HK 62₈₉, JP 42₁₇₈, SC 55₄₃₉, TC 57₅₄₂. + '3l3k10a3y,' + // #541: 2 fonts: HK 64₉₁, TC 59₅₄₄. + '3n17k,' + // #542: 3 fonts: HK 66₉₃, SC 59₄₄₃, TC 61₅₄₆. + '3p13l3y,' + // #543: 4 fonts: HK 67₉₄, JP 45₁₈₁, SC 59₄₄₃, TC 62₅₄₇. + '3q3i10b3z,' + // #544: 4 fonts: HK 68₉₅, JP 46₁₈₂, SC 62₄₄₆, TC 64₅₄₉. + '3r3i10d3y,' + // #545: 3 fonts: HK 68₉₅, SC 61₄₄₅, TC 64₅₄₉. + '3r13l3z,' + // #546: 3 fonts: HK 71₉₈, JP 49₁₈₅, TC 67₅₅₂. + '3u3i14c,' + // #547: 3 fonts: HK 72₉₉, JP 49₁₈₅, TC 68₅₅₃. + '3v3h14d,' + // #548: 3 fonts: HK 72₉₉, SC 66₄₅₀, TC 68₅₅₃. + '3v13m3y,' + // #549: 5 fonts: HK 80₁₀₇, JP 57₁₉₃, KR 97₃₅₇, TC 74₅₅₉, Symbols₇₀₂. + '4d3h6h7t5m,' + // #550: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 102₃₆₂, SC 96₄₈₀, TC 95₅₈₀. + '4w4y4c4n3v,' + // #551: 7 fonts: HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 99₄₈₃, TC 103₅₈₈, Noto Sans₅₉₁. + '5e2i2k4t3w4ac,' + // #552: 2 fonts: JP 11₁₄₇, SC 12₃₉₆. + '5r9o,' + // #553: 2 fonts: JP 21₁₅₇, SC 24₄₀₈. + '6b9q,' + // #554: 2 fonts: JP 27₁₆₃, SC 34₄₁₈. + '6h9u,' + // #555: 2 fonts: JP 30₁₆₆, SC 38₄₂₂. + '6k9v,' + // #556: 2 fonts: JP 33₁₆₉, SC 43₄₂₇. + '6n9x,' + // #557: 2 fonts: JP 51₁₈₇, KR 93₃₅₃. + '7f6j,' + // #558: 1 font: JP 56₁₉₂. + '7k,' + // #559: 2 fonts: JP 58₁₉₄, Math₆₅₅. + '7m17s,' + // #560: 1 font: KR 6₂₆₆. + '10g,' + // #561: 1 font: KR 7₂₆₇. + '10h,' + // #562: 1 font: KR 39₂₉₉. + '11n,' + // #563: 1 font: Hebrew₆₂₈. + '24e,' + // #564: 143 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 84₁₁₁, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3gx2h2l4t2yx3cxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #565: 4 fonts: HK 12₃₉, JP 4₁₄₀, KR 0₂₆₀, TC 3₄₈₈. + '1n3w4p8t,' + // #566: 4 fonts: HK 12₃₉, JP 4₁₄₀, KR 1₂₆₁, TC 3₄₈₈. + '1n3w4q8s,' + // #567: 4 fonts: HK 15₄₂, JP 5₁₄₁, SC 4₃₈₈, TC 5₄₉₀. + '1q3u9m3x,' + // #568: 1 font: HK 16₄₃. + '1r,' + // #569: 4 fonts: HK 17₄₄, JP 8₁₄₄, SC 6₃₉₀, TC 7₄₉₂. + '1s3v9l3x,' + // #570: 3 fonts: HK 17₄₄, SC 6₃₉₀, TC 7₄₉₂. + '1s13h3x,' + // #571: 5 fonts: HK 18₄₅, JP 8₁₄₄, KR 65₃₂₅, SC 7₃₉₁, TC 8₄₉₃. + '1t3u6y2n3x,' + // #572: 3 fonts: HK 20₄₇, JP 9₁₄₅, TC 10₄₉₅. + '1v3t13l,' + // #573: 2 fonts: HK 20₄₇, JP 10₁₄₆. + '1v3u,' + // #574: 3 fonts: HK 22₄₉, JP 10₁₄₆, TC 12₄₉₇. + '1x3s13m,' + // #575: 5 fonts: HK 22₄₉, JP 11₁₄₇, KR 67₃₂₇, SC 12₃₉₆, TC 13₄₉₈. + '1x3t6x2q3x,' + // #576: 3 fonts: HK 23₅₀, SC 12₃₉₆, TC 13₄₉₈. + '1y13h3x,' + // #577: 3 fonts: HK 24₅₁, SC 13₃₉₇, TC 14₄₉₉. + '1z13h3x,' + // #578: 2 fonts: HK 24₅₁, TC 14₄₉₉. + '1z17f,' + // #579: 2 fonts: HK 24₅₁, TC 15₅₀₀. + '1z17g,' + // #580: 4 fonts: HK 25₅₂, JP 13₁₄₉, SC 15₃₉₉, TC 16₅₀₁. + '2a3s9p3x,' + // #581: 3 fonts: HK 25₅₂, JP 13₁₄₉, TC 16₅₀₁. + '2a3s13n,' + // #582: 2 fonts: HK 26₅₃, JP 14₁₅₀. + '2b3s,' + // #583: 1 font: HK 30₅₇. + '2f,' + // #584: 3 fonts: HK 31₅₈, JP 18₁₅₄, TC 22₅₀₇. + '2g3r13o,' + // #585: 3 fonts: HK 31₅₈, JP 18₁₅₄, TC 23₅₀₈. + '2g3r13p,' + // #586: 3 fonts: HK 34₆₁, JP 20₁₅₆, TC 26₅₁₁. + '2j3q13q,' + // #587: 2 fonts: HK 36₆₃, JP 22₁₅₈. + '2l3q,' + // #588: 2 fonts: HK 38₆₅, TC 30₅₁₅. + '2n17h,' + // #589: 3 fonts: HK 42₆₉, JP 26₁₆₂, TC 34₅₁₉. + '2r3o13s,' + // #590: 5 fonts: HK 42₆₉, JP 27₁₆₃, KR 76₃₃₆, SC 33₄₁₇, TC 35₅₂₀. + '2r3p6q3c3y,' + // #591: 3 fonts: HK 42₆₉, SC 32₄₁₆, TC 34₅₁₉. + '2r13i3y,' + // #592: 4 fonts: HK 44₇₁, JP 29₁₆₅, SC 36₄₂₀, TC 37₅₂₂. + '2t3p9u3x,' + // #593: 2 fonts: HK 44₇₁, TC 37₅₂₂. + '2t17i,' + // #594: 5 fonts: HK 45₇₂, JP 29₁₆₅, KR 78₃₃₈, SC 37₄₂₁, TC 38₅₂₃. + '2u3o6q3e3x,' + // #595: 4 fonts: HK 47₇₄, JP 31₁₆₇, SC 39₄₂₃, TC 40₅₂₅. + '2w3o9v3x,' + // #596: 5 fonts: HK 51₇₈, JP 33₁₆₉, KR 81₃₄₁, SC 42₄₂₆, TC 44₅₂₉. + '3a3m6p3g3y,' + // #597: 5 fonts: HK 51₇₈, JP 33₁₆₉, KR 81₃₄₁, SC 43₄₂₇, TC 45₅₃₀. + '3a3m6p3h3y,' + // #598: 4 fonts: HK 51₇₈, JP 33₁₆₉, SC 42₄₂₆, TC 45₅₃₀. + '3a3m9w3z,' + // #599: 3 fonts: HK 51₇₈, SC 42₄₂₆, TC 44₅₂₉. + '3a13j3y,' + // #600: 4 fonts: HK 52₇₉, JP 33₁₆₉, SC 43₄₂₇, TC 46₅₃₁. + '3b3l9x3z,' + // #601: 2 fonts: HK 53₈₀, JP 34₁₇₀. + '3c3l,' + // #602: 4 fonts: HK 54₈₁, JP 36₁₇₂, SC 46₄₃₀, TC 48₅₃₃. + '3d3m9x3y,' + // #603: 3 fonts: HK 55₈₂, SC 47₄₃₁, TC 49₅₃₄. + '3e13k3y,' + // #604: 3 fonts: HK 56₈₃, SC 48₄₃₂, TC 50₅₃₅. + '3f13k3y,' + // #605: 4 fonts: HK 57₈₄, JP 37₁₇₃, SC 48₄₃₂, TC 51₅₃₆. + '3g3k9y3z,' + // #606: 5 fonts: HK 62₈₉, JP 41₁₇₇, KR 87₃₄₇, SC 54₄₃₈, TC 57₅₄₂. + '3l3j6n3m3z,' + // #607: 4 fonts: HK 62₈₉, JP 41₁₇₇, SC 54₄₃₈, TC 56₅₄₁. + '3l3j10a3y,' + // #608: 3 fonts: HK 62₈₉, JP 41₁₇₇, TC 57₅₄₂. + '3l3j14a,' + // #609: 3 fonts: HK 63₉₀, JP 42₁₇₈, TC 58₅₄₃. + '3m3j14a,' + // #610: 1 font: HK 65₉₂. + '3o,' + // #611: 3 fonts: HK 65₉₂, JP 43₁₇₉, TC 60₅₄₅. + '3o3i14b,' + // #612: 1 font: HK 66₉₃. + '3p,' + // #613: 2 fonts: HK 67₉₄, JP 45₁₈₁. + '3q3i,' + // #614: 3 fonts: HK 67₉₄, SC 60₄₄₄, TC 63₅₄₈. + '3q13l3z,' + // #615: 5 fonts: HK 68₉₅, JP 46₁₈₂, KR 90₃₅₀, SC 61₄₄₅, TC 63₅₄₈. + '3r3i6l3q3y,' + // #616: 4 fonts: HK 69₉₆, JP 47₁₈₃, SC 62₄₄₆, TC 65₅₅₀. + '3s3i10c3z,' + // #617: 4 fonts: HK 70₉₇, JP 47₁₈₃, SC 63₄₄₇, TC 66₅₅₁. + '3t3h10d3z,' + // #618: 2 fonts: HK 72₉₉, TC 68₅₅₃. + '3v17l,' + // #619: 2 fonts: HK 75₁₀₂, JP 51₁₈₇. + '3y3g,' + // #620: 4 fonts: HK 80₁₀₇, JP 57₁₉₃, SC 70₄₅₄, TC 73₅₅₈. + '4d3h10a3z,' + // #621: 4 fonts: HK 80₁₀₇, JP 57₁₉₃, SC 70₄₅₄, TC 74₅₅₉. + '4d3h10a4a,' + // #622: 2 fonts: JP 27₁₆₃, SC 33₄₁₇. + '6h9t,' + // #623: 2 fonts: JP 36₁₇₂, SC 46₄₃₀. + '6q9x,' + // #624: 2 fonts: JP 45₁₈₁, SC 59₄₄₃. + '6z10b,' + // #625: 1 font: KR 10₂₇₀. + '10k,' + // #626: 1 font: KR 17₂₇₇. + '10r,' + // #627: 1 font: KR 30₂₉₀. + '11e,' + // #628: 1 font: KR 31₂₉₁. + '11f,' + // #629: 1 font: KR 51₃₁₁. + '11z,' + // #630: 1 font: SC 20₄₀₄. + '15o,' + // #631: 1 font: Kharoshthi₆₃₇. + '24n,' + // #632: 1 font: Linear B₆₄₅. + '24v,' + // #633: 1 font: Noto Color Emoji 11₁₁. + 'l,' + // #634: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc4ad4oe4pd3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #635: 1 font: HK 15₄₂. + '1q,' + // #636: 3 fonts: HK 15₄₂, JP 5₁₄₁, TC 4₄₈₉. + '1q3u13j,' + // #637: 5 fonts: HK 20₄₇, JP 10₁₄₆, KR 66₃₂₆, SC 9₃₉₃, TC 10₄₉₅. + '1v3u6x2o3x,' + // #638: 4 fonts: HK 20₄₇, JP 10₁₄₆, SC 9₃₉₃, TC 11₄₉₆. + '1v3u9m3y,' + // #639: 3 fonts: HK 20₄₇, JP 10₁₄₆, TC 10₄₉₅. + '1v3u13k,' + // #640: 4 fonts: HK 22₄₉, JP 10₁₄₆, SC 11₃₉₅, TC 12₄₉₇. + '1x3s9o3x,' + // #641: 3 fonts: HK 22₄₉, SC 11₃₉₅, TC 12₄₉₇. + '1x13h3x,' + // #642: 3 fonts: HK 22₄₉, SC 12₃₉₆, TC 13₄₉₈. + '1x13i3x,' + // #643: 1 font: HK 24₅₁. + '1z,' + // #644: 1 font: HK 27₅₄. '2c,' - // #108: 1 font: Myanmar₈₁. - '3d,' - // #109: 1 font: New Tai Lue₈₄. + // #645: 3 fonts: HK 27₅₄, SC 16₄₀₀, TC 18₅₀₃. + '2c13h3y,' + // #646: 2 fonts: HK 29₅₆, TC 20₅₀₅. + '2e17g,' + // #647: 5 fonts: HK 30₅₇, JP 17₁₅₃, KR 71₃₃₁, SC 19₄₀₃, TC 21₅₀₆. + '2f3r6v2t3y,' + // #648: 4 fonts: HK 30₅₇, JP 17₁₅₃, SC 19₄₀₃, TC 22₅₀₇. + '2f3r9p3z,' + // #649: 3 fonts: HK 30₅₇, JP 17₁₅₃, TC 22₅₀₇. + '2f3r13p,' + // #650: 4 fonts: HK 31₅₈, JP 17₁₅₃, SC 20₄₀₄, TC 22₅₀₇. + '2g3q9q3y,' + // #651: 2 fonts: HK 31₅₈, JP 18₁₅₄. + '2g3r,' + // #652: 3 fonts: HK 33₆₀, JP 19₁₅₅, TC 24₅₀₉. + '2i3q13p,' + // #653: 3 fonts: HK 33₆₀, SC 22₄₀₆, TC 25₅₁₀. + '2i13h3z,' + // #654: 4 fonts: HK 34₆₁, JP 20₁₅₆, SC 23₄₀₇, TC 25₅₁₀. + '2j3q9q3y,' + // #655: 4 fonts: HK 35₆₂, JP 21₁₅₇, SC 24₄₀₈, TC 26₅₁₁. + '2k3q9q3y,' + // #656: 2 fonts: HK 35₆₂, TC 27₅₁₂. + '2k17h,' + // #657: 5 fonts: HK 36₆₃, JP 22₁₅₈, KR 74₃₃₄, SC 26₄₁₀, TC 28₅₁₃. + '2l3q6t2x3y,' + // #658: 2 fonts: HK 36₆₃, TC 28₅₁₃. + '2l17h,' + // #659: 4 fonts: HK 37₆₄, JP 22₁₅₈, SC 26₄₁₀, TC 29₅₁₄. + '2m3p9r3z,' + // #660: 3 fonts: HK 41₆₈, JP 26₁₆₂, TC 33₅₁₈. + '2q3p13r,' + // #661: 3 fonts: HK 41₆₈, SC 31₄₁₅, TC 33₅₁₈. + '2q13i3y,' + // #662: 3 fonts: HK 41₆₈, SC 32₄₁₆, TC 34₅₁₉. + '2q13j3y,' + // #663: 2 fonts: HK 42₆₉, TC 34₅₁₉. + '2r17h,' + // #664: 5 fonts: HK 43₇₀, JP 28₁₆₄, KR 77₃₃₇, SC 34₄₁₈, TC 36₅₂₁. + '2s3p6q3c3y,' + // #665: 3 fonts: HK 43₇₀, SC 34₄₁₈, TC 36₅₂₁. + '2s13j3y,' + // #666: 3 fonts: HK 44₇₁, JP 28₁₆₄, TC 37₅₂₂. + '2t3o13t,' + // #667: 2 fonts: HK 45₇₂, JP 29₁₆₅. + '2u3o,' + // #668: 5 fonts: HK 46₇₃, JP 29₁₆₅, KR 78₃₃₈, SC 37₄₂₁, TC 39₅₂₄. + '2v3n6q3e3y,' + // #669: 3 fonts: HK 46₇₃, JP 30₁₆₆, TC 39₅₂₄. + '2v3o13t,' + // #670: 3 fonts: HK 49₇₆, JP 31₁₆₇, TC 42₅₂₇. + '2y3m13v,' + // #671: 1 font: HK 50₇₇. + '2z,' + // #672: 3 fonts: HK 51₇₈, JP 33₁₆₉, TC 44₅₂₉. + '3a3m13v,' + // #673: 2 fonts: HK 51₇₈, TC 44₅₂₉. + '3a17i,' + // #674: 3 fonts: HK 53₈₀, JP 34₁₇₀, TC 46₅₃₁. + '3c3l13w,' + // #675: 2 fonts: HK 53₈₀, TC 47₅₃₂. + '3c17j,' + // #676: 4 fonts: HK 54₈₁, JP 35₁₇₁, SC 45₄₂₉, TC 48₅₃₃. + '3d3l9x3z,' + // #677: 2 fonts: HK 54₈₁, TC 48₅₃₃. + '3d17j,' + // #678: 2 fonts: HK 55₈₂, JP 36₁₇₂. + '3e3l,' + // #679: 5 fonts: HK 55₈₂, JP 36₁₇₂, KR 83₃₄₃, SC 46₄₃₀, TC 49₅₃₄. + '3e3l6o3i3z,' + // #680: 5 fonts: HK 56₈₃, JP 36₁₇₂, KR 83₃₄₃, SC 47₄₃₁, TC 50₅₃₅. + '3f3k6o3j3z,' + // #681: 4 fonts: HK 56₈₃, JP 37₁₇₃, SC 47₄₃₁, TC 50₅₃₅. + '3f3l9x3z,' + // #682: 1 font: HK 57₈₄. '3g,' - // #110: 3 fonts: Symbols 2₁₂₀, Noto Color Emoji 7₁₅₀, Noto Color Emoji 9₁₅₂. - '4q1db,' - // #111: 139 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂, Noto Color Emoji 2₁₄₅. - 'aaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac,' - // #112: 113 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, Hanunoo₄₀, Hatran₄₁, Indic Siyaq Numbers₄₄, Javanese₄₈, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Lao₅₇, Lepcha₅₈, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Manichaean₆₈, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old South Arabian₉₅, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Rejang₁₀₆, Runic₁₀₇, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaaaaaabaaaadaaaaabaabacdcaaaababaabaabbbaaaaababaaaaaaabaabcaaaabbabaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaa,' - // #113: 7 fonts: Noto Sans₁, Adlam₂, HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, TC₁₂₂. - 'ba1khb2gn,' - // #114: 2 fonts: Noto Sans₁, Syriac₁₂₁. - 'b4p,' - // #115: 1 font: Adlam₂. - 'c,' - // #116: 4 fonts: Arabic₄, NKo₈₂, Syriac₁₂₁, Thaana₁₃₂. - 'e2z1mk,' - // #117: 2 fonts: Arabic₄, Syriac₁₂₁. - 'e4m,' - // #118: 1 font: Brahmi₁₃. - 'n,' - // #119: 1 font: Canadian Aboriginal₁₆. - 'q,' - // #120: 1 font: Cherokee₂₁. - 'v,' - // #121: 1 font: Coptic₂₂. - 'w,' - // #122: 2 fonts: Devanagari₂₆, Grantha₃₅. - '1ai,' - // #123: 6 fonts: HK₃₉, JP₄₇, KR₄₉, Mongolian₇₈, SC₁₀₈, TC₁₂₂. - '1nhb1c1dn,' - // #124: 7 fonts: HK₃₉, JP₄₇, KR₄₉, New Tai Lue₈₄, SC₁₀₈, TC₁₂₂, Yi₁₄₀. - '1nhb1ixnr,' - // #125: 7 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols 2₁₂₀, TC₁₂₂, Noto Color Emoji 3₁₄₆. - '1nhb2glbx,' - // #126: 6 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, TC₁₂₂, Yi₁₄₀. - '1nhb2gnr,' - // #127: 1 font: Hatran₄₁. - '1p,' - // #128: 1 font: Javanese₄₈. - '1w,' - // #129: 1 font: Lepcha₅₈. - '2g,' - // #130: 1 font: Linear A₆₀. - '2i,' - // #131: 1 font: Marchen₆₉. - '2r,' - // #132: 1 font: Meetei Mayek₇₄. - '2w,' - // #133: 1 font: Meroitic₇₅. - '2x,' - // #134: 1 font: Miao₇₆. + // #683: 3 fonts: HK 58₈₅, SC 50₄₃₄, TC 52₅₃₇. + '3h13k3y,' + // #684: 2 fonts: HK 58₈₅, TC 52₅₃₇. + '3h17j,' + // #685: 4 fonts: HK 59₈₆, JP 39₁₇₅, SC 51₄₃₅, TC 53₅₃₈. + '3i3k9z3y,' + // #686: 2 fonts: HK 60₈₇, JP 40₁₇₆. + '3j3k,' + // #687: 2 fonts: HK 60₈₇, TC 55₅₄₀. + '3j17k,' + // #688: 3 fonts: HK 64₉₁, SC 57₄₄₁, TC 60₅₄₅. + '3n13l3z,' + // #689: 5 fonts: HK 65₉₂, JP 43₁₇₉, KR 88₃₄₈, SC 57₄₄₁, TC 60₅₄₅. + '3o3i6m3o3z,' + // #690: 5 fonts: HK 65₉₂, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 61₅₄₆. + '3o3j6m3o3z,' + // #691: 3 fonts: HK 66₉₃, JP 44₁₈₀, TC 61₅₄₆. + '3p3i14b,' + // #692: 2 fonts: HK 68₉₅, TC 64₅₄₉. + '3r17l,' + // #693: 3 fonts: HK 69₉₆, JP 46₁₈₂, TC 64₅₄₉. + '3s3h14c,' + // #694: 2 fonts: HK 70₉₇, JP 47₁₈₃. + '3t3h,' + // #695: 2 fonts: HK 76₁₀₃, JP 51₁₈₇. + '3z3f,' + // #696: 5 fonts: HK 77₁₀₄, JP 54₁₉₀, KR 109₃₆₉, SC 67₄₅₁, TC 70₅₅₅. + '4a3h6w3d3z,' + // #697: 3 fonts: HK 80₁₀₇, JP 57₁₉₃, TC 74₅₅₉. + '4d3h14b,' + // #698: 5 fonts: HK 80₁₀₇, JP 58₁₉₄, KR 97₃₅₇, TC 74₅₅₉, Symbols₇₀₂. + '4d3i6g7t5m,' + // #699: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 100₃₆₀, SC 96₄₈₀, TC 95₅₈₀. + '4w4y4a4p3v,' + // #700: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 101₃₆₁, SC 96₄₈₀, TC 95₅₈₀. + '4w4y4b4o3v,' + // #701: 2 fonts: JP 22₁₅₈, SC 25₄₀₉. + '6c9q,' + // #702: 2 fonts: JP 41₁₇₇, SC 53₄₃₇. + '6v9z,' + // #703: 2 fonts: JP 43₁₇₉, SC 56₄₄₀. + '6x10a,' + // #704: 2 fonts: JP 45₁₈₁, SC 60₄₄₄. + '6z10c,' + // #705: 1 font: JP 52₁₈₈. + '7g,' + // #706: 1 font: KR 36₂₉₆. + '11k,' + // #707: 1 font: KR 65₃₂₅. + '12n,' + // #708: 1 font: SC 18₄₀₂. + '15m,' + // #709: 2 fonts: Bengali₆₀₁, Devanagari₆₁₅. + '23dn,' + // #710: 1 font: Glagolitic₆₂₀. + '23w,' + // #711: 1 font: Malayalam₆₅₀. + '25a,' + // #712: 1 font: Masaram Gondi₆₅₄. + '25e,' + // #713: 2 fonts: Noto Color Emoji 2₂, Noto Color Emoji 9₉. + 'cg,' + // #714: 2 fonts: Noto Color Emoji 5₅, Symbols₇₀₂. + 'f26u,' + // #715: 2 fonts: Noto Color Emoji 7₇, Noto Color Emoji 9₉. + 'hb,' + // #716: 2 fonts: Noto Color Emoji 8₈, Symbols 2 3₁₅. + 'ig,' + // #717: 147 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'nbbccc4ad4oe4pd3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #718: 143 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad4oe4t3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #719: 2 fonts: HK 6₃₃, JP 1₁₃₇. + '1h3z,' + // #720: 2 fonts: HK 9₃₆, TC 0₄₈₅. + '1k17g,' + // #721: 5 fonts: HK 12₃₉, JP 4₁₄₀, KR 1₂₆₁, SC 2₃₈₆, TC 3₄₈₈. + '1n3w4q4u3x,' + // #722: 3 fonts: HK 14₄₁, JP 5₁₄₁, TC 4₄₈₉. + '1p3v13j,' + // #723: 4 fonts: HK 16₄₃, JP 6₁₄₂, SC 5₃₈₉, TC 6₄₉₁. + '1r3u9m3x,' + // #724: 2 fonts: HK 18₄₅, JP 8₁₄₄. + '1t3u,' + // #725: 2 fonts: HK 19₄₆, TC 9₄₉₄. + '1u17f,' + // #726: 2 fonts: HK 22₄₉, JP 11₁₄₇. + '1x3t,' + // #727: 2 fonts: HK 22₄₉, SC 11₃₉₅. + '1x13h,' + // #728: 2 fonts: HK 23₅₀, JP 11₁₄₇. + '1y3s,' + // #729: 5 fonts: HK 23₅₀, JP 11₁₄₇, KR 67₃₂₇, SC 12₃₉₆, TC 13₄₉₈. + '1y3s6x2q3x,' + // #730: 2 fonts: HK 25₅₂, JP 13₁₄₉. + '2a3s,' + // #731: 3 fonts: HK 25₅₂, JP 13₁₄₉, TC 15₅₀₀. + '2a3s13m,' + // #732: 5 fonts: HK 27₅₄, JP 14₁₅₀, KR 69₃₂₉, SC 16₄₀₀, TC 18₅₀₃. + '2c3r6w2s3y,' + // #733: 5 fonts: HK 27₅₄, JP 15₁₅₁, KR 69₃₂₉, SC 16₄₀₀, TC 18₅₀₃. + '2c3s6v2s3y,' + // #734: 2 fonts: HK 28₅₅, JP 15₁₅₁. + '2d3r,' + // #735: 2 fonts: HK 29₅₆, JP 16₁₅₂. + '2e3r,' + // #736: 2 fonts: HK 30₅₇, JP 17₁₅₃. + '2f3r,' + // #737: 5 fonts: HK 30₅₇, JP 17₁₅₃, KR 71₃₃₁, SC 20₄₀₄, TC 22₅₀₇. + '2f3r6v2u3y,' + // #738: 5 fonts: HK 31₅₈, JP 18₁₅₄, KR 71₃₃₁, SC 20₄₀₄, TC 23₅₀₈. + '2g3r6u2u3z,' + // #739: 2 fonts: HK 32₅₉, JP 19₁₅₅. + '2h3r,' + // #740: 4 fonts: HK 32₅₉, JP 19₁₅₅, SC 21₄₀₅, TC 23₅₀₈. + '2h3r9p3y,' + // #741: 3 fonts: HK 32₅₉, JP 19₁₅₅, TC 23₅₀₈. + '2h3r13o,' + // #742: 2 fonts: HK 32₅₉, TC 23₅₀₈. + '2h17g,' + // #743: 2 fonts: HK 32₅₉, TC 24₅₀₉. + '2h17h,' + // #744: 5 fonts: HK 33₆₀, JP 19₁₅₅, KR 72₃₃₂, SC 22₄₀₆, TC 24₅₀₉. + '2i3q6u2v3y,' + // #745: 2 fonts: HK 33₆₀, JP 20₁₅₆. + '2i3r,' + // #746: 5 fonts: HK 34₆₁, JP 21₁₅₇, KR 72₃₃₂, SC 23₄₀₇, TC 26₅₁₁. + '2j3r6s2w3z,' + // #747: 4 fonts: HK 34₆₁, JP 21₁₅₇, SC 23₄₀₇, TC 26₅₁₁. + '2j3r9p3z,' + // #748: 4 fonts: HK 34₆₁, JP 21₁₅₇, SC 24₄₀₈, TC 26₅₁₁. + '2j3r9q3y,' + // #749: 2 fonts: HK 34₆₁, TC 26₅₁₁. + '2j17h,' + // #750: 3 fonts: HK 35₆₂, SC 24₄₀₈, TC 27₅₁₂. + '2k13h3z,' + // #751: 3 fonts: HK 35₆₂, SC 25₄₀₉, TC 27₅₁₂. + '2k13i3y,' + // #752: 2 fonts: HK 36₆₃, SC 25₄₀₉. + '2l13h,' + // #753: 3 fonts: HK 37₆₄, JP 22₁₅₈, TC 29₅₁₄. + '2m3p13r,' + // #754: 4 fonts: HK 37₆₄, JP 23₁₅₉, SC 27₄₁₁, TC 30₅₁₅. + '2m3q9r3z,' + // #755: 1 font: HK 38₆₅. + '2n,' + // #756: 5 fonts: HK 38₆₅, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 30₅₁₅. + '2n3p6s2y3z,' + // #757: 5 fonts: HK 38₆₅, JP 23₁₅₉, KR 74₃₃₄, SC 28₄₁₂, TC 30₅₁₅. + '2n3p6s2z3y,' + // #758: 4 fonts: HK 38₆₅, JP 23₁₅₉, SC 28₄₁₂, TC 31₅₁₆. + '2n3p9s3z,' + // #759: 3 fonts: HK 38₆₅, JP 23₁₅₉, TC 31₅₁₆. + '2n3p13s,' + // #760: 3 fonts: HK 38₆₅, JP 24₁₆₀, TC 31₅₁₆. + '2n3q13r,' + // #761: 3 fonts: HK 38₆₅, SC 27₄₁₁, TC 30₅₁₅. + '2n13h3z,' + // #762: 1 font: HK 39₆₆. + '2o,' + // #763: 5 fonts: HK 40₆₇, JP 25₁₆₁, KR 75₃₃₅, SC 31₄₁₅, TC 33₅₁₈. + '2p3p6r3b3y,' + // #764: 2 fonts: HK 41₆₈, JP 26₁₆₂. + '2q3p,' + // #765: 5 fonts: HK 41₆₈, JP 26₁₆₂, KR 76₃₃₆, SC 32₄₁₆, TC 34₅₁₉. + '2q3p6r3b3y,' + // #766: 3 fonts: HK 42₆₉, JP 27₁₆₃, TC 35₅₂₀. + '2r3p13s,' + // #767: 2 fonts: HK 42₆₉, TC 35₅₂₀. + '2r17i,' + // #768: 3 fonts: HK 43₇₀, JP 28₁₆₄, TC 36₅₂₁. + '2s3p13s,' + // #769: 2 fonts: HK 43₇₀, TC 35₅₂₀. + '2s17h,' + // #770: 5 fonts: HK 44₇₁, JP 29₁₆₅, KR 77₃₃₇, SC 36₄₂₀, TC 37₅₂₂. + '2t3p6p3e3x,' + // #771: 3 fonts: HK 45₇₂, JP 29₁₆₅, TC 38₅₂₃. + '2u3o13t,' + // #772: 3 fonts: HK 47₇₄, SC 39₄₂₃, TC 40₅₂₅. + '2w13k3x,' + // #773: 1 font: HK 49₇₆. '2y,' - // #135: 1 font: Mro₇₉. + // #774: 5 fonts: HK 49₇₆, JP 32₁₆₈, KR 80₃₄₀, SC 41₄₂₅, TC 43₅₂₈. + '2y3n6p3g3y,' + // #775: 2 fonts: HK 51₇₈, TC 45₅₃₀. + '3a17j,' + // #776: 1 font: HK 52₇₉. '3b,' - // #136: 1 font: Old Hungarian₈₉. + // #777: 2 fonts: HK 52₇₉, TC 46₅₃₁. + '3b17j,' + // #778: 3 fonts: HK 53₈₀, JP 34₁₇₀, TC 47₅₃₂. + '3c3l13x,' + // #779: 3 fonts: HK 53₈₀, JP 35₁₇₁, TC 47₅₃₂. + '3c3m13w,' + // #780: 2 fonts: HK 54₈₁, JP 35₁₇₁. + '3d3l,' + // #781: 3 fonts: HK 54₈₁, JP 35₁₇₁, TC 47₅₃₂. + '3d3l13w,' + // #782: 4 fonts: HK 55₈₂, JP 36₁₇₂, SC 46₄₃₀, TC 48₅₃₃. + '3e3l9x3y,' + // #783: 4 fonts: HK 57₈₄, JP 38₁₇₄, SC 49₄₃₃, TC 52₅₃₇. + '3g3l9y3z,' + // #784: 3 fonts: HK 57₈₄, SC 49₄₃₃, TC 52₅₃₇. + '3g13k3z,' + // #785: 1 font: HK 58₈₅. + '3h,' + // #786: 2 fonts: HK 59₈₆, TC 54₅₃₉. + '3i17k,' + // #787: 3 fonts: HK 60₈₇, JP 40₁₇₆, TC 54₅₃₉. + '3j3k13y,' + // #788: 4 fonts: HK 61₈₈, JP 40₁₇₆, SC 53₄₃₇, TC 55₅₄₀. + '3k3j10a3y,' + // #789: 3 fonts: HK 61₈₈, JP 41₁₇₇, TC 56₅₄₁. + '3k3k13z,' + // #790: 3 fonts: HK 61₈₈, SC 53₄₃₇, TC 56₅₄₁. + '3k13k3z,' + // #791: 2 fonts: HK 61₈₈, TC 56₅₄₁. + '3k17k,' + // #792: 1 font: HK 62₈₉. '3l,' - // #137: 1 font: Psalter Pahlavi₁₀₅. - '4b,' - // #138: 1 font: Syriac₁₂₁. - '4r,' - // #139: 1 font: Tagbanwa₁₂₄. - '4u,' - // #140: 1 font: Tifinagh₁₃₄. - '5e,' - // #141: 2 fonts: Noto Color Emoji 3₁₄₆, Noto Color Emoji 4₁₄₇. - '5qa,' - // #142: 2 fonts: Noto Color Emoji 3₁₄₆, Noto Color Emoji 8₁₅₁. - '5qe,' - // #143: 2 fonts: Noto Color Emoji 4₁₄₇, Noto Color Emoji 8₁₅₁. - '5rd,' - // #144: 2 fonts: Noto Color Emoji 4₁₄₇, Noto Color Emoji 9₁₅₂. - '5re,' - // #145: 2 fonts: Noto Color Emoji 5₁₄₈, Noto Color Emoji 8₁₅₁. - '5sc,' - // #146: 143 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Arabic₄, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lycian₆₃, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Math₇₁, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, Myanmar₈₁, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phags Pa₁₀₃, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #147: 139 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Arabic₄, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #148: 140 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Math₇₁, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂, Noto Color Emoji 2₁₄₅. - 'aaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac,' - // #149: 139 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, Myanmar₈₁, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #150: 134 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, Javanese₄₈, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Math₇₁, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabbaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaabaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaa,' - // #151: 118 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Indic Siyaq Numbers₄₄, JP₄₇, Javanese₄₈, KR₄₉, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Lao₅₇, Lepcha₅₈, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Manichaean₆₈, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old South Arabian₉₅, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaaaaaabaaaadaaaaabaaaaaccaabaaaababaabaabbbaaaaababaaaaaaabaabcaaaabbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #152: 2 fonts: Noto Sans₁, Adlam₂. - 'ba,' - // #153: 3 fonts: Noto Sans₁, Adlam₂, Arabic₄. - 'bab,' - // #154: 8 fonts: Noto Sans₁, Elbasan₂₉, HK₃₉, JP₄₇, KR₄₉, Math₇₁, SC₁₀₈, TC₁₂₂. - 'b1bjhbv1kn,' - // #155: 2 fonts: Noto Sans₁, Georgian₃₂. - 'b1e,' - // #156: 7 fonts: Noto Sans₁, HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, TC₁₂₂, Tamil₁₂₉. - 'b1lhb2gng,' - // #157: 2 fonts: Noto Sans₁, Tifinagh₁₃₄. - 'b5c,' - // #158: 2 fonts: Arabic₄, Indic Siyaq Numbers₄₄. - 'e1n,' - // #159: 1 font: Avestan₆. - 'g,' - // #160: 1 font: Balinese₇. - 'h,' - // #161: 1 font: Bamum₈. - 'i,' - // #162: 1 font: Bassa Vah₉. - 'j,' - // #163: 1 font: Batak₁₀. - 'k,' - // #164: 4 fonts: Bengali₁₁, Devanagari₂₆, Grantha₃₅, Kannada₅₁. - 'loip,' - // #165: 1 font: Buginese₁₄. - 'o,' - // #166: 1 font: Caucasian Albanian₁₈. - 's,' - // #167: 1 font: Chakma₁₉. - 't,' - // #168: 3 fonts: Cypriot₂₄, Linear A₆₀, Linear B₆₁. - 'y1ja,' - // #169: 1 font: Egyptian Hieroglyphs₂₈. - '1c,' - // #170: 7 fonts: HK₃₉, JP₄₇, KR₄₉, Math₇₁, SC₁₀₈, Symbols₁₁₉, TC₁₂₂. - '1nhbv1kkc,' - // #171: 10 fonts: HK₃₉, JP₄₇, KR₄₉, Mongolian₇₈, New Tai Lue₈₄, Phags Pa₁₀₃, SC₁₀₈, TC₁₂₂, Tai Le₁₂₅, Yi₁₄₀. - '1nhb1cfsenco,' - // #172: 6 fonts: HK₃₉, JP₄₇, KR₄₉, New Tai Lue₈₄, SC₁₀₈, TC₁₂₂. - '1nhb1ixn,' - // #173: 7 fonts: HK₃₉, JP₄₇, KR₄₉, Phags Pa₁₀₃, SC₁₀₈, TC₁₂₂, Yi₁₄₀. - '1nhb2benr,' - // #174: 9 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols₁₁₉, TC₁₂₂, Noto Color Emoji 2₁₄₅, Noto Color Emoji 8₁₅₁, Noto Color Emoji 10₁₅₃. - '1nhb2gkcwfb,' - // #175: 7 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols 2₁₂₀, TC₁₂₂, Noto Color Emoji 7₁₅₀. - '1nhb2glb1b,' - // #176: 1 font: Imperial Aramaic₄₃. - '1r,' - // #177: 1 font: Inscriptional Pahlavi₄₅. - '1t,' - // #178: 1 font: Inscriptional Parthian₄₆. - '1u,' - // #179: 4 fonts: JP₄₇, KR₄₉, SC₁₀₈, TC₁₂₂. - '1vb2gn,' - // #180: 1 font: Kaithi₅₀. - '1y,' - // #181: 1 font: Kayah Li₅₂. - '2a,' - // #182: 1 font: Khojki₅₅. - '2d,' - // #183: 1 font: Khudawadi₅₆. + // #793: 5 fonts: HK 62₈₉, JP 42₁₇₈, KR 87₃₄₇, SC 54₄₃₈, TC 57₅₄₂. + '3l3k6m3m3z,' + // #794: 3 fonts: HK 62₈₉, JP 42₁₇₈, TC 57₅₄₂. + '3l3k13z,' + // #795: 5 fonts: HK 63₉₀, JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉, TC 58₅₄₃. + '3m3j6m3n3z,' + // #796: 5 fonts: HK 63₉₀, JP 42₁₇₈, KR 88₃₄₈, SC 56₄₄₀, TC 58₅₄₃. + '3m3j6n3n3y,' + // #797: 3 fonts: HK 64₉₁, SC 57₄₄₁, TC 59₅₄₄. + '3n13l3y,' + // #798: 4 fonts: HK 65₉₂, JP 44₁₈₀, SC 58₄₄₂, TC 60₅₄₅. + '3o3j10b3y,' + // #799: 4 fonts: HK 65₉₂, JP 44₁₈₀, SC 58₄₄₂, TC 61₅₄₆. + '3o3j10b3z,' + // #800: 4 fonts: HK 66₉₃, JP 44₁₈₀, KR 89₃₄₉, TC 61₅₄₆. + '3p3i6m7o,' + // #801: 5 fonts: HK 66₉₃, JP 45₁₈₁, KR 89₃₄₉, SC 59₄₄₃, TC 62₅₄₇. + '3p3j6l3p3z,' + // #802: 2 fonts: HK 66₉₃, TC 61₅₄₆. + '3p17k,' + // #803: 1 font: HK 68₉₅. + '3r,' + // #804: 3 fonts: HK 68₉₅, SC 62₄₄₆, TC 64₅₄₉. + '3r13m3y,' + // #805: 5 fonts: HK 69₉₆, JP 46₁₈₂, KR 90₃₅₀, SC 62₄₄₆, TC 64₅₄₉. + '3s3h6l3r3y,' + // #806: 3 fonts: HK 69₉₆, SC 62₄₄₆, TC 65₅₅₀. + '3s13l3z,' + // #807: 2 fonts: HK 69₉₆, TC 65₅₅₀. + '3s17l,' + // #808: 5 fonts: HK 70₉₇, JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇, TC 66₅₅₁. + '3t3h6l3r3z,' + // #809: 2 fonts: HK 70₉₇, JP 48₁₈₄. + '3t3i,' + // #810: 5 fonts: HK 70₉₇, JP 48₁₈₄, KR 91₃₅₁, SC 64₄₄₈, TC 66₅₅₁. + '3t3i6k3s3y,' + // #811: 5 fonts: HK 71₉₈, JP 48₁₈₄, KR 92₃₅₂, SC 64₄₄₈, TC 67₅₅₂. + '3u3h6l3r3z,' + // #812: 4 fonts: HK 71₉₈, JP 48₁₈₄, SC 65₄₄₉, TC 67₅₅₂. + '3u3h10e3y,' + // #813: 3 fonts: HK 77₁₀₄, JP 53₁₈₉, TC 70₅₅₅. + '4a3g14b,' + // #814: 4 fonts: HK 77₁₀₄, JP 54₁₉₀, SC 68₄₅₂, TC 70₅₅₅. + '4a3h10b3y,' + // #815: 2 fonts: JP 26₁₆₂, SC 31₄₁₅. + '6g9s,' + // #816: 2 fonts: JP 29₁₆₅, SC 36₄₂₀. + '6j9u,' + // #817: 2 fonts: JP 38₁₇₄, SC 50₄₃₄. + '6s9z,' + // #818: 2 fonts: JP 40₁₇₆, SC 53₄₃₇. + '6u10a,' + // #819: 2 fonts: JP 42₁₇₈, SC 54₄₃₈. + '6w9z,' + // #820: 2 fonts: JP 42₁₇₈, SC 55₄₃₉. + '6w10a,' + // #821: 2 fonts: JP 46₁₈₂, SC 61₄₄₅. + '7a10c,' + // #822: 2 fonts: JP 46₁₈₂, SC 62₄₄₆. + '7a10d,' + // #823: 1 font: KR 29₂₈₉. + '11d,' + // #824: 1 font: KR 50₃₁₀. + '11y,' + // #825: 1 font: Cypriot₆₁₃. + '23p,' + // #826: 2 fonts: Grantha₆₂₂, Tamil₇₁₀. + '23y3j,' + // #827: 1 font: Gunjala Gondi₆₂₄. + '24a,' + // #828: 2 fonts: Math₆₅₅, Symbols₇₀₂. + '25f1u,' + // #829: 1 font: Mongolian₆₆₂. + '25m,' + // #830: 1 font: Noto Serif Tibetan₇₂₃. + '27v,' + // #831: 2 fonts: Noto Color Emoji 3₃, Symbols₇₀₂. + 'd26w,' + // #832: 2 fonts: Noto Color Emoji 6₆, Symbols 2 3₁₅. + 'gi,' + // #833: 2 fonts: Noto Color Emoji 8₈, Noto Color Emoji 10₁₀. + 'ib,' + // #834: 3 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, Math₆₅₅. + 'nb24p,' + // #835: 143 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, HK 84₁₁₁, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 76₄₆₀, SC 99₄₈₃, TC 80₅₆₅, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc3hw2i2k4t2zw3dwbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #836: 1 font: Duployan 0₂₁. + 'v,' + // #837: 5 fonts: HK 11₃₈, JP 89₂₂₅, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m7e1i4u3x,' + // #838: 2 fonts: HK 14₄₁, JP 5₁₄₁. + '1p3v,' + // #839: 4 fonts: HK 14₄₁, JP 5₁₄₁, SC 3₃₈₇, TC 4₄₈₉. + '1p3v9l3x,' + // #840: 5 fonts: HK 15₄₂, JP 5₁₄₁, KR 65₃₂₅, SC 4₃₈₈, TC 5₄₉₀. + '1q3u7b2k3x,' + // #841: 2 fonts: HK 15₄₂, JP 6₁₄₂. + '1q3v,' + // #842: 4 fonts: HK 15₄₂, JP 6₁₄₂, SC 5₃₈₉, TC 5₄₉₀. + '1q3v9m3w,' + // #843: 2 fonts: HK 16₄₃, JP 6₁₄₂. + '1r3u,' + // #844: 1 font: HK 17₄₄. + '1s,' + // #845: 3 fonts: HK 17₄₄, JP 7₁₄₃, TC 6₄₉₁. + '1s3u13j,' + // #846: 4 fonts: HK 18₄₅, JP 8₁₄₄, KR 66₃₂₆, TC 8₄₉₃. + '1t3u6z6k,' + // #847: 3 fonts: HK 18₄₅, JP 8₁₄₄, TC 9₄₉₄. + '1t3u13l,' + // #848: 1 font: HK 20₄₇. + '1v,' + // #849: 2 fonts: HK 22₄₉, TC 13₄₉₈. + '1x17g,' + // #850: 4 fonts: HK 23₅₀, JP 12₁₄₈, SC 13₃₉₇, TC 14₄₉₉. + '1y3t9o3x,' + // #851: 5 fonts: HK 24₅₁, JP 12₁₄₈, KR 67₃₂₇, SC 13₃₉₇, TC 14₄₉₉. + '1z3s6w2r3x,' + // #852: 3 fonts: HK 24₅₁, SC 13₃₉₇, TC 15₅₀₀. + '1z13h3y,' + // #853: 4 fonts: HK 25₅₂, JP 12₁₄₈, SC 14₃₉₈, TC 15₅₀₀. + '2a3r9p3x,' + // #854: 2 fonts: HK 25₅₂, TC 16₅₀₁. + '2a17g,' + // #855: 1 font: HK 26₅₃. + '2b,' + // #856: 4 fonts: HK 26₅₃, JP 14₁₅₀, KR 69₃₂₉, TC 17₅₀₂. + '2b3s6w6q,' + // #857: 2 fonts: HK 27₅₄, JP 15₁₅₁. + '2c3s,' + // #858: 1 font: HK 29₅₆. '2e,' - // #184: 1 font: Lisu₆₂. - '2k,' - // #185: 1 font: Lydian₆₄. - '2m,' - // #186: 1 font: Mandaic₆₇. - '2p,' - // #187: 1 font: Manichaean₆₈. - '2q,' - // #188: 1 font: Modi₇₇. - '2z,' - // #189: 2 fonts: Mongolian₇₈, Phags Pa₁₀₃. - '3ay,' - // #190: 1 font: NKo₈₂. - '3e,' - // #191: 1 font: Nabataean₈₃. + // #859: 3 fonts: HK 30₅₇, JP 16₁₅₂, TC 21₅₀₆. + '2f3q13p,' + // #860: 3 fonts: HK 31₅₈, JP 17₁₅₃, TC 22₅₀₇. + '2g3q13p,' + // #861: 5 fonts: HK 31₅₈, JP 18₁₅₄, KR 71₃₃₁, SC 20₄₀₄, TC 22₅₀₇. + '2g3r6u2u3y,' + // #862: 3 fonts: HK 31₅₈, SC 20₄₀₄, TC 22₅₀₇. + '2g13h3y,' + // #863: 3 fonts: HK 32₅₉, JP 18₁₅₄, TC 23₅₀₈. + '2h3q13p,' + // #864: 4 fonts: HK 32₅₉, JP 19₁₅₅, KR 71₃₃₁, TC 24₅₀₉. + '2h3r6t6v,' + // #865: 2 fonts: HK 34₆₁, SC 23₄₀₇. + '2j13h,' + // #866: 4 fonts: HK 35₆₂, JP 21₁₅₇, KR 73₃₃₃, TC 27₅₁₂. + '2k3q6t6w,' + // #867: 1 font: HK 36₆₃. + '2l,' + // #868: 3 fonts: HK 37₆₄, JP 23₁₅₉, TC 30₅₁₅. + '2m3q13r,' + // #869: 4 fonts: HK 38₆₅, JP 23₁₅₉, KR 74₃₃₄, TC 30₅₁₅. + '2n3p6s6y,' + // #870: 4 fonts: HK 38₆₅, JP 24₁₆₀, SC 28₄₁₂, TC 31₅₁₆. + '2n3q9r3z,' + // #871: 3 fonts: HK 38₆₅, SC 28₄₁₂, TC 31₅₁₆. + '2n13i3z,' + // #872: 4 fonts: HK 39₆₆, JP 24₁₆₀, SC 28₄₁₂, TC 31₅₁₆. + '2o3p9r3z,' + // #873: 3 fonts: HK 39₆₆, JP 24₁₆₀, TC 32₅₁₇. + '2o3p13s,' + // #874: 3 fonts: HK 39₆₆, SC 29₄₁₃, TC 31₅₁₆. + '2o13i3y,' + // #875: 2 fonts: HK 41₆₈, TC 33₅₁₈. + '2q17h,' + // #876: 2 fonts: HK 41₆₈, TC 34₅₁₉. + '2q17i,' + // #877: 2 fonts: HK 42₆₉, JP 26₁₆₂. + '2r3o,' + // #878: 5 fonts: HK 42₆₉, JP 26₁₆₂, KR 76₃₃₆, SC 32₄₁₆, TC 34₅₁₉. + '2r3o6r3b3y,' + // #879: 1 font: HK 43₇₀. + '2s,' + // #880: 3 fonts: HK 43₇₀, SC 33₄₁₇, TC 35₅₂₀. + '2s13i3y,' + // #881: 5 fonts: HK 44₇₁, JP 28₁₆₄, KR 77₃₃₇, SC 35₄₁₉, TC 36₅₂₁. + '2t3o6q3d3x,' + // #882: 2 fonts: HK 44₇₁, JP 29₁₆₅. + '2t3p,' + // #883: 3 fonts: HK 44₇₁, SC 35₄₁₉, TC 37₅₂₂. + '2t13j3y,' + // #884: 5 fonts: HK 45₇₂, JP 29₁₆₅, KR 77₃₃₇, SC 36₄₂₀, TC 37₅₂₂. + '2u3o6p3e3x,' + // #885: 4 fonts: HK 45₇₂, JP 29₁₆₅, SC 36₄₂₀, TC 37₅₂₂. + '2u3o9u3x,' + // #886: 3 fonts: HK 46₇₃, JP 29₁₆₅, TC 39₅₂₄. + '2v3n13u,' + // #887: 3 fonts: HK 46₇₃, SC 38₄₂₂, TC 39₅₂₄. + '2v13k3x,' + // #888: 2 fonts: HK 47₇₄, JP 30₁₆₆. + '2w3n,' + // #889: 5 fonts: HK 47₇₄, JP 30₁₆₆, KR 78₃₃₈, SC 38₄₂₂, TC 40₅₂₅. + '2w3n6p3f3y,' + // #890: 5 fonts: HK 47₇₄, JP 31₁₆₇, KR 79₃₃₉, SC 39₄₂₃, TC 40₅₂₅. + '2w3o6p3f3x,' + // #891: 5 fonts: HK 50₇₇, JP 32₁₆₈, KR 80₃₄₀, SC 42₄₂₆, TC 44₅₂₉. + '2z3m6p3h3y,' + // #892: 3 fonts: HK 50₇₇, JP 32₁₆₈, TC 44₅₂₉. + '2z3m13w,' + // #893: 1 font: HK 51₇₈. + '3a,' + // #894: 5 fonts: HK 53₈₀, JP 34₁₇₀, KR 82₃₄₂, SC 44₄₂₈, TC 46₅₃₁. + '3c3l6p3h3y,' + // #895: 1 font: HK 54₈₁. + '3d,' + // #896: 5 fonts: HK 54₈₁, JP 35₁₇₁, KR 82₃₄₂, SC 45₄₂₉, TC 47₅₃₂. + '3d3l6o3i3y,' + // #897: 3 fonts: HK 54₈₁, SC 45₄₂₉, TC 47₅₃₂. + '3d13j3y,' + // #898: 2 fonts: HK 55₈₂, TC 49₅₃₄. + '3e17j,' + // #899: 1 font: HK 56₈₃. '3f,' - // #192: 1 font: Newa₈₅. - '3h,' - // #193: 1 font: Nushu₈₆. - '3i,' - // #194: 1 font: Old Italic₉₀. + // #900: 5 fonts: HK 57₈₄, JP 38₁₇₄, KR 84₃₄₄, SC 49₄₃₃, TC 51₅₃₆. + '3g3l6n3k3y,' + // #901: 4 fonts: HK 58₈₅, JP 38₁₇₄, SC 50₄₃₄, TC 53₅₃₈. + '3h3k9z3z,' + // #902: 4 fonts: HK 58₈₅, JP 39₁₇₅, SC 50₄₃₄, TC 53₅₃₈. + '3h3l9y3z,' + // #903: 5 fonts: HK 59₈₆, JP 39₁₇₅, KR 85₃₄₅, SC 50₄₃₄, TC 53₅₃₈. + '3i3k6n3k3z,' + // #904: 5 fonts: HK 59₈₆, JP 39₁₇₅, KR 85₃₄₅, SC 51₄₃₅, TC 53₅₃₈. + '3i3k6n3l3y,' + // #905: 2 fonts: HK 59₈₆, TC 53₅₃₈. + '3i17j,' + // #906: 4 fonts: HK 60₈₇, JP 39₁₇₅, SC 51₄₃₅, TC 54₅₃₉. + '3j3j9z3z,' + // #907: 5 fonts: HK 60₈₇, JP 40₁₇₆, KR 86₃₄₆, SC 51₄₃₅, TC 54₅₃₉. + '3j3k6n3k3z,' + // #908: 5 fonts: HK 60₈₇, JP 40₁₇₆, KR 86₃₄₆, SC 52₄₃₆, TC 54₅₃₉. + '3j3k6n3l3y,' + // #909: 5 fonts: HK 61₈₈, JP 41₁₇₇, KR 86₃₄₆, SC 53₄₃₇, TC 56₅₄₁. + '3k3k6m3m3z,' + // #910: 2 fonts: HK 62₈₉, JP 41₁₇₇. + '3l3j,' + // #911: 2 fonts: HK 62₈₉, SC 54₄₃₈. + '3l13k,' + // #912: 1 font: HK 63₉₀. '3m,' - // #195: 1 font: Old Persian₉₃. - '3p,' - // #196: 1 font: Osage₉₈. - '3u,' - // #197: 1 font: Osmanya₉₉. + // #913: 2 fonts: HK 64₉₁, JP 43₁₇₉. + '3n3j,' + // #914: 4 fonts: HK 64₉₁, JP 43₁₇₉, SC 57₄₄₁, TC 60₅₄₅. + '3n3j10b3z,' + // #915: 2 fonts: HK 66₉₃, JP 44₁₈₀. + '3p3i,' + // #916: 1 font: HK 67₉₄. + '3q,' + // #917: 3 fonts: HK 67₉₄, JP 45₁₈₁, TC 63₅₄₈. + '3q3i14c,' + // #918: 2 fonts: HK 67₉₄, TC 63₅₄₈. + '3q17l,' + // #919: 3 fonts: HK 68₉₅, JP 46₁₈₂, TC 63₅₄₈. + '3r3i14b,' + // #920: 2 fonts: HK 68₉₅, TC 63₅₄₈. + '3r17k,' + // #921: 4 fonts: HK 71₉₈, JP 48₁₈₄, SC 64₄₄₈, TC 66₅₅₁. + '3u3h10d3y,' + // #922: 5 fonts: HK 73₁₀₀, JP 50₁₈₆, KR 93₃₅₃, SC 66₄₅₀, TC 69₅₅₄. + '3w3h6k3s3z,' + // #923: 2 fonts: HK 76₁₀₃, TC 70₅₅₅. + '3z17j,' + // #924: 3 fonts: HK 77₁₀₄, JP 52₁₈₈, TC 70₅₅₅. + '4a3f14c,' + // #925: 5 fonts: HK 77₁₀₄, JP 119₂₅₅, KR 95₃₅₅, SC 68₄₅₂, TC 71₅₅₆. + '4a5u3v3s3z,' + // #926: 6 fonts: HK 80₁₀₇, JP 57₁₉₃, KR 97₃₅₇, SC 71₄₅₅, TC 74₅₅₉, Symbols₇₀₂. + '4d3h6h3t3z5m,' + // #927: 6 fonts: HK 80₁₀₇, JP 58₁₉₄, KR 97₃₅₇, SC 71₄₅₅, TC 74₅₅₉, Symbols₇₀₂. + '4d3i6g3t3z5m,' + // #928: 10 fonts: HK 84₁₁₁, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 75₄₅₉, SC 99₄₈₃, TC 79₅₆₄, TC 103₅₈₈, Noto Sans₅₉₁. + '4hw2i2k4t2yx3cxc,' + // #929: 3 fonts: HK 90₁₁₇, SC 95₄₇₉, TC 86₅₇₁. + '4n13x3n,' + // #930: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 101₃₆₁, SC 95₄₇₉, TC 99₅₈₄. + '5a4r4e4n4a,' + // #931: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 102₃₆₂, SC 95₄₇₉, TC 99₅₈₄. + '5a4r4f4m4a,' + // #932: 8 fonts: HK 106₁₃₃, JP 59₁₉₅, JP 121₂₅₇, KR 121₃₈₁, SC 73₄₅₇, SC 98₄₈₂, TC 102₅₈₇, Noto Sans₅₉₁. + '5d2j2j4t2xy4ad,' + // #933: 2 fonts: JP 5₁₄₁, KR 2₂₆₂. + '5l4q,' + // #934: 2 fonts: JP 12₁₄₈, SC 13₃₉₇. + '5s9o,' + // #935: 2 fonts: JP 19₁₅₅, SC 21₄₀₅. + '5z9p,' + // #936: 2 fonts: JP 24₁₆₀, SC 28₄₁₂. + '6e9r,' + // #937: 2 fonts: JP 30₁₆₆, SC 37₄₂₁. + '6k9u,' + // #938: 2 fonts: JP 31₁₆₇, SC 39₄₂₃. + '6l9v,' + // #939: 2 fonts: JP 32₁₆₈, SC 41₄₂₅. + '6m9w,' + // #940: 2 fonts: JP 35₁₇₁, SC 46₄₃₀. + '6p9y,' + // #941: 2 fonts: JP 39₁₇₅, SC 51₄₃₅. + '6t9z,' + // #942: 2 fonts: JP 41₁₇₇, SC 54₄₃₈. + '6v10a,' + // #943: 2 fonts: JP 44₁₈₀, KR 89₃₄₉. + '6y6m,' + // #944: 2 fonts: JP 44₁₈₀, SC 59₄₄₃. + '6y10c,' + // #945: 2 fonts: JP 47₁₈₃, SC 62₄₄₆. + '7b10c,' + // #946: 1 font: JP 57₁₉₃. + '7l,' + // #947: 3 fonts: JP 58₁₉₄, KR 97₃₅₇, Symbols₇₀₂. + '7m6g13g,' + // #948: 1 font: KR 37₂₉₇. + '11l,' + // #949: 3 fonts: KR 99₃₅₉, Noto Sans₅₉₁, Math₆₅₅. + '13v8x2l,' + // #950: 2 fonts: Noto Sans₅₉₁, Devanagari₆₁₅. + '22tx,' + // #951: 1 font: Armenian₅₉₅. + '22x,' + // #952: 1 font: Limbu₆₄₃. + '24t,' + // #953: 1 font: Multani₆₆₄. + '25o,' + // #954: 1 font: Pahawh Hmong₆₈₄. + '26i,' + // #955: 1 font: Tai Tham₇₀₇. + '27f,' + // #956: 3 fonts: Noto Color Emoji 7₇, Noto Color Emoji 9₉, Symbols 2 3₁₅. + 'hbf,' + // #957: 143 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, HK 104₁₃₁, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 96₄₈₀, SC 99₄₈₃, TC 100₅₈₅, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc4bc2i2k4t3tc3xcbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #958: 1 font: Cuneiform 0₁₈. + 's,' + // #959: 2 fonts: HK 1₂₈, JP 0₁₃₆. + '1c4d,' + // #960: 2 fonts: HK 2₂₉, JP 0₁₃₆. + '1d4c,' + // #961: 2 fonts: HK 3₃₀, JP 0₁₃₆. + '1e4b,' + // #962: 2 fonts: HK 4₃₁, TC 0₄₈₅. + '1f17l,' + // #963: 4 fonts: HK 11₃₈, JP 90₂₂₆, SC 1₃₈₅, TC 1₄₈₆. + '1m7f6c3w,' + // #964: 5 fonts: HK 12₃₉, JP 4₁₄₀, KR 0₂₆₀, SC 2₃₈₆, TC 3₄₈₈. + '1n3w4p4v3x,' + // #965: 4 fonts: HK 14₄₁, JP 5₁₄₁, KR 65₃₂₅, TC 4₄₈₉. + '1p3v7b6h,' + // #966: 2 fonts: HK 14₄₁, TC 4₄₈₉. + '1p17f,' + // #967: 3 fonts: HK 15₄₂, JP 5₁₄₁, SC 4₃₈₈. + '1q3u9m,' + // #968: 3 fonts: HK 15₄₂, JP 5₁₄₁, TC 5₄₉₀. + '1q3u13k,' + // #969: 4 fonts: HK 16₄₃, JP 6₁₄₂, SC 5₃₈₉, TC 5₄₉₀. + '1r3u9m3w,' + // #970: 4 fonts: HK 16₄₃, JP 6₁₄₂, SC 6₃₉₀, TC 6₄₉₁. + '1r3u9n3w,' + // #971: 2 fonts: HK 16₄₃, JP 7₁₄₃. + '1r3v,' + // #972: 2 fonts: HK 16₄₃, TC 6₄₉₁. + '1r17f,' + // #973: 2 fonts: HK 17₄₄, JP 8₁₄₄. + '1s3v,' + // #974: 4 fonts: HK 18₄₅, JP 8₁₄₄, KR 65₃₂₅, TC 8₄₉₃. + '1t3u6y6l,' + // #975: 5 fonts: HK 18₄₅, JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 8₄₉₃. + '1t3u6z2m3x,' + // #976: 3 fonts: HK 19₄₆, JP 8₁₄₄, TC 9₄₉₄. + '1u3t13l,' + // #977: 5 fonts: HK 20₄₇, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 10₄₉₅. + '1v3t6y2n3y,' + // #978: 2 fonts: HK 20₄₇, TC 11₄₉₆. + '1v17g,' + // #979: 5 fonts: HK 21₄₈, JP 10₁₄₆, KR 67₃₂₇, SC 10₃₉₄, TC 11₄₉₆. + '1w3t6y2o3x,' + // #980: 3 fonts: HK 22₄₉, JP 11₁₄₇, TC 12₄₉₇. + '1x3t13l,' + // #981: 3 fonts: HK 23₅₀, SC 13₃₉₇, TC 14₄₉₉. + '1y13i3x,' + // #982: 5 fonts: HK 24₅₁, JP 12₁₄₈, KR 68₃₂₈, SC 13₃₉₇, TC 15₅₀₀. + '1z3s6x2q3y,' + // #983: 2 fonts: HK 26₅₃, JP 13₁₄₉. + '2b3r,' + // #984: 3 fonts: HK 26₅₃, SC 15₃₉₉, TC 17₅₀₂. + '2b13h3y,' + // #985: 4 fonts: HK 27₅₄, JP 14₁₅₀, SC 16₄₀₀, TC 17₅₀₂. + '2c3r9p3x,' + // #986: 5 fonts: HK 29₅₆, JP 16₁₅₂, KR 70₃₃₀, SC 19₄₀₃, TC 21₅₀₆. + '2e3r6v2u3y,' + // #987: 4 fonts: HK 30₅₇, JP 16₁₅₂, SC 19₄₀₃, TC 21₅₀₆. + '2f3q9q3y,' + // #988: 2 fonts: HK 31₅₈, TC 23₅₀₈. + '2g17h,' + // #989: 4 fonts: HK 33₆₀, JP 19₁₅₅, KR 72₃₃₂, TC 24₅₀₉. + '2i3q6u6u,' + // #990: 5 fonts: HK 34₆₁, JP 20₁₅₆, KR 72₃₃₂, SC 23₄₀₇, TC 25₅₁₀. + '2j3q6t2w3y,' + // #991: 1 font: HK 35₆₂. + '2k,' + // #992: 4 fonts: HK 35₆₂, JP 22₁₅₈, SC 25₄₀₉, TC 27₅₁₂. + '2k3r9q3y,' + // #993: 2 fonts: HK 35₆₂, SC 24₄₀₈. + '2k13h,' + // #994: 5 fonts: HK 37₆₄, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 30₅₁₅. + '2m3q6s2y3z,' + // #995: 2 fonts: HK 38₆₅, JP 23₁₅₉. + '2n3p,' + // #996: 5 fonts: HK 39₆₆, JP 24₁₆₀, KR 75₃₃₅, SC 29₄₁₃, TC 31₅₁₆. + '2o3p6s2z3y,' + // #997: 2 fonts: HK 39₆₆, SC 29₄₁₃. + '2o13i,' + // #998: 2 fonts: HK 40₆₇, SC 30₄₁₄. + '2p13i,' + // #999: 3 fonts: HK 40₆₇, SC 31₄₁₅, TC 33₅₁₈. + '2p13j3y,' + // #1000: 2 fonts: HK 40₆₇, TC 33₅₁₈. + '2p17i,' + // #1001: 5 fonts: HK 41₆₈, JP 25₁₆₁, KR 76₃₃₆, SC 31₄₁₅, TC 33₅₁₈. + '2q3o6s3a3y,' + // #1002: 1 font: HK 42₆₉. + '2r,' + // #1003: 2 fonts: HK 42₆₉, SC 33₄₁₇. + '2r13j,' + // #1004: 3 fonts: HK 43₇₀, JP 27₁₆₃, TC 35₅₂₀. + '2s3o13s,' + // #1005: 3 fonts: HK 43₇₀, JP 27₁₆₃, TC 36₅₂₁. + '2s3o13t,' + // #1006: 2 fonts: HK 43₇₀, TC 36₅₂₁. + '2s17i,' + // #1007: 3 fonts: HK 45₇₂, SC 37₄₂₁, TC 38₅₂₃. + '2u13k3x,' + // #1008: 1 font: HK 46₇₃. + '2v,' + // #1009: 3 fonts: HK 46₇₃, JP 29₁₆₅, TC 38₅₂₃. + '2v3n13t,' + // #1010: 4 fonts: HK 47₇₄, JP 30₁₆₆, SC 39₄₂₃, TC 40₅₂₅. + '2w3n9w3x,' + // #1011: 3 fonts: HK 47₇₄, JP 31₁₆₇, TC 40₅₂₅. + '2w3o13t,' + // #1012: 5 fonts: HK 49₇₆, JP 31₁₆₇, KR 80₃₄₀, SC 40₄₂₄, TC 42₅₂₇. + '2y3m6q3f3y,' + // #1013: 3 fonts: HK 49₇₆, SC 41₄₂₅, TC 43₅₂₈. + '2y13k3y,' + // #1014: 3 fonts: HK 52₇₉, SC 44₄₂₈, TC 46₅₃₁. + '3b13k3y,' + // #1015: 2 fonts: HK 52₇₉, TC 45₅₃₀. + '3b17i,' + // #1016: 2 fonts: HK 53₈₀, JP 35₁₇₁. + '3c3m,' + // #1017: 5 fonts: HK 54₈₁, JP 36₁₇₂, KR 82₃₄₂, SC 46₄₃₀, TC 48₅₃₃. + '3d3m6n3j3y,' + // #1018: 1 font: HK 55₈₂. + '3e,' + // #1019: 2 fonts: HK 56₈₃, JP 37₁₇₃. + '3f3l,' + // #1020: 5 fonts: HK 56₈₃, JP 37₁₇₃, KR 83₃₄₃, SC 48₄₃₂, TC 50₅₃₅. + '3f3l6n3k3y,' + // #1021: 2 fonts: HK 56₈₃, TC 50₅₃₅. + '3f17j,' + // #1022: 4 fonts: HK 57₈₄, JP 37₁₇₃, SC 49₄₃₃, TC 51₅₃₆. + '3g3k9z3y,' + // #1023: 3 fonts: HK 57₈₄, SC 48₄₃₂, TC 51₅₃₆. + '3g13j3z,' + // #1024: 5 fonts: HK 58₈₅, JP 38₁₇₄, KR 85₃₄₅, SC 50₄₃₄, TC 52₅₃₇. + '3h3k6o3k3y,' + // #1025: 3 fonts: HK 58₈₅, JP 39₁₇₅, TC 53₅₃₈. + '3h3l13y,' + // #1026: 3 fonts: HK 58₈₅, SC 49₄₃₃, TC 52₅₃₇. + '3h13j3z,' + // #1027: 3 fonts: HK 59₈₆, SC 50₄₃₄, TC 53₅₃₈. + '3i13j3z,' + // #1028: 3 fonts: HK 59₈₆, SC 51₄₃₅, TC 53₅₃₈. + '3i13k3y,' + // #1029: 4 fonts: HK 60₈₇, JP 40₁₇₆, SC 51₄₃₅, TC 54₅₃₉. + '3j3k9y3z,' + // #1030: 3 fonts: HK 60₈₇, SC 52₄₃₆, TC 55₅₄₀. + '3j13k3z,' + // #1031: 5 fonts: HK 61₈₈, JP 40₁₇₆, KR 86₃₄₆, SC 53₄₃₇, TC 55₅₄₀. + '3k3j6n3m3y,' + // #1032: 5 fonts: HK 61₈₈, JP 41₁₇₇, KR 87₃₄₇, SC 53₄₃₇, TC 56₅₄₁. + '3k3k6n3l3z,' + // #1033: 3 fonts: HK 62₈₉, JP 41₁₇₇, TC 56₅₄₁. + '3l3j13z,' + // #1034: 2 fonts: HK 63₉₀, SC 55₄₃₉. + '3m13k,' + // #1035: 2 fonts: HK 65₉₂, JP 43₁₇₉. + '3o3i,' + // #1036: 5 fonts: HK 65₉₂, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 60₅₄₅. + '3o3j6m3o3y,' + // #1037: 3 fonts: HK 65₉₂, JP 44₁₈₀, TC 61₅₄₆. + '3o3j14b,' + // #1038: 5 fonts: HK 66₉₃, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 61₅₄₆. + '3p3i6m3o3z,' + // #1039: 5 fonts: HK 66₉₃, JP 44₁₈₀, KR 89₃₄₉, SC 59₄₄₃, TC 61₅₄₆. + '3p3i6m3p3y,' + // #1040: 2 fonts: HK 66₉₃, SC 58₄₄₂. + '3p13k,' + // #1041: 2 fonts: HK 66₉₃, SC 59₄₄₃. + '3p13l,' + // #1042: 5 fonts: HK 67₉₄, JP 45₁₈₁, KR 90₃₅₀, SC 60₄₄₄, TC 62₅₄₇. + '3q3i6m3p3y,' + // #1043: 2 fonts: HK 67₉₄, TC 62₅₄₇. + '3q17k,' + // #1044: 3 fonts: HK 68₉₅, JP 46₁₈₂, TC 64₅₄₉. + '3r3i14c,' + // #1045: 1 font: HK 70₉₇. + '3t,' + // #1046: 5 fonts: HK 70₉₇, JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇, TC 65₅₅₀. + '3t3h6l3r3y,' + // #1047: 3 fonts: HK 70₉₇, JP 47₁₈₃, TC 65₅₅₀. + '3t3h14c,' + // #1048: 3 fonts: HK 70₉₇, JP 47₁₈₃, TC 66₅₅₁. + '3t3h14d,' + // #1049: 3 fonts: HK 70₉₇, SC 64₄₄₈, TC 66₅₅₁. + '3t13m3y,' + // #1050: 5 fonts: HK 71₉₈, JP 48₁₈₄, KR 92₃₅₂, SC 65₄₄₉, TC 67₅₅₂. + '3u3h6l3s3y,' + // #1051: 2 fonts: HK 71₉₈, JP 49₁₈₅. + '3u3i,' + // #1052: 3 fonts: HK 71₉₈, SC 65₄₄₉, TC 67₅₅₂. + '3u13m3y,' + // #1053: 1 font: HK 72₉₉. '3v,' - // #198: 1 font: Phoenician₁₀₄. - '4a,' - // #199: 1 font: Rejang₁₀₆. - '4c,' - // #200: 2 fonts: SC₁₀₈, TC₁₂₂. - '4en,' - // #201: 1 font: Saurashtra₁₀₉. - '4f,' - // #202: 1 font: Siddham₁₁₂. - '4i,' - // #203: 1 font: Sora Sompeng₁₁₅. - '4l,' - // #204: 1 font: Sundanese₁₁₇. - '4n,' - // #205: 2 fonts: Symbols₁₁₉, Symbols 2₁₂₀. - '4pa,' - // #206: 2 fonts: Symbols₁₁₉, Noto Color Emoji 4₁₄₇. - '4p1b,' - // #207: 2 fonts: Symbols₁₁₉, Noto Color Emoji 8₁₅₁. - '4p1f,' - // #208: 2 fonts: Symbols₁₁₉, Noto Color Emoji 9₁₅₂. - '4p1g,' - // #209: 3 fonts: Symbols 2₁₂₀, Noto Color Emoji 2₁₄₅, Noto Color Emoji 10₁₅₃. - '4qyh,' - // #210: 3 fonts: Symbols 2₁₂₀, Noto Color Emoji 3₁₄₆, Noto Color Emoji 4₁₄₇. - '4qza,' - // #211: 3 fonts: Symbols 2₁₂₀, Noto Color Emoji 3₁₄₆, Noto Color Emoji 8₁₅₁. - '4qze,' - // #212: 3 fonts: Symbols 2₁₂₀, Noto Color Emoji 5₁₄₈, Noto Color Emoji 8₁₅₁. - '4q1bc,' - // #213: 1 font: Tagalog₁₂₃. - '4t,' - // #214: 1 font: Tai Le₁₂₅. - '4v,' - // #215: 1 font: Tai Viet₁₂₇. - '4x,' - // #216: 1 font: Takri₁₂₈. - '4y,' - // #217: 1 font: Tamil Supplement₁₃₀. - '5a,' - // #218: 1 font: Thai₁₃₃. - '5d,' - // #219: 1 font: Tirhuta₁₃₅. - '5f,' - // #220: 1 font: Ugaritic₁₃₆. - '5g,' - // #221: 1 font: Wancho₁₃₈. - '5i,' - // #222: 1 font: Warang Citi₁₃₉. - '5j,' - // #223: 1 font: Yi₁₄₀. + // #1054: 2 fonts: HK 72₉₉, JP 49₁₈₅. + '3v3h,' + // #1055: 4 fonts: HK 72₉₉, JP 49₁₈₅, SC 65₄₄₉, TC 67₅₅₂. + '3v3h10d3y,' + // #1056: 4 fonts: HK 72₉₉, JP 50₁₈₆, SC 66₄₅₀, TC 68₅₅₃. + '3v3i10d3y,' + // #1057: 3 fonts: HK 72₉₉, JP 50₁₈₆, TC 68₅₅₃. + '3v3i14c,' + // #1058: 2 fonts: HK 73₁₀₀, JP 50₁₈₆. + '3w3h,' + // #1059: 5 fonts: HK 73₁₀₀, JP 50₁₈₆, KR 93₃₅₃, SC 67₄₅₁, TC 69₅₅₄. + '3w3h6k3t3y,' + // #1060: 3 fonts: HK 73₁₀₀, SC 66₄₅₀, TC 69₅₅₄. + '3w13l3z,' + // #1061: 3 fonts: HK 73₁₀₀, SC 67₄₅₁, TC 69₅₅₄. + '3w13m3y,' + // #1062: 2 fonts: HK 75₁₀₂, TC 70₅₅₅. + '3y17k,' + // #1063: 3 fonts: HK 80₁₀₇, JP 57₁₉₃, TC 73₅₅₈. + '4d3h14a,' + // #1064: 6 fonts: HK 81₁₀₈, JP 58₁₉₄, KR 99₃₅₉, SC 72₄₅₆, TC 76₅₆₁, Noto Sans₅₉₁. + '4e3h6i3s4a1d,' + // #1065: 4 fonts: HK 85₁₁₂, JP 11₁₄₇, SC 12₃₉₆, TC 81₅₆₆. + '4i1i9o6n,' + // #1066: 5 fonts: HK 89₁₁₆, JP 12₁₄₈, KR 67₃₂₇, SC 13₃₉₇, TC 85₅₇₀. + '4m1f6w2r6q,' + // #1067: 5 fonts: HK 90₁₁₇, JP 16₁₅₂, KR 70₃₃₀, SC 18₄₀₂, TC 86₅₇₁. + '4n1i6v2t6m,' + // #1068: 5 fonts: HK 97₁₂₄, JP 5₁₄₁, KR 65₃₂₅, SC 4₃₈₈, TC 93₅₇₈. + '4uq7b2k7h,' + // #1069: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 102₃₆₂, SC 95₄₇₉, TC 99₅₈₄. + '5a4q4g4m4a,' + // #1070: 4 fonts: HK 104₁₃₁, JP 54₁₉₀, SC 68₄₅₂, TC 100₅₈₅. + '5b2g10b5c,' + // #1071: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 103₃₆₃, SC 95₄₇₉, TC 100₅₈₅. + '5b4r4f4l4b,' + // #1072: 2 fonts: JP 7₁₄₃, SC 6₃₉₀. + '5n9m,' + // #1073: 2 fonts: JP 8₁₄₄, SC 6₃₉₀. + '5o9l,' + // #1074: 2 fonts: JP 9₁₄₅, SC 8₃₉₂. + '5p9m,' + // #1075: 2 fonts: JP 16₁₅₂, SC 18₄₀₂. + '5w9p,' + // #1076: 2 fonts: JP 17₁₅₃, SC 19₄₀₃. + '5x9p,' + // #1077: 2 fonts: JP 18₁₅₄, SC 21₄₀₅. + '5y9q,' + // #1078: 2 fonts: JP 20₁₅₆, SC 23₄₀₇. + '6a9q,' + // #1079: 2 fonts: JP 22₁₅₈, SC 26₄₁₀. + '6c9r,' + // #1080: 2 fonts: JP 28₁₆₄, SC 34₄₁₈. + '6i9t,' + // #1081: 2 fonts: JP 31₁₆₇, SC 40₄₂₄. + '6l9w,' + // #1082: 2 fonts: JP 56₁₉₂, KR 96₃₅₆. + '7k6h,' + // #1083: 2 fonts: JP 58₁₉₄, Noto Sans₅₉₁. + '7m15g,' + // #1084: 2 fonts: JP 58₁₉₄, Symbols₇₀₂. + '7m19n,' + // #1085: 2 fonts: JP 59₁₉₅, Noto Sans₅₉₁. + '7n15f,' + // #1086: 1 font: Noto Music₅₉₀. + '22s,' + // #1087: 130 fonts: Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + '22saaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1088: 2 fonts: Noto Sans₅₉₁, Elbasan₆₁₆. + '22ty,' + // #1089: 2 fonts: Noto Sans₅₉₁, Glagolitic₆₂₀. + '22t1c,' + // #1090: 2 fonts: Arabic₅₉₄, Thaana₇₁₃. + '22w4o,' + // #1091: 1 font: Bhaiksuki₆₀₂. + '23e,' + // #1092: 1 font: Cham₆₁₀. + '23m,' + // #1093: 2 fonts: Devanagari₆₁₅, Sharada₆₉₃. + '23r2z,' + // #1094: 1 font: Khmer₆₃₈. + '24o,' + // #1095: 1 font: Myanmar₆₆₅. + '25p,' + // #1096: 1 font: New Tai Lue₆₆₈. + '25s,' + // #1097: 5 fonts: Noto Color Emoji 2₂, HK 11₃₈, JP 2₁₃₈, SC 0₃₈₄, TC 1₄₈₆. + 'c1j3v9l3x,' + // #1098: 4 fonts: Noto Color Emoji 2₂, HK 11₃₈, JP 2₁₃₈, TC 1₄₈₆. + 'c1j3v13j,' + // #1099: 2 fonts: Noto Color Emoji 2₂, JP 2₁₃₈. + 'c5f,' + // #1100: 2 fonts: Noto Color Emoji 3₃, Noto Color Emoji 4₄. + 'da,' + // #1101: 2 fonts: Noto Color Emoji 3₃, Noto Color Emoji 8₈. + 'de,' + // #1102: 2 fonts: Noto Color Emoji 4₄, Noto Color Emoji 8₈. + 'ed,' + // #1103: 2 fonts: Noto Color Emoji 4₄, Noto Color Emoji 9₉. + 'ee,' + // #1104: 2 fonts: Noto Color Emoji 5₅, Noto Color Emoji 8₈. + 'fc,' + // #1105: 146 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'nbbccc4ad4oe4pd3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1106: 6 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, KR 96₃₅₆, SC 70₄₅₄, TC 73₅₅₈. + 'p3m3h6h3t3z,' + // #1107: 6 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 57₁₉₃, KR 96₃₅₆, SC 70₄₅₄, TC 73₅₅₈. + 'p3m3i6g3t3z,' + // #1108: 2 fonts: Symbols 2 3₁₅, JP 56₁₉₂. + 'p6u,' + // #1109: 140 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 99₄₈₃, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc4e2i2k4t3w4abaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1110: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 84₁₁₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3gx4oe3vx2yx3cxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1111: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad4oe3vx2yx3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1112: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad4oe4pd3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1113: 143 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad4oe4t2yx3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1114: 134 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc21raaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1115: 1 font: Egyptian Hieroglyphs 0₂₄. + 'y,' + // #1116: 2 fonts: HK 0₂₇, JP 0₁₃₆. + '1b4e,' + // #1117: 2 fonts: HK 9₃₆, JP 1₁₃₇. + '1k3w,' + // #1118: 4 fonts: HK 11₃₈, JP 2₁₃₈, SC 1₃₈₅, TC 1₄₈₆. + '1m3v9m3w,' + // #1119: 4 fonts: HK 11₃₈, JP 78₂₁₄, SC 1₃₈₅, TC 1₄₈₆. + '1m6t6o3w,' + // #1120: 4 fonts: HK 11₃₈, JP 78₂₁₄, SC 1₃₈₅, TC 2₄₈₇. + '1m6t6o3x,' + // #1121: 5 fonts: HK 12₃₉, JP 95₂₃₁, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1n7j1c4u3x,' + // #1122: 1 font: HK 14₄₁. + '1p,' + // #1123: 5 fonts: HK 15₄₂, JP 6₁₄₂, KR 65₃₂₅, SC 5₃₈₉, TC 5₄₉₀. + '1q3v7a2l3w,' + // #1124: 5 fonts: HK 16₄₃, JP 7₁₄₃, KR 65₃₂₅, SC 6₃₉₀, TC 6₄₉₁. + '1r3v6z2m3w,' + // #1125: 4 fonts: HK 16₄₃, JP 7₁₄₃, KR 65₃₂₅, TC 6₄₉₁. + '1r3v6z6j,' + // #1126: 4 fonts: HK 16₄₃, JP 7₁₄₃, SC 6₃₉₀, TC 6₄₉₁. + '1r3v9m3w,' + // #1127: 5 fonts: HK 17₄₄, JP 7₁₄₃, KR 65₃₂₅, SC 6₃₉₀, TC 7₄₉₂. + '1s3u6z2m3x,' + // #1128: 3 fonts: HK 18₄₅, JP 8₁₄₄, SC 7₃₉₁. + '1t3u9m,' + // #1129: 4 fonts: HK 18₄₅, JP 8₁₄₄, SC 7₃₉₁, TC 9₄₉₄. + '1t3u9m3y,' + // #1130: 5 fonts: HK 19₄₆, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 9₄₉₄. + '1u3u6y2n3x,' + // #1131: 4 fonts: HK 19₄₆, JP 9₁₄₅, SC 8₃₉₂, TC 10₄₉₅. + '1u3u9m3y,' + // #1132: 3 fonts: HK 19₄₆, SC 8₃₉₂, TC 9₄₉₄. + '1u13h3x,' + // #1133: 2 fonts: HK 19₄₆, TC 10₄₉₅. + '1u17g,' + // #1134: 5 fonts: HK 20₄₇, JP 9₁₄₅, KR 66₃₂₆, SC 9₃₉₃, TC 10₄₉₅. + '1v3t6y2o3x,' + // #1135: 5 fonts: HK 20₄₇, JP 10₁₄₆, KR 67₃₂₇, SC 9₃₉₃, TC 10₄₉₅. + '1v3u6y2n3x,' + // #1136: 3 fonts: HK 20₄₇, JP 10₁₄₆, TC 11₄₉₆. + '1v3u13l,' + // #1137: 3 fonts: HK 20₄₇, SC 8₃₉₂, TC 10₄₉₅. + '1v13g3y,' + // #1138: 5 fonts: HK 21₄₈, JP 10₁₄₆, KR 67₃₂₇, SC 9₃₉₃, TC 11₄₉₆. + '1w3t6y2n3y,' + // #1139: 4 fonts: HK 21₄₈, JP 10₁₄₆, SC 9₃₉₃, TC 11₄₉₆. + '1w3t9m3y,' + // #1140: 3 fonts: HK 21₄₈, SC 10₃₉₄, TC 11₄₉₆. + '1w13h3x,' + // #1141: 4 fonts: HK 22₄₉, JP 11₁₄₇, SC 11₃₉₅, TC 12₄₉₇. + '1x3t9n3x,' + // #1142: 4 fonts: HK 23₅₀, JP 11₁₄₇, KR 67₃₂₇, TC 13₄₉₈. + '1y3s6x6o,' + // #1143: 4 fonts: HK 23₅₀, JP 11₁₄₇, SC 13₃₉₇, TC 14₄₉₉. + '1y3s9p3x,' + // #1144: 5 fonts: HK 23₅₀, JP 12₁₄₈, KR 67₃₂₇, SC 13₃₉₇, TC 14₄₉₉. + '1y3t6w2r3x,' + // #1145: 1 font: HK 25₅₂. + '2a,' + // #1146: 3 fonts: HK 25₅₂, JP 12₁₄₈, TC 15₅₀₀. + '2a3r13n,' + // #1147: 2 fonts: HK 25₅₂, TC 15₅₀₀. + '2a17f,' + // #1148: 3 fonts: HK 26₅₃, JP 13₁₄₉, TC 17₅₀₂. + '2b3r13o,' + // #1149: 2 fonts: HK 27₅₄, JP 14₁₅₀. + '2c3r,' + // #1150: 5 fonts: HK 27₅₄, JP 14₁₅₀, KR 69₃₂₉, SC 15₃₉₉, TC 17₅₀₂. + '2c3r6w2r3y,' + // #1151: 3 fonts: HK 27₅₄, JP 14₁₅₀, TC 18₅₀₃. + '2c3r13o,' + // #1152: 5 fonts: HK 28₅₅, JP 15₁₅₁, KR 69₃₂₉, SC 16₄₀₀, TC 19₅₀₄. + '2d3r6v2s3z,' + // #1153: 5 fonts: HK 28₅₅, JP 15₁₅₁, KR 69₃₂₉, SC 17₄₀₁, TC 19₅₀₄. + '2d3r6v2t3y,' + // #1154: 5 fonts: HK 28₅₅, JP 15₁₅₁, KR 70₃₃₀, SC 17₄₀₁, TC 19₅₀₄. + '2d3r6w2s3y,' + // #1155: 5 fonts: HK 30₅₇, JP 17₁₅₃, KR 71₃₃₁, SC 19₄₀₃, TC 22₅₀₇. + '2f3r6v2t3z,' + // #1156: 1 font: HK 31₅₈. + '2g,' + // #1157: 2 fonts: HK 31₅₈, JP 17₁₅₃. + '2g3q,' + // #1158: 5 fonts: HK 31₅₈, JP 18₁₅₄, KR 71₃₃₁, SC 21₄₀₅, TC 23₅₀₈. + '2g3r6u2v3y,' + // #1159: 5 fonts: HK 32₅₉, JP 18₁₅₄, KR 71₃₃₁, SC 21₄₀₅, TC 23₅₀₈. + '2h3q6u2v3y,' + // #1160: 2 fonts: HK 33₆₀, JP 19₁₅₅. + '2i3q,' + // #1161: 4 fonts: HK 33₆₀, JP 19₁₅₅, SC 22₄₀₆, TC 25₅₁₀. + '2i3q9q3z,' + // #1162: 2 fonts: HK 33₆₀, TC 24₅₀₉. + '2i17g,' + // #1163: 2 fonts: HK 34₆₁, JP 20₁₅₆. + '2j3q,' + // #1164: 3 fonts: HK 34₆₁, JP 20₁₅₆, TC 25₅₁₀. + '2j3q13p,' + // #1165: 2 fonts: HK 34₆₁, TC 25₅₁₀. + '2j17g,' + // #1166: 2 fonts: HK 35₆₂, SC 25₄₀₉. + '2k13i,' + // #1167: 5 fonts: HK 37₆₄, JP 22₁₅₈, KR 74₃₃₄, SC 26₄₁₀, TC 29₅₁₄. + '2m3p6t2x3z,' + // #1168: 3 fonts: HK 37₆₄, JP 23₁₅₉, TC 29₅₁₄. + '2m3q13q,' + // #1169: 3 fonts: HK 37₆₄, SC 26₄₁₀, TC 29₅₁₄. + '2m13h3z,' + // #1170: 2 fonts: HK 37₆₄, TC 29₅₁₄. + '2m17h,' + // #1171: 3 fonts: HK 38₆₅, JP 23₁₅₉, SC 27₄₁₁. + '2n3p9r,' + // #1172: 2 fonts: HK 38₆₅, TC 31₅₁₆. + '2n17i,' + // #1173: 5 fonts: HK 39₆₆, JP 25₁₆₁, KR 75₃₃₅, SC 30₄₁₄, TC 32₅₁₇. + '2o3q6r3a3y,' + // #1174: 3 fonts: HK 39₆₆, SC 29₄₁₃, TC 32₅₁₇. + '2o13i3z,' + // #1175: 2 fonts: HK 39₆₆, TC 31₅₁₆. + '2o17h,' + // #1176: 1 font: HK 40₆₇. + '2p,' + // #1177: 3 fonts: HK 40₆₇, JP 25₁₆₁, TC 32₅₁₇. + '2p3p13r,' + // #1178: 2 fonts: HK 40₆₇, TC 32₅₁₇. + '2p17h,' + // #1179: 5 fonts: HK 41₆₈, JP 26₁₆₂, KR 76₃₃₆, SC 31₄₁₅, TC 33₅₁₈. + '2q3p6r3a3y,' + // #1180: 2 fonts: HK 42₆₉, JP 27₁₆₃. + '2r3p,' + // #1181: 2 fonts: HK 42₆₉, SC 32₄₁₆. + '2r13i,' + // #1182: 2 fonts: HK 43₇₀, JP 27₁₆₃. + '2s3o,' + // #1183: 5 fonts: HK 43₇₀, JP 27₁₆₃, KR 77₃₃₇, SC 34₄₁₈, TC 36₅₂₁. + '2s3o6r3c3y,' + // #1184: 4 fonts: HK 43₇₀, JP 28₁₆₄, KR 77₃₃₇, TC 36₅₂₁. + '2s3p6q7b,' + // #1185: 2 fonts: HK 44₇₁, JP 28₁₆₄. + '2t3o,' + // #1186: 3 fonts: HK 44₇₁, JP 29₁₆₅, TC 37₅₂₂. + '2t3p13s,' + // #1187: 3 fonts: HK 44₇₁, SC 35₄₁₉, TC 36₅₂₁. + '2t13j3x,' + // #1188: 4 fonts: HK 45₇₂, JP 29₁₆₅, KR 77₃₃₇, TC 37₅₂₂. + '2u3o6p7c,' + // #1189: 4 fonts: HK 45₇₂, JP 29₁₆₅, SC 37₄₂₁, TC 38₅₂₃. + '2u3o9v3x,' + // #1190: 3 fonts: HK 46₇₃, JP 29₁₆₅, SC 37₄₂₁. + '2v3n9v,' + // #1191: 5 fonts: HK 46₇₃, JP 30₁₆₆, KR 78₃₃₈, SC 38₄₂₂, TC 39₅₂₄. + '2v3o6p3f3x,' + // #1192: 2 fonts: HK 46₇₃, TC 38₅₂₃. + '2v17h,' + // #1193: 2 fonts: HK 47₇₄, SC 38₄₂₂. + '2w13j,' + // #1194: 2 fonts: HK 48₇₅, JP 31₁₆₇. + '2x3n,' + // #1195: 4 fonts: HK 48₇₅, JP 31₁₆₇, SC 40₄₂₄, TC 41₅₂₆. + '2x3n9w3x,' + // #1196: 3 fonts: HK 48₇₅, JP 31₁₆₇, TC 42₅₂₇. + '2x3n13v,' + // #1197: 5 fonts: HK 49₇₆, JP 32₁₆₈, KR 80₃₄₀, SC 40₄₂₄, TC 42₅₂₇. + '2y3n6p3f3y,' + // #1198: 2 fonts: HK 49₇₆, SC 40₄₂₄. + '2y13j,' + // #1199: 2 fonts: HK 49₇₆, TC 43₅₂₈. + '2y17j,' + // #1200: 2 fonts: HK 50₇₇, JP 32₁₆₈. + '2z3m,' + // #1201: 2 fonts: HK 50₇₇, TC 44₅₂₉. + '2z17j,' + // #1202: 3 fonts: HK 51₇₈, JP 33₁₆₉, TC 45₅₃₀. + '3a3m13w,' + // #1203: 3 fonts: HK 51₇₈, SC 43₄₂₇, TC 45₅₃₀. + '3a13k3y,' + // #1204: 5 fonts: HK 52₇₉, JP 33₁₆₉, KR 81₃₄₁, SC 43₄₂₇, TC 45₅₃₀. + '3b3l6p3h3y,' + // #1205: 5 fonts: HK 52₇₉, JP 33₁₆₉, KR 81₃₄₁, SC 43₄₂₇, TC 46₅₃₁. + '3b3l6p3h3z,' + // #1206: 3 fonts: HK 52₇₉, JP 33₁₆₉, TC 45₅₃₀. + '3b3l13w,' + // #1207: 2 fonts: HK 52₇₉, JP 34₁₇₀. + '3b3m,' + // #1208: 5 fonts: HK 52₇₉, JP 34₁₇₀, KR 81₃₄₁, SC 44₄₂₈, TC 46₅₃₁. + '3b3m6o3i3y,' + // #1209: 4 fonts: HK 53₈₀, JP 34₁₇₀, KR 82₃₄₂, TC 46₅₃₁. + '3c3l6p7g,' + // #1210: 4 fonts: HK 53₈₀, JP 34₁₇₀, SC 44₄₂₈, TC 47₅₃₂. + '3c3l9x3z,' + // #1211: 3 fonts: HK 53₈₀, SC 44₄₂₈, TC 46₅₃₁. + '3c13j3y,' + // #1212: 2 fonts: HK 53₈₀, TC 46₅₃₁. + '3c17i,' + // #1213: 5 fonts: HK 54₈₁, JP 35₁₇₁, KR 82₃₄₂, SC 45₄₂₉, TC 48₅₃₃. + '3d3l6o3i3z,' + // #1214: 5 fonts: HK 54₈₁, JP 35₁₇₁, KR 82₃₄₂, SC 46₄₃₀, TC 48₅₃₃. + '3d3l6o3j3y,' + // #1215: 3 fonts: HK 54₈₁, SC 45₄₂₉, TC 48₅₃₃. + '3d13j3z,' + // #1216: 2 fonts: HK 54₈₁, TC 47₅₃₂. + '3d17i,' + // #1217: 3 fonts: HK 55₈₂, SC 46₄₃₀, TC 48₅₃₃. + '3e13j3y,' + // #1218: 2 fonts: HK 55₈₂, SC 47₄₃₁. + '3e13k,' + // #1219: 5 fonts: HK 56₈₃, JP 36₁₇₂, KR 83₃₄₃, SC 47₄₃₁, TC 49₅₃₄. + '3f3k6o3j3y,' + // #1220: 4 fonts: HK 56₈₃, JP 36₁₇₂, SC 47₄₃₁, TC 50₅₃₅. + '3f3k9y3z,' + // #1221: 2 fonts: HK 57₈₄, JP 37₁₇₃. + '3g3k,' + // #1222: 5 fonts: HK 57₈₄, JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂, TC 51₅₃₆. + '3g3k6o3j3z,' + // #1223: 2 fonts: HK 57₈₄, SC 49₄₃₃. + '3g13k,' + // #1224: 5 fonts: HK 58₈₅, JP 38₁₇₄, KR 84₃₄₄, SC 49₄₃₃, TC 52₅₃₇. + '3h3k6n3k3z,' + // #1225: 2 fonts: HK 58₈₅, SC 50₄₃₄. + '3h13k,' + // #1226: 2 fonts: HK 58₈₅, TC 53₅₃₈. + '3h17k,' + // #1227: 3 fonts: HK 59₈₆, JP 39₁₇₅, KR 85₃₄₅. + '3i3k6n,' + // #1228: 5 fonts: HK 59₈₆, JP 39₁₇₅, KR 86₃₄₆, SC 51₄₃₅, TC 54₅₃₉. + '3i3k6o3k3z,' + // #1229: 2 fonts: HK 59₈₆, SC 51₄₃₅. + '3i13k,' + // #1230: 3 fonts: HK 59₈₆, SC 51₄₃₅, TC 54₅₃₉. + '3i13k3z,' + // #1231: 3 fonts: HK 60₈₇, SC 51₄₃₅, TC 54₅₃₉. + '3j13j3z,' + // #1232: 3 fonts: HK 60₈₇, SC 52₄₃₆, TC 54₅₃₉. + '3j13k3y,' + // #1233: 5 fonts: HK 62₈₉, JP 41₁₇₇, KR 87₃₄₇, SC 54₄₃₈, TC 56₅₄₁. + '3l3j6n3m3y,' + // #1234: 4 fonts: HK 62₈₉, JP 41₁₇₇, KR 87₃₄₇, TC 57₅₄₂. + '3l3j6n7m,' + // #1235: 3 fonts: HK 62₈₉, SC 55₄₃₉, TC 57₅₄₂. + '3l13l3y,' + // #1236: 5 fonts: HK 63₉₀, JP 42₁₇₈, KR 88₃₄₈, SC 55₄₃₉, TC 58₅₄₃. + '3m3j6n3m3z,' + // #1237: 3 fonts: HK 63₉₀, JP 42₁₇₈, TC 57₅₄₂. + '3m3j13z,' + // #1238: 2 fonts: HK 63₉₀, JP 43₁₇₉. + '3m3k,' + // #1239: 5 fonts: HK 63₉₀, JP 43₁₇₉, KR 88₃₄₈, SC 56₄₄₀, TC 58₅₄₃. + '3m3k6m3n3y,' + // #1240: 2 fonts: HK 64₉₁, SC 56₄₄₀. + '3n13k,' + // #1241: 3 fonts: HK 65₉₂, JP 43₁₇₉, SC 57₄₄₁. + '3o3i10b,' + // #1242: 2 fonts: HK 66₉₃, JP 45₁₈₁. + '3p3j,' + // #1243: 3 fonts: HK 66₉₃, JP 45₁₈₁, SC 59₄₄₃. + '3p3j10b,' + // #1244: 2 fonts: HK 66₉₃, TC 62₅₄₇. + '3p17l,' + // #1245: 5 fonts: HK 67₉₄, JP 45₁₈₁, KR 89₃₄₉, SC 59₄₄₃, TC 62₅₄₇. + '3q3i6l3p3z,' + // #1246: 5 fonts: HK 68₉₅, JP 46₁₈₂, KR 90₃₅₀, SC 61₄₄₅, TC 64₅₄₉. + '3r3i6l3q3z,' + // #1247: 3 fonts: HK 68₉₅, SC 60₄₄₄, TC 63₅₄₈. + '3r13k3z,' + // #1248: 1 font: HK 69₉₆. + '3s,' + // #1249: 2 fonts: HK 69₉₆, JP 47₁₈₃. + '3s3i,' + // #1250: 3 fonts: HK 69₉₆, JP 47₁₈₃, TC 65₅₅₀. + '3s3i14c,' + // #1251: 2 fonts: HK 69₉₆, SC 62₄₄₆. + '3s13l,' + // #1252: 2 fonts: HK 70₉₇, TC 66₅₅₁. + '3t17l,' + // #1253: 1 font: HK 71₉₈. + '3u,' + // #1254: 2 fonts: HK 71₉₈, JP 48₁₈₄. + '3u3h,' + // #1255: 5 fonts: HK 71₉₈, JP 49₁₈₅, KR 92₃₅₂, SC 65₄₄₉, TC 67₅₅₂. + '3u3i6k3s3y,' + // #1256: 3 fonts: HK 71₉₈, SC 64₄₄₈, TC 66₅₅₁. + '3u13l3y,' + // #1257: 3 fonts: HK 71₉₈, SC 64₄₄₈, TC 67₅₅₂. + '3u13l3z,' + // #1258: 2 fonts: HK 73₁₀₀, TC 69₅₅₄. + '3w17l,' + // #1259: 5 fonts: HK 77₁₀₄, JP 54₁₉₀, KR 110₃₇₀, SC 67₄₅₁, TC 70₅₅₅. + '4a3h6x3c3z,' + // #1260: 4 fonts: HK 77₁₀₄, JP 54₁₉₀, SC 67₄₅₁, TC 70₅₅₅. + '4a3h10a3z,' + // #1261: 5 fonts: HK 77₁₀₄, JP 55₁₉₁, KR 95₃₅₅, SC 68₄₅₂, TC 71₅₅₆. + '4a3i6h3s3z,' + // #1262: 3 fonts: HK 77₁₀₄, JP 55₁₉₁, TC 71₅₅₆. + '4a3i14a,' + // #1263: 4 fonts: HK 77₁₀₄, JP 119₂₅₅, SC 68₄₅₂, TC 70₅₅₅. + '4a5u7o3y,' + // #1264: 5 fonts: HK 78₁₀₅, JP 55₁₉₁, KR 96₃₅₆, SC 68₄₅₂, TC 71₅₅₆. + '4b3h6i3r3z,' + // #1265: 5 fonts: HK 80₁₀₇, JP 57₁₉₃, KR 96₃₅₆, SC 71₄₅₅, TC 74₅₅₉. + '4d3h6g3u3z,' + // #1266: 4 fonts: HK 80₁₀₇, JP 57₁₉₃, TC 74₅₅₉, Symbols₇₀₂. + '4d3h14b5m,' + // #1267: 5 fonts: HK 81₁₀₈, JP 58₁₉₄, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e3h10b3z3q,' + // #1268: 11 fonts: HK 84₁₁₁, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Sans₅₉₁. + '4hx2h2l3vx2yx3cxb,' + // #1269: 5 fonts: HK 85₁₁₂, JP 6₁₄₂, KR 65₃₂₅, SC 5₃₈₉, TC 81₅₆₆. + '4i1d7a2l6u,' + // #1270: 4 fonts: HK 85₁₁₂, JP 46₁₈₂, SC 62₄₄₆, TC 81₅₆₆. + '4i2r10d4p,' + // #1271: 5 fonts: HK 86₁₁₃, JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂, TC 82₅₆₇. + '4j2h6o3j5e,' + // #1272: 4 fonts: HK 86₁₁₃, JP 43₁₇₉, SC 56₄₄₀, TC 82₅₆₇. + '4j2n10a4w,' + // #1273: 5 fonts: HK 88₁₁₅, JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉, TC 84₅₆₉. + '4l2k6m3n4z,' + // #1274: 4 fonts: HK 89₁₁₆, JP 45₁₈₁, SC 60₄₄₄, TC 85₅₇₀. + '4m2m10c4v,' + // #1275: 4 fonts: HK 91₁₁₈, JP 38₁₇₄, SC 49₄₃₃, TC 87₅₇₂. + '4o2d9y5i,' + // #1276: 3 fonts: HK 91₁₁₈, SC 95₄₇₉, TC 87₅₇₂. + '4o13w3o,' + // #1277: 5 fonts: HK 94₁₂₁, JP 13₁₄₉, KR 68₃₂₈, SC 14₃₉₈, TC 90₅₇₅. + '4r1b6w2r6u,' + // #1278: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 107₃₆₇, SC 96₄₈₀, TC 95₅₈₀. + '4w4y4h4i3v,' + // #1279: 5 fonts: HK 100₁₂₇, JP 10₁₄₆, KR 66₃₂₆, SC 9₃₉₃, TC 96₅₈₁. + '4xs6x2o7f,' + // #1280: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 100₃₆₀, SC 95₄₇₉, TC 99₅₈₄. + '5a4p4f4o4a,' + // #1281: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 103₃₆₃, SC 95₄₇₉, TC 99₅₈₄. + '5a4s4f4l4a,' + // #1282: 6 fonts: HK 105₁₃₂, JP 120₂₅₆, KR 120₃₈₀, SC 97₄₈₁, TC 101₅₈₆, Noto Sans₅₉₁. + '5c4t4t3w4ae,' + // #1283: 12 fonts: HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 98₄₈₂, SC 99₄₈₃, TC 102₅₈₇, TC 103₅₈₈, Noto Sans₅₉₁. + '5da2i2ja4sa3va3zac,' + // #1284: 8 fonts: HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 75₄₅₉, SC 99₄₈₃, TC 103₅₈₈, Noto Sans₅₉₁. + '5e2i2k4t2yx4ac,' + // #1285: 1 font: JP 4₁₄₀. '5k,' - // #224: 2 fonts: Noto Color Emoji 2₁₄₅, Noto Color Emoji 5₁₄₈. - '5pc,' - // #225: 2 fonts: Noto Color Emoji 6₁₄₉, Noto Color Emoji 7₁₅₀. - '5ta,' - // #226: 2 fonts: Noto Color Emoji 6₁₄₉, Noto Color Emoji 8₁₅₁. - '5tb,' - // #227: 2 fonts: Noto Color Emoji 8₁₅₁, Noto Color Emoji 9₁₅₂. - '5va,' - // #228: 2 fonts: Noto Color Emoji 9₁₅₂, Noto Color Emoji 10₁₅₃. - '5wa,' - // #229: 141 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Arabic₄, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Math₇₁, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂, Noto Color Emoji 2₁₄₅. - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac,' - // #230: 139 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Arabic₄, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Math₇₁, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaabaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #231: 140 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Math₇₁, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, Myanmar₈₁, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #232: 140 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, Myanmar₈₁, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phags Pa₁₀₃, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #233: 135 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Carian₁₇, Chakma₁₉, Cham₂₀, Cherokee₂₁, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaaaabaabaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' - // #234: 132 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Armenian₅, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Carian₁₇, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Cherokee₂₁, Coptic₂₂, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Elymaic₃₀, Ethiopic₃₁, Georgian₃₂, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Imperial Aramaic₄₃, Indic Siyaq Numbers₄₄, Inscriptional Pahlavi₄₅, Inscriptional Parthian₄₆, Javanese₄₈, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Linear A₆₀, Linear B₆₁, Lisu₆₂, Lydian₆₄, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Meroitic₇₅, Miao₇₆, Modi₇₇, Mongolian₇₈, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old Italic₉₀, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old Sogdian₉₄, Old South Arabian₉₅, Old Turkic₉₆, Oriya₉₇, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Tamil Supplement₁₃₀, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaabaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaabaaaaaabbaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaabaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaa,' - // #235: 91 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Avestan₆, Balinese₇, Bamum₈, Bassa Vah₉, Bengali₁₁, Bhaiksuki₁₂, Buginese₁₄, Buhid₁₅, Carian₁₇, Chakma₁₉, Cherokee₂₁, Cuneiform₂₃, Cypriot₂₄, Deseret₂₅, Elbasan₂₉, Ethiopic₃₁, Gothic₃₄, Gunjala Gondi₃₇, Gurmukhi₃₈, Hanunoo₄₀, Hatran₄₁, Indic Siyaq Numbers₄₄, Javanese₄₈, Kayah Li₅₂, Kharoshthi₅₃, Lao₅₇, Lepcha₅₈, Linear A₆₀, Linear B₆₁, Lisu₆₂, Mahajani₆₅, Malayalam₆₆, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Miao₇₆, Modi₇₇, Mro₇₉, Multani₈₀, NKo₈₂, Nabataean₈₃, New Tai Lue₈₄, Newa₈₅, Nushu₈₆, Ogham₈₇, Ol Chiki₈₈, Old Hungarian₈₉, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old South Arabian₉₅, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Rejang₁₀₆, Runic₁₀₇, Saurashtra₁₀₉, Sharada₁₁₀, Shavian₁₁₁, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, Syloti Nagri₁₁₈, Symbols₁₁₉, Syriac₁₂₁, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Tamil₁₂₉, Tamil Supplement₁₃₀, Tifinagh₁₃₄, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaaacaaabababbbbaadbccabacddadabaacadbaabababaaaaaaabaabcaaaabbabaaaaaaababbaaaabadaaaaaaaa,' - // #236: 98 fonts: Noto Music₀, Noto Sans₁, Adlam₂, Arabic₄, Armenian₅, Balinese₇, Bamum₈, Bassa Vah₉, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Canadian Aboriginal₁₆, Caucasian Albanian₁₈, Chakma₁₉, Cham₂₀, Coptic₂₂, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Elbasan₂₉, Ethiopic₃₁, Glagolitic₃₃, Gothic₃₄, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, HK₃₉, Hanunoo₄₀, Hebrew₄₂, JP₄₇, Javanese₄₈, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Lisu₆₂, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Marchen₆₉, Masaram Gondi₇₀, Math₇₁, Meetei Mayek₇₄, Miao₇₆, Modi₇₇, Mongolian₇₈, Myanmar₈₁, NKo₈₂, New Tai Lue₈₄, Newa₈₅, Old Permic₉₂, Old Sogdian₉₄, Oriya₉₇, Osage₉₈, Pahawh Hmong₁₀₀, Phags Pa₁₀₃, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Runic₁₀₇, SC₁₀₈, Saurashtra₁₀₉, Sharada₁₁₀, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Soyombo₁₁₆, Sundanese₁₁₇, Syloti Nagri₁₁₈, Symbols₁₁₉, Symbols 2₁₂₀, Syriac₁₂₁, TC₁₂₂, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Wancho₁₃₈, Zanabazar Square₁₄₁, Noto Serif Tibetan₁₄₂. - 'aaababaaaaaaaaabaabdaaabbaaaaaaabeaaaaaaaaaaaaccaaaaaacbaacabagbcabcbaaaaabaabaaaaaaaaabaabaaaacca,' - // #237: 8 fonts: Noto Music₀, HK₃₉, JP₄₇, KR₄₉, Math₇₁, SC₁₀₈, Symbols₁₁₉, TC₁₂₂. - 'a1mhbv1kkc,' - // #238: 7 fonts: Noto Music₀, HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols₁₁₉, TC₁₂₂. - 'a1mhb2gkc,' - // #239: 61 fonts: Noto Sans₁, Adlam₂, Anatolian Hieroglyphs₃, Balinese₇, Bassa Vah₉, Bengali₁₁, Bhaiksuki₁₂, Buginese₁₄, Buhid₁₅, Chakma₁₉, Cherokee₂₁, Cuneiform₂₃, Deseret₂₅, Gothic₃₄, HK₃₉, Hatran₄₁, Indic Siyaq Numbers₄₄, JP₄₇, KR₄₉, Kayah Li₅₂, Linear B₆₁, Lisu₆₂, Mahajani₆₅, Masaram Gondi₇₀, Mayan Numerals₇₂, Medefaidrin₇₃, Meetei Mayek₇₄, Miao₇₆, Mro₇₉, Multani₈₀, Nabataean₈₃, Ogham₈₇, Ol Chiki₈₈, Old North Arabian₉₁, Old Permic₉₂, Old Persian₉₃, Old South Arabian₉₅, Osage₉₈, Osmanya₉₉, Pahawh Hmong₁₀₀, Palmyrene₁₀₁, Pau Cin Hau₁₀₂, Phoenician₁₀₄, Runic₁₀₇, SC₁₀₈, Shavian₁₁₁, Sinhala₁₁₃, Sogdian₁₁₄, Sora Sompeng₁₁₅, Soyombo₁₁₆, TC₁₂₂, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tirhuta₁₃₅, Ugaritic₁₃₆, Vai₁₃₇, Wancho₁₃₈, Warang Citi₁₃₉, Yi₁₄₀, Noto Serif Tibetan₁₄₂. - 'baadbbabadbbbiebccbciacebaabcacdacaabcaaaabcacbaaafbaaiaaaaab,' - // #240: 4 fonts: Noto Sans₁, Adlam₂, Duployan₂₇, Syriac₁₂₁. - 'bay3p,' - // #241: 42 fonts: Noto Sans₁, Anatolian Hieroglyphs₃, Arabic₄, Balinese₇, Batak₁₀, Bengali₁₁, Bhaiksuki₁₂, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Devanagari₂₆, Gujarati₃₆, Gurmukhi₃₈, Hanunoo₄₀, Javanese₄₈, Kaithi₅₀, Kannada₅₁, Kharoshthi₅₃, Khmer₅₄, Lao₅₇, Lepcha₅₈, Limbu₅₉, Malayalam₆₆, Meetei Mayek₇₄, Myanmar₈₁, Oriya₉₇, Phags Pa₁₀₃, Rejang₁₀₆, Saurashtra₁₀₉, Sinhala₁₁₃, Sundanese₁₁₇, Syloti Nagri₁₁₈, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Tamil₁₂₉, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Noto Serif Tibetan₁₄₂. - 'bbaccaaaaakjbbhbabacaaghgpfccddaeaaaabbaai,' - // #242: 31 fonts: Noto Sans₁, Arabic₄, Armenian₅, Bengali₁₁, Coptic₂₂, Devanagari₂₆, Ethiopic₃₁, Georgian₃₂, Gujarati₃₆, Gurmukhi₃₈, HK₃₉, Hebrew₄₂, JP₄₇, KR₄₉, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Lisu₆₂, Malayalam₆₆, Oriya₉₇, SC₁₀₈, Sora Sompeng₁₁₅, Sundanese₁₁₇, Syloti Nagri₁₁₈, TC₁₂₂, Tamil₁₂₉, Telugu₁₃₁, Thai₁₃₃, Noto Serif Tibetan₁₄₂. - 'bcafkdeadbacebaaaaahd1ekgbadgbbi,' - // #243: 70 fonts: Noto Sans₁, Arabic₄, Avestan₆, Balinese₇, Batak₁₀, Bengali₁₁, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Chakma₁₉, Cham₂₀, Devanagari₂₆, Duployan₂₇, Egyptian Hieroglyphs₂₈, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, Hanunoo₄₀, Hatran₄₁, Hebrew₄₂, Javanese₄₈, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Meetei Mayek₇₄, Modi₇₇, Mongolian₇₈, Myanmar₈₁, NKo₈₂, New Tai Lue₈₄, Newa₈₅, Oriya₉₇, Pahawh Hmong₁₀₀, Phags Pa₁₀₃, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Saurashtra₁₀₉, Sharada₁₁₀, Siddham₁₁₂, Sinhala₁₁₃, Sogdian₁₁₄, Sundanese₁₁₇, Syloti Nagri₁₁₈, Syriac₁₂₁, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Warang Citi₁₃₉, Noto Serif Tibetan₁₄₂. - 'bcbacabaadafaagaaabaafbaaaaaaaaafaaafcacabalccbacabaacacbaaaaaabaaaadc,' - // #244: 75 fonts: Noto Sans₁, Arabic₄, Avestan₆, Balinese₇, Batak₁₀, Bengali₁₁, Brahmi₁₃, Buginese₁₄, Buhid₁₅, Chakma₁₉, Cham₂₀, Devanagari₂₆, Egyptian Hieroglyphs₂₈, Grantha₃₅, Gujarati₃₆, Gunjala Gondi₃₇, Gurmukhi₃₈, Hanunoo₄₀, Hebrew₄₂, Javanese₄₈, Kaithi₅₀, Kannada₅₁, Kayah Li₅₂, Kharoshthi₅₃, Khmer₅₄, Khojki₅₅, Khudawadi₅₆, Lao₅₇, Lepcha₅₈, Limbu₅₉, Mahajani₆₅, Malayalam₆₆, Mandaic₆₇, Manichaean₆₈, Meetei Mayek₇₄, Modi₇₇, Mongolian₇₈, Myanmar₈₁, NKo₈₂, New Tai Lue₈₄, Newa₈₅, Old Hungarian₈₉, Old Turkic₉₆, Oriya₉₇, Pahawh Hmong₁₀₀, Phags Pa₁₀₃, Psalter Pahlavi₁₀₅, Rejang₁₀₆, Saurashtra₁₀₉, Sharada₁₁₀, Siddham₁₁₂, Sinhala₁₁₃, Sundanese₁₁₇, Syloti Nagri₁₁₈, Syriac₁₂₁, Tagalog₁₂₃, Tagbanwa₁₂₄, Tai Le₁₂₅, Tai Tham₁₂₆, Tai Viet₁₂₇, Takri₁₂₈, Tamil₁₂₉, Telugu₁₃₁, Thaana₁₃₂, Thai₁₃₃, Tifinagh₁₃₄, Tirhuta₁₃₅, Warang Citi₁₃₉, Noto Serif Tibetan₁₄₂, Noto Color Emoji 1₁₄₄, Noto Color Emoji 2₁₄₅, Noto Color Emoji 7₁₅₀, Noto Color Emoji 8₁₅₁, Noto Color Emoji 9₁₅₂, Noto Color Emoji 10₁₅₃. - 'bcbacabaadafbgaaabbfbaaaaaaaaafaaafcacabadgaccbacabadacbaaaaaabaaaadcbaeaaa,' - // #245: 8 fonts: Noto Sans₁, Arabic₄, HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Syloti Nagri₁₁₈, TC₁₂₂. - 'bc1ihb2gjd,' - // #246: 3 fonts: Noto Sans₁, Arabic₄, Hebrew₄₂. - 'bc1l,' - // #247: 7 fonts: Noto Sans₁, Arabic₄, Hebrew₄₂, NKo₈₂, Phags Pa₁₀₃, Syriac₁₂₁, Thaana₁₃₂. - 'bc1l1nurk,' - // #248: 2 fonts: Noto Sans₁, Armenian₅. - 'bd,' - // #249: 2 fonts: Noto Sans₁, Avestan₆. - 'be,' - // #250: 20 fonts: Noto Sans₁, Bengali₁₁, Devanagari₂₆, Grantha₃₅, Gujarati₃₆, Gurmukhi₃₈, Kannada₅₁, Khudawadi₅₆, Limbu₅₉, Mahajani₆₅, Malayalam₆₆, Masaram Gondi₇₀, Multani₈₀, Oriya₉₇, Sinhala₁₁₃, Syloti Nagri₁₁₈, Takri₁₂₈, Tamil₁₂₉, Telugu₁₃₁, Tirhuta₁₃₅. - 'bjoiabmecfadjqpejabd,' - // #251: 12 fonts: Noto Sans₁, Bengali₁₁, Devanagari₂₆, Grantha₃₅, Gujarati₃₆, Gurmukhi₃₈, Kannada₅₁, Malayalam₆₆, Sharada₁₁₀, Tamil₁₂₉, Telugu₁₃₁, Tirhuta₁₃₅. - 'bjoiabmo1rsbd,' - // #252: 12 fonts: Noto Sans₁, Bengali₁₁, Devanagari₂₆, Gujarati₃₆, Gurmukhi₃₈, Kannada₅₁, Malayalam₆₆, Meetei Mayek₇₄, Ol Chiki₈₈, Oriya₉₇, Tamil₁₂₉, Telugu₁₃₁. - 'bjojbmohni1fb,' - // #253: 16 fonts: Noto Sans₁, Bengali₁₁, Devanagari₂₆, Gurmukhi₃₈, HK₃₉, JP₄₇, KR₄₉, Kannada₅₁, Khmer₅₄, Malayalam₆₆, Oriya₉₇, SC₁₀₈, Sinhala₁₁₃, TC₁₂₂, Tamil₁₂₉, Telugu₁₃₁. - 'bjolahbbcl1ekeigb,' - // #254: 7 fonts: Noto Sans₁, Bengali₁₁, Devanagari₂₆, Gurmukhi₃₈, Lisu₆₂, Oriya₉₇, Thai₁₃₃. - 'bjolx1i1j,' - // #255: 3 fonts: Noto Sans₁, Canadian Aboriginal₁₆, Mongolian₇₈. - 'bo2j,' - // #256: 2 fonts: Noto Sans₁, Caucasian Albanian₁₈. - 'bq,' - // #257: 8 fonts: Noto Sans₁, Caucasian Albanian₁₈, Cherokee₂₁, Duployan₂₇, Gothic₃₄, Syriac₁₂₁, Thai₁₃₃, Tifinagh₁₃₄. - 'bqcfg3ila,' - // #258: 3 fonts: Noto Sans₁, Caucasian Albanian₁₈, Coptic₂₂. - 'bqd,' - // #259: 4 fonts: Noto Sans₁, Caucasian Albanian₁₈, Coptic₂₂, Glagolitic₃₃. - 'bqdk,' - // #260: 9 fonts: Noto Sans₁, Cherokee₂₁, Coptic₂₂, Duployan₂₇, Lydian₆₄, Malayalam₆₆, Runic₁₀₇, Syriac₁₂₁, Tifinagh₁₃₄. - 'btae1kb1onm,' - // #261: 4 fonts: Noto Sans₁, Cherokee₂₁, Duployan₂₇, Syriac₁₂₁. - 'btf3p,' - // #262: 4 fonts: Noto Sans₁, Cherokee₂₁, Math₇₁, Syriac₁₂₁. - 'bt1x1x,' - // #263: 6 fonts: Noto Sans₁, Coptic₂₂, Elbasan₂₉, Glagolitic₃₃, Gothic₃₄, Math₇₁. - 'bugda1k,' - // #264: 3 fonts: Noto Sans₁, Devanagari₂₆, Grantha₃₅. - 'byi,' - // #265: 4 fonts: Noto Sans₁, Devanagari₂₆, Kaithi₅₀, Mahajani₆₅. - 'byxo,' - // #266: 3 fonts: Noto Sans₁, Devanagari₂₆, Modi₇₇. - 'by1y,' - // #267: 7 fonts: Noto Sans₁, Duployan₂₇, HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, TC₁₂₂. - 'bzlhb2gn,' - // #268: 2 fonts: Noto Sans₁, Ethiopic₃₁. - 'b1d,' - // #269: 7 fonts: Noto Sans₁, Georgian₃₂, HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, TC₁₂₂. - 'b1eghb2gn,' - // #270: 3 fonts: Noto Sans₁, Glagolitic₃₃, Old Permic₉₂. - 'b1f2g,' - // #271: 7 fonts: Noto Sans₁, HK₃₉, JP₄₇, KR₄₉, Mongolian₇₈, SC₁₀₈, TC₁₂₂. - 'b1lhb1c1dn,' - // #272: 8 fonts: Noto Sans₁, HK₃₉, JP₄₇, KR₄₉, Mongolian₇₈, SC₁₀₈, TC₁₂₂, Noto Color Emoji 2₁₄₅. - 'b1lhb1c1dnw,' - // #273: 7 fonts: Noto Sans₁, HK₃₉, JP₄₇, KR₄₉, Phags Pa₁₀₃, SC₁₀₈, TC₁₂₂. - 'b1lhb2ben,' - // #274: 7 fonts: Noto Sans₁, HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, TC₁₂₂, Noto Color Emoji 2₁₄₅. - 'b1lhb2gnw,' - // #275: 2 fonts: Noto Sans₁, Hebrew₄₂. - 'b1o,' - // #276: 3 fonts: Noto Sans₁, Kayah Li₅₂, Myanmar₈₁. - 'b1y1c,' - // #277: 2 fonts: Noto Sans₁, Lao₅₇. - 'b2d,' - // #278: 2 fonts: Noto Sans₁, Lisu₆₂. - 'b2i,' - // #279: 4 fonts: Noto Sans₁, Manichaean₆₈, Myanmar₈₁, Phags Pa₁₀₃. - 'b2omv,' - // #280: 3 fonts: Noto Sans₁, Meroitic₇₅, Old Hungarian₈₉. - 'b2vn,' - // #281: 2 fonts: Noto Sans₁, NKo₈₂. - 'b3c,' - // #282: 2 fonts: Noto Sans₁, Newa₈₅. - 'b3f,' - // #283: 2 fonts: Noto Sans₁, Old Hungarian₈₉. - 'b3j,' - // #284: 3 fonts: Noto Sans₁, Old Hungarian₈₉, Old Turkic₉₆. - 'b3jg,' - // #285: 2 fonts: Noto Sans₁, Old Permic₉₂. - 'b3m,' - // #286: 2 fonts: Noto Sans₁, Oriya₉₇. - 'b3r,' - // #287: 2 fonts: Noto Sans₁, Osage₉₈. - 'b3s,' - // #288: 2 fonts: Noto Sans₁, Syloti Nagri₁₁₈. - 'b4m,' - // #289: 2 fonts: Noto Sans₁, Symbols₁₁₉. - 'b4n,' - // #290: 2 fonts: Noto Sans₁, Tamil₁₂₉. - 'b4x,' - // #291: 2 fonts: Noto Sans₁, Thai₁₃₃. - 'b5b,' - // #292: 2 fonts: Noto Sans₁, Noto Color Emoji 2₁₄₅. - 'b5n,' - // #293: 7 fonts: Adlam₂, Arabic₄, Mandaic₆₇, Manichaean₆₈, Psalter Pahlavi₁₀₅, Sogdian₁₁₄, Syriac₁₂₁. - 'cb2ka1kig,' - // #294: 5 fonts: Adlam₂, Arabic₄, NKo₈₂, Syriac₁₂₁, Thaana₁₃₂. - 'cb2z1mk,' - // #295: 1 font: Anatolian Hieroglyphs₃. - 'd,' - // #296: 2 fonts: Arabic₄, Coptic₂₂. - 'er,' - // #297: 4 fonts: Arabic₄, Indic Siyaq Numbers₄₄, Syriac₁₂₁, Thaana₁₃₂. - 'e1n2yk,' - // #298: 3 fonts: Arabic₄, NKo₈₂, Thaana₁₃₂. - 'e2z1x,' - // #299: 3 fonts: Arabic₄, Syriac₁₂₁, Thaana₁₃₂. - 'e4mk,' - // #300: 2 fonts: Armenian₅, Georgian₃₂. - 'f1a,' - // #301: 3 fonts: Bengali₁₁, Chakma₁₉, Syloti Nagri₁₁₈. - 'lh3u,' - // #302: 6 fonts: Bengali₁₁, Devanagari₂₆, Grantha₃₅, Kannada₅₁, Telugu₁₃₁, Tirhuta₁₃₅. - 'loip3bd,' - // #303: 3 fonts: Bengali₁₁, Devanagari₂₆, Kannada₅₁. - 'loy,' - // #304: 2 fonts: Bengali₁₁, Tirhuta₁₃₅. - 'l4t,' - // #305: 2 fonts: Buginese₁₄, Javanese₄₈. - 'o1h,' - // #306: 1 font: Buhid₁₅. - 'p,' - // #307: 4 fonts: Buhid₁₅, Hanunoo₄₀, Tagalog₁₂₃, Tagbanwa₁₂₄. - 'py3ea,' - // #308: 1 font: Carian₁₇. - 'r,' - // #309: 3 fonts: Chakma₁₉, Myanmar₈₁, Tai Le₁₂₅. - 't2j1r,' - // #310: 2 fonts: Coptic₂₂, Symbols 2₁₂₀. - 'w3t,' - // #311: 1 font: Deseret₂₅. - 'z,' - // #312: 3 fonts: Devanagari₂₆, Grantha₃₅, Kannada₅₁. - '1aip,' - // #313: 12 fonts: Devanagari₂₆, Gujarati₃₆, Gurmukhi₃₈, Kaithi₅₀, Kannada₅₁, Khojki₅₅, Khudawadi₅₆, Mahajani₆₅, Malayalam₆₆, Modi₇₇, Takri₁₂₈, Tirhuta₁₃₅. - '1ajbladaiak1yg,' - // #314: 11 fonts: Devanagari₂₆, Gujarati₃₆, Gurmukhi₃₈, Kaithi₅₀, Kannada₅₁, Khojki₅₅, Khudawadi₅₆, Mahajani₆₅, Modi₇₇, Takri₁₂₈, Tirhuta₁₃₅. - '1ajbladail1yg,' - // #315: 10 fonts: Devanagari₂₆, Gujarati₃₆, Gurmukhi₃₈, Kaithi₅₀, Khojki₅₅, Khudawadi₅₆, Mahajani₆₅, Modi₇₇, Takri₁₂₈, Tirhuta₁₃₅. - '1ajbleail1yg,' - // #316: 5 fonts: Devanagari₂₆, Kannada₅₁, Malayalam₆₆, Tamil₁₂₉, Telugu₁₃₁. - '1ayo2kb,' - // #317: 2 fonts: Devanagari₂₆, Tamil₁₂₉. - '1a3y,' - // #318: 7 fonts: Duployan₂₇, HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols₁₁₉, TC₁₂₂. - '1blhb2gkc,' - // #319: 3 fonts: Duployan₂₇, Math₇₁, Symbols₁₁₉. - '1b1r1v,' - // #320: 3 fonts: Duployan₂₇, Symbols₁₁₉, Noto Color Emoji 2₁₄₅. - '1b3nz,' - // #321: 4 fonts: Duployan₂₇, Symbols 2₁₂₀, Noto Color Emoji 1₁₄₄, Noto Color Emoji 9₁₅₂. - '1b3oxh,' - // #322: 1 font: Elbasan₂₉. - '1d,' - // #323: 1 font: Elymaic₃₀. - '1e,' - // #324: 2 fonts: Ethiopic₃₁, Math₇₁. - '1f1n,' - // #325: 1 font: Gothic₃₄. - '1i,' - // #326: 2 fonts: Gujarati₃₆, Khojki₅₅. - '1ks,' - // #327: 2 fonts: Gurmukhi₃₈, Multani₈₀. - '1m1p,' - // #328: 2 fonts: Gurmukhi₃₈, Symbols₁₁₉. - '1m3c,' - // #329: 11 fonts: HK₃₉, JP₄₇, KR₄₉, Lisu₆₂, Mongolian₇₈, New Tai Lue₈₄, Phags Pa₁₀₃, SC₁₀₈, TC₁₂₂, Tai Le₁₂₅, Yi₁₄₀. - '1nhbmpfsenco,' - // #330: 8 fonts: HK₃₉, JP₄₇, KR₄₉, Math₇₁, SC₁₀₈, Symbols₁₁₉, TC₁₂₂, Noto Color Emoji 2₁₄₅. - '1nhbv1kkcw,' - // #331: 9 fonts: HK₃₉, JP₄₇, KR₄₉, Math₇₁, SC₁₀₈, Symbols₁₁₉, TC₁₂₂, Noto Color Emoji 2₁₄₅, Noto Color Emoji 10₁₅₃. - '1nhbv1kkcwh,' - // #332: 7 fonts: HK₃₉, JP₄₇, KR₄₉, Math₇₁, SC₁₀₈, TC₁₂₂, Noto Color Emoji 2₁₄₅. - '1nhbv1knw,' - // #333: 7 fonts: HK₃₉, JP₄₇, KR₄₉, Mongolian₇₈, New Tai Lue₈₄, SC₁₀₈, TC₁₂₂. - '1nhb1cfxn,' - // #334: 8 fonts: HK₃₉, JP₄₇, KR₄₉, Mongolian₇₈, Phags Pa₁₀₃, SC₁₀₈, TC₁₂₂, Yi₁₄₀. - '1nhb1cyenr,' - // #335: 6 fonts: HK₃₉, JP₄₇, KR₄₉, Phags Pa₁₀₃, SC₁₀₈, TC₁₂₂. - '1nhb2ben,' - // #336: 8 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols 2₁₂₀, TC₁₂₂, Noto Color Emoji 2₁₄₅, Noto Color Emoji 9₁₅₂. - '1nhb2glbwg,' - // #337: 8 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols 2₁₂₀, TC₁₂₂, Noto Color Emoji 2₁₄₅, Noto Color Emoji 10₁₅₃. - '1nhb2glbwh,' - // #338: 7 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols 2₁₂₀, TC₁₂₂, Noto Color Emoji 4₁₄₇. - '1nhb2glby,' - // #339: 7 fonts: HK₃₉, JP₄₇, KR₄₉, SC₁₀₈, Symbols 2₁₂₀, TC₁₂₂, Noto Color Emoji 9₁₅₂. - '1nhb2glb1d,' - // #340: 2 fonts: HK₃₉, SC₁₀₈. - '1n2q,' - // #341: 1 font: Hanunoo₄₀. - '1o,' - // #342: 1 font: Indic Siyaq Numbers₄₄. - '1s,' - // #343: 1 font: Lycian₆₃. - '2l,' - // #344: 1 font: Mahajani₆₅. - '2n,' - // #345: 2 fonts: Math₇₁, Old Permic₉₂. - '2tu,' - // #346: 3 fonts: Math₇₁, Symbols 2₁₂₀, Tai Tham₁₂₆. - '2t1wf,' - // #347: 3 fonts: Math₇₁, Symbols 2₁₂₀, Noto Color Emoji 2₁₄₅. - '2t1wy,' - // #348: 2 fonts: Math₇₁, Noto Color Emoji 2₁₄₅. - '2t2v,' - // #349: 2 fonts: Mayan Numerals₇₂, Symbols 2₁₂₀. - '2u1v,' - // #350: 1 font: Medefaidrin₇₃. - '2v,' - // #351: 1 font: Ogham₈₇. - '3j,' - // #352: 1 font: Ol Chiki₈₈. + // #1286: 2 fonts: JP 5₁₄₁, SC 4₃₈₈. + '5l9m,' + // #1287: 2 fonts: JP 6₁₄₂, SC 5₃₈₉. + '5m9m,' + // #1288: 2 fonts: JP 14₁₅₀, SC 15₃₉₉. + '5u9o,' + // #1289: 2 fonts: JP 16₁₅₂, SC 19₄₀₃. + '5w9q,' + // #1290: 2 fonts: JP 18₁₅₄, SC 20₄₀₄. + '5y9p,' + // #1291: 2 fonts: JP 20₁₅₆, SC 22₄₀₆. + '6a9p,' + // #1292: 2 fonts: JP 23₁₅₉, SC 28₄₁₂. + '6d9s,' + // #1293: 2 fonts: JP 33₁₆₉, SC 42₄₂₆. + '6n9w,' + // #1294: 2 fonts: JP 36₁₇₂, SC 47₄₃₁. + '6q9y,' + // #1295: 2 fonts: JP 43₁₇₉, SC 57₄₄₁. + '6x10b,' + // #1296: 2 fonts: JP 49₁₈₅, SC 66₄₅₀. + '7d10e,' + // #1297: 2 fonts: JP 54₁₉₀, KR 95₃₅₅. + '7i6i,' + // #1298: 2 fonts: JP 54₁₉₀, KR 100₃₆₀. + '7i6n,' + // #1299: 3 fonts: JP 55₁₉₁, KR 96₃₅₆, SC 68₄₅₂. + '7j6i3r,' + // #1300: 3 fonts: JP 56₁₉₂, KR 96₃₅₆, Symbols₇₀₂. + '7k6h13h,' + // #1301: 2 fonts: JP 56₁₉₂, Symbols₇₀₂. + '7k19p,' + // #1302: 3 fonts: JP 57₁₉₃, KR 96₃₅₆, SC 71₄₅₅. + '7l6g3u,' + // #1303: 2 fonts: JP 57₁₉₃, KR 97₃₅₇. + '7l6h,' + // #1304: 1 font: KR 79₃₃₉. + '13b,' + // #1305: 1 font: KR 93₃₅₃. + '13p,' + // #1306: 4 fonts: KR 99₃₅₉, Noto Sans₅₉₁, Elbasan₆₁₆, Math₆₅₅. + '13v8xy1m,' + // #1307: 3 fonts: KR 109₃₆₉, Noto Sans₅₉₁, Math₆₅₅. + '14f8n2l,' + // #1308: 1 font: SC 3₃₈₇. + '14x,' + // #1309: 1 font: SC 13₃₉₇. + '15h,' + // #1310: 2 fonts: Noto Sans₅₉₁, Syriac₇₀₃. + '22t4h,' + // #1311: 1 font: Adlam₅₉₂. + '22u,' + // #1312: 4 fonts: Arabic₅₉₄, NKo₆₆₆, Syriac₇₀₃, Thaana₇₁₃. + '22w2t1kj,' + // #1313: 2 fonts: Arabic₅₉₄, Syriac₇₀₃. + '22w4e,' + // #1314: 1 font: Brahmi₆₀₃. + '23f,' + // #1315: 1 font: Canadian Aboriginal₆₀₆. + '23i,' + // #1316: 1 font: Cherokee₆₁₁. + '23n,' + // #1317: 1 font: Coptic₆₁₂. + '23o,' + // #1318: 2 fonts: Devanagari₆₁₅, Grantha₆₂₂. + '23rg,' + // #1319: 1 font: Hatran₆₂₇. + '24d,' + // #1320: 1 font: Javanese₆₃₃. + '24j,' + // #1321: 1 font: Lepcha₆₄₂. + '24s,' + // #1322: 1 font: Linear A₆₄₄. + '24u,' + // #1323: 1 font: Marchen₆₅₃. + '25d,' + // #1324: 1 font: Meetei Mayek₆₅₈. + '25i,' + // #1325: 1 font: Meroitic₆₅₉. + '25j,' + // #1326: 1 font: Miao₆₆₀. + '25k,' + // #1327: 1 font: Mro₆₆₃. + '25n,' + // #1328: 1 font: Old Hungarian₆₇₃. + '25x,' + // #1329: 1 font: Psalter Pahlavi₆₈₉. + '26n,' + // #1330: 1 font: Syriac₇₀₃. + '27b,' + // #1331: 1 font: Tagbanwa₇₀₅. + '27d,' + // #1332: 1 font: Tifinagh₇₁₅. + '27n,' + // #1333: 2 fonts: Noto Color Emoji 2₂, Noto Color Emoji 5₅. + 'cc,' + // #1334: 3 fonts: Noto Color Emoji 2₂, Noto Color Emoji 10₁₀, Symbols 2 3₁₅. + 'che,' + // #1335: 148 fonts: Noto Color Emoji 2₂, Symbols 2 1₁₃, Symbols 2 3₁₅, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ckbbccc4ad4oe4pd3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1336: 7 fonts: Noto Color Emoji 2₂, Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, KR 96₃₅₆, SC 69₄₅₃, TC 72₅₅₇. + 'cm3m3h6h3s3z,' + // #1337: 145 fonts: Noto Color Emoji 2₂, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'coccc4ad4oe4pd3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1338: 5 fonts: Noto Color Emoji 2₂, HK 11₃₈, JP 1₁₃₇, SC 0₃₈₄, TC 1₄₈₆. + 'c1j3u9m3x,' + // #1339: 5 fonts: Noto Color Emoji 2₂, HK 11₃₈, JP 2₁₃₈, SC 1₃₈₅, TC 1₄₈₆. + 'c1j3v9m3w,' + // #1340: 5 fonts: Noto Color Emoji 2₂, HK 77₁₀₄, JP 53₁₈₉, SC 67₄₅₁, TC 70₅₅₅. + 'c3x3g10b3z,' + // #1341: 8 fonts: Noto Color Emoji 2₂, HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅, Symbols₇₀₂. + 'c4b3h6h3t3z3q1u,' + // #1342: 3 fonts: Noto Color Emoji 3₃, Noto Color Emoji 4₄, Symbols 2 3₁₅. + 'dak,' + // #1343: 3 fonts: Noto Color Emoji 3₃, Noto Color Emoji 8₈, Symbols 2 3₁₅. + 'deg,' + // #1344: 2 fonts: Noto Color Emoji 4₄, Symbols₇₀₂. + 'e26v,' + // #1345: 3 fonts: Noto Color Emoji 5₅, Noto Color Emoji 8₈, Symbols 2 3₁₅. + 'fcg,' + // #1346: 2 fonts: Noto Color Emoji 6₆, Noto Color Emoji 7₇. + 'ga,' + // #1347: 2 fonts: Noto Color Emoji 6₆, Noto Color Emoji 8₈. + 'gb,' + // #1348: 6 fonts: Noto Color Emoji 7₇, Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, SC 70₄₅₄, TC 73₅₅₈. + 'hh3m3h10b3z,' + // #1349: 2 fonts: Noto Color Emoji 8₈, Noto Color Emoji 9₉. + 'ia,' + // #1350: 2 fonts: Noto Color Emoji 8₈, Symbols₇₀₂. + 'i26r,' + // #1351: 2 fonts: Noto Color Emoji 9₉, Noto Color Emoji 10₁₀. + 'ja,' + // #1352: 2 fonts: Noto Color Emoji 9₉, Symbols₇₀₂. + 'j26q,' + // #1353: 8 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, HK 79₁₀₆, JP 57₁₉₃, KR 96₃₅₆, SC 70₄₅₄, TC 73₅₅₈, Math₆₅₅. + 'nb3m3i6g3t3z3s,' + // #1354: 140 fonts: Symbols 2 1₁₃, Symbols 2 4₁₆, Symbols 2 5₁₇, Cuneiform 1₁₉, Cuneiform 2₂₀, Duployan 1₂₂, Duployan 2₂₃, Egyptian Hieroglyphs 1₂₅, Egyptian Hieroglyphs 2₂₆, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ncabababa21raaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1355: 6 fonts: Symbols 2 3₁₅, HK 78₁₀₅, JP 56₁₉₂, KR 96₃₅₆, SC 69₄₅₃, TC 72₅₅₇. + 'p3l3i6h3s3z,' + // #1356: 6 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, KR 96₃₅₆, SC 69₄₅₃, TC 72₅₅₇. + 'p3m3h6h3s3z,' + // #1357: 5 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, SC 70₄₅₄, TC 73₅₅₈. + 'p3m3h10b3z,' + // #1358: 5 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 57₁₉₃, KR 96₃₅₆, TC 73₅₅₈. + 'p3m3i6g7t,' + // #1359: 3 fonts: Symbols 2 3₁₅, JP 57₁₉₃, KR 96₃₅₆. + 'p6v6g,' + // #1360: 2 fonts: Symbols 2 3₁₅, JP 58₁₉₄. + 'p6w,' + // #1361: 2 fonts: Symbols 2 3₁₅, Symbols₇₀₂. + 'p26k,' + // #1362: 123 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, HK 84₁₁₁, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 76₄₆₀, SC 99₄₈₃, TC 80₅₆₅, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Indic Siyaq Numbers₆₃₀, Javanese₆₃₃, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Lao₆₄₁, Lepcha₆₄₂, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Manichaean₆₅₂, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old South Arabian₆₇₉, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qc3nw2i2k4t2zw3dwbaaaaaaaaaaaaaaaaaaabaaabaaaaabaaaaccbaaaababaabaabbbaaaaababaaaaaaabaabcaaaabbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1363: 114 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Indic Siyaq Numbers₆₃₀, Javanese₆₃₃, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Lao₆₄₁, Lepcha₆₄₂, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Manichaean₆₅₂, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old South Arabian₆₇₉, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qc21yaaaaaaaaaaaaaaaaaaabaaabaaaaabaaaaccbaaaababaabaabbbaaaaababaaaaaaabaabcaaaabbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1364: 143 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 82₁₀₉, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 123₃₈₃, SC 73₄₅₇, SC 100₄₈₄, TC 76₅₆₁, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3ez2h2l4t2v1a2y1baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1365: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad2h2l3vx2yx3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1366: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 108₃₆₈, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad4oe4eo3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1367: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 110₃₇₀, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad4oe4gm3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1368: 2 fonts: HK 3₃₀, TC 0₄₈₅. + '1e17m,' + // #1369: 2 fonts: HK 4₃₁, JP 1₁₃₇. + '1f4b,' + // #1370: 2 fonts: HK 5₃₂, JP 1₁₃₇. + '1g4a,' + // #1371: 2 fonts: HK 8₃₅, TC 0₄₈₅. + '1j17h,' + // #1372: 6 fonts: HK 11₃₈, JP 3₁₃₉, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇, Math₆₅₅. + '1m3w4q4u3x6l,' + // #1373: 5 fonts: HK 11₃₈, JP 66₂₀₂, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m6h2f4u3x,' + // #1374: 3 fonts: HK 11₃₈, JP 74₂₁₀, TC 1₄₈₆. + '1m6p10p,' + // #1375: 3 fonts: HK 11₃₈, JP 75₂₁₁, TC 1₄₈₆. + '1m6q10o,' + // #1376: 5 fonts: HK 11₃₈, JP 76₂₁₂, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m6r1v4u3x,' + // #1377: 5 fonts: HK 11₃₈, JP 78₂₁₄, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m6t1t4u3x,' + // #1378: 5 fonts: HK 11₃₈, JP 80₂₁₆, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m6v1r4u3x,' + // #1379: 4 fonts: HK 11₃₈, JP 80₂₁₆, SC 1₃₈₅, TC 1₄₈₆. + '1m6v6m3w,' + // #1380: 5 fonts: HK 11₃₈, JP 82₂₁₈, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m6x1p4u3x,' + // #1381: 4 fonts: HK 11₃₈, JP 83₂₁₉, SC 1₃₈₅, TC 1₄₈₆. + '1m6y6j3w,' + // #1382: 5 fonts: HK 11₃₈, JP 84₂₂₀, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m6z1n4u3x,' + // #1383: 4 fonts: HK 11₃₈, JP 85₂₂₁, SC 1₃₈₅, TC 1₄₈₆. + '1m7a6h3w,' + // #1384: 4 fonts: HK 11₃₈, JP 87₂₂₃, SC 1₃₈₅, TC 1₄₈₆. + '1m7c6f3w,' + // #1385: 4 fonts: HK 11₃₈, JP 90₂₂₆, SC 1₃₈₅, TC 2₄₈₇. + '1m7f6c3x,' + // #1386: 5 fonts: HK 11₃₈, JP 92₂₂₈, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m7h1f4u3x,' + // #1387: 5 fonts: HK 12₃₉, JP 3₁₃₉, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1n3v4q4u3x,' + // #1388: 5 fonts: HK 12₃₉, JP 96₂₃₂, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1n7k1b4u3x,' + // #1389: 5 fonts: HK 12₃₉, JP 97₂₃₃, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1n7l1a4u3x,' + // #1390: 3 fonts: HK 13₄₀, JP 5₁₄₁, KR 2₂₆₂. + '1o3w4q,' + // #1391: 4 fonts: HK 13₄₀, JP 5₁₄₁, KR 2₂₆₂, TC 3₄₈₈. + '1o3w4q8r,' + // #1392: 4 fonts: HK 14₄₁, JP 5₁₄₁, SC 4₃₈₈, TC 4₄₈₉. + '1p3v9m3w,' + // #1393: 4 fonts: HK 15₄₂, JP 5₁₄₁, KR 65₃₂₅, TC 4₄₈₉. + '1q3u7b6h,' + // #1394: 4 fonts: HK 15₄₂, JP 6₁₄₂, SC 4₃₈₈, TC 5₄₉₀. + '1q3v9l3x,' + // #1395: 3 fonts: HK 15₄₂, SC 4₃₈₈, TC 4₄₈₉. + '1q13h3w,' + // #1396: 3 fonts: HK 15₄₂, SC 4₃₈₈, TC 5₄₉₀. + '1q13h3x,' + // #1397: 5 fonts: HK 16₄₃, JP 6₁₄₂, KR 65₃₂₅, SC 6₃₉₀, TC 6₄₉₁. + '1r3u7a2m3w,' + // #1398: 3 fonts: HK 16₄₃, SC 5₃₈₉, TC 6₄₉₁. + '1r13h3x,' + // #1399: 2 fonts: HK 16₄₃, TC 5₄₉₀. + '1r17e,' + // #1400: 4 fonts: HK 17₄₄, JP 7₁₄₃, SC 6₃₉₀, TC 6₄₉₁. + '1s3u9m3w,' + // #1401: 3 fonts: HK 17₄₄, JP 8₁₄₄, TC 7₄₉₂. + '1s3v13j,' + // #1402: 3 fonts: HK 18₄₅, SC 7₃₉₁, TC 8₄₉₃. + '1t13h3x,' + // #1403: 4 fonts: HK 19₄₆, JP 8₁₄₄, KR 66₃₂₆, TC 9₄₉₄. + '1u3t6z6l,' + // #1404: 4 fonts: HK 19₄₆, JP 8₁₄₄, SC 7₃₉₁, TC 9₄₉₄. + '1u3t9m3y,' + // #1405: 5 fonts: HK 19₄₆, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 10₄₉₅. + '1u3u6y2n3y,' + // #1406: 4 fonts: HK 19₄₆, JP 9₁₄₅, KR 66₃₂₆, TC 9₄₉₄. + '1u3u6y6l,' + // #1407: 4 fonts: HK 19₄₆, JP 9₁₄₅, SC 7₃₉₁, TC 9₄₉₄. + '1u3u9l3y,' + // #1408: 3 fonts: HK 20₄₇, JP 9₁₄₅, SC 8₃₉₂. + '1v3t9m,' + // #1409: 4 fonts: HK 20₄₇, JP 9₁₄₅, SC 9₃₉₃, TC 10₄₉₅. + '1v3t9n3x,' + // #1410: 5 fonts: HK 20₄₇, JP 10₁₄₆, KR 67₃₂₇, SC 9₃₉₃, TC 11₄₉₆. + '1v3u6y2n3y,' + // #1411: 3 fonts: HK 20₄₇, SC 9₃₉₃, TC 10₄₉₅. + '1v13h3x,' + // #1412: 1 font: HK 21₄₈. + '1w,' + // #1413: 3 fonts: HK 21₄₈, SC 91₄₇₅, TC 12₄₉₇. + '1w16kv,' + // #1414: 5 fonts: HK 22₄₉, JP 11₁₄₇, KR 67₃₂₇, SC 11₃₉₅, TC 12₄₉₇. + '1x3t6x2p3x,' + // #1415: 2 fonts: HK 22₄₉, TC 12₄₉₇. + '1x17f,' + // #1416: 5 fonts: HK 23₅₀, JP 11₁₄₇, KR 67₃₂₇, SC 13₃₉₇, TC 14₄₉₉. + '1y3s6x2r3x,' + // #1417: 3 fonts: HK 23₅₀, JP 11₁₄₇, SC 12₃₉₆. + '1y3s9o,' + // #1418: 2 fonts: HK 23₅₀, SC 12₃₉₆. + '1y13h,' + // #1419: 4 fonts: HK 24₅₁, JP 12₁₄₈, KR 67₃₂₇, TC 14₄₉₉. + '1z3s6w6p,' + // #1420: 3 fonts: HK 24₅₁, SC 78₄₆₂, TC 15₅₀₀. + '1z15u1l,' + // #1421: 5 fonts: HK 25₅₂, JP 13₁₄₉, KR 68₃₂₈, SC 14₃₉₈, TC 15₅₀₀. + '2a3s6w2r3x,' + // #1422: 5 fonts: HK 25₅₂, JP 13₁₄₉, KR 68₃₂₈, SC 79₄₆₃, TC 16₅₀₁. + '2a3s6w5e1l,' + // #1423: 3 fonts: HK 25₅₂, SC 14₃₉₈, TC 15₅₀₀. + '2a13h3x,' + // #1424: 4 fonts: HK 26₅₃, JP 13₁₄₉, KR 69₃₂₉, TC 17₅₀₂. + '2b3r6x6q,' + // #1425: 4 fonts: HK 26₅₃, JP 13₁₄₉, SC 15₃₉₉, TC 16₅₀₁. + '2b3r9p3x,' + // #1426: 5 fonts: HK 27₅₄, JP 14₁₅₀, KR 69₃₂₉, SC 16₄₀₀, TC 17₅₀₂. + '2c3r6w2s3x,' + // #1427: 4 fonts: HK 27₅₄, JP 14₁₅₀, SC 15₃₉₉, TC 17₅₀₂. + '2c3r9o3y,' + // #1428: 1 font: HK 28₅₅. + '2d,' + // #1429: 5 fonts: HK 28₅₅, JP 15₁₅₁, KR 70₃₃₀, SC 18₄₀₂, TC 20₅₀₅. + '2d3r6w2t3y,' + // #1430: 4 fonts: HK 28₅₅, JP 15₁₅₁, KR 70₃₃₀, TC 20₅₀₅. + '2d3r6w6s,' + // #1431: 4 fonts: HK 28₅₅, JP 15₁₅₁, SC 17₄₀₁, TC 20₅₀₅. + '2d3r9p3z,' + // #1432: 2 fonts: HK 28₅₅, TC 19₅₀₄. + '2d17g,' + // #1433: 2 fonts: HK 29₅₆, JP 15₁₅₁. + '2e3q,' + // #1434: 3 fonts: HK 29₅₆, JP 15₁₅₁, TC 20₅₀₅. + '2e3q13p,' + // #1435: 2 fonts: HK 30₅₇, JP 16₁₅₂. + '2f3q,' + // #1436: 4 fonts: HK 30₅₇, JP 17₁₅₃, KR 71₃₃₁, TC 21₅₀₆. + '2f3r6v6s,' + // #1437: 4 fonts: HK 30₅₇, JP 17₁₅₃, KR 71₃₃₁, TC 22₅₀₇. + '2f3r6v6t,' + // #1438: 3 fonts: HK 30₅₇, SC 19₄₀₃, TC 22₅₀₇. + '2f13h3z,' + // #1439: 2 fonts: HK 30₅₇, TC 22₅₀₇. + '2f17h,' + // #1440: 5 fonts: HK 31₅₈, JP 17₁₅₃, KR 71₃₃₁, SC 20₄₀₄, TC 22₅₀₇. + '2g3q6v2u3y,' + // #1441: 3 fonts: HK 31₅₈, SC 20₄₀₄, TC 23₅₀₈. + '2g13h3z,' + // #1442: 3 fonts: HK 31₅₈, SC 21₄₀₅, TC 23₅₀₈. + '2g13i3y,' + // #1443: 4 fonts: HK 32₅₉, JP 18₁₅₄, SC 79₄₆₃, TC 23₅₀₈. + '2h3q11w1s,' + // #1444: 5 fonts: HK 32₅₉, JP 19₁₅₅, KR 72₃₃₂, SC 21₄₀₅, TC 24₅₀₉. + '2h3r6u2u3z,' + // #1445: 4 fonts: HK 32₅₉, JP 19₁₅₅, KR 72₃₃₂, TC 24₅₀₉. + '2h3r6u6u,' + // #1446: 3 fonts: HK 32₅₉, SC 22₄₀₆, TC 24₅₀₉. + '2h13i3y,' + // #1447: 5 fonts: HK 33₆₀, JP 19₁₅₅, KR 72₃₃₂, SC 22₄₀₆, TC 25₅₁₀. + '2i3q6u2v3z,' + // #1448: 3 fonts: HK 33₆₀, SC 22₄₀₆, TC 24₅₀₉. + '2i13h3y,' + // #1449: 1 font: HK 34₆₁. + '2j,' + // #1450: 5 fonts: HK 34₆₁, JP 21₁₅₇, KR 72₃₃₂, SC 24₄₀₈, TC 26₅₁₁. + '2j3r6s2x3y,' + // #1451: 3 fonts: HK 34₆₁, SC 22₄₀₆, TC 25₅₁₀. + '2j13g3z,' + // #1452: 3 fonts: HK 34₆₁, SC 23₄₀₇, TC 25₅₁₀. + '2j13h3y,' + // #1453: 3 fonts: HK 34₆₁, SC 91₄₇₅, TC 26₅₁₁. + '2j15x1j,' + // #1454: 5 fonts: HK 35₆₂, JP 21₁₅₇, KR 73₃₃₃, SC 77₄₆₁, TC 27₅₁₂. + '2k3q6t4x1y,' + // #1455: 4 fonts: HK 35₆₂, JP 21₁₅₇, SC 25₄₀₉, TC 27₅₁₂. + '2k3q9r3y,' + // #1456: 5 fonts: HK 35₆₂, JP 22₁₅₈, KR 73₃₃₃, SC 25₄₀₉, TC 27₅₁₂. + '2k3r6s2x3y,' + // #1457: 3 fonts: HK 35₆₂, JP 22₁₅₈, TC 27₅₁₂. + '2k3r13p,' + // #1458: 5 fonts: HK 35₆₂, JP 69₂₀₅, KR 73₃₃₃, SC 24₄₀₈, TC 27₅₁₂. + '2k5m4x2w3z,' + // #1459: 5 fonts: HK 36₆₃, JP 22₁₅₈, KR 73₃₃₃, SC 25₄₀₉, TC 27₅₁₂. + '2l3q6s2x3y,' + // #1460: 4 fonts: HK 36₆₃, JP 22₁₅₈, SC 25₄₀₉, TC 27₅₁₂. + '2l3q9q3y,' + // #1461: 4 fonts: HK 36₆₃, JP 22₁₅₈, SC 26₄₁₀, TC 29₅₁₄. + '2l3q9r3z,' + // #1462: 3 fonts: HK 36₆₃, SC 25₄₀₉, TC 27₅₁₂. + '2l13h3y,' + // #1463: 2 fonts: HK 37₆₄, JP 23₁₅₉. + '2m3q,' + // #1464: 5 fonts: HK 37₆₄, JP 23₁₅₉, KR 74₃₃₄, SC 26₄₁₀, TC 29₅₁₄. + '2m3q6s2x3z,' + // #1465: 5 fonts: HK 38₆₅, JP 23₁₅₉, KR 74₃₃₄, SC 28₄₁₂, TC 31₅₁₆. + '2n3p6s2z3z,' + // #1466: 5 fonts: HK 38₆₅, JP 24₁₆₀, KR 74₃₃₄, SC 28₄₁₂, TC 31₅₁₆. + '2n3q6r2z3z,' + // #1467: 2 fonts: HK 38₆₅, SC 27₄₁₁. + '2n13h,' + // #1468: 5 fonts: HK 39₆₆, JP 24₁₆₀, KR 75₃₃₅, SC 28₄₁₂, TC 31₅₁₆. + '2o3p6s2y3z,' + // #1469: 5 fonts: HK 39₆₆, JP 24₁₆₀, KR 75₃₃₅, SC 29₄₁₃, TC 32₅₁₇. + '2o3p6s2z3z,' + // #1470: 3 fonts: HK 39₆₆, JP 24₁₆₀, SC 29₄₁₃. + '2o3p9s,' + // #1471: 2 fonts: HK 39₆₆, JP 25₁₆₁. + '2o3q,' + // #1472: 3 fonts: HK 39₆₆, SC 28₄₁₂, TC 31₅₁₆. + '2o13h3z,' + // #1473: 2 fonts: HK 39₆₆, SC 30₄₁₄. + '2o13j,' + // #1474: 2 fonts: HK 39₆₆, TC 32₅₁₇. + '2o17i,' + // #1475: 4 fonts: HK 40₆₇, JP 25₁₆₁, KR 75₃₃₅, TC 33₅₁₈. + '2p3p6r7a,' + // #1476: 4 fonts: HK 40₆₇, JP 25₁₆₁, SC 30₄₁₄, TC 33₅₁₈. + '2p3p9s3z,' + // #1477: 2 fonts: HK 41₆₈, JP 25₁₆₁. + '2q3o,' + // #1478: 4 fonts: HK 41₆₈, JP 25₁₆₁, SC 31₄₁₅, TC 33₅₁₈. + '2q3o9t3y,' + // #1479: 3 fonts: HK 41₆₈, JP 25₁₆₁, TC 33₅₁₈. + '2q3o13s,' + // #1480: 4 fonts: HK 41₆₈, JP 26₁₆₂, KR 76₃₃₆, TC 33₅₁₈. + '2q3p6r6z,' + // #1481: 5 fonts: HK 41₆₈, JP 62₁₉₈, KR 76₃₃₆, SC 31₄₁₅, TC 33₅₁₈. + '2q4z5h3a3y,' + // #1482: 2 fonts: HK 41₆₈, SC 31₄₁₅. + '2q13i,' + // #1483: 2 fonts: HK 41₆₈, SC 32₄₁₆. + '2q13j,' + // #1484: 3 fonts: HK 42₆₉, JP 26₁₆₂, SC 32₄₁₆. + '2r3o9t,' + // #1485: 5 fonts: HK 42₆₉, JP 27₁₆₃, KR 76₃₃₆, SC 77₄₆₁, TC 35₅₂₀. + '2r3p6q4u2g,' + // #1486: 4 fonts: HK 42₆₉, JP 27₁₆₃, SC 32₄₁₆, TC 34₅₁₉. + '2r3p9s3y,' + // #1487: 3 fonts: HK 42₆₉, JP 27₁₆₃, SC 33₄₁₇. + '2r3p9t,' + // #1488: 4 fonts: HK 42₆₉, JP 27₁₆₃, SC 33₄₁₇, TC 34₅₁₉. + '2r3p9t3x,' + // #1489: 5 fonts: HK 42₆₉, JP 62₁₉₈, KR 76₃₃₆, SC 33₄₁₇, TC 35₅₂₀. + '2r4y5h3c3y,' + // #1490: 3 fonts: HK 43₇₀, JP 27₁₆₃, SC 33₄₁₇. + '2s3o9t,' + // #1491: 3 fonts: HK 43₇₀, JP 27₁₆₃, SC 34₄₁₈. + '2s3o9u,' + // #1492: 4 fonts: HK 43₇₀, JP 27₁₆₃, SC 34₄₁₈, TC 35₅₂₀. + '2s3o9u3x,' + // #1493: 2 fonts: HK 43₇₀, SC 33₄₁₇. + '2s13i,' + // #1494: 1 font: HK 44₇₁. + '2t,' + // #1495: 3 fonts: HK 44₇₁, JP 28₁₆₄, TC 36₅₂₁. + '2t3o13s,' + // #1496: 3 fonts: HK 44₇₁, JP 29₁₆₅, SC 36₄₂₀. + '2t3p9u,' + // #1497: 5 fonts: HK 45₇₂, JP 29₁₆₅, KR 77₃₃₇, SC 36₄₂₀, TC 38₅₂₃. + '2u3o6p3e3y,' + // #1498: 4 fonts: HK 45₇₂, JP 29₁₆₅, SC 82₄₆₆, TC 38₅₂₃. + '2u3o11o2e,' + // #1499: 5 fonts: HK 45₇₂, JP 63₁₉₉, KR 78₃₃₈, SC 36₄₂₀, TC 38₅₂₃. + '2u4w5i3d3y,' + // #1500: 4 fonts: HK 45₇₂, KR 78₃₃₈, SC 36₄₂₀, TC 38₅₂₃. + '2u10f3d3y,' + // #1501: 2 fonts: HK 45₇₂, SC 36₄₂₀. + '2u13j,' + // #1502: 3 fonts: HK 45₇₂, SC 36₄₂₀, TC 37₅₂₂. + '2u13j3x,' + // #1503: 5 fonts: HK 46₇₃, JP 29₁₆₅, KR 78₃₃₈, SC 37₄₂₁, TC 38₅₂₃. + '2v3n6q3e3x,' + // #1504: 4 fonts: HK 46₇₃, JP 29₁₆₅, SC 37₄₂₁, TC 38₅₂₃. + '2v3n9v3x,' + // #1505: 4 fonts: HK 46₇₃, JP 30₁₆₆, KR 78₃₃₈, TC 39₅₂₄. + '2v3o6p7d,' + // #1506: 3 fonts: HK 46₇₃, JP 30₁₆₆, SC 37₄₂₁. + '2v3o9u,' + // #1507: 4 fonts: HK 46₇₃, JP 30₁₆₆, SC 38₄₂₂, TC 40₅₂₅. + '2v3o9v3y,' + // #1508: 3 fonts: HK 46₇₃, JP 30₁₆₆, TC 40₅₂₅. + '2v3o13u,' + // #1509: 5 fonts: HK 47₇₄, JP 30₁₆₆, KR 79₃₃₉, SC 39₄₂₃, TC 40₅₂₅. + '2w3n6q3f3x,' + // #1510: 4 fonts: HK 47₇₄, JP 30₁₆₆, KR 79₃₃₉, TC 40₅₂₅. + '2w3n6q7d,' + // #1511: 2 fonts: HK 47₇₄, JP 31₁₆₇. + '2w3o,' + // #1512: 2 fonts: HK 47₇₄, SC 39₄₂₃. + '2w13k,' + // #1513: 5 fonts: HK 48₇₅, JP 31₁₆₇, KR 79₃₃₉, SC 40₄₂₄, TC 41₅₂₆. + '2x3n6p3g3x,' + // #1514: 5 fonts: HK 48₇₅, JP 31₁₆₇, KR 79₃₃₉, SC 40₄₂₄, TC 42₅₂₇. + '2x3n6p3g3y,' + // #1515: 4 fonts: HK 48₇₅, JP 31₁₆₇, KR 79₃₃₉, TC 41₅₂₆. + '2x3n6p7e,' + // #1516: 2 fonts: HK 49₇₆, JP 31₁₆₇. + '2y3m,' + // #1517: 2 fonts: HK 49₇₆, JP 32₁₆₈. + '2y3n,' + // #1518: 4 fonts: HK 49₇₆, JP 32₁₆₈, SC 79₄₆₃, TC 42₅₂₇. + '2y3n11i2l,' + // #1519: 3 fonts: HK 49₇₆, JP 32₁₆₈, TC 43₅₂₈. + '2y3n13v,' + // #1520: 5 fonts: HK 50₇₇, JP 32₁₆₈, KR 80₃₄₀, SC 41₄₂₅, TC 44₅₂₉. + '2z3m6p3g3z,' + // #1521: 4 fonts: HK 50₇₇, JP 32₁₆₈, SC 41₄₂₅, TC 44₅₂₉. + '2z3m9w3z,' + // #1522: 3 fonts: HK 50₇₇, SC 41₄₂₅, TC 44₅₂₉. + '2z13j3z,' + // #1523: 5 fonts: HK 51₇₈, JP 33₁₆₉, KR 80₃₄₀, SC 42₄₂₆, TC 44₅₂₉. + '3a3m6o3h3y,' + // #1524: 5 fonts: HK 51₇₈, JP 33₁₆₉, KR 81₃₄₁, SC 42₄₂₆, TC 45₅₃₀. + '3a3m6p3g3z,' + // #1525: 5 fonts: HK 51₇₈, JP 33₁₆₉, KR 81₃₄₁, SC 81₄₆₅, TC 45₅₃₀. + '3a3m6p4t2m,' + // #1526: 3 fonts: HK 51₇₈, JP 33₁₆₉, SC 42₄₂₆. + '3a3m9w,' + // #1527: 4 fonts: HK 51₇₈, JP 33₁₆₉, SC 88₄₇₂, TC 45₅₃₀. + '3a3m11q2f,' + // #1528: 2 fonts: HK 51₇₈, SC 42₄₂₆. + '3a13j,' + // #1529: 3 fonts: HK 51₇₈, SC 42₄₂₆, TC 45₅₃₀. + '3a13j3z,' + // #1530: 4 fonts: HK 52₇₉, JP 34₁₇₀, KR 81₃₄₁, TC 46₅₃₁. + '3b3m6o7h,' + // #1531: 5 fonts: HK 52₇₉, JP 34₁₇₀, KR 82₃₄₂, SC 44₄₂₈, TC 46₅₃₁. + '3b3m6p3h3y,' + // #1532: 2 fonts: HK 52₇₉, SC 44₄₂₈. + '3b13k,' + // #1533: 5 fonts: HK 53₈₀, JP 34₁₇₀, KR 82₃₄₂, SC 44₄₂₈, TC 47₅₃₂. + '3c3l6p3h3z,' + // #1534: 4 fonts: HK 53₈₀, JP 34₁₇₀, SC 45₄₂₉, TC 47₅₃₂. + '3c3l9y3y,' + // #1535: 5 fonts: HK 53₈₀, JP 65₂₀₁, KR 82₃₄₂, SC 44₄₂₈, TC 46₅₃₁. + '3c4q5k3h3y,' + // #1536: 2 fonts: HK 54₈₁, JP 36₁₇₂. + '3d3m,' + // #1537: 3 fonts: HK 54₈₁, JP 36₁₇₂, TC 48₅₃₃. + '3d3m13w,' + // #1538: 2 fonts: HK 54₈₁, SC 46₄₃₀. + '3d13k,' + // #1539: 3 fonts: HK 54₈₁, SC 46₄₃₀, TC 48₅₃₃. + '3d13k3y,' + // #1540: 5 fonts: HK 55₈₂, JP 36₁₇₂, KR 82₃₄₂, SC 46₄₃₀, TC 48₅₃₃. + '3e3l6n3j3y,' + // #1541: 5 fonts: HK 55₈₂, JP 36₁₇₂, KR 83₃₄₃, SC 46₄₃₀, TC 48₅₃₃. + '3e3l6o3i3y,' + // #1542: 5 fonts: HK 55₈₂, JP 36₁₇₂, KR 83₃₄₃, SC 79₄₆₃, TC 49₅₃₄. + '3e3l6o4p2s,' + // #1543: 3 fonts: HK 55₈₂, JP 36₁₇₂, TC 48₅₃₃. + '3e3l13w,' + // #1544: 3 fonts: HK 55₈₂, SC 46₄₃₀, TC 49₅₃₄. + '3e13j3z,' + // #1545: 2 fonts: HK 56₈₃, JP 36₁₇₂. + '3f3k,' + // #1546: 4 fonts: HK 56₈₃, JP 36₁₇₂, SC 47₄₃₁, TC 49₅₃₄. + '3f3k9y3y,' + // #1547: 4 fonts: HK 56₈₃, JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂. + '3f3l6o3j,' + // #1548: 4 fonts: HK 56₈₃, JP 37₁₇₃, SC 48₄₃₂, TC 51₅₃₆. + '3f3l9y3z,' + // #1549: 5 fonts: HK 57₈₄, JP 37₁₇₃, KR 84₃₄₄, SC 49₄₃₃, TC 51₅₃₆. + '3g3k6o3k3y,' + // #1550: 4 fonts: HK 57₈₄, JP 37₁₇₃, KR 84₃₄₄, TC 51₅₃₆. + '3g3k6o7j,' + // #1551: 5 fonts: HK 57₈₄, JP 38₁₇₄, KR 84₃₄₄, SC 49₄₃₃, TC 52₅₃₇. + '3g3l6n3k3z,' + // #1552: 3 fonts: HK 57₈₄, JP 38₁₇₄, TC 52₅₃₇. + '3g3l13y,' + // #1553: 2 fonts: HK 57₈₄, TC 52₅₃₇. + '3g17k,' + // #1554: 2 fonts: HK 58₈₅, JP 38₁₇₄. + '3h3k,' + // #1555: 5 fonts: HK 58₈₅, JP 38₁₇₄, KR 85₃₄₅, SC 50₄₃₄, TC 53₅₃₈. + '3h3k6o3k3z,' + // #1556: 5 fonts: HK 58₈₅, JP 39₁₇₅, KR 85₃₄₅, SC 50₄₃₄, TC 53₅₃₈. + '3h3l6n3k3z,' + // #1557: 1 font: HK 59₈₆. + '3i,' + // #1558: 4 fonts: HK 59₈₆, JP 39₁₇₅, KR 85₃₄₅, TC 54₅₃₉. + '3i3k6n7l,' + // #1559: 4 fonts: HK 59₈₆, JP 39₁₇₅, SC 78₄₆₂, TC 53₅₃₈. + '3i3k11a2x,' + // #1560: 5 fonts: HK 60₈₇, JP 39₁₇₅, KR 86₃₄₆, SC 51₄₃₅, TC 54₅₃₉. + '3j3j6o3k3z,' + // #1561: 3 fonts: HK 60₈₇, JP 39₁₇₅, SC 51₄₃₅. + '3j3j9z,' + // #1562: 2 fonts: HK 60₈₇, TC 54₅₃₉. + '3j17j,' + // #1563: 1 font: HK 61₈₈. '3k,' - // #353: 1 font: Old North Arabian₉₁. - '3n,' - // #354: 1 font: Old Permic₉₂. - '3o,' - // #355: 1 font: Old Sogdian₉₄. - '3q,' - // #356: 1 font: Old South Arabian₉₅. - '3r,' - // #357: 1 font: Old Turkic₉₆. - '3s,' - // #358: 1 font: Palmyrene₁₀₁. - '3x,' - // #359: 1 font: Pau Cin Hau₁₀₂. - '3y,' - // #360: 1 font: Phags Pa₁₀₃. - '3z,' - // #361: 1 font: Runic₁₀₇. - '4d,' - // #362: 1 font: Sharada₁₁₀. - '4g,' - // #363: 1 font: Shavian₁₁₁. - '4h,' - // #364: 1 font: Sogdian₁₁₄. - '4k,' - // #365: 1 font: Soyombo₁₁₆. - '4m,' - // #366: 1 font: Syloti Nagri₁₁₈. - '4o,' - // #367: 3 fonts: Symbols₁₁₉, Symbols 2₁₂₀, Noto Color Emoji 2₁₄₅. - '4pay,' - // #368: 2 fonts: Symbols₁₁₉, Syriac₁₂₁. - '4pb,' - // #369: 3 fonts: Symbols₁₁₉, Noto Serif Tibetan₁₄₂, Noto Color Emoji 2₁₄₅. - '4pwc,' - // #370: 3 fonts: Symbols₁₁₉, Noto Color Emoji 1₁₄₄, Noto Color Emoji 2₁₄₅. - '4pya,' - // #371: 3 fonts: Symbols₁₁₉, Noto Color Emoji 2₁₄₅, Noto Color Emoji 8₁₅₁. - '4pzf,' - // #372: 3 fonts: Symbols₁₁₉, Noto Color Emoji 3₁₄₆, Noto Color Emoji 8₁₅₁. - '4p1ae,' - // #373: 2 fonts: Symbols₁₁₉, Noto Color Emoji 7₁₅₀. - '4p1e,' - // #374: 2 fonts: Symbols 2₁₂₀, Noto Color Emoji 1₁₄₄. - '4qx,' - // #375: 3 fonts: Symbols 2₁₂₀, Noto Color Emoji 2₁₄₅, Noto Color Emoji 7₁₅₀. - '4qye,' - // #376: 4 fonts: Symbols 2₁₂₀, Noto Color Emoji 2₁₄₅, Noto Color Emoji 8₁₅₁, Noto Color Emoji 9₁₅₂. - '4qyfa,' - // #377: 3 fonts: Symbols 2₁₂₀, Noto Color Emoji 2₁₄₅, Noto Color Emoji 9₁₅₂. - '4qyg,' - // #378: 3 fonts: Symbols 2₁₂₀, Noto Color Emoji 3₁₄₆, Noto Color Emoji 7₁₅₀. - '4qzd,' - // #379: 3 fonts: Symbols 2₁₂₀, Noto Color Emoji 3₁₄₆, Noto Color Emoji 9₁₅₂. - '4qzf,' - // #380: 3 fonts: Symbols 2₁₂₀, Noto Color Emoji 3₁₄₆, Noto Color Emoji 10₁₅₃. - '4qzg,' - // #381: 3 fonts: Symbols 2₁₂₀, Noto Color Emoji 7₁₅₀, Noto Color Emoji 10₁₅₃. - '4q1dc,' - // #382: 1 font: Thaana₁₃₂. - '5c,' - // #383: 1 font: Vai₁₃₇. - '5h,' - // #384: 1 font: Zanabazar Square₁₄₁. - '5l,' - // #385: 1 font: Noto Color Emoji 0₁₄₃. - '5n,' - // #386: 2 fonts: Noto Color Emoji 1₁₄₄, Noto Color Emoji 7₁₅₀. - '5of,' - // #387: 2 fonts: Noto Color Emoji 3₁₄₆, Noto Color Emoji 5₁₄₈. - '5qb,' - // #388: 2 fonts: Noto Color Emoji 3₁₄₆, Noto Color Emoji 7₁₅₀. - '5qd,' - // #389: 3 fonts: Noto Color Emoji 3₁₄₆, Noto Color Emoji 8₁₅₁, Noto Color Emoji 10₁₅₃. - '5qeb,' - // #390: 2 fonts: Noto Color Emoji 3₁₄₆, Noto Color Emoji 9₁₅₂. - '5qf,' - // #391: 2 fonts: Noto Color Emoji 4₁₄₇, Noto Color Emoji 6₁₄₉. - '5rb,' - // #392: 3 fonts: Noto Color Emoji 5₁₄₈, Noto Color Emoji 8₁₅₁, Noto Color Emoji 10₁₅₃. - '5scb,' - // #393: 2 fonts: Noto Color Emoji 6₁₄₉, Noto Color Emoji 10₁₅₃. - '5td,' - // #394: 2 fonts: Noto Color Emoji 7₁₅₀, Noto Color Emoji 8₁₅₁. - '5ua,' - // #395: 3 fonts: Noto Color Emoji 7₁₅₀, Noto Color Emoji 9₁₅₂, Noto Color Emoji 10₁₅₃. - '5uba,' - // #396: 2 fonts: Noto Color Emoji 7₁₅₀, Noto Color Emoji 10₁₅₃. - '5uc,' - // #397: 3 fonts: Noto Color Emoji 8₁₅₁, Noto Color Emoji 9₁₅₂, Noto Color Emoji 10₁₅₃. - '5vaa' + // #1564: 4 fonts: HK 61₈₈, JP 40₁₇₆, SC 53₄₃₇, TC 56₅₄₁. + '3k3j10a3z,' + // #1565: 3 fonts: HK 61₈₈, JP 40₁₇₆, TC 55₅₄₀. + '3k3j13z,' + // #1566: 2 fonts: HK 61₈₈, JP 41₁₇₇. + '3k3k,' + // #1567: 3 fonts: HK 61₈₈, JP 41₁₇₇, SC 53₄₃₇. + '3k3k9z,' + // #1568: 4 fonts: HK 61₈₈, JP 41₁₇₇, SC 54₄₃₈, TC 56₅₄₁. + '3k3k10a3y,' + // #1569: 5 fonts: HK 61₈₈, JP 62₁₉₈, KR 86₃₄₆, SC 53₄₃₇, TC 55₅₄₀. + '3k4f5r3m3y,' + // #1570: 3 fonts: HK 61₈₈, SC 52₄₃₆, TC 55₅₄₀. + '3k13j3z,' + // #1571: 2 fonts: HK 61₈₈, TC 55₅₄₀. + '3k17j,' + // #1572: 2 fonts: HK 63₉₀, JP 42₁₇₈. + '3m3j,' + // #1573: 5 fonts: HK 63₉₀, JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉, TC 57₅₄₂. + '3m3j6m3n3y,' + // #1574: 4 fonts: HK 63₉₀, JP 42₁₇₈, SC 55₄₃₉, TC 57₅₄₂. + '3m3j10a3y,' + // #1575: 4 fonts: HK 63₉₀, JP 42₁₇₈, SC 56₄₄₀, TC 58₅₄₃. + '3m3j10b3y,' + // #1576: 5 fonts: HK 63₉₀, JP 43₁₇₉, KR 88₃₄₈, SC 56₄₄₀, TC 59₅₄₄. + '3m3k6m3n3z,' + // #1577: 4 fonts: HK 63₉₀, JP 43₁₇₉, SC 56₄₄₀, TC 59₅₄₄. + '3m3k10a3z,' + // #1578: 3 fonts: HK 63₉₀, JP 43₁₇₉, TC 58₅₄₃. + '3m3k13z,' + // #1579: 3 fonts: HK 63₉₀, SC 56₄₄₀, TC 58₅₄₃. + '3m13l3y,' + // #1580: 4 fonts: HK 64₉₁, KR 88₃₄₈, SC 56₄₄₀, TC 59₅₄₄. + '3n9w3n3z,' + // #1581: 2 fonts: HK 65₉₂, JP 44₁₈₀. + '3o3j,' + // #1582: 4 fonts: HK 65₉₂, JP 44₁₈₀, SC 57₄₄₁, TC 60₅₄₅. + '3o3j10a3z,' + // #1583: 3 fonts: HK 65₉₂, JP 44₁₈₀, TC 60₅₄₅. + '3o3j14a,' + // #1584: 2 fonts: HK 65₉₂, SC 57₄₄₁. + '3o13k,' + // #1585: 3 fonts: HK 65₉₂, SC 58₄₄₂, TC 61₅₄₆. + '3o13l3z,' + // #1586: 5 fonts: HK 67₉₄, JP 45₁₈₁, KR 90₃₅₀, SC 60₄₄₄, TC 63₅₄₈. + '3q3i6m3p3z,' + // #1587: 2 fonts: HK 68₉₅, JP 46₁₈₂. + '3r3i,' + // #1588: 4 fonts: HK 68₉₅, JP 46₁₈₂, SC 60₄₄₄, TC 63₅₄₈. + '3r3i10b3z,' + // #1589: 3 fonts: HK 68₉₅, JP 46₁₈₂, SC 61₄₄₅. + '3r3i10c,' + // #1590: 3 fonts: HK 68₉₅, JP 46₁₈₂, SC 62₄₄₆. + '3r3i10d,' + // #1591: 2 fonts: HK 68₉₅, SC 61₄₄₅. + '3r13l,' + // #1592: 4 fonts: HK 69₉₆, JP 46₁₈₂, SC 81₄₆₅, TC 64₅₄₉. + '3s3h10w3f,' + // #1593: 5 fonts: HK 69₉₆, JP 47₁₈₃, KR 90₃₅₀, SC 62₄₄₆, TC 65₅₅₀. + '3s3i6k3r3z,' + // #1594: 5 fonts: HK 69₉₆, JP 47₁₈₃, KR 90₃₅₀, SC 63₄₄₇, TC 65₅₅₀. + '3s3i6k3s3y,' + // #1595: 5 fonts: HK 69₉₆, JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇, TC 65₅₅₀. + '3s3i6l3r3y,' + // #1596: 4 fonts: HK 69₉₆, JP 47₁₈₃, SC 91₄₇₅, TC 65₅₅₀. + '3s3i11f2w,' + // #1597: 2 fonts: HK 69₉₆, TC 64₅₄₉. + '3s17k,' + // #1598: 3 fonts: HK 70₉₇, SC 63₄₄₇, TC 65₅₅₀. + '3t13l3y,' + // #1599: 4 fonts: HK 71₉₈, JP 48₁₈₄, KR 92₃₅₂, TC 67₅₅₂. + '3u3h6l7r,' + // #1600: 4 fonts: HK 71₉₈, JP 48₁₈₄, SC 92₄₇₆, TC 66₅₅₁. + '3u3h11f2w,' + // #1601: 3 fonts: HK 71₉₈, JP 48₁₈₄, TC 66₅₅₁. + '3u3h14c,' + // #1602: 3 fonts: HK 71₉₈, JP 49₁₈₅, SC 65₄₄₉. + '3u3i10d,' + // #1603: 5 fonts: HK 72₉₉, JP 49₁₈₅, KR 92₃₅₂, SC 65₄₄₉, TC 67₅₅₂. + '3v3h6k3s3y,' + // #1604: 5 fonts: HK 72₉₉, JP 50₁₈₆, KR 93₃₅₃, SC 66₄₅₀, TC 69₅₅₄. + '3v3i6k3s3z,' + // #1605: 3 fonts: HK 72₉₉, SC 65₄₄₉, TC 68₅₅₃. + '3v13l3z,' + // #1606: 3 fonts: HK 72₉₉, SC 66₄₅₀, TC 69₅₅₄. + '3v13m3z,' + // #1607: 3 fonts: HK 73₁₀₀, JP 50₁₈₆, SC 67₄₅₁. + '3w3h10e,' + // #1608: 3 fonts: HK 73₁₀₀, JP 50₁₈₆, TC 69₅₅₄. + '3w3h14d,' + // #1609: 2 fonts: HK 73₁₀₀, SC 66₄₅₀. + '3w13l,' + // #1610: 3 fonts: HK 73₁₀₀, SC 92₄₇₆, TC 69₅₅₄. + '3w14l2z,' + // #1611: 2 fonts: HK 74₁₀₁, TC 69₅₅₄. + '3x17k,' + // #1612: 3 fonts: HK 75₁₀₂, SC 67₄₅₁, TC 70₅₅₅. + '3y13k3z,' + // #1613: 2 fonts: HK 75₁₀₂, TC 69₅₅₄. + '3y17j,' + // #1614: 3 fonts: HK 76₁₀₃, JP 51₁₈₇, TC 70₅₅₅. + '3z3f14d,' + // #1615: 3 fonts: HK 76₁₀₃, SC 67₄₅₁, TC 70₅₅₅. + '3z13j3z,' + // #1616: 2 fonts: HK 77₁₀₄, JP 51₁₈₇. + '4a3e,' + // #1617: 4 fonts: HK 77₁₀₄, JP 53₁₈₉, SC 67₄₅₁, TC 70₅₅₅. + '4a3g10b3z,' + // #1618: 5 fonts: HK 77₁₀₄, JP 54₁₉₀, KR 111₃₇₁, SC 67₄₅₁, TC 70₅₅₅. + '4a3h6y3b3z,' + // #1619: 3 fonts: HK 77₁₀₄, JP 54₁₉₀, TC 70₅₅₅. + '4a3h14a,' + // #1620: 5 fonts: HK 77₁₀₄, JP 55₁₉₁, KR 96₃₅₆, SC 68₄₅₂, TC 71₅₅₆. + '4a3i6i3r3z,' + // #1621: 4 fonts: HK 77₁₀₄, JP 55₁₉₁, KR 96₃₅₆, TC 71₅₅₆. + '4a3i6i7r,' + // #1622: 4 fonts: HK 77₁₀₄, JP 55₁₉₁, SC 68₄₅₂, TC 71₅₅₆. + '4a3i10a3z,' + // #1623: 5 fonts: HK 77₁₀₄, JP 61₁₉₇, KR 95₃₅₅, SC 68₄₅₂, TC 70₅₅₅. + '4a3o6b3s3y,' + // #1624: 2 fonts: HK 77₁₀₄, TC 70₅₅₅. + '4a17i,' + // #1625: 5 fonts: HK 78₁₀₅, JP 56₁₉₂, KR 96₃₅₆, SC 68₄₅₂, TC 71₅₅₆. + '4b3i6h3r3z,' + // #1626: 6 fonts: HK 78₁₀₅, JP 56₁₉₂, KR 96₃₅₆, SC 69₄₅₃, TC 71₅₅₆, Symbols₇₀₂. + '4b3i6h3s3y5p,' + // #1627: 4 fonts: HK 80₁₀₇, JP 57₁₉₃, SC 71₄₅₅, TC 74₅₅₉. + '4d3h10b3z,' + // #1628: 5 fonts: HK 80₁₀₇, JP 68₂₀₄, KR 97₃₅₇, SC 71₄₅₅, TC 74₅₅₉. + '4d3s5w3t3z,' + // #1629: 6 fonts: HK 81₁₀₈, JP 61₁₉₇, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e3k6e3t3z3q,' + // #1630: 6 fonts: HK 81₁₀₈, JP 91₂₂₇, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e4o5a3t3z3q,' + // #1631: 15 fonts: HK 82₁₀₉, HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 73₄₅₇, SC 98₄₈₂, SC 99₄₈₃, TC 76₅₆₁, TC 102₅₈₇, TC 103₅₈₈, Noto Sans₅₉₁. + '4fxa2i2ja4sa2wya2zzac,' + // #1632: 9 fonts: HK 82₁₀₉, HK 106₁₃₃, JP 59₁₉₅, JP 121₂₅₇, KR 121₃₈₁, SC 98₄₈₂, TC 76₅₆₁, TC 102₅₈₇, Noto Sans₅₉₁. + '4fx2j2j4t3w3azd,' + // #1633: 7 fonts: HK 82₁₀₉, JP 59₁₉₅, KR 99₃₅₉, SC 73₄₅₇, TC 76₅₆₁, Noto Sans₅₉₁, Adlam₅₉₂. + '4f3h6h3t3z1da,' + // #1634: 10 fonts: HK 83₁₁₀, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 75₄₅₉, SC 99₄₈₃, TC 79₅₆₄, TC 103₅₈₈, Noto Sans₅₉₁. + '4gx2i2k4t2yx3cxc,' + // #1635: 15 fonts: HK 84₁₁₁, HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 76₄₆₀, SC 98₄₈₂, SC 99₄₈₃, TC 80₅₆₅, TC 102₅₈₇, TC 103₅₈₈, Noto Sans₅₉₁. + '4hva2i2ja4sa2zva3dvac,' + // #1636: 10 fonts: HK 84₁₁₁, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Sans₅₉₁. + '4hx2h2l4t2yx3cxb,' + // #1637: 5 fonts: HK 85₁₁₂, JP 8₁₄₄, KR 65₃₂₅, SC 7₃₉₁, TC 81₅₆₆. + '4i1f6y2n6s,' + // #1638: 5 fonts: HK 85₁₁₂, JP 13₁₄₉, KR 68₃₂₈, SC 79₄₆₃, TC 81₅₆₆. + '4i1k6w5e3y,' + // #1639: 5 fonts: HK 85₁₁₂, JP 16₁₅₂, KR 70₃₃₀, SC 18₄₀₂, TC 81₅₆₆. + '4i1n6v2t6h,' + // #1640: 4 fonts: HK 85₁₁₂, JP 19₁₅₅, SC 22₄₀₆, TC 81₅₆₆. + '4i1q9q6d,' + // #1641: 5 fonts: HK 85₁₁₂, JP 31₁₆₇, KR 79₃₃₉, SC 39₄₂₃, TC 81₅₆₆. + '4i2c6p3f5m,' + // #1642: 4 fonts: HK 85₁₁₂, JP 31₁₆₇, SC 40₄₂₄, TC 81₅₆₆. + '4i2c9w5l,' + // #1643: 5 fonts: HK 85₁₁₂, JP 33₁₆₉, KR 81₃₄₁, SC 43₄₂₇, TC 81₅₆₆. + '4i2e6p3h5i,' + // #1644: 4 fonts: HK 85₁₁₂, JP 38₁₇₄, SC 50₄₃₄, TC 81₅₆₆. + '4i2j9z5b,' + // #1645: 4 fonts: HK 85₁₁₂, JP 42₁₇₈, SC 55₄₃₉, TC 81₅₆₆. + '4i2n10a4w,' + // #1646: 5 fonts: HK 85₁₁₂, JP 43₁₇₉, KR 88₃₄₈, SC 56₄₄₀, TC 81₅₆₆. + '4i2o6m3n4v,' + // #1647: 5 fonts: HK 85₁₁₂, JP 46₁₈₂, KR 90₃₅₀, SC 78₄₆₂, TC 81₅₆₆. + '4i2r6l4h3z,' + // #1648: 4 fonts: HK 85₁₁₂, JP 54₁₉₀, SC 68₄₅₂, TC 81₅₆₆. + '4i2z10b4j,' + // #1649: 5 fonts: HK 85₁₁₂, JP 99₂₃₅, KR 0₂₆₀, SC 1₃₈₅, TC 81₅₆₆. + '4i4sy4u6y,' + // #1650: 3 fonts: HK 85₁₁₂, SC 61₄₄₅, TC 81₅₆₆. + '4i12u4q,' + // #1651: 3 fonts: HK 85₁₁₂, SC 93₄₇₇, TC 81₅₆₆. + '4i14a3k,' + // #1652: 5 fonts: HK 86₁₁₃, JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 82₅₆₇. + '4j1e6z2m6t,' + // #1653: 5 fonts: HK 86₁₁₃, JP 15₁₅₁, KR 69₃₂₉, SC 17₄₀₁, TC 82₅₆₇. + '4j1l6v2t6j,' + // #1654: 5 fonts: HK 86₁₁₃, JP 18₁₅₄, KR 71₃₃₁, SC 20₄₀₄, TC 82₅₆₇. + '4j1o6u2u6g,' + // #1655: 4 fonts: HK 86₁₁₃, JP 18₁₅₄, SC 78₄₆₂, TC 82₅₆₇. + '4j1o11v4a,' + // #1656: 4 fonts: HK 86₁₁₃, JP 20₁₅₆, SC 22₄₀₆, TC 82₅₆₇. + '4j1q9p6e,' + // #1657: 4 fonts: HK 86₁₁₃, JP 27₁₆₃, SC 33₄₁₇, TC 82₅₆₇. + '4j1x9t5t,' + // #1658: 4 fonts: HK 86₁₁₃, JP 27₁₆₃, SC 77₄₆₁, TC 82₅₆₇. + '4j1x11l4b,' + // #1659: 5 fonts: HK 86₁₁₃, JP 31₁₆₇, KR 79₃₃₉, SC 39₄₂₃, TC 82₅₆₇. + '4j2b6p3f5n,' + // #1660: 5 fonts: HK 86₁₁₃, JP 32₁₆₈, KR 80₃₄₀, SC 41₄₂₅, TC 82₅₆₇. + '4j2c6p3g5l,' + // #1661: 4 fonts: HK 86₁₁₃, JP 42₁₇₈, SC 55₄₃₉, TC 82₅₆₇. + '4j2m10a4x,' + // #1662: 4 fonts: HK 86₁₁₃, JP 43₁₇₉, SC 57₄₄₁, TC 82₅₆₇. + '4j2n10b4v,' + // #1663: 4 fonts: HK 86₁₁₃, JP 46₁₈₂, SC 61₄₄₅, TC 82₅₆₇. + '4j2q10c4r,' + // #1664: 4 fonts: HK 86₁₁₃, JP 46₁₈₂, SC 80₄₆₄, TC 82₅₆₇. + '4j2q10v3y,' + // #1665: 3 fonts: HK 86₁₁₃, SC 28₄₁₂, TC 82₅₆₇. + '4j11m5y,' + // #1666: 3 fonts: HK 86₁₁₃, SC 82₄₆₆, TC 82₅₆₇. + '4j13o3w,' + // #1667: 5 fonts: HK 87₁₁₄, JP 12₁₄₈, KR 68₃₂₈, SC 13₃₉₇, TC 83₅₆₈. + '4k1h6x2q6o,' + // #1668: 5 fonts: HK 87₁₁₄, JP 13₁₄₉, KR 68₃₂₈, SC 14₃₉₈, TC 83₅₆₈. + '4k1i6w2r6n,' + // #1669: 5 fonts: HK 87₁₁₄, JP 35₁₇₁, KR 82₃₄₂, SC 45₄₂₉, TC 83₅₆₈. + '4k2e6o3i5i,' + // #1670: 4 fonts: HK 87₁₁₄, JP 37₁₇₃, SC 48₄₃₂, TC 83₅₆₈. + '4k2g9y5f,' + // #1671: 5 fonts: HK 87₁₁₄, JP 38₁₇₄, KR 84₃₄₄, SC 49₄₃₃, TC 83₅₆₈. + '4k2h6n3k5e,' + // #1672: 5 fonts: HK 87₁₁₄, JP 45₁₈₁, KR 90₃₅₀, SC 60₄₄₄, TC 83₅₆₈. + '4k2o6m3p4t,' + // #1673: 4 fonts: HK 87₁₁₄, JP 46₁₈₂, SC 62₄₄₆, TC 83₅₆₈. + '4k2p10d4r,' + // #1674: 5 fonts: HK 87₁₁₄, JP 100₂₃₆, KR 0₂₆₀, SC 1₃₈₅, TC 83₅₆₈. + '4k4rx4u7a,' + // #1675: 3 fonts: HK 87₁₁₄, SC 93₄₇₇, TC 83₅₆₈. + '4k13y3m,' + // #1676: 4 fonts: HK 88₁₁₅, JP 3₁₃₉, SC 2₃₈₆, TC 84₅₆₉. + '4lx9m7a,' + // #1677: 5 fonts: HK 88₁₁₅, JP 6₁₄₂, KR 65₃₂₅, SC 5₃₈₉, TC 84₅₆₉. + '4l1a7a2l6x,' + // #1678: 4 fonts: HK 88₁₁₅, JP 18₁₅₄, SC 20₄₀₄, TC 84₅₆₉. + '4l1m9p6i,' + // #1679: 5 fonts: HK 88₁₁₅, JP 22₁₅₈, KR 73₃₃₃, SC 79₄₆₃, TC 84₅₆₉. + '4l1q6s4z4b,' + // #1680: 5 fonts: HK 88₁₁₅, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 84₅₆₉. + '4l1r6s2y6b,' + // #1681: 5 fonts: HK 88₁₁₅, JP 28₁₆₄, KR 77₃₃₇, SC 34₄₁₈, TC 84₅₆₉. + '4l1w6q3c5u,' + // #1682: 5 fonts: HK 88₁₁₅, JP 29₁₆₅, KR 77₃₃₇, SC 36₄₂₀, TC 84₅₆₉. + '4l1x6p3e5s,' + // #1683: 5 fonts: HK 88₁₁₅, JP 31₁₆₇, KR 79₃₃₉, SC 40₄₂₄, TC 84₅₆₉. + '4l1z6p3g5o,' + // #1684: 5 fonts: HK 88₁₁₅, JP 37₁₇₃, KR 83₃₄₃, SC 47₄₃₁, TC 84₅₆₉. + '4l2f6n3j5h,' + // #1685: 4 fonts: HK 88₁₁₅, JP 45₁₈₁, SC 60₄₄₄, TC 84₅₆₉. + '4l2n10c4u,' + // #1686: 5 fonts: HK 88₁₁₅, JP 49₁₈₅, KR 92₃₅₂, SC 65₄₄₉, TC 84₅₆₉. + '4l2r6k3s4p,' + // #1687: 4 fonts: HK 88₁₁₅, JP 57₁₉₃, SC 70₄₅₄, TC 84₅₆₉. + '4l2z10a4k,' + // #1688: 6 fonts: HK 88₁₁₅, JP 95₂₃₁, KR 0₂₆₀, SC 79₄₆₃, TC 84₅₆₉, Mongolian₆₆₂. + '4l4l1c7u4b3o,' + // #1689: 3 fonts: HK 88₁₁₅, SC 94₄₇₈, TC 84₅₆₉. + '4l13y3m,' + // #1690: 4 fonts: HK 89₁₁₆, JP 7₁₄₃, SC 6₃₉₀, TC 85₅₇₀. + '4m1a9m6x,' + // #1691: 4 fonts: HK 89₁₁₆, JP 12₁₄₈, SC 13₃₉₇, TC 85₅₇₀. + '4m1f9o6q,' + // #1692: 5 fonts: HK 89₁₁₆, JP 27₁₆₃, KR 76₃₃₆, SC 80₄₆₄, TC 85₅₇₀. + '4m1u6q4x4b,' + // #1693: 5 fonts: HK 89₁₁₆, JP 28₁₆₄, KR 77₃₃₇, SC 81₄₆₅, TC 85₅₇₀. + '4m1v6q4x4a,' + // #1694: 5 fonts: HK 89₁₁₆, JP 30₁₆₆, KR 79₃₃₉, SC 38₄₂₂, TC 85₅₇₀. + '4m1x6q3e5r,' + // #1695: 5 fonts: HK 89₁₁₆, JP 34₁₇₀, KR 81₃₄₁, SC 44₄₂₈, TC 85₅₇₀. + '4m2b6o3i5l,' + // #1696: 5 fonts: HK 89₁₁₆, JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂, TC 85₅₇₀. + '4m2e6o3j5h,' + // #1697: 5 fonts: HK 89₁₁₆, JP 41₁₇₇, KR 87₃₄₇, SC 81₄₆₅, TC 85₅₇₀. + '4m2i6n4n4a,' + // #1698: 5 fonts: HK 89₁₁₆, JP 43₁₇₉, KR 88₃₄₈, SC 81₄₆₅, TC 85₅₇₀. + '4m2k6m4m4a,' + // #1699: 5 fonts: HK 89₁₁₆, JP 44₁₈₀, KR 89₃₄₉, SC 84₄₆₈, TC 85₅₇₀. + '4m2l6m4o3x,' + // #1700: 5 fonts: HK 89₁₁₆, JP 47₁₈₃, KR 91₃₅₁, SC 81₄₆₅, TC 85₅₇₀. + '4m2o6l4j4a,' + // #1701: 5 fonts: HK 89₁₁₆, JP 50₁₈₆, KR 93₃₅₃, SC 81₄₆₅, TC 85₅₇₀. + '4m2r6k4h4a,' + // #1702: 3 fonts: HK 89₁₁₆, SC 94₄₇₈, TC 85₅₇₀. + '4m13x3n,' + // #1703: 5 fonts: HK 90₁₁₇, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 86₅₇₁. + '4n1b6y2n6w,' + // #1704: 5 fonts: HK 90₁₁₇, JP 10₁₄₆, KR 67₃₂₇, SC 10₃₉₄, TC 86₅₇₁. + '4n1c6y2o6u,' + // #1705: 5 fonts: HK 90₁₁₇, JP 12₁₄₈, KR 67₃₂₇, SC 13₃₉₇, TC 86₅₇₁. + '4n1e6w2r6r,' + // #1706: 5 fonts: HK 90₁₁₇, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 86₅₇₁. + '4n1n6t2w6g,' + // #1707: 5 fonts: HK 90₁₁₇, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 86₅₇₁. + '4n1p6s2y6d,' + // #1708: 5 fonts: HK 90₁₁₇, JP 26₁₆₂, KR 76₃₃₆, SC 31₄₁₅, TC 86₅₇₁. + '4n1s6r3a5z,' + // #1709: 5 fonts: HK 90₁₁₇, JP 34₁₇₀, KR 82₃₄₂, SC 44₄₂₈, TC 86₅₇₁. + '4n2a6p3h5m,' + // #1710: 5 fonts: HK 90₁₁₇, JP 35₁₇₁, KR 82₃₄₂, SC 82₄₆₆, TC 86₅₇₁. + '4n2b6o4t4a,' + // #1711: 5 fonts: HK 90₁₁₇, JP 49₁₈₅, KR 92₃₅₂, SC 65₄₄₉, TC 86₅₇₁. + '4n2p6k3s4r,' + // #1712: 4 fonts: HK 90₁₁₇, JP 49₁₈₅, SC 82₄₆₆, TC 86₅₇₁. + '4n2p10u4a,' + // #1713: 3 fonts: HK 90₁₁₇, SC 82₄₆₆, TC 86₅₇₁. + '4n13k4a,' + // #1714: 5 fonts: HK 91₁₁₈, JP 12₁₄₈, KR 68₃₂₈, SC 82₄₆₆, TC 87₅₇₂. + '4o1d6x5h4b,' + // #1715: 5 fonts: HK 91₁₁₈, JP 17₁₅₃, KR 71₃₃₁, SC 82₄₆₆, TC 87₅₇₂. + '4o1i6v5e4b,' + // #1716: 5 fonts: HK 91₁₁₈, JP 22₁₅₈, KR 74₃₃₄, SC 26₄₁₀, TC 87₅₇₂. + '4o1n6t2x6f,' + // #1717: 5 fonts: HK 91₁₁₈, JP 23₁₅₉, KR 74₃₃₄, SC 82₄₆₆, TC 87₅₇₂. + '4o1o6s5b4b,' + // #1718: 5 fonts: HK 91₁₁₈, JP 24₁₆₀, KR 75₃₃₅, SC 82₄₆₆, TC 87₅₇₂. + '4o1p6s5a4b,' + // #1719: 5 fonts: HK 91₁₁₈, JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇, TC 87₅₇₂. + '4o2m6l3r4u,' + // #1720: 5 fonts: HK 92₁₁₉, JP 6₁₄₂, KR 65₃₂₅, SC 5₃₈₉, TC 88₅₇₃. + '4pw7a2l7b,' + // #1721: 5 fonts: HK 92₁₁₉, JP 13₁₄₉, KR 68₃₂₈, SC 14₃₉₈, TC 88₅₇₃. + '4p1d6w2r6s,' + // #1722: 5 fonts: HK 92₁₁₉, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 88₅₇₃. + '4p1l6t2w6i,' + // #1723: 4 fonts: HK 92₁₁₉, JP 30₁₆₆, SC 85₄₆₉, TC 88₅₇₃. + '4p1u11q3z,' + // #1724: 5 fonts: HK 92₁₁₉, JP 32₁₆₈, KR 80₃₄₀, SC 40₄₂₄, TC 88₅₇₃. + '4p1w6p3f5s,' + // #1725: 4 fonts: HK 92₁₁₉, JP 34₁₇₀, SC 44₄₂₈, TC 88₅₇₃. + '4p1y9x5o,' + // #1726: 5 fonts: HK 92₁₁₉, JP 37₁₇₃, KR 84₃₄₄, SC 83₄₆₇, TC 88₅₇₃. + '4p2b6o4s4b,' + // #1727: 4 fonts: HK 92₁₁₉, JP 41₁₇₇, SC 53₄₃₇, TC 88₅₇₃. + '4p2f9z5f,' + // #1728: 3 fonts: HK 92₁₁₉, SC 95₄₇₉, TC 88₅₇₃. + '4p13v3p,' + // #1729: 5 fonts: HK 93₁₂₀, JP 24₁₆₀, KR 75₃₃₅, SC 29₄₁₃, TC 89₅₇₄. + '4q1n6s2z6e,' + // #1730: 5 fonts: HK 93₁₂₀, JP 73₂₀₉, KR 70₃₃₀, SC 18₄₀₂, TC 89₅₇₄. + '4q3k4q2t6p,' + // #1731: 3 fonts: HK 93₁₂₀, SC 87₄₇₁, TC 89₅₇₄. + '4q13m3y,' + // #1732: 5 fonts: HK 94₁₂₁, JP 8₁₄₄, KR 65₃₂₅, SC 7₃₉₁, TC 90₅₇₅. + '4rw6y2n7b,' + // #1733: 5 fonts: HK 94₁₂₁, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 90₅₇₅. + '4r1l6s2y6h,' + // #1734: 4 fonts: HK 94₁₂₁, JP 26₁₆₂, SC 32₄₁₆, TC 90₅₇₅. + '4r1o9t6c,' + // #1735: 4 fonts: HK 94₁₂₁, JP 29₁₆₅, SC 85₄₆₉, TC 90₅₇₅. + '4r1r11r4b,' + // #1736: 5 fonts: HK 95₁₂₂, JP 25₁₆₁, KR 75₃₃₅, SC 31₄₁₅, TC 91₅₇₆. + '4s1m6r3b6e,' + // #1737: 5 fonts: HK 95₁₂₂, JP 29₁₆₅, KR 78₃₃₈, SC 37₄₂₁, TC 91₅₇₆. + '4s1q6q3e5y,' + // #1738: 5 fonts: HK 95₁₂₂, JP 32₁₆₈, KR 80₃₄₀, SC 41₄₂₅, TC 91₅₇₆. + '4s1t6p3g5u,' + // #1739: 5 fonts: HK 95₁₂₂, JP 35₁₇₁, KR 82₃₄₂, SC 86₄₇₀, TC 91₅₇₆. + '4s1w6o4x4b,' + // #1740: 4 fonts: HK 95₁₂₂, JP 36₁₇₂, SC 47₄₃₁, TC 91₅₇₆. + '4s1x9y5o,' + // #1741: 5 fonts: HK 95₁₂₂, JP 43₁₇₉, KR 88₃₄₈, SC 86₄₇₀, TC 91₅₇₆. + '4s2e6m4r4b,' + // #1742: 4 fonts: HK 95₁₂₂, JP 45₁₈₁, SC 88₄₇₂, TC 91₅₇₆. + '4s2g11e3z,' + // #1743: 5 fonts: HK 95₁₂₂, JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇, TC 91₅₇₆. + '4s2i6l3r4y,' + // #1744: 5 fonts: HK 95₁₂₂, JP 97₂₃₃, KR 70₃₃₀, SC 18₄₀₂, TC 91₅₇₆. + '4s4g3s2t6r,' + // #1745: 5 fonts: HK 96₁₂₃, JP 7₁₄₃, KR 65₃₂₅, SC 6₃₉₀, TC 92₅₇₇. + '4tt6z2m7e,' + // #1746: 5 fonts: HK 96₁₂₃, JP 13₁₄₉, KR 68₃₂₈, SC 14₃₉₈, TC 92₅₇₇. + '4tz6w2r6w,' + // #1747: 4 fonts: HK 96₁₂₃, JP 15₁₅₁, SC 16₄₀₀, TC 92₅₇₇. + '4t1b9o6u,' + // #1748: 5 fonts: HK 96₁₂₃, JP 18₁₅₄, KR 71₃₃₁, SC 20₄₀₄, TC 92₅₇₇. + '4t1e6u2u6q,' + // #1749: 5 fonts: HK 96₁₂₃, JP 32₁₆₈, KR 80₃₄₀, SC 40₄₂₄, TC 92₅₇₇. + '4t1s6p3f5w,' + // #1750: 5 fonts: HK 96₁₂₃, JP 36₁₇₂, KR 83₃₄₃, SC 47₄₃₁, TC 92₅₇₇. + '4t1w6o3j5p,' + // #1751: 4 fonts: HK 96₁₂₃, JP 44₁₈₀, SC 58₄₄₂, TC 92₅₇₇. + '4t2e10b5e,' + // #1752: 5 fonts: HK 97₁₂₄, JP 15₁₅₁, KR 69₃₂₉, SC 16₄₀₀, TC 93₅₇₈. + '4u1a6v2s6v,' + // #1753: 5 fonts: HK 97₁₂₄, JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂, TC 93₅₇₈. + '4u1w6o3j5p,' + // #1754: 5 fonts: HK 97₁₂₄, JP 38₁₇₄, KR 85₃₄₅, SC 87₄₇₁, TC 93₅₇₈. + '4u1x6o4v4c,' + // #1755: 5 fonts: HK 97₁₂₄, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 93₅₇₈. + '4u2d6m3o5f,' + // #1756: 5 fonts: HK 97₁₂₄, JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇, TC 93₅₇₈. + '4u2g6l3r5a,' + // #1757: 5 fonts: HK 97₁₂₄, JP 83₂₁₉, KR 67₃₂₇, SC 12₃₉₆, TC 93₅₇₈. + '4u3q4d2q6z,' + // #1758: 5 fonts: HK 97₁₂₄, JP 90₂₂₆, KR 84₃₄₄, SC 88₄₇₂, TC 93₅₇₈. + '4u3x4n4x4b,' + // #1759: 5 fonts: HK 97₁₂₄, JP 91₂₂₇, KR 88₃₄₈, SC 89₄₇₃, TC 93₅₇₈. + '4u3y4q4u4a,' + // #1760: 5 fonts: HK 97₁₂₄, JP 98₂₃₄, KR 80₃₄₀, SC 89₄₇₃, TC 93₅₇₈. + '4u4f4b5c4a,' + // #1761: 5 fonts: HK 97₁₂₄, JP 101₂₃₇, KR 90₃₅₀, SC 89₄₇₃, TC 93₅₇₈. + '4u4i4i4s4a,' + // #1762: 5 fonts: HK 98₁₂₅, JP 38₁₇₄, KR 85₃₄₅, SC 90₄₇₄, TC 94₅₇₉. + '4v1w6o4y4a,' + // #1763: 5 fonts: HK 98₁₂₅, JP 41₁₇₇, KR 87₃₄₇, SC 53₄₃₇, TC 94₅₇₉. + '4v1z6n3l5l,' + // #1764: 5 fonts: HK 98₁₂₅, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 94₅₇₉. + '4v2c6m3o5g,' + // #1765: 5 fonts: HK 98₁₂₅, JP 100₂₃₆, KR 81₃₄₁, SC 91₄₇₅, TC 94₅₇₉. + '4v4g4a5d3z,' + // #1766: 5 fonts: HK 99₁₂₆, JP 23₁₅₉, KR 74₃₃₄, SC 28₄₁₂, TC 95₅₈₀. + '4w1g6s2z6l,' + // #1767: 5 fonts: HK 99₁₂₆, JP 30₁₆₆, KR 79₃₃₉, SC 38₄₂₂, TC 95₅₈₀. + '4w1n6q3e6b,' + // #1768: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 108₃₆₈, SC 96₄₈₀, TC 95₅₈₀. + '4w4y4i4h3v,' + // #1769: 5 fonts: HK 100₁₂₇, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 96₅₈₁. + '4x1d6t2w6q,' + // #1770: 5 fonts: HK 100₁₂₇, JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂, TC 96₅₈₁. + '4x1t6o3j5s,' + // #1771: 4 fonts: HK 100₁₂₇, JP 46₁₈₂, SC 91₄₇₅, TC 96₅₈₁. + '4x2c11g4b,' + // #1772: 5 fonts: HK 100₁₂₇, JP 102₂₃₈, KR 81₃₄₁, SC 91₄₇₅, TC 96₅₈₁. + '4x4g3y5d4b,' + // #1773: 5 fonts: HK 100₁₂₇, JP 108₂₄₄, KR 66₃₂₆, SC 8₃₉₂, TC 96₅₈₁. + '4x4m3d2n7g,' + // #1774: 5 fonts: HK 101₁₂₈, JP 10₁₄₆, KR 66₃₂₆, SC 9₃₉₃, TC 97₅₈₂. + '4yr6x2o7g,' + // #1775: 5 fonts: HK 101₁₂₈, JP 14₁₅₀, KR 69₃₂₉, SC 15₃₉₉, TC 97₅₈₂. + '4yv6w2r7a,' + // #1776: 5 fonts: HK 101₁₂₈, JP 38₁₇₄, KR 84₃₄₄, SC 49₄₃₃, TC 97₅₈₂. + '4y1t6n3k5s,' + // #1777: 5 fonts: HK 101₁₂₈, JP 103₂₃₉, KR 75₃₃₅, SC 92₄₇₆, TC 97₅₈₂. + '4y4g3r5k4b,' + // #1778: 5 fonts: HK 101₁₂₈, JP 105₂₄₁, KR 70₃₃₀, SC 18₄₀₂, TC 97₅₈₂. + '4y4i3k2t6x,' + // #1779: 5 fonts: HK 101₁₂₈, JP 109₂₄₅, KR 91₃₅₁, SC 92₄₇₆, TC 97₅₈₂. + '4y4m4b4u4b,' + // #1780: 5 fonts: HK 101₁₂₈, JP 109₂₄₅, KR 101₃₆₁, SC 93₄₇₇, TC 97₅₈₂. + '4y4m4l4l4a,' + // #1781: 5 fonts: HK 101₁₂₈, JP 113₂₄₉, KR 74₃₃₄, SC 28₄₁₂, TC 97₅₈₂. + '4y4q3g2z6n,' + // #1782: 5 fonts: HK 102₁₂₉, JP 15₁₅₁, KR 70₃₃₀, SC 17₄₀₁, TC 98₅₈₃. + '4zv6w2s6z,' + // #1783: 5 fonts: HK 102₁₂₉, JP 100₂₃₆, KR 74₃₃₄, SC 28₄₁₂, TC 98₅₈₃. + '4z4c3t2z6o,' + // #1784: 5 fonts: HK 102₁₂₉, JP 107₂₄₃, KR 90₃₅₀, SC 94₄₇₈, TC 98₅₈₃. + '4z4j4c4x4a,' + // #1785: 5 fonts: HK 102₁₂₉, JP 110₂₄₆, KR 70₃₃₀, SC 18₄₀₂, TC 98₅₈₃. + '4z4m3f2t6y,' + // #1786: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 103₃₆₃, SC 94₄₇₈, TC 98₅₈₃. + '4z4n4l4k4a,' + // #1787: 5 fonts: HK 103₁₃₀, JP 111₂₄₇, KR 101₃₆₁, SC 94₄₇₈, TC 99₅₈₄. + '5a4m4j4m4b,' + // #1788: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 81₃₄₁, SC 95₄₇₉, TC 99₅₈₄. + '5a4p3m5h4a,' + // #1789: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 100₃₆₀, SC 94₄₇₈, TC 99₅₈₄. + '5a4p4f4n4b,' + // #1790: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 101₃₆₁, SC 94₄₇₈, TC 99₅₈₄. + '5a4p4g4m4b,' + // #1791: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 101₃₆₁, SC 95₄₇₉, TC 99₅₈₄. + '5a4p4g4n4a,' + // #1792: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 101₃₆₁, SC 95₄₇₉, TC 99₅₈₄. + '5a4q4f4n4a,' + // #1793: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 103₃₆₃, SC 95₄₇₉, TC 99₅₈₄. + '5a4r4g4l4a,' + // #1794: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 104₃₆₄, SC 95₄₇₉, TC 99₅₈₄. + '5a4r4h4k4a,' + // #1795: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 105₃₆₅, SC 95₄₇₉, TC 99₅₈₄. + '5a4r4i4j4a,' + // #1796: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 100₃₆₀, SC 95₄₇₉, TC 99₅₈₄. + '5a4s4c4o4a,' + // #1797: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 101₃₆₁, SC 95₄₇₉, TC 99₅₈₄. + '5a4s4d4n4a,' + // #1798: 10 fonts: HK 104₁₃₁, HK 106₁₃₃, JP 59₁₉₅, JP 121₂₅₇, KR 121₃₈₁, SC 73₄₅₇, SC 98₄₈₂, TC 100₅₈₅, TC 102₅₈₇, Noto Sans₅₉₁. + '5bb2j2j4t2xy3ybd,' + // #1799: 10 fonts: HK 104₁₃₁, HK 106₁₃₃, JP 118₂₅₄, JP 121₂₅₇, KR 121₃₈₁, SC 96₄₈₀, SC 98₄₈₂, TC 100₅₈₅, TC 102₅₈₇, Noto Sans₅₉₁. + '5bb4qc4t3ub3ybd,' + // #1800: 5 fonts: HK 104₁₃₁, JP 115₂₅₁, KR 102₃₆₂, SC 95₄₇₉, TC 100₅₈₅. + '5b4p4g4m4b,' + // #1801: 5 fonts: HK 104₁₃₁, JP 115₂₅₁, KR 103₃₆₃, SC 95₄₇₉, TC 100₅₈₅. + '5b4p4h4l4b,' + // #1802: 5 fonts: HK 104₁₃₁, JP 115₂₅₁, KR 105₃₆₅, SC 96₄₈₀, TC 100₅₈₅. + '5b4p4j4k4a,' + // #1803: 5 fonts: HK 104₁₃₁, JP 116₂₅₂, KR 104₃₆₄, SC 95₄₇₉, TC 100₅₈₅. + '5b4q4h4k4b,' + // #1804: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 102₃₆₂, SC 95₄₇₉, TC 100₅₈₅. + '5b4r4e4m4b,' + // #1805: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 105₃₆₅, SC 95₄₇₉, TC 100₅₈₅. + '5b4r4h4j4b,' + // #1806: 2 fonts: JP 3₁₃₉, SC 2₃₈₆. + '5j9m,' + // #1807: 2 fonts: JP 3₁₃₉, Noto Sans₅₉₁. + '5j17j,' + // #1808: 2 fonts: JP 8₁₄₄, KR 66₃₂₆. + '5o6z,' + // #1809: 2 fonts: JP 8₁₄₄, SC 7₃₉₁. + '5o9m,' + // #1810: 2 fonts: JP 10₁₄₆, SC 9₃₉₃. + '5q9m,' + // #1811: 2 fonts: JP 10₁₄₆, SC 11₃₉₅. + '5q9o,' + // #1812: 2 fonts: JP 11₁₄₇, SC 11₃₉₅. + '5r9n,' + // #1813: 3 fonts: JP 13₁₄₉, KR 68₃₂₈, SC 14₃₉₈. + '5t6w2r,' + // #1814: 2 fonts: JP 13₁₄₉, SC 14₃₉₈. + '5t9o,' + // #1815: 2 fonts: JP 13₁₄₉, SC 15₃₉₉. + '5t9p,' + // #1816: 2 fonts: JP 14₁₅₀, KR 69₃₂₉. + '5u6w,' + // #1817: 2 fonts: JP 14₁₅₀, SC 16₄₀₀. + '5u9p,' + // #1818: 2 fonts: JP 17₁₅₃, SC 20₄₀₄. + '5x9q,' + // #1819: 2 fonts: JP 23₁₅₉, KR 74₃₃₄. + '6d6s,' + // #1820: 2 fonts: JP 24₁₆₀, SC 30₄₁₄. + '6e9t,' + // #1821: 2 fonts: JP 29₁₆₅, KR 78₃₃₈. + '6j6q,' + // #1822: 2 fonts: JP 29₁₆₅, SC 37₄₂₁. + '6j9v,' + // #1823: 2 fonts: JP 32₁₆₈, KR 80₃₄₀. + '6m6p,' + // #1824: 2 fonts: JP 32₁₆₈, SC 40₄₂₄. + '6m9v,' + // #1825: 2 fonts: JP 32₁₆₈, SC 42₄₂₆. + '6m9x,' + // #1826: 2 fonts: JP 38₁₇₄, SC 49₄₃₃. + '6s9y,' + // #1827: 3 fonts: JP 41₁₇₇, KR 86₃₄₆, SC 53₄₃₇. + '6v6m3m,' + // #1828: 3 fonts: JP 41₁₇₇, KR 87₃₄₇, SC 54₄₃₈. + '6v6n3m,' + // #1829: 3 fonts: JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉. + '6w6m3n,' + // #1830: 2 fonts: JP 48₁₈₄, SC 94₄₇₈. + '7c11h,' + // #1831: 2 fonts: JP 49₁₈₅, KR 92₃₅₂. + '7d6k,' + // #1832: 2 fonts: JP 51₁₈₇, SC 67₄₅₁. + '7f10d,' + // #1833: 2 fonts: JP 52₁₈₈, KR 93₃₅₃. + '7g6i,' + // #1834: 2 fonts: JP 52₁₈₈, KR 94₃₅₄. + '7g6j,' + // #1835: 2 fonts: JP 54₁₉₀, KR 101₃₆₁. + '7i6o,' + // #1836: 2 fonts: JP 54₁₉₀, KR 108₃₆₈. + '7i6v,' + // #1837: 3 fonts: JP 57₁₉₃, KR 97₃₅₇, Symbols₇₀₂. + '7l6h13g,' + // #1838: 3 fonts: JP 58₁₉₄, KR 98₃₅₈, Symbols₇₀₂. + '7m6h13f,' + // #1839: 3 fonts: JP 58₁₉₄, Noto Sans₅₉₁, Math₆₅₅. + '7m15g2l,' + // #1840: 1 font: JP 71₂₀₇. + '7z,' + // #1841: 2 fonts: JP 77₂₁₃, SC 47₄₃₁. + '8f8j,' + // #1842: 2 fonts: JP 80₂₁₆, SC 50₄₃₄. + '8i8j,' + // #1843: 2 fonts: JP 85₂₂₁, SC 50₄₃₄. + '8n8e,' + // #1844: 2 fonts: JP 107₂₄₃, SC 50₄₃₄. + '9j7i,' + // #1845: 2 fonts: JP 110₂₄₆, SC 41₄₂₅. + '9m6w,' + // #1846: 2 fonts: JP 115₂₅₁, SC 45₄₂₉. + '9r6v,' + // #1847: 1 font: KR 20₂₈₀. + '10u,' + // #1848: 1 font: KR 74₃₃₄. + '12w,' + // #1849: 1 font: KR 75₃₃₅. + '12x,' + // #1850: 1 font: KR 87₃₄₇. + '13j,' + // #1851: 2 fonts: KR 98₃₅₈, Symbols₇₀₂. + '13u13f,' + // #1852: 3 fonts: KR 108₃₆₈, Noto Sans₅₉₁, Math₆₅₅. + '14e8o2l,' + // #1853: 2 fonts: Noto Sans₅₉₁, Adlam₅₉₂. + '22ta,' + // #1854: 3 fonts: Noto Sans₅₉₁, Adlam₅₉₂, Arabic₅₉₄. + '22tab,' + // #1855: 2 fonts: Noto Sans₅₉₁, Arabic₅₉₄. + '22tc,' + // #1856: 2 fonts: Noto Sans₅₉₁, Georgian₆₁₉. + '22t1b,' + // #1857: 2 fonts: Noto Sans₅₉₁, Tifinagh₇₁₅. + '22t4t,' + // #1858: 2 fonts: Arabic₅₉₄, Indic Siyaq Numbers₆₃₀. + '22w1j,' + // #1859: 1 font: Avestan₅₉₆. + '22y,' + // #1860: 1 font: Balinese₅₉₇. + '22z,' + // #1861: 1 font: Bamum₅₉₈. + '23a,' + // #1862: 1 font: Bassa Vah₅₉₉. + '23b,' + // #1863: 1 font: Batak₆₀₀. + '23c,' + // #1864: 4 fonts: Bengali₆₀₁, Devanagari₆₁₅, Grantha₆₂₂, Kannada₆₃₅. + '23dngm,' + // #1865: 1 font: Buginese₆₀₄. + '23g,' + // #1866: 1 font: Caucasian Albanian₆₀₈. + '23k,' + // #1867: 1 font: Chakma₆₀₉. + '23l,' + // #1868: 3 fonts: Cypriot₆₁₃, Linear A₆₄₄, Linear B₆₄₅. + '23p1ea,' + // #1869: 1 font: Imperial Aramaic₆₂₉. + '24f,' + // #1870: 1 font: Inscriptional Pahlavi₆₃₁. + '24h,' + // #1871: 1 font: Inscriptional Parthian₆₃₂. + '24i,' + // #1872: 1 font: Kaithi₆₃₄. + '24k,' + // #1873: 1 font: Kayah Li₆₃₆. + '24m,' + // #1874: 1 font: Khojki₆₃₉. + '24p,' + // #1875: 1 font: Khudawadi₆₄₀. + '24q,' + // #1876: 1 font: Lisu₆₄₆. + '24w,' + // #1877: 1 font: Lydian₆₄₈. + '24y,' + // #1878: 1 font: Mandaic₆₅₁. + '25b,' + // #1879: 1 font: Manichaean₆₅₂. + '25c,' + // #1880: 1 font: Modi₆₆₁. + '25l,' + // #1881: 2 fonts: Mongolian₆₆₂, Phags Pa₆₈₇. + '25my,' + // #1882: 1 font: NKo₆₆₆. + '25q,' + // #1883: 1 font: Nabataean₆₆₇. + '25r,' + // #1884: 1 font: Newa₆₆₉. + '25t,' + // #1885: 1 font: Nushu₆₇₀. + '25u,' + // #1886: 1 font: Old Italic₆₇₄. + '25y,' + // #1887: 1 font: Old Persian₆₇₇. + '26b,' + // #1888: 1 font: Osage₆₈₂. + '26g,' + // #1889: 1 font: Osmanya₆₈₃. + '26h,' + // #1890: 1 font: Phoenician₆₈₈. + '26m,' + // #1891: 1 font: Rejang₆₉₀. + '26o,' + // #1892: 1 font: Saurashtra₆₉₂. + '26q,' + // #1893: 1 font: Siddham₆₉₅. + '26t,' + // #1894: 1 font: Sora Sompeng₆₉₈. + '26w,' + // #1895: 1 font: Sundanese₇₀₀. + '26y,' + // #1896: 1 font: Tagalog₇₀₄. + '27c,' + // #1897: 1 font: Tai Le₇₀₆. + '27e,' + // #1898: 1 font: Tai Viet₇₀₈. + '27g,' + // #1899: 1 font: Takri₇₀₉. + '27h,' + // #1900: 1 font: Tamil Supplement₇₁₁. + '27j,' + // #1901: 1 font: Thai₇₁₄. + '27m,' + // #1902: 1 font: Tirhuta₇₁₆. + '27o,' + // #1903: 1 font: Ugaritic₇₁₇. + '27p,' + // #1904: 1 font: Wancho₇₁₉. + '27r,' + // #1905: 1 font: Warang Citi₇₂₀. + '27s,' + // #1906: 1 font: Yi₇₂₁. + '27t,' + // #1907: 1 font: Noto Color Emoji 0₀. + 'a,' + // #1908: 74 fonts: Noto Color Emoji 1₁, Noto Color Emoji 2₂, Noto Color Emoji 7₇, Noto Color Emoji 8₈, Noto Color Emoji 9₉, Noto Color Emoji 10₁₀, Noto Sans₅₉₁, Arabic₅₉₄, Avestan₅₉₆, Balinese₅₉₇, Batak₆₀₀, Bengali₆₀₁, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Chakma₆₀₉, Cham₆₁₀, Devanagari₆₁₅, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hebrew₆₂₈, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Meetei Mayek₆₅₈, Modi₆₆₁, Mongolian₆₆₂, Myanmar₆₆₅, NKo₆₆₆, New Tai Lue₆₆₈, Newa₆₆₉, Old Hungarian₆₇₃, Old Turkic₆₈₀, Oriya₆₈₁, Pahawh Hmong₆₈₄, Phags Pa₆₈₇, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Saurashtra₆₉₂, Sharada₆₉₃, Siddham₆₉₅, Sinhala₆₉₆, Sundanese₇₀₀, Syloti Nagri₇₀₁, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Warang Citi₇₂₀, Noto Serif Tibetan₇₂₃. + 'baeaaa22icbacabaadaegaaaabeaaaaaaaaaafaaafcacabadgaccbababadabaaaaaaabaaaadc,' + // #1909: 3 fonts: Noto Color Emoji 1₁, Noto Color Emoji 2₂, Symbols₇₀₂. + 'ba26x,' + // #1910: 2 fonts: Noto Color Emoji 1₁, Noto Color Emoji 7₇. + 'bf,' + // #1911: 3 fonts: Noto Color Emoji 1₁, Noto Color Emoji 9₉, Symbols 2 3₁₅. + 'bhf,' + // #1912: 2 fonts: Noto Color Emoji 1₁, Symbols 2 3₁₅. + 'bn,' + // #1913: 3 fonts: Noto Color Emoji 2₂, Noto Color Emoji 7₇, Symbols 2 3₁₅. + 'ceh,' + // #1914: 4 fonts: Noto Color Emoji 2₂, Noto Color Emoji 8₈, Noto Color Emoji 9₉, Symbols 2 3₁₅. + 'cfaf,' + // #1915: 9 fonts: Noto Color Emoji 2₂, Noto Color Emoji 8₈, Noto Color Emoji 10₁₀, HK 79₁₀₆, JP 75₂₁₁, KR 102₃₆₂, SC 70₄₅₄, TC 73₅₅₈, Symbols₇₀₂. + 'cfb3r4a5u3n3z5n,' + // #1916: 9 fonts: Noto Color Emoji 2₂, Noto Color Emoji 8₈, Noto Color Emoji 10₁₀, HK 85₁₁₂, JP 75₂₁₁, KR 104₃₆₄, SC 70₄₅₄, TC 81₅₆₆, Symbols₇₀₂. + 'cfb3x3u5w3l4h5f,' + // #1917: 3 fonts: Noto Color Emoji 2₂, Noto Color Emoji 8₈, Symbols₇₀₂. + 'cf26r,' + // #1918: 3 fonts: Noto Color Emoji 2₂, Noto Color Emoji 9₉, Symbols 2 3₁₅. + 'cgf,' + // #1919: 8 fonts: Noto Color Emoji 2₂, Noto Color Emoji 9₉, Symbols 2 3₁₅, HK 93₁₂₀, JP 86₂₂₂, KR 112₃₇₂, SC 69₄₅₃, TC 89₅₇₄. + 'cgf4a3x5t3c4q,' + // #1920: 7 fonts: Noto Color Emoji 2₂, Noto Color Emoji 10₁₀, Symbols 2 3₁₅, HK 78₁₀₅, JP 56₁₉₂, SC 68₄₅₂, TC 71₅₅₆. + 'che3l3i9z3z,' + // #1921: 8 fonts: Noto Color Emoji 2₂, Noto Color Emoji 10₁₀, HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, TC 75₅₆₀, Math₆₅₅, Symbols₇₀₂. + 'ch3t3h6h7t3q1u,' + // #1922: 9 fonts: Noto Color Emoji 2₂, Noto Color Emoji 10₁₀, HK 81₁₀₈, JP 58₁₉₄, KR 100₃₆₀, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅, Symbols₇₀₂. + 'ch3t3h6j3r3z3q1u,' + // #1923: 148 fonts: Noto Color Emoji 2₂, Symbols 2 1₁₃, Symbols 2 3₁₅, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 100₁₂₇, HK 108₁₃₅, JP 107₂₄₃, JP 123₂₅₉, KR 115₃₇₅, KR 123₃₈₃, SC 89₄₇₃, SC 100₄₈₄, TC 96₅₈₁, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ckbbccc3wh4dp4lh3lk3shaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1924: 148 fonts: Noto Color Emoji 2₂, Symbols 2 1₁₃, Symbols 2 3₁₅, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 118₃₇₈, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ckbbccc4ad4oe4oe3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1925: 4 fonts: Noto Color Emoji 2₂, Symbols 2 1₁₃, Symbols 2 3₁₅, Math₆₅₅. + 'ckb24p,' + // #1926: 6 fonts: Noto Color Emoji 2₂, Symbols 2 3₁₅, HK 78₁₀₅, JP 56₁₉₂, SC 68₄₅₂, TC 71₅₅₆. + 'cm3l3i9z3z,' + // #1927: 7 fonts: Noto Color Emoji 2₂, Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, KR 103₃₆₃, SC 69₄₅₃, TC 72₅₅₇. + 'cm3m3h6o3l3z,' + // #1928: 6 fonts: Noto Color Emoji 2₂, Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, SC 69₄₅₃, TC 72₅₅₇. + 'cm3m3h10a3z,' + // #1929: 7 fonts: Noto Color Emoji 2₂, Symbols 2 3₁₅, HK 79₁₀₆, JP 57₁₉₃, KR 111₃₇₁, SC 70₄₅₄, TC 73₅₅₈. + 'cm3m3i6v3e3z,' + // #1930: 6 fonts: Noto Color Emoji 2₂, Symbols 2 3₁₅, HK 79₁₀₆, JP 57₁₉₃, SC 70₄₅₄, TC 73₅₅₈. + 'cm3m3i10a3z,' + // #1931: 6 fonts: Noto Color Emoji 2₂, Symbols 2 3₁₅, HK 79₁₀₆, JP 60₁₉₆, SC 69₄₅₃, TC 72₅₅₇. + 'cm3m3l9w3z,' + // #1932: 7 fonts: Noto Color Emoji 2₂, Symbols 2 3₁₅, HK 88₁₁₅, JP 81₂₁₇, KR 113₃₇₃, SC 70₄₅₄, TC 84₅₆₉. + 'cm3v3x5z3c4k,' + // #1933: 3 fonts: Noto Color Emoji 2₂, Symbols 2 3₁₅, Symbols₇₀₂. + 'cm26k,' + // #1934: 145 fonts: Noto Color Emoji 2₂, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 103₁₃₀, HK 108₁₃₅, JP 116₂₅₂, JP 123₂₅₉, KR 115₃₇₅, KR 123₃₈₃, SC 95₄₇₉, SC 100₄₈₄, TC 99₅₈₄, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'coccc3ze4mg4lh3re3veaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1935: 4 fonts: Noto Color Emoji 2₂, HK 11₃₈, JP 1₁₃₇, TC 1₄₈₆. + 'c1j3u13k,' + // #1936: 6 fonts: Noto Color Emoji 2₂, HK 11₃₈, JP 2₁₃₈, SC 1₃₈₅, TC 1₄₈₆, Symbols₇₀₂. + 'c1j3v9m3w8h,' + // #1937: 5 fonts: Noto Color Emoji 2₂, HK 11₃₈, JP 2₁₃₈, TC 1₄₈₆, Symbols₇₀₂. + 'c1j3v13j8h,' + // #1938: 5 fonts: Noto Color Emoji 2₂, HK 77₁₀₄, JP 54₁₉₀, SC 68₄₅₂, TC 71₅₅₆. + 'c3x3h10b3z,' + // #1939: 4 fonts: Noto Color Emoji 2₂, HK 77₁₀₄, JP 54₁₉₀, TC 71₅₅₆. + 'c3x3h14b,' + // #1940: 6 fonts: Noto Color Emoji 2₂, HK 78₁₀₅, JP 56₁₉₂, SC 68₄₅₂, TC 71₅₅₆, Math₆₅₅. + 'c3y3i9z3z3u,' + // #1941: 7 fonts: Noto Color Emoji 2₂, HK 79₁₀₆, JP 56₁₉₂, KR 96₃₅₆, SC 70₄₅₄, TC 73₅₅₈, Symbols₇₀₂. + 'c3z3h6h3t3z5n,' + // #1942: 6 fonts: Noto Color Emoji 2₂, HK 79₁₀₆, JP 56₁₉₂, SC 69₄₅₃, TC 72₅₅₇, Symbols₇₀₂. + 'c3z3h10a3z5o,' + // #1943: 7 fonts: Noto Color Emoji 2₂, HK 80₁₀₇, JP 58₁₉₄, KR 97₃₅₇, SC 71₄₅₅, TC 74₅₅₉, Symbols₇₀₂. + 'c4a3i6g3t3z5m,' + // #1944: 7 fonts: Noto Color Emoji 2₂, HK 82₁₀₉, JP 118₂₅₄, SC 73₄₅₇, TC 76₅₆₁, Noto Sans₅₉₁, Mongolian₆₆₂. + 'c4c5o7u3z1d2s,' + // #1945: 8 fonts: Noto Color Emoji 2₂, HK 88₁₁₅, JP 58₁₉₄, KR 98₃₅₈, SC 72₄₅₆, TC 84₅₆₉, Math₆₅₅, Symbols₇₀₂. + 'c4i3a6h3t4i3h1u,' + // #1946: 7 fonts: Noto Color Emoji 2₂, HK 104₁₃₁, JP 118₂₅₄, KR 99₃₅₉, SC 73₄₅₇, TC 100₅₈₅, Noto Sans₅₉₁. + 'c4y4s4a3t4xf,' + // #1947: 2 fonts: Noto Color Emoji 2₂, JP 1₁₃₇. + 'c5e,' + // #1948: 3 fonts: Noto Color Emoji 2₂, JP 2₁₃₈, Symbols₇₀₂. + 'c5f21r,' + // #1949: 2 fonts: Noto Color Emoji 2₂, Noto Sans₅₉₁. + 'c22q,' + // #1950: 2 fonts: Noto Color Emoji 2₂, Math₆₅₅. + 'c25c,' + // #1951: 3 fonts: Noto Color Emoji 2₂, Symbols₇₀₂, Noto Serif Tibetan₇₂₃. + 'c26xu,' + // #1952: 2 fonts: Noto Color Emoji 3₃, Noto Color Emoji 5₅. + 'db,' + // #1953: 2 fonts: Noto Color Emoji 3₃, Noto Color Emoji 7₇. + 'dd,' + // #1954: 3 fonts: Noto Color Emoji 3₃, Noto Color Emoji 7₇, Symbols 2 3₁₅. + 'ddh,' + // #1955: 3 fonts: Noto Color Emoji 3₃, Noto Color Emoji 8₈, Noto Color Emoji 10₁₀. + 'deb,' + // #1956: 3 fonts: Noto Color Emoji 3₃, Noto Color Emoji 8₈, Symbols₇₀₂. + 'de26r,' + // #1957: 2 fonts: Noto Color Emoji 3₃, Noto Color Emoji 9₉. + 'df,' + // #1958: 3 fonts: Noto Color Emoji 3₃, Noto Color Emoji 9₉, Symbols 2 3₁₅. + 'dff,' + // #1959: 3 fonts: Noto Color Emoji 3₃, Noto Color Emoji 10₁₀, Symbols 2 3₁₅. + 'dge,' + // #1960: 6 fonts: Noto Color Emoji 3₃, Symbols 2 3₁₅, HK 78₁₀₅, JP 56₁₉₂, SC 69₄₅₃, TC 72₅₅₇. + 'dl3l3i10a3z,' + // #1961: 7 fonts: Noto Color Emoji 3₃, Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, KR 107₃₆₇, SC 70₄₅₄, TC 73₅₅₈. + 'dl3m3h6s3i3z,' + // #1962: 6 fonts: Noto Color Emoji 3₃, Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, SC 70₄₅₄, TC 73₅₅₈. + 'dl3m3h10b3z,' + // #1963: 2 fonts: Noto Color Emoji 4₄, Noto Color Emoji 6₆. + 'eb,' + // #1964: 6 fonts: Noto Color Emoji 4₄, Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, SC 69₄₅₃, TC 72₅₅₇. + 'ek3m3h10a3z,' + // #1965: 3 fonts: Noto Color Emoji 5₅, Noto Color Emoji 8₈, Noto Color Emoji 10₁₀. + 'fcb,' + // #1966: 2 fonts: Noto Color Emoji 6₆, Noto Color Emoji 10₁₀. + 'gd,' + // #1967: 2 fonts: Noto Color Emoji 7₇, Noto Color Emoji 8₈. + 'ha,' + // #1968: 3 fonts: Noto Color Emoji 7₇, Noto Color Emoji 9₉, Noto Color Emoji 10₁₀. + 'hba,' + // #1969: 2 fonts: Noto Color Emoji 7₇, Noto Color Emoji 10₁₀. + 'hc,' + // #1970: 3 fonts: Noto Color Emoji 7₇, Noto Color Emoji 10₁₀, Symbols 2 3₁₅. + 'hce,' + // #1971: 6 fonts: Noto Color Emoji 7₇, Symbols 2 3₁₅, HK 79₁₀₆, JP 67₂₀₃, SC 70₄₅₄, TC 73₅₅₈. + 'hh3m3s9q3z,' + // #1972: 2 fonts: Noto Color Emoji 7₇, Symbols₇₀₂. + 'h26s,' + // #1973: 3 fonts: Noto Color Emoji 8₈, Noto Color Emoji 9₉, Noto Color Emoji 10₁₀. + 'iaa,' + // #1974: 7 fonts: Noto Color Emoji 9₉, Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, KR 96₃₅₆, SC 70₄₅₄, TC 73₅₅₈. + 'jf3m3h6h3t3z,' + // #1975: 712 fonts: Symbols 2 0₁₂, Symbols 2 1₁₃, Symbols 2 2₁₄, Symbols 2 3₁₅, Symbols 2 4₁₆, Symbols 2 5₁₇, Cuneiform 0₁₈, Cuneiform 1₁₉, Cuneiform 2₂₀, Duployan 0₂₁, Duployan 1₂₂, Duployan 2₂₃, Egyptian Hieroglyphs 0₂₄, Egyptian Hieroglyphs 1₂₅, Egyptian Hieroglyphs 2₂₆, HK 0₂₇, HK 1₂₈, HK 2₂₉, HK 3₃₀, HK 4₃₁, HK 5₃₂, HK 6₃₃, HK 7₃₄, HK 8₃₅, HK 9₃₆, HK 10₃₇, HK 11₃₈, HK 12₃₉, HK 13₄₀, HK 14₄₁, HK 15₄₂, HK 16₄₃, HK 17₄₄, HK 18₄₅, HK 19₄₆, HK 20₄₇, HK 21₄₈, HK 22₄₉, HK 23₅₀, HK 24₅₁, HK 25₅₂, HK 26₅₃, HK 27₅₄, HK 28₅₅, HK 29₅₆, HK 30₅₇, HK 31₅₈, HK 32₅₉, HK 33₆₀, HK 34₆₁, HK 35₆₂, HK 36₆₃, HK 37₆₄, HK 38₆₅, HK 39₆₆, HK 40₆₇, HK 41₆₈, HK 42₆₉, HK 43₇₀, HK 44₇₁, HK 45₇₂, HK 46₇₃, HK 47₇₄, HK 48₇₅, HK 49₇₆, HK 50₇₇, HK 51₇₈, HK 52₇₉, HK 53₈₀, HK 54₈₁, HK 55₈₂, HK 56₈₃, HK 57₈₄, HK 58₈₅, HK 59₈₆, HK 60₈₇, HK 61₈₈, HK 62₈₉, HK 63₉₀, HK 64₉₁, HK 65₉₂, HK 66₉₃, HK 67₉₄, HK 68₉₅, HK 69₉₆, HK 70₉₇, HK 71₉₈, HK 72₉₉, HK 73₁₀₀, HK 74₁₀₁, HK 75₁₀₂, HK 76₁₀₃, HK 77₁₀₄, HK 78₁₀₅, HK 79₁₀₆, HK 80₁₀₇, HK 81₁₀₈, HK 82₁₀₉, HK 83₁₁₀, HK 84₁₁₁, HK 85₁₁₂, HK 86₁₁₃, HK 87₁₁₄, HK 88₁₁₅, HK 89₁₁₆, HK 90₁₁₇, HK 91₁₁₈, HK 92₁₁₉, HK 93₁₂₀, HK 94₁₂₁, HK 95₁₂₂, HK 96₁₂₃, HK 97₁₂₄, HK 98₁₂₅, HK 99₁₂₆, HK 100₁₂₇, HK 101₁₂₈, HK 102₁₂₉, HK 103₁₃₀, HK 104₁₃₁, HK 105₁₃₂, HK 106₁₃₃, HK 107₁₃₄, HK 108₁₃₅, JP 0₁₃₆, JP 1₁₃₇, JP 2₁₃₈, JP 3₁₃₉, JP 4₁₄₀, JP 5₁₄₁, JP 6₁₄₂, JP 7₁₄₃, JP 8₁₄₄, JP 9₁₄₅, JP 10₁₄₆, JP 11₁₄₇, JP 12₁₄₈, JP 13₁₄₉, JP 14₁₅₀, JP 15₁₅₁, JP 16₁₅₂, JP 17₁₅₃, JP 18₁₅₄, JP 19₁₅₅, JP 20₁₅₆, JP 21₁₅₇, JP 22₁₅₈, JP 23₁₅₉, JP 24₁₆₀, JP 25₁₆₁, JP 26₁₆₂, JP 27₁₆₃, JP 28₁₆₄, JP 29₁₆₅, JP 30₁₆₆, JP 31₁₆₇, JP 32₁₆₈, JP 33₁₆₉, JP 34₁₇₀, JP 35₁₇₁, JP 36₁₇₂, JP 37₁₇₃, JP 38₁₇₄, JP 39₁₇₅, JP 40₁₇₆, JP 41₁₇₇, JP 42₁₇₈, JP 43₁₇₉, JP 44₁₈₀, JP 45₁₈₁, JP 46₁₈₂, JP 47₁₈₃, JP 48₁₈₄, JP 49₁₈₅, JP 50₁₈₆, JP 51₁₈₇, JP 52₁₈₈, JP 53₁₈₉, JP 54₁₉₀, JP 55₁₉₁, JP 56₁₉₂, JP 57₁₉₃, JP 58₁₉₄, JP 59₁₉₅, JP 60₁₉₆, JP 61₁₉₇, JP 62₁₉₈, JP 63₁₉₉, JP 64₂₀₀, JP 65₂₀₁, JP 66₂₀₂, JP 67₂₀₃, JP 68₂₀₄, JP 69₂₀₅, JP 70₂₀₆, JP 71₂₀₇, JP 72₂₀₈, JP 73₂₀₉, JP 74₂₁₀, JP 75₂₁₁, JP 76₂₁₂, JP 77₂₁₃, JP 78₂₁₄, JP 79₂₁₅, JP 80₂₁₆, JP 81₂₁₇, JP 82₂₁₈, JP 83₂₁₉, JP 84₂₂₀, JP 85₂₂₁, JP 86₂₂₂, JP 87₂₂₃, JP 88₂₂₄, JP 89₂₂₅, JP 90₂₂₆, JP 91₂₂₇, JP 92₂₂₈, JP 93₂₂₉, JP 94₂₃₀, JP 95₂₃₁, JP 96₂₃₂, JP 97₂₃₃, JP 98₂₃₄, JP 99₂₃₅, JP 100₂₃₆, JP 101₂₃₇, JP 102₂₃₈, JP 103₂₃₉, JP 104₂₄₀, JP 105₂₄₁, JP 106₂₄₂, JP 107₂₄₃, JP 108₂₄₄, JP 109₂₄₅, JP 110₂₄₆, JP 111₂₄₇, JP 112₂₄₈, JP 113₂₄₉, JP 114₂₅₀, JP 115₂₅₁, JP 116₂₅₂, JP 117₂₅₃, JP 118₂₅₄, JP 119₂₅₅, JP 120₂₅₆, JP 121₂₅₇, JP 122₂₅₈, JP 123₂₅₉, KR 0₂₆₀, KR 1₂₆₁, KR 2₂₆₂, KR 3₂₆₃, KR 4₂₆₄, KR 5₂₆₅, KR 6₂₆₆, KR 7₂₆₇, KR 8₂₆₈, KR 9₂₆₉, KR 10₂₇₀, KR 11₂₇₁, KR 12₂₇₂, KR 13₂₇₃, KR 14₂₇₄, KR 15₂₇₅, KR 16₂₇₆, KR 17₂₇₇, KR 18₂₇₈, KR 19₂₇₉, KR 20₂₈₀, KR 21₂₈₁, KR 22₂₈₂, KR 23₂₈₃, KR 24₂₈₄, KR 25₂₈₅, KR 26₂₈₆, KR 27₂₈₇, KR 28₂₈₈, KR 29₂₈₉, KR 30₂₉₀, KR 31₂₉₁, KR 32₂₉₂, KR 33₂₉₃, KR 34₂₉₄, KR 35₂₉₅, KR 36₂₉₆, KR 37₂₉₇, KR 38₂₉₈, KR 39₂₉₉, KR 40₃₀₀, KR 41₃₀₁, KR 42₃₀₂, KR 43₃₀₃, KR 44₃₀₄, KR 45₃₀₅, KR 46₃₀₆, KR 47₃₀₇, KR 48₃₀₈, KR 49₃₀₉, KR 50₃₁₀, KR 51₃₁₁, KR 52₃₁₂, KR 53₃₁₃, KR 54₃₁₄, KR 55₃₁₅, KR 56₃₁₆, KR 57₃₁₇, KR 58₃₁₈, KR 59₃₁₉, KR 60₃₂₀, KR 61₃₂₁, KR 62₃₂₂, KR 63₃₂₃, KR 64₃₂₄, KR 65₃₂₅, KR 66₃₂₆, KR 67₃₂₇, KR 68₃₂₈, KR 69₃₂₉, KR 70₃₃₀, KR 71₃₃₁, KR 72₃₃₂, KR 73₃₃₃, KR 74₃₃₄, KR 75₃₃₅, KR 76₃₃₆, KR 77₃₃₇, KR 78₃₃₈, KR 79₃₃₉, KR 80₃₄₀, KR 81₃₄₁, KR 82₃₄₂, KR 83₃₄₃, KR 84₃₄₄, KR 85₃₄₅, KR 86₃₄₆, KR 87₃₄₇, KR 88₃₄₈, KR 89₃₄₉, KR 90₃₅₀, KR 91₃₅₁, KR 92₃₅₂, KR 93₃₅₃, KR 94₃₅₄, KR 95₃₅₅, KR 96₃₅₆, KR 97₃₅₇, KR 98₃₅₈, KR 99₃₅₉, KR 100₃₆₀, KR 101₃₆₁, KR 102₃₆₂, KR 103₃₆₃, KR 104₃₆₄, KR 105₃₆₅, KR 106₃₆₆, KR 107₃₆₇, KR 108₃₆₈, KR 109₃₆₉, KR 110₃₇₀, KR 111₃₇₁, KR 112₃₇₂, KR 113₃₇₃, KR 114₃₇₄, KR 115₃₇₅, KR 116₃₇₆, KR 117₃₇₇, KR 118₃₇₈, KR 119₃₇₉, KR 120₃₈₀, KR 121₃₈₁, KR 122₃₈₂, KR 123₃₈₃, SC 0₃₈₄, SC 1₃₈₅, SC 2₃₈₆, SC 3₃₈₇, SC 4₃₈₈, SC 5₃₈₉, SC 6₃₉₀, SC 7₃₉₁, SC 8₃₉₂, SC 9₃₉₃, SC 10₃₉₄, SC 11₃₉₅, SC 12₃₉₆, SC 13₃₉₇, SC 14₃₉₈, SC 15₃₉₉, SC 16₄₀₀, SC 17₄₀₁, SC 18₄₀₂, SC 19₄₀₃, SC 20₄₀₄, SC 21₄₀₅, SC 22₄₀₆, SC 23₄₀₇, SC 24₄₀₈, SC 25₄₀₉, SC 26₄₁₀, SC 27₄₁₁, SC 28₄₁₂, SC 29₄₁₃, SC 30₄₁₄, SC 31₄₁₅, SC 32₄₁₆, SC 33₄₁₇, SC 34₄₁₈, SC 35₄₁₉, SC 36₄₂₀, SC 37₄₂₁, SC 38₄₂₂, SC 39₄₂₃, SC 40₄₂₄, SC 41₄₂₅, SC 42₄₂₆, SC 43₄₂₇, SC 44₄₂₈, SC 45₄₂₉, SC 46₄₃₀, SC 47₄₃₁, SC 48₄₃₂, SC 49₄₃₃, SC 50₄₃₄, SC 51₄₃₅, SC 52₄₃₆, SC 53₄₃₇, SC 54₄₃₈, SC 55₄₃₉, SC 56₄₄₀, SC 57₄₄₁, SC 58₄₄₂, SC 59₄₄₃, SC 60₄₄₄, SC 61₄₄₅, SC 62₄₄₆, SC 63₄₄₇, SC 64₄₄₈, SC 65₄₄₉, SC 66₄₅₀, SC 67₄₅₁, SC 68₄₅₂, SC 69₄₅₃, SC 70₄₅₄, SC 71₄₅₅, SC 72₄₅₆, SC 73₄₅₇, SC 74₄₅₈, SC 75₄₅₉, SC 76₄₆₀, SC 77₄₆₁, SC 78₄₆₂, SC 79₄₆₃, SC 80₄₆₄, SC 81₄₆₅, SC 82₄₆₆, SC 83₄₆₇, SC 84₄₆₈, SC 85₄₆₉, SC 86₄₇₀, SC 87₄₇₁, SC 88₄₇₂, SC 89₄₇₃, SC 90₄₇₄, SC 91₄₇₅, SC 92₄₇₆, SC 93₄₇₇, SC 94₄₇₈, SC 95₄₇₉, SC 96₄₈₀, SC 97₄₈₁, SC 98₄₈₂, SC 99₄₈₃, SC 100₄₈₄, TC 0₄₈₅, TC 1₄₈₆, TC 2₄₈₇, TC 3₄₈₈, TC 4₄₈₉, TC 5₄₉₀, TC 6₄₉₁, TC 7₄₉₂, TC 8₄₉₃, TC 9₄₉₄, TC 10₄₉₅, TC 11₄₉₆, TC 12₄₉₇, TC 13₄₉₈, TC 14₄₉₉, TC 15₅₀₀, TC 16₅₀₁, TC 17₅₀₂, TC 18₅₀₃, TC 19₅₀₄, TC 20₅₀₅, TC 21₅₀₆, TC 22₅₀₇, TC 23₅₀₈, TC 24₅₀₉, TC 25₅₁₀, TC 26₅₁₁, TC 27₅₁₂, TC 28₅₁₃, TC 29₅₁₄, TC 30₅₁₅, TC 31₅₁₆, TC 32₅₁₇, TC 33₅₁₈, TC 34₅₁₉, TC 35₅₂₀, TC 36₅₂₁, TC 37₅₂₂, TC 38₅₂₃, TC 39₅₂₄, TC 40₅₂₅, TC 41₅₂₆, TC 42₅₂₇, TC 43₅₂₈, TC 44₅₂₉, TC 45₅₃₀, TC 46₅₃₁, TC 47₅₃₂, TC 48₅₃₃, TC 49₅₃₄, TC 50₅₃₅, TC 51₅₃₆, TC 52₅₃₇, TC 53₅₃₈, TC 54₅₃₉, TC 55₅₄₀, TC 56₅₄₁, TC 57₅₄₂, TC 58₅₄₃, TC 59₅₄₄, TC 60₅₄₅, TC 61₅₄₆, TC 62₅₄₇, TC 63₅₄₈, TC 64₅₄₉, TC 65₅₅₀, TC 66₅₅₁, TC 67₅₅₂, TC 68₅₅₃, TC 69₅₅₄, TC 70₅₅₅, TC 71₅₅₆, TC 72₅₅₇, TC 73₅₅₈, TC 74₅₅₉, TC 75₅₆₀, TC 76₅₆₁, TC 77₅₆₂, TC 78₅₆₃, TC 79₅₆₄, TC 80₅₆₅, TC 81₅₆₆, TC 82₅₆₇, TC 83₅₆₈, TC 84₅₆₉, TC 85₅₇₀, TC 86₅₇₁, TC 87₅₇₂, TC 88₅₇₃, TC 89₅₇₄, TC 90₅₇₅, TC 91₅₇₆, TC 92₅₇₇, TC 93₅₇₈, TC 94₅₇₉, TC 95₅₈₀, TC 96₅₈₁, TC 97₅₈₂, TC 98₅₈₃, TC 99₅₈₄, TC 100₅₈₅, TC 101₅₈₆, TC 102₅₈₇, TC 103₅₈₈, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lycian₆₄₇, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, Myanmar₆₆₅, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phags Pa₆₈₇, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1976: 711 fonts: Symbols 2 0₁₂, Symbols 2 1₁₃, Symbols 2 2₁₄, Symbols 2 3₁₅, Symbols 2 4₁₆, Symbols 2 5₁₇, Cuneiform 0₁₈, Cuneiform 2₂₀, Duployan 0₂₁, Duployan 1₂₂, Duployan 2₂₃, Egyptian Hieroglyphs 0₂₄, Egyptian Hieroglyphs 1₂₅, Egyptian Hieroglyphs 2₂₆, HK 0₂₇, HK 1₂₈, HK 2₂₉, HK 3₃₀, HK 4₃₁, HK 5₃₂, HK 6₃₃, HK 7₃₄, HK 8₃₅, HK 9₃₆, HK 10₃₇, HK 11₃₈, HK 12₃₉, HK 13₄₀, HK 14₄₁, HK 15₄₂, HK 16₄₃, HK 17₄₄, HK 18₄₅, HK 19₄₆, HK 20₄₇, HK 21₄₈, HK 22₄₉, HK 23₅₀, HK 24₅₁, HK 25₅₂, HK 26₅₃, HK 27₅₄, HK 28₅₅, HK 29₅₆, HK 30₅₇, HK 31₅₈, HK 32₅₉, HK 33₆₀, HK 34₆₁, HK 35₆₂, HK 36₆₃, HK 37₆₄, HK 38₆₅, HK 39₆₆, HK 40₆₇, HK 41₆₈, HK 42₆₉, HK 43₇₀, HK 44₇₁, HK 45₇₂, HK 46₇₃, HK 47₇₄, HK 48₇₅, HK 49₇₆, HK 50₇₇, HK 51₇₈, HK 52₇₉, HK 53₈₀, HK 54₈₁, HK 55₈₂, HK 56₈₃, HK 57₈₄, HK 58₈₅, HK 59₈₆, HK 60₈₇, HK 61₈₈, HK 62₈₉, HK 63₉₀, HK 64₉₁, HK 65₉₂, HK 66₉₃, HK 67₉₄, HK 68₉₅, HK 69₉₆, HK 70₉₇, HK 71₉₈, HK 72₉₉, HK 73₁₀₀, HK 74₁₀₁, HK 75₁₀₂, HK 76₁₀₃, HK 77₁₀₄, HK 78₁₀₅, HK 79₁₀₆, HK 80₁₀₇, HK 81₁₀₈, HK 82₁₀₉, HK 83₁₁₀, HK 84₁₁₁, HK 85₁₁₂, HK 86₁₁₃, HK 87₁₁₄, HK 88₁₁₅, HK 89₁₁₆, HK 90₁₁₇, HK 91₁₁₈, HK 92₁₁₉, HK 93₁₂₀, HK 94₁₂₁, HK 95₁₂₂, HK 96₁₂₃, HK 97₁₂₄, HK 98₁₂₅, HK 99₁₂₆, HK 100₁₂₇, HK 101₁₂₈, HK 102₁₂₉, HK 103₁₃₀, HK 104₁₃₁, HK 105₁₃₂, HK 106₁₃₃, HK 107₁₃₄, HK 108₁₃₅, JP 0₁₃₆, JP 1₁₃₇, JP 2₁₃₈, JP 3₁₃₉, JP 4₁₄₀, JP 5₁₄₁, JP 6₁₄₂, JP 7₁₄₃, JP 8₁₄₄, JP 9₁₄₅, JP 10₁₄₆, JP 11₁₄₇, JP 12₁₄₈, JP 13₁₄₉, JP 14₁₅₀, JP 15₁₅₁, JP 16₁₅₂, JP 17₁₅₃, JP 18₁₅₄, JP 19₁₅₅, JP 20₁₅₆, JP 21₁₅₇, JP 22₁₅₈, JP 23₁₅₉, JP 24₁₆₀, JP 25₁₆₁, JP 26₁₆₂, JP 27₁₆₃, JP 28₁₆₄, JP 29₁₆₅, JP 30₁₆₆, JP 31₁₆₇, JP 32₁₆₈, JP 33₁₆₉, JP 34₁₇₀, JP 35₁₇₁, JP 36₁₇₂, JP 37₁₇₃, JP 38₁₇₄, JP 39₁₇₅, JP 40₁₇₆, JP 41₁₇₇, JP 42₁₇₈, JP 43₁₇₉, JP 44₁₈₀, JP 45₁₈₁, JP 46₁₈₂, JP 47₁₈₃, JP 48₁₈₄, JP 49₁₈₅, JP 50₁₈₆, JP 51₁₈₇, JP 52₁₈₈, JP 53₁₈₉, JP 54₁₉₀, JP 55₁₉₁, JP 56₁₉₂, JP 57₁₉₃, JP 58₁₉₄, JP 59₁₉₅, JP 60₁₉₆, JP 61₁₉₇, JP 62₁₉₈, JP 63₁₉₉, JP 64₂₀₀, JP 65₂₀₁, JP 66₂₀₂, JP 67₂₀₃, JP 68₂₀₄, JP 69₂₀₅, JP 70₂₀₆, JP 71₂₀₇, JP 72₂₀₈, JP 73₂₀₉, JP 74₂₁₀, JP 75₂₁₁, JP 76₂₁₂, JP 77₂₁₃, JP 78₂₁₄, JP 79₂₁₅, JP 80₂₁₆, JP 81₂₁₇, JP 82₂₁₈, JP 83₂₁₉, JP 84₂₂₀, JP 85₂₂₁, JP 86₂₂₂, JP 87₂₂₃, JP 88₂₂₄, JP 89₂₂₅, JP 90₂₂₆, JP 91₂₂₇, JP 92₂₂₈, JP 93₂₂₉, JP 94₂₃₀, JP 95₂₃₁, JP 96₂₃₂, JP 97₂₃₃, JP 98₂₃₄, JP 99₂₃₅, JP 100₂₃₆, JP 101₂₃₇, JP 102₂₃₈, JP 103₂₃₉, JP 104₂₄₀, JP 105₂₄₁, JP 106₂₄₂, JP 107₂₄₃, JP 108₂₄₄, JP 109₂₄₅, JP 110₂₄₆, JP 111₂₄₇, JP 112₂₄₈, JP 113₂₄₉, JP 114₂₅₀, JP 115₂₅₁, JP 116₂₅₂, JP 117₂₅₃, JP 118₂₅₄, JP 119₂₅₅, JP 120₂₅₆, JP 121₂₅₇, JP 122₂₅₈, JP 123₂₅₉, KR 0₂₆₀, KR 1₂₆₁, KR 2₂₆₂, KR 3₂₆₃, KR 4₂₆₄, KR 5₂₆₅, KR 6₂₆₆, KR 7₂₆₇, KR 8₂₆₈, KR 9₂₆₉, KR 10₂₇₀, KR 11₂₇₁, KR 12₂₇₂, KR 13₂₇₃, KR 14₂₇₄, KR 15₂₇₅, KR 16₂₇₆, KR 17₂₇₇, KR 18₂₇₈, KR 19₂₇₉, KR 20₂₈₀, KR 21₂₈₁, KR 22₂₈₂, KR 23₂₈₃, KR 24₂₈₄, KR 25₂₈₅, KR 26₂₈₆, KR 27₂₈₇, KR 28₂₈₈, KR 29₂₈₉, KR 30₂₉₀, KR 31₂₉₁, KR 32₂₉₂, KR 33₂₉₃, KR 34₂₉₄, KR 35₂₉₅, KR 36₂₉₆, KR 37₂₉₇, KR 38₂₉₈, KR 39₂₉₉, KR 40₃₀₀, KR 41₃₀₁, KR 42₃₀₂, KR 43₃₀₃, KR 44₃₀₄, KR 45₃₀₅, KR 46₃₀₆, KR 47₃₀₇, KR 48₃₀₈, KR 49₃₀₉, KR 50₃₁₀, KR 51₃₁₁, KR 52₃₁₂, KR 53₃₁₃, KR 54₃₁₄, KR 55₃₁₅, KR 56₃₁₆, KR 57₃₁₇, KR 58₃₁₈, KR 59₃₁₉, KR 60₃₂₀, KR 61₃₂₁, KR 62₃₂₂, KR 63₃₂₃, KR 64₃₂₄, KR 65₃₂₅, KR 66₃₂₆, KR 67₃₂₇, KR 68₃₂₈, KR 69₃₂₉, KR 70₃₃₀, KR 71₃₃₁, KR 72₃₃₂, KR 73₃₃₃, KR 74₃₃₄, KR 75₃₃₅, KR 76₃₃₆, KR 77₃₃₇, KR 78₃₃₈, KR 79₃₃₉, KR 80₃₄₀, KR 81₃₄₁, KR 82₃₄₂, KR 83₃₄₃, KR 84₃₄₄, KR 85₃₄₅, KR 86₃₄₆, KR 87₃₄₇, KR 88₃₄₈, KR 89₃₄₉, KR 90₃₅₀, KR 91₃₅₁, KR 92₃₅₂, KR 93₃₅₃, KR 94₃₅₄, KR 95₃₅₅, KR 96₃₅₆, KR 97₃₅₇, KR 98₃₅₈, KR 99₃₅₉, KR 100₃₆₀, KR 101₃₆₁, KR 102₃₆₂, KR 103₃₆₃, KR 104₃₆₄, KR 105₃₆₅, KR 106₃₆₆, KR 107₃₆₇, KR 108₃₆₈, KR 109₃₆₉, KR 110₃₇₀, KR 111₃₇₁, KR 112₃₇₂, KR 113₃₇₃, KR 114₃₇₄, KR 115₃₇₅, KR 116₃₇₆, KR 117₃₇₇, KR 118₃₇₈, KR 119₃₇₉, KR 120₃₈₀, KR 121₃₈₁, KR 122₃₈₂, KR 123₃₈₃, SC 0₃₈₄, SC 1₃₈₅, SC 2₃₈₆, SC 3₃₈₇, SC 4₃₈₈, SC 5₃₈₉, SC 6₃₉₀, SC 7₃₉₁, SC 8₃₉₂, SC 9₃₉₃, SC 10₃₉₄, SC 11₃₉₅, SC 12₃₉₆, SC 13₃₉₇, SC 14₃₉₈, SC 15₃₉₉, SC 16₄₀₀, SC 17₄₀₁, SC 18₄₀₂, SC 19₄₀₃, SC 20₄₀₄, SC 21₄₀₅, SC 22₄₀₆, SC 23₄₀₇, SC 24₄₀₈, SC 25₄₀₉, SC 26₄₁₀, SC 27₄₁₁, SC 28₄₁₂, SC 29₄₁₃, SC 30₄₁₄, SC 31₄₁₅, SC 32₄₁₆, SC 33₄₁₇, SC 34₄₁₈, SC 35₄₁₉, SC 36₄₂₀, SC 37₄₂₁, SC 38₄₂₂, SC 39₄₂₃, SC 40₄₂₄, SC 41₄₂₅, SC 42₄₂₆, SC 43₄₂₇, SC 44₄₂₈, SC 45₄₂₉, SC 46₄₃₀, SC 47₄₃₁, SC 48₄₃₂, SC 49₄₃₃, SC 50₄₃₄, SC 51₄₃₅, SC 52₄₃₆, SC 53₄₃₇, SC 54₄₃₈, SC 55₄₃₉, SC 56₄₄₀, SC 57₄₄₁, SC 58₄₄₂, SC 59₄₄₃, SC 60₄₄₄, SC 61₄₄₅, SC 62₄₄₆, SC 63₄₄₇, SC 64₄₄₈, SC 65₄₄₉, SC 66₄₅₀, SC 67₄₅₁, SC 68₄₅₂, SC 69₄₅₃, SC 70₄₅₄, SC 71₄₅₅, SC 72₄₅₆, SC 73₄₅₇, SC 74₄₅₈, SC 75₄₅₉, SC 76₄₆₀, SC 77₄₆₁, SC 78₄₆₂, SC 79₄₆₃, SC 80₄₆₄, SC 81₄₆₅, SC 82₄₆₆, SC 83₄₆₇, SC 84₄₆₈, SC 85₄₆₉, SC 86₄₇₀, SC 87₄₇₁, SC 88₄₇₂, SC 89₄₇₃, SC 90₄₇₄, SC 91₄₇₅, SC 92₄₇₆, SC 93₄₇₇, SC 94₄₇₈, SC 95₄₇₉, SC 96₄₈₀, SC 97₄₈₁, SC 98₄₈₂, SC 99₄₈₃, SC 100₄₈₄, TC 0₄₈₅, TC 1₄₈₆, TC 2₄₈₇, TC 3₄₈₈, TC 4₄₈₉, TC 5₄₉₀, TC 6₄₉₁, TC 7₄₉₂, TC 8₄₉₃, TC 9₄₉₄, TC 10₄₉₅, TC 11₄₉₆, TC 12₄₉₇, TC 13₄₉₈, TC 14₄₉₉, TC 15₅₀₀, TC 16₅₀₁, TC 17₅₀₂, TC 18₅₀₃, TC 19₅₀₄, TC 20₅₀₅, TC 21₅₀₆, TC 22₅₀₇, TC 23₅₀₈, TC 24₅₀₉, TC 25₅₁₀, TC 26₅₁₁, TC 27₅₁₂, TC 28₅₁₃, TC 29₅₁₄, TC 30₅₁₅, TC 31₅₁₆, TC 32₅₁₇, TC 33₅₁₈, TC 34₅₁₉, TC 35₅₂₀, TC 36₅₂₁, TC 37₅₂₂, TC 38₅₂₃, TC 39₅₂₄, TC 40₅₂₅, TC 41₅₂₆, TC 42₅₂₇, TC 43₅₂₈, TC 44₅₂₉, TC 45₅₃₀, TC 46₅₃₁, TC 47₅₃₂, TC 48₅₃₃, TC 49₅₃₄, TC 50₅₃₅, TC 51₅₃₆, TC 52₅₃₇, TC 53₅₃₈, TC 54₅₃₉, TC 55₅₄₀, TC 56₅₄₁, TC 57₅₄₂, TC 58₅₄₃, TC 59₅₄₄, TC 60₅₄₅, TC 61₅₄₆, TC 62₅₄₇, TC 63₅₄₈, TC 64₅₄₉, TC 65₅₅₀, TC 66₅₅₁, TC 67₅₅₂, TC 68₅₅₃, TC 69₅₅₄, TC 70₅₅₅, TC 71₅₅₆, TC 72₅₅₇, TC 73₅₅₈, TC 74₅₅₉, TC 75₅₆₀, TC 76₅₆₁, TC 77₅₆₂, TC 78₅₆₃, TC 79₅₆₄, TC 80₅₆₅, TC 81₅₆₆, TC 82₅₆₇, TC 83₅₆₈, TC 84₅₆₉, TC 85₅₇₀, TC 86₅₇₁, TC 87₅₇₂, TC 88₅₇₃, TC 89₅₇₄, TC 90₅₇₅, TC 91₅₇₆, TC 92₅₇₇, TC 93₅₇₈, TC 94₅₇₉, TC 95₅₈₀, TC 96₅₈₁, TC 97₅₈₂, TC 98₅₈₃, TC 99₅₈₄, TC 100₅₈₅, TC 101₅₈₆, TC 102₅₈₇, TC 103₅₈₈, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lycian₆₄₇, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, Myanmar₆₆₅, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phags Pa₆₈₇, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'maaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1977: 2 fonts: Symbols 2 0₁₂, Symbols 2 3₁₅. + 'mc,' + // #1978: 147 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 117₂₅₃, JP 123₂₅₉, KR 118₃₇₈, KR 123₃₈₃, SC 94₄₇₈, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'nbbccc4ad4nf4oe3qf3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1979: 147 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 117₃₇₇, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'nbbccc4ad4oe4nf3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1980: 147 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 118₃₇₈, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'nbbccc4ad4oe4oe3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1981: 146 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 118₃₇₈, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'nbbccc4ad4oe4oe3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1982: 97 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, Duployan 0₂₁, HK 79₁₀₆, JP 57₁₉₃, KR 96₃₅₆, TC 73₅₅₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Arabic₅₉₄, Armenian₅₉₅, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Coptic₆₁₂, Devanagari₆₁₅, Elbasan₆₁₆, Ethiopic₆₁₈, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hebrew₆₂₈, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Lisu₆₄₆, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Meetei Mayek₆₅₈, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Myanmar₆₆₅, NKo₆₆₆, New Tai Lue₆₆₈, Newa₆₆₉, Old Permic₆₇₆, Old Sogdian₆₇₈, Oriya₆₈₁, Osage₆₈₂, Pahawh Hmong₆₈₄, Phags Pa₆₈₇, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Wancho₇₁₉, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'nbf3g3i6g7t1faababaaaaaaaaabaabcabbaaaaaabeaaaaaaaaaaccaaaaaacbaacabagbcabcbaaaabaabaaaaaaabaabaaaacca,' + // #1983: 6 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, HK 78₁₀₅, JP 56₁₉₂, TC 71₅₅₆, Math₆₅₅. + 'nb3l3i13z3u,' + // #1984: 8 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, HK 79₁₀₆, JP 57₁₉₃, KR 106₃₆₆, SC 70₄₅₄, TC 73₅₅₈, Math₆₅₅. + 'nb3m3i6q3j3z3s,' + // #1985: 8 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, HK 79₁₀₆, JP 88₂₂₄, KR 106₃₆₆, SC 70₄₅₄, TC 73₅₅₈, Math₆₅₅. + 'nb3m4n5l3j3z3s,' + // #1986: 8 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, HK 81₁₀₈, JP 58₁₉₄, KR 104₃₆₄, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + 'nb3o3h6n3n3z3q,' + // #1987: 8 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, HK 87₁₁₄, JP 79₂₁₅, KR 106₃₆₆, SC 70₄₅₄, TC 83₅₆₈, Math₆₅₅. + 'nb3u3w5u3j4j3i,' + // #1988: 4 fonts: Symbols 2 1₁₃, Symbols 2 3₁₅, Math₆₅₅, Tai Tham₇₀₇. + 'nb24p1z,' + // #1989: 701 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 0₂₁, Duployan 1₂₂, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 0₂₇, HK 1₂₈, HK 2₂₉, HK 3₃₀, HK 4₃₁, HK 5₃₂, HK 6₃₃, HK 7₃₄, HK 8₃₅, HK 9₃₆, HK 10₃₇, HK 11₃₈, HK 12₃₉, HK 13₄₀, HK 14₄₁, HK 15₄₂, HK 16₄₃, HK 17₄₄, HK 18₄₅, HK 19₄₆, HK 20₄₇, HK 21₄₈, HK 22₄₉, HK 23₅₀, HK 24₅₁, HK 25₅₂, HK 26₅₃, HK 27₅₄, HK 28₅₅, HK 29₅₆, HK 30₅₇, HK 31₅₈, HK 32₅₉, HK 33₆₀, HK 34₆₁, HK 35₆₂, HK 36₆₃, HK 37₆₄, HK 38₆₅, HK 39₆₆, HK 40₆₇, HK 41₆₈, HK 42₆₉, HK 43₇₀, HK 44₇₁, HK 45₇₂, HK 46₇₃, HK 47₇₄, HK 48₇₅, HK 49₇₆, HK 50₇₇, HK 51₇₈, HK 52₇₉, HK 53₈₀, HK 54₈₁, HK 55₈₂, HK 56₈₃, HK 57₈₄, HK 58₈₅, HK 59₈₆, HK 60₈₇, HK 61₈₈, HK 62₈₉, HK 63₉₀, HK 64₉₁, HK 65₉₂, HK 66₉₃, HK 67₉₄, HK 68₉₅, HK 69₉₆, HK 70₉₇, HK 71₉₈, HK 72₉₉, HK 73₁₀₀, HK 74₁₀₁, HK 75₁₀₂, HK 76₁₀₃, HK 77₁₀₄, HK 78₁₀₅, HK 79₁₀₆, HK 80₁₀₇, HK 81₁₀₈, HK 82₁₀₉, HK 83₁₁₀, HK 84₁₁₁, HK 85₁₁₂, HK 86₁₁₃, HK 87₁₁₄, HK 88₁₁₅, HK 89₁₁₆, HK 90₁₁₇, HK 91₁₁₈, HK 92₁₁₉, HK 93₁₂₀, HK 94₁₂₁, HK 95₁₂₂, HK 96₁₂₃, HK 97₁₂₄, HK 98₁₂₅, HK 99₁₂₆, HK 100₁₂₇, HK 101₁₂₈, HK 102₁₂₉, HK 103₁₃₀, HK 104₁₃₁, HK 105₁₃₂, HK 106₁₃₃, HK 107₁₃₄, HK 108₁₃₅, JP 0₁₃₆, JP 1₁₃₇, JP 2₁₃₈, JP 3₁₃₉, JP 4₁₄₀, JP 5₁₄₁, JP 6₁₄₂, JP 7₁₄₃, JP 8₁₄₄, JP 9₁₄₅, JP 10₁₄₆, JP 11₁₄₇, JP 12₁₄₈, JP 13₁₄₉, JP 14₁₅₀, JP 15₁₅₁, JP 16₁₅₂, JP 17₁₅₃, JP 18₁₅₄, JP 19₁₅₅, JP 20₁₅₆, JP 21₁₅₇, JP 22₁₅₈, JP 23₁₅₉, JP 24₁₆₀, JP 25₁₆₁, JP 26₁₆₂, JP 27₁₆₃, JP 28₁₆₄, JP 29₁₆₅, JP 30₁₆₆, JP 31₁₆₇, JP 32₁₆₈, JP 33₁₆₉, JP 34₁₇₀, JP 35₁₇₁, JP 36₁₇₂, JP 37₁₇₃, JP 38₁₇₄, JP 39₁₇₅, JP 40₁₇₆, JP 41₁₇₇, JP 42₁₇₈, JP 43₁₇₉, JP 44₁₈₀, JP 45₁₈₁, JP 46₁₈₂, JP 47₁₈₃, JP 48₁₈₄, JP 49₁₈₅, JP 50₁₈₆, JP 51₁₈₇, JP 52₁₈₈, JP 53₁₈₉, JP 54₁₉₀, JP 55₁₉₁, JP 56₁₉₂, JP 57₁₉₃, JP 58₁₉₄, JP 59₁₉₅, JP 60₁₉₆, JP 61₁₉₇, JP 62₁₉₈, JP 63₁₉₉, JP 64₂₀₀, JP 65₂₀₁, JP 66₂₀₂, JP 67₂₀₃, JP 68₂₀₄, JP 69₂₀₅, JP 70₂₀₆, JP 71₂₀₇, JP 72₂₀₈, JP 73₂₀₉, JP 74₂₁₀, JP 75₂₁₁, JP 76₂₁₂, JP 77₂₁₃, JP 78₂₁₄, JP 79₂₁₅, JP 80₂₁₆, JP 81₂₁₇, JP 82₂₁₈, JP 83₂₁₉, JP 84₂₂₀, JP 85₂₂₁, JP 86₂₂₂, JP 87₂₂₃, JP 88₂₂₄, JP 89₂₂₅, JP 90₂₂₆, JP 91₂₂₇, JP 92₂₂₈, JP 93₂₂₉, JP 94₂₃₀, JP 95₂₃₁, JP 96₂₃₂, JP 97₂₃₃, JP 98₂₃₄, JP 99₂₃₅, JP 100₂₃₆, JP 101₂₃₇, JP 102₂₃₈, JP 103₂₃₉, JP 104₂₄₀, JP 105₂₄₁, JP 106₂₄₂, JP 107₂₄₃, JP 108₂₄₄, JP 109₂₄₅, JP 110₂₄₆, JP 111₂₄₇, JP 112₂₄₈, JP 113₂₄₉, JP 114₂₅₀, JP 115₂₅₁, JP 116₂₅₂, JP 117₂₅₃, JP 118₂₅₄, JP 119₂₅₅, JP 120₂₅₆, JP 121₂₅₇, JP 122₂₅₈, JP 123₂₅₉, KR 0₂₆₀, KR 1₂₆₁, KR 2₂₆₂, KR 3₂₆₃, KR 4₂₆₄, KR 5₂₆₅, KR 6₂₆₆, KR 7₂₆₇, KR 8₂₆₈, KR 9₂₆₉, KR 10₂₇₀, KR 11₂₇₁, KR 12₂₇₂, KR 13₂₇₃, KR 14₂₇₄, KR 15₂₇₅, KR 16₂₇₆, KR 17₂₇₇, KR 18₂₇₈, KR 19₂₇₉, KR 20₂₈₀, KR 21₂₈₁, KR 22₂₈₂, KR 23₂₈₃, KR 24₂₈₄, KR 25₂₈₅, KR 26₂₈₆, KR 27₂₈₇, KR 28₂₈₈, KR 29₂₈₉, KR 30₂₉₀, KR 31₂₉₁, KR 32₂₉₂, KR 33₂₉₃, KR 34₂₉₄, KR 35₂₉₅, KR 36₂₉₆, KR 37₂₉₇, KR 38₂₉₈, KR 39₂₉₉, KR 40₃₀₀, KR 41₃₀₁, KR 42₃₀₂, KR 43₃₀₃, KR 44₃₀₄, KR 45₃₀₅, KR 46₃₀₆, KR 47₃₀₇, KR 48₃₀₈, KR 49₃₀₉, KR 50₃₁₀, KR 51₃₁₁, KR 52₃₁₂, KR 53₃₁₃, KR 54₃₁₄, KR 55₃₁₅, KR 56₃₁₆, KR 57₃₁₇, KR 58₃₁₈, KR 59₃₁₉, KR 60₃₂₀, KR 61₃₂₁, KR 62₃₂₂, KR 63₃₂₃, KR 64₃₂₄, KR 65₃₂₅, KR 66₃₂₆, KR 67₃₂₇, KR 68₃₂₈, KR 69₃₂₉, KR 70₃₃₀, KR 71₃₃₁, KR 72₃₃₂, KR 73₃₃₃, KR 74₃₃₄, KR 75₃₃₅, KR 76₃₃₆, KR 77₃₃₇, KR 78₃₃₈, KR 79₃₃₉, KR 80₃₄₀, KR 81₃₄₁, KR 82₃₄₂, KR 83₃₄₃, KR 84₃₄₄, KR 85₃₄₅, KR 86₃₄₆, KR 87₃₄₇, KR 88₃₄₈, KR 89₃₄₉, KR 90₃₅₀, KR 91₃₅₁, KR 92₃₅₂, KR 93₃₅₃, KR 94₃₅₄, KR 95₃₅₅, KR 96₃₅₆, KR 97₃₅₇, KR 98₃₅₈, KR 99₃₅₉, KR 100₃₆₀, KR 101₃₆₁, KR 102₃₆₂, KR 103₃₆₃, KR 104₃₆₄, KR 105₃₆₅, KR 106₃₆₆, KR 107₃₆₇, KR 108₃₆₈, KR 109₃₆₉, KR 110₃₇₀, KR 111₃₇₁, KR 112₃₇₂, KR 113₃₇₃, KR 114₃₇₄, KR 115₃₇₅, KR 116₃₇₆, KR 117₃₇₇, KR 118₃₇₈, KR 119₃₇₉, KR 120₃₈₀, KR 121₃₈₁, KR 122₃₈₂, KR 123₃₈₃, SC 0₃₈₄, SC 1₃₈₅, SC 2₃₈₆, SC 3₃₈₇, SC 4₃₈₈, SC 5₃₈₉, SC 6₃₉₀, SC 7₃₉₁, SC 8₃₉₂, SC 9₃₉₃, SC 10₃₉₄, SC 11₃₉₅, SC 12₃₉₆, SC 13₃₉₇, SC 14₃₉₈, SC 15₃₉₉, SC 16₄₀₀, SC 17₄₀₁, SC 18₄₀₂, SC 19₄₀₃, SC 20₄₀₄, SC 21₄₀₅, SC 22₄₀₆, SC 23₄₀₇, SC 24₄₀₈, SC 25₄₀₉, SC 26₄₁₀, SC 27₄₁₁, SC 28₄₁₂, SC 29₄₁₃, SC 30₄₁₄, SC 31₄₁₅, SC 32₄₁₆, SC 33₄₁₇, SC 34₄₁₈, SC 35₄₁₉, SC 36₄₂₀, SC 37₄₂₁, SC 38₄₂₂, SC 39₄₂₃, SC 40₄₂₄, SC 41₄₂₅, SC 42₄₂₆, SC 43₄₂₇, SC 44₄₂₈, SC 45₄₂₉, SC 46₄₃₀, SC 47₄₃₁, SC 48₄₃₂, SC 49₄₃₃, SC 50₄₃₄, SC 51₄₃₅, SC 52₄₃₆, SC 53₄₃₇, SC 54₄₃₈, SC 55₄₃₉, SC 56₄₄₀, SC 57₄₄₁, SC 58₄₄₂, SC 59₄₄₃, SC 60₄₄₄, SC 61₄₄₅, SC 62₄₄₆, SC 63₄₄₇, SC 64₄₄₈, SC 65₄₄₉, SC 66₄₅₀, SC 67₄₅₁, SC 68₄₅₂, SC 69₄₅₃, SC 70₄₅₄, SC 71₄₅₅, SC 72₄₅₆, SC 73₄₅₇, SC 74₄₅₈, SC 75₄₅₉, SC 76₄₆₀, SC 77₄₆₁, SC 78₄₆₂, SC 79₄₆₃, SC 80₄₆₄, SC 81₄₆₅, SC 82₄₆₆, SC 83₄₆₇, SC 84₄₆₈, SC 85₄₆₉, SC 86₄₇₀, SC 87₄₇₁, SC 88₄₇₂, SC 89₄₇₃, SC 90₄₇₄, SC 91₄₇₅, SC 92₄₇₆, SC 93₄₇₇, SC 94₄₇₈, SC 95₄₇₉, SC 96₄₈₀, SC 97₄₈₁, SC 98₄₈₂, SC 99₄₈₃, SC 100₄₈₄, TC 0₄₈₅, TC 1₄₈₆, TC 2₄₈₇, TC 3₄₈₈, TC 4₄₈₉, TC 5₄₉₀, TC 6₄₉₁, TC 7₄₉₂, TC 8₄₉₃, TC 9₄₉₄, TC 10₄₉₅, TC 11₄₉₆, TC 12₄₉₇, TC 13₄₉₈, TC 14₄₉₉, TC 15₅₀₀, TC 16₅₀₁, TC 17₅₀₂, TC 18₅₀₃, TC 19₅₀₄, TC 20₅₀₅, TC 21₅₀₆, TC 22₅₀₇, TC 23₅₀₈, TC 24₅₀₉, TC 25₅₁₀, TC 26₅₁₁, TC 27₅₁₂, TC 28₅₁₃, TC 29₅₁₄, TC 30₅₁₅, TC 31₅₁₆, TC 32₅₁₇, TC 33₅₁₈, TC 34₅₁₉, TC 35₅₂₀, TC 36₅₂₁, TC 37₅₂₂, TC 38₅₂₃, TC 39₅₂₄, TC 40₅₂₅, TC 41₅₂₆, TC 42₅₂₇, TC 43₅₂₈, TC 44₅₂₉, TC 45₅₃₀, TC 46₅₃₁, TC 47₅₃₂, TC 48₅₃₃, TC 49₅₃₄, TC 50₅₃₅, TC 51₅₃₆, TC 52₅₃₇, TC 53₅₃₈, TC 54₅₃₉, TC 55₅₄₀, TC 56₅₄₁, TC 57₅₄₂, TC 58₅₄₃, TC 59₅₄₄, TC 60₅₄₅, TC 61₅₄₆, TC 62₅₄₇, TC 63₅₄₈, TC 64₅₄₉, TC 65₅₅₀, TC 66₅₅₁, TC 67₅₅₂, TC 68₅₅₃, TC 69₅₅₄, TC 70₅₅₅, TC 71₅₅₆, TC 72₅₅₇, TC 73₅₅₈, TC 74₅₅₉, TC 75₅₆₀, TC 76₅₆₁, TC 77₅₆₂, TC 78₅₆₃, TC 79₅₆₄, TC 80₅₆₅, TC 81₅₆₆, TC 82₅₆₇, TC 83₅₆₈, TC 84₅₆₉, TC 85₅₇₀, TC 86₅₇₁, TC 87₅₇₂, TC 88₅₇₃, TC 89₅₇₄, TC 90₅₇₅, TC 91₅₇₆, TC 92₅₇₇, TC 93₅₇₈, TC 94₅₇₉, TC 95₅₈₀, TC 96₅₈₁, TC 97₅₈₂, TC 98₅₈₃, TC 99₅₈₄, TC 100₅₈₅, TC 101₅₈₆, TC 102₅₈₇, TC 103₅₈₈, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndcaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1990: 148 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 0₂₁, Duployan 1₂₂, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 100₁₂₇, HK 108₁₃₅, JP 86₂₂₂, JP 123₂₅₉, KR 109₃₆₉, KR 123₃₈₃, SC 85₄₆₉, SC 100₄₈₄, TC 96₅₈₁, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndcaaac3wh3i1k4fn3ho3shaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1991: 699 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 0₂₇, HK 1₂₈, HK 2₂₉, HK 3₃₀, HK 4₃₁, HK 5₃₂, HK 6₃₃, HK 7₃₄, HK 8₃₅, HK 9₃₆, HK 10₃₇, HK 11₃₈, HK 12₃₉, HK 13₄₀, HK 14₄₁, HK 15₄₂, HK 16₄₃, HK 17₄₄, HK 18₄₅, HK 19₄₆, HK 20₄₇, HK 21₄₈, HK 22₄₉, HK 23₅₀, HK 24₅₁, HK 25₅₂, HK 26₅₃, HK 27₅₄, HK 28₅₅, HK 29₅₆, HK 30₅₇, HK 31₅₈, HK 32₅₉, HK 33₆₀, HK 34₆₁, HK 35₆₂, HK 36₆₃, HK 37₆₄, HK 38₆₅, HK 39₆₆, HK 40₆₇, HK 41₆₈, HK 42₆₉, HK 43₇₀, HK 44₇₁, HK 45₇₂, HK 46₇₃, HK 47₇₄, HK 48₇₅, HK 49₇₆, HK 50₇₇, HK 51₇₈, HK 52₇₉, HK 53₈₀, HK 54₈₁, HK 55₈₂, HK 56₈₃, HK 57₈₄, HK 58₈₅, HK 59₈₆, HK 60₈₇, HK 61₈₈, HK 62₈₉, HK 63₉₀, HK 64₉₁, HK 65₉₂, HK 66₉₃, HK 67₉₄, HK 68₉₅, HK 69₉₆, HK 70₉₇, HK 71₉₈, HK 72₉₉, HK 73₁₀₀, HK 74₁₀₁, HK 75₁₀₂, HK 76₁₀₃, HK 77₁₀₄, HK 78₁₀₅, HK 79₁₀₆, HK 80₁₀₇, HK 81₁₀₈, HK 82₁₀₉, HK 83₁₁₀, HK 84₁₁₁, HK 85₁₁₂, HK 86₁₁₃, HK 87₁₁₄, HK 88₁₁₅, HK 89₁₁₆, HK 90₁₁₇, HK 91₁₁₈, HK 92₁₁₉, HK 93₁₂₀, HK 94₁₂₁, HK 95₁₂₂, HK 96₁₂₃, HK 97₁₂₄, HK 98₁₂₅, HK 99₁₂₆, HK 100₁₂₇, HK 101₁₂₈, HK 102₁₂₉, HK 103₁₃₀, HK 104₁₃₁, HK 105₁₃₂, HK 106₁₃₃, HK 107₁₃₄, HK 108₁₃₅, JP 0₁₃₆, JP 1₁₃₇, JP 2₁₃₈, JP 3₁₃₉, JP 4₁₄₀, JP 5₁₄₁, JP 6₁₄₂, JP 7₁₄₃, JP 8₁₄₄, JP 9₁₄₅, JP 10₁₄₆, JP 11₁₄₇, JP 12₁₄₈, JP 13₁₄₉, JP 14₁₅₀, JP 15₁₅₁, JP 16₁₅₂, JP 17₁₅₃, JP 18₁₅₄, JP 19₁₅₅, JP 20₁₅₆, JP 21₁₅₇, JP 22₁₅₈, JP 23₁₅₉, JP 24₁₆₀, JP 25₁₆₁, JP 26₁₆₂, JP 27₁₆₃, JP 28₁₆₄, JP 29₁₆₅, JP 30₁₆₆, JP 31₁₆₇, JP 32₁₆₈, JP 33₁₆₉, JP 34₁₇₀, JP 35₁₇₁, JP 36₁₇₂, JP 37₁₇₃, JP 38₁₇₄, JP 39₁₇₅, JP 40₁₇₆, JP 41₁₇₇, JP 42₁₇₈, JP 43₁₇₉, JP 44₁₈₀, JP 45₁₈₁, JP 46₁₈₂, JP 47₁₈₃, JP 48₁₈₄, JP 49₁₈₅, JP 50₁₈₆, JP 51₁₈₇, JP 52₁₈₈, JP 53₁₈₉, JP 54₁₉₀, JP 55₁₉₁, JP 56₁₉₂, JP 57₁₉₃, JP 58₁₉₄, JP 59₁₉₅, JP 60₁₉₆, JP 61₁₉₇, JP 62₁₉₈, JP 63₁₉₉, JP 64₂₀₀, JP 65₂₀₁, JP 66₂₀₂, JP 67₂₀₃, JP 68₂₀₄, JP 69₂₀₅, JP 70₂₀₆, JP 71₂₀₇, JP 72₂₀₈, JP 73₂₀₉, JP 74₂₁₀, JP 75₂₁₁, JP 76₂₁₂, JP 77₂₁₃, JP 78₂₁₄, JP 79₂₁₅, JP 80₂₁₆, JP 81₂₁₇, JP 82₂₁₈, JP 83₂₁₉, JP 84₂₂₀, JP 85₂₂₁, JP 86₂₂₂, JP 87₂₂₃, JP 88₂₂₄, JP 89₂₂₅, JP 90₂₂₆, JP 91₂₂₇, JP 92₂₂₈, JP 93₂₂₉, JP 94₂₃₀, JP 95₂₃₁, JP 96₂₃₂, JP 97₂₃₃, JP 98₂₃₄, JP 99₂₃₅, JP 100₂₃₆, JP 101₂₃₇, JP 102₂₃₈, JP 103₂₃₉, JP 104₂₄₀, JP 105₂₄₁, JP 106₂₄₂, JP 107₂₄₃, JP 108₂₄₄, JP 109₂₄₅, JP 110₂₄₆, JP 111₂₄₇, JP 112₂₄₈, JP 113₂₄₉, JP 114₂₅₀, JP 115₂₅₁, JP 116₂₅₂, JP 117₂₅₃, JP 118₂₅₄, JP 119₂₅₅, JP 120₂₅₆, JP 121₂₅₇, JP 122₂₅₈, JP 123₂₅₉, KR 0₂₆₀, KR 1₂₆₁, KR 2₂₆₂, KR 3₂₆₃, KR 4₂₆₄, KR 5₂₆₅, KR 6₂₆₆, KR 7₂₆₇, KR 8₂₆₈, KR 9₂₆₉, KR 10₂₇₀, KR 11₂₇₁, KR 12₂₇₂, KR 13₂₇₃, KR 14₂₇₄, KR 15₂₇₅, KR 16₂₇₆, KR 17₂₇₇, KR 18₂₇₈, KR 19₂₇₉, KR 20₂₈₀, KR 21₂₈₁, KR 22₂₈₂, KR 23₂₈₃, KR 24₂₈₄, KR 25₂₈₅, KR 26₂₈₆, KR 27₂₈₇, KR 28₂₈₈, KR 29₂₈₉, KR 30₂₉₀, KR 31₂₉₁, KR 32₂₉₂, KR 33₂₉₃, KR 34₂₉₄, KR 35₂₉₅, KR 36₂₉₆, KR 37₂₉₇, KR 38₂₉₈, KR 39₂₉₉, KR 40₃₀₀, KR 41₃₀₁, KR 42₃₀₂, KR 43₃₀₃, KR 44₃₀₄, KR 45₃₀₅, KR 46₃₀₆, KR 47₃₀₇, KR 48₃₀₈, KR 49₃₀₉, KR 50₃₁₀, KR 51₃₁₁, KR 52₃₁₂, KR 53₃₁₃, KR 54₃₁₄, KR 55₃₁₅, KR 56₃₁₆, KR 57₃₁₇, KR 58₃₁₈, KR 59₃₁₉, KR 60₃₂₀, KR 61₃₂₁, KR 62₃₂₂, KR 63₃₂₃, KR 64₃₂₄, KR 65₃₂₅, KR 66₃₂₆, KR 67₃₂₇, KR 68₃₂₈, KR 69₃₂₉, KR 70₃₃₀, KR 71₃₃₁, KR 72₃₃₂, KR 73₃₃₃, KR 74₃₃₄, KR 75₃₃₅, KR 76₃₃₆, KR 77₃₃₇, KR 78₃₃₈, KR 79₃₃₉, KR 80₃₄₀, KR 81₃₄₁, KR 82₃₄₂, KR 83₃₄₃, KR 84₃₄₄, KR 85₃₄₅, KR 86₃₄₆, KR 87₃₄₇, KR 88₃₄₈, KR 89₃₄₉, KR 90₃₅₀, KR 91₃₅₁, KR 92₃₅₂, KR 93₃₅₃, KR 94₃₅₄, KR 95₃₅₅, KR 96₃₅₆, KR 97₃₅₇, KR 98₃₅₈, KR 99₃₅₉, KR 100₃₆₀, KR 101₃₆₁, KR 102₃₆₂, KR 103₃₆₃, KR 104₃₆₄, KR 105₃₆₅, KR 106₃₆₆, KR 107₃₆₇, KR 108₃₆₈, KR 109₃₆₉, KR 110₃₇₀, KR 111₃₇₁, KR 112₃₇₂, KR 113₃₇₃, KR 114₃₇₄, KR 115₃₇₅, KR 116₃₇₆, KR 117₃₇₇, KR 118₃₇₈, KR 119₃₇₉, KR 120₃₈₀, KR 121₃₈₁, KR 122₃₈₂, KR 123₃₈₃, SC 0₃₈₄, SC 1₃₈₅, SC 2₃₈₆, SC 3₃₈₇, SC 4₃₈₈, SC 5₃₈₉, SC 6₃₉₀, SC 7₃₉₁, SC 8₃₉₂, SC 9₃₉₃, SC 10₃₉₄, SC 11₃₉₅, SC 12₃₉₆, SC 13₃₉₇, SC 14₃₉₈, SC 15₃₉₉, SC 16₄₀₀, SC 17₄₀₁, SC 18₄₀₂, SC 19₄₀₃, SC 20₄₀₄, SC 21₄₀₅, SC 22₄₀₆, SC 23₄₀₇, SC 24₄₀₈, SC 25₄₀₉, SC 26₄₁₀, SC 27₄₁₁, SC 28₄₁₂, SC 29₄₁₃, SC 30₄₁₄, SC 31₄₁₅, SC 32₄₁₆, SC 33₄₁₇, SC 34₄₁₈, SC 35₄₁₉, SC 36₄₂₀, SC 37₄₂₁, SC 38₄₂₂, SC 39₄₂₃, SC 40₄₂₄, SC 41₄₂₅, SC 42₄₂₆, SC 43₄₂₇, SC 44₄₂₈, SC 45₄₂₉, SC 46₄₃₀, SC 47₄₃₁, SC 48₄₃₂, SC 49₄₃₃, SC 50₄₃₄, SC 51₄₃₅, SC 52₄₃₆, SC 53₄₃₇, SC 54₄₃₈, SC 55₄₃₉, SC 56₄₄₀, SC 57₄₄₁, SC 58₄₄₂, SC 59₄₄₃, SC 60₄₄₄, SC 61₄₄₅, SC 62₄₄₆, SC 63₄₄₇, SC 64₄₄₈, SC 65₄₄₉, SC 66₄₅₀, SC 67₄₅₁, SC 68₄₅₂, SC 69₄₅₃, SC 70₄₅₄, SC 71₄₅₅, SC 72₄₅₆, SC 73₄₅₇, SC 74₄₅₈, SC 75₄₅₉, SC 76₄₆₀, SC 77₄₆₁, SC 78₄₆₂, SC 79₄₆₃, SC 80₄₆₄, SC 81₄₆₅, SC 82₄₆₆, SC 83₄₆₇, SC 84₄₆₈, SC 85₄₆₉, SC 86₄₇₀, SC 87₄₇₁, SC 88₄₇₂, SC 89₄₇₃, SC 90₄₇₄, SC 91₄₇₅, SC 92₄₇₆, SC 93₄₇₇, SC 94₄₇₈, SC 95₄₇₉, SC 96₄₈₀, SC 97₄₈₁, SC 98₄₈₂, SC 99₄₈₃, SC 100₄₈₄, TC 0₄₈₅, TC 1₄₈₆, TC 2₄₈₇, TC 3₄₈₈, TC 4₄₈₉, TC 5₄₉₀, TC 6₄₉₁, TC 7₄₉₂, TC 8₄₉₃, TC 9₄₉₄, TC 10₄₉₅, TC 11₄₉₆, TC 12₄₉₇, TC 13₄₉₈, TC 14₄₉₉, TC 15₅₀₀, TC 16₅₀₁, TC 17₅₀₂, TC 18₅₀₃, TC 19₅₀₄, TC 20₅₀₅, TC 21₅₀₆, TC 22₅₀₇, TC 23₅₀₈, TC 24₅₀₉, TC 25₅₁₀, TC 26₅₁₁, TC 27₅₁₂, TC 28₅₁₃, TC 29₅₁₄, TC 30₅₁₅, TC 31₅₁₆, TC 32₅₁₇, TC 33₅₁₈, TC 34₅₁₉, TC 35₅₂₀, TC 36₅₂₁, TC 37₅₂₂, TC 38₅₂₃, TC 39₅₂₄, TC 40₅₂₅, TC 41₅₂₆, TC 42₅₂₇, TC 43₅₂₈, TC 44₅₂₉, TC 45₅₃₀, TC 46₅₃₁, TC 47₅₃₂, TC 48₅₃₃, TC 49₅₃₄, TC 50₅₃₅, TC 51₅₃₆, TC 52₅₃₇, TC 53₅₃₈, TC 54₅₃₉, TC 55₅₄₀, TC 56₅₄₁, TC 57₅₄₂, TC 58₅₄₃, TC 59₅₄₄, TC 60₅₄₅, TC 61₅₄₆, TC 62₅₄₇, TC 63₅₄₈, TC 64₅₄₉, TC 65₅₅₀, TC 66₅₅₁, TC 67₅₅₂, TC 68₅₅₃, TC 69₅₅₄, TC 70₅₅₅, TC 71₅₅₆, TC 72₅₅₇, TC 73₅₅₈, TC 74₅₅₉, TC 75₅₆₀, TC 76₅₆₁, TC 77₅₆₂, TC 78₅₆₃, TC 79₅₆₄, TC 80₅₆₅, TC 81₅₆₆, TC 82₅₆₇, TC 83₅₆₈, TC 84₅₆₉, TC 85₅₇₀, TC 86₅₇₁, TC 87₅₇₂, TC 88₅₇₃, TC 89₅₇₄, TC 90₅₇₅, TC 91₅₇₆, TC 92₅₇₇, TC 93₅₇₈, TC 94₅₇₉, TC 95₅₈₀, TC 96₅₈₁, TC 97₅₈₂, TC 98₅₈₃, TC 99₅₈₄, TC 100₅₈₅, TC 101₅₈₆, TC 102₅₈₇, TC 103₅₈₈, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndcccaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1992: 145 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 81₁₀₈, HK 108₁₃₅, JP 86₂₂₂, JP 123₂₅₉, KR 123₃₈₃, SC 72₄₅₆, SC 100₄₈₄, TC 75₅₆₀, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3d1a3i1k4t2u1b2x1caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1993: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 84₁₁₁, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3gx2h2l3vx2yx3cxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1994: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 88₁₁₅, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 84₅₆₉, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3kt2h2l3vx2yx3gtaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1995: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 88₁₁₅, HK 108₁₃₅, JP 91₂₂₇, JP 123₂₅₉, KR 109₃₆₉, KR 123₃₈₃, SC 80₄₆₄, SC 100₄₈₄, TC 84₅₆₉, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3kt3n1f4fn3ct3gtaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1996: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 96₁₂₃, HK 108₁₃₅, JP 102₂₃₈, JP 123₂₅₉, KR 113₃₇₃, KR 123₃₈₃, SC 82₄₆₆, SC 100₄₈₄, TC 92₅₇₇, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3sl3yu4jj3er3olaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1997: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 98₁₂₅, HK 108₁₃₅, JP 107₂₄₃, JP 123₂₅₉, KR 114₃₇₄, KR 123₃₈₃, SC 90₄₇₄, SC 100₄₈₄, TC 94₅₇₉, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3uj4dp4ki3mj3qjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1998: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 98₁₂₅, HK 108₁₃₅, JP 111₂₄₇, JP 123₂₅₉, KR 110₃₇₀, KR 123₃₈₃, SC 90₄₇₄, SC 100₄₈₄, TC 94₅₇₉, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3uj4hl4gm3mj3qjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #1999: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 100₁₂₇, HK 108₁₃₅, JP 102₂₃₈, JP 123₂₅₉, KR 114₃₇₄, KR 123₃₈₃, SC 89₄₇₃, SC 100₄₈₄, TC 96₅₈₁, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3wh3yu4ki3lk3shaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2000: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 100₁₂₇, HK 108₁₃₅, JP 105₂₄₁, JP 123₂₅₉, KR 115₃₇₅, KR 123₃₈₃, SC 92₄₇₆, SC 100₄₈₄, TC 96₅₈₁, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3wh4br4lh3oh3shaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2001: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 101₁₂₈, HK 108₁₃₅, JP 110₂₄₆, JP 123₂₅₉, KR 116₃₇₆, KR 123₃₈₃, SC 93₄₇₇, SC 100₄₈₄, TC 97₅₈₂, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3xg4gm4mg3pg3tgaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2002: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 101₁₂₈, HK 108₁₃₅, JP 113₂₄₉, JP 123₂₅₉, KR 115₃₇₅, KR 123₃₈₃, SC 95₄₇₉, SC 100₄₈₄, TC 97₅₈₂, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3xg4jj4lh3re3tgaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2003: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 101₁₂₈, HK 108₁₃₅, JP 113₂₄₉, JP 123₂₅₉, KR 116₃₇₆, KR 123₃₈₃, SC 90₄₇₄, SC 100₄₈₄, TC 97₅₈₂, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3xg4jj4mg3mj3tgaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2004: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 102₁₂₉, HK 108₁₃₅, JP 97₂₃₃, JP 123₂₅₉, KR 116₃₇₆, KR 123₃₈₃, SC 91₄₇₅, SC 100₄₈₄, TC 98₅₈₃, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3yf3tz4mg3ni3ufaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2005: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 102₁₂₉, HK 108₁₃₅, JP 114₂₅₀, JP 123₂₅₉, KR 116₃₇₆, KR 123₃₈₃, SC 95₄₇₉, SC 100₄₈₄, TC 98₅₈₃, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3yf4ki4mg3re3ufaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2006: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 103₁₃₀, HK 108₁₃₅, JP 111₂₄₇, JP 123₂₅₉, KR 116₃₇₆, KR 123₃₈₃, SC 93₄₇₇, SC 100₄₈₄, TC 99₅₈₄, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3ze4hl4mg3pg3veaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2007: 146 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 103₁₃₀, HK 108₁₃₅, JP 115₂₅₁, JP 123₂₅₉, KR 117₃₇₇, KR 123₃₈₃, SC 95₄₇₉, SC 100₄₈₄, TC 99₅₈₄, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc3ze4lh4nf3re3veaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2008: 147 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, Myanmar₆₆₅, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc4ad4oe4pd3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2009: 145 fonts: Symbols 2 1₁₃, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'ndccc4ad4oe4pd3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaabaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2010: 136 fonts: Symbols 2 1₁₃, HK 83₁₁₀, JP 59₁₉₅, SC 74₄₅₈, TC 78₅₆₃, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'n3s3g10c4a1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2011: 132 fonts: Symbols 2 1₁₃, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Math₆₅₅, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'n22eaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2012: 131 fonts: Symbols 2 1₁₃, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'n22eaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2013: 113 fonts: Symbols 2 1₁₃, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Indic Siyaq Numbers₆₃₀, Javanese₆₃₃, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Lao₆₄₁, Lepcha₆₄₂, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Manichaean₆₅₂, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old South Arabian₆₇₉, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'n22eaaaaaaaaaaaaaaaaaaabaaabaaaaabaaaaccbaaaababaabaabbbaaaaababaaaaaaabaabcaaaabbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2014: 3 fonts: Symbols 2 2₁₄, Symbols 2 3₁₅, Mayan Numerals₆₅₆. + 'oa24q,' + // #2015: 145 fonts: Symbols 2 3₁₅, Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 99₁₂₆, HK 108₁₃₅, JP 86₂₂₂, JP 123₂₅₉, KR 111₃₇₁, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 95₅₈₀, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'pbccc3vi3i1k4hl3sd3riaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2016: 6 fonts: Symbols 2 3₁₅, HK 78₁₀₅, JP 56₁₉₂, KR 96₃₅₆, SC 69₄₅₃, TC 71₅₅₆. + 'p3l3i6h3s3y,' + // #2017: 5 fonts: Symbols 2 3₁₅, HK 78₁₀₅, JP 56₁₉₂, SC 69₄₅₃, TC 72₅₅₇. + 'p3l3i10a3z,' + // #2018: 6 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, KR 101₃₆₁, SC 70₄₅₄, TC 73₅₅₈. + 'p3m3h6m3o3z,' + // #2019: 6 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, KR 107₃₆₇, SC 70₄₅₄, TC 73₅₅₈. + 'p3m3h6s3i3z,' + // #2020: 5 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 56₁₉₂, SC 69₄₅₃, TC 72₅₅₇. + 'p3m3h10a3z,' + // #2021: 6 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 57₁₉₃, KR 101₃₆₁, SC 70₄₅₄, TC 73₅₅₈. + 'p3m3i6l3o3z,' + // #2022: 5 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 57₁₉₃, SC 70₄₅₄, TC 73₅₅₈. + 'p3m3i10a3z,' + // #2023: 4 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 57₁₉₃, TC 73₅₅₈. + 'p3m3i14a,' + // #2024: 6 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 77₂₁₃, KR 96₃₅₆, SC 70₄₅₄, TC 73₅₅₈. + 'p3m4c5m3t3z,' + // #2025: 6 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 80₂₁₆, KR 101₃₆₁, SC 70₄₅₄, TC 73₅₅₈. + 'p3m4f5o3o3z,' + // #2026: 6 fonts: Symbols 2 3₁₅, HK 79₁₀₆, JP 85₂₂₁, KR 103₃₆₃, SC 78₄₆₂, TC 73₅₅₈. + 'p3m4k5l3u3r,' + // #2027: 6 fonts: Symbols 2 3₁₅, HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀. + 'p3o3h6h3t3z,' + // #2028: 5 fonts: Symbols 2 3₁₅, HK 81₁₀₈, JP 58₁₉₄, SC 72₄₅₆, TC 75₅₆₀. + 'p3o3h10b3z,' + // #2029: 5 fonts: Symbols 2 3₁₅, HK 85₁₁₂, JP 63₁₉₉, SC 69₄₅₃, TC 81₅₆₆. + 'p3s3i9t4i,' + // #2030: 5 fonts: Symbols 2 3₁₅, HK 87₁₁₄, JP 60₁₉₆, SC 69₄₅₃, TC 83₅₆₈. + 'p3u3d9w4k,' + // #2031: 6 fonts: Symbols 2 3₁₅, HK 88₁₁₅, JP 97₂₃₃, KR 111₃₇₁, SC 69₄₅₃, TC 84₅₆₉. + 'p3v4n5h3d4l,' + // #2032: 6 fonts: Symbols 2 3₁₅, HK 90₁₁₇, JP 105₂₄₁, KR 108₃₆₈, SC 77₄₆₁, TC 86₅₇₁. + 'p3x4t4w3o4f,' + // #2033: 6 fonts: Symbols 2 3₁₅, HK 91₁₁₈, JP 98₂₃₄, KR 105₃₆₅, SC 70₄₅₄, TC 87₅₇₂. + 'p3y4l5a3k4n,' + // #2034: 6 fonts: Symbols 2 3₁₅, HK 92₁₁₉, JP 93₂₂₉, KR 96₃₅₆, SC 78₄₆₂, TC 88₅₇₃. + 'p3z4f4w4b4g,' + // #2035: 6 fonts: Symbols 2 3₁₅, HK 93₁₂₀, JP 97₂₃₃, KR 105₃₆₅, SC 83₄₆₇, TC 89₅₇₄. + 'p4a4i5b3x4c,' + // #2036: 6 fonts: Symbols 2 3₁₅, HK 94₁₂₁, JP 102₂₃₈, KR 106₃₆₆, SC 82₄₆₆, TC 90₅₇₅. + 'p4b4m4x3v4e,' + // #2037: 6 fonts: Symbols 2 3₁₅, HK 94₁₂₁, JP 108₂₄₄, KR 109₃₆₉, SC 80₄₆₄, TC 90₅₇₅. + 'p4b4s4u3q4g,' + // #2038: 6 fonts: Symbols 2 3₁₅, HK 95₁₂₂, JP 101₂₃₇, KR 110₃₇₀, SC 82₄₆₆, TC 91₅₇₆. + 'p4c4k5c3r4f,' + // #2039: 6 fonts: Symbols 2 3₁₅, HK 96₁₂₃, JP 108₂₄₄, KR 111₃₇₁, SC 86₄₇₀, TC 92₅₇₇. + 'p4d4q4w3u4c,' + // #2040: 6 fonts: Symbols 2 3₁₅, HK 97₁₂₄, JP 95₂₃₁, KR 113₃₇₃, SC 79₄₆₃, TC 93₅₇₈. + 'p4e4c5l3l4k,' + // #2041: 2 fonts: Symbols 2 3₁₅, JP 57₁₉₃. + 'p6v,' + // #2042: 2 fonts: Symbols 2 3₁₅, Coptic₆₁₂. + 'p22y,' + // #2043: 157 fonts: Symbols 2 4₁₆, Symbols 2 5₁₇, Cuneiform 1₁₉, Cuneiform 2₂₀, Duployan 1₂₂, Duployan 2₂₃, Egyptian Hieroglyphs 1₂₅, Egyptian Hieroglyphs 2₂₆, HK 83₁₁₀, HK 106₁₃₃, HK 107₁₃₄, HK 108₁₃₅, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, JP 123₂₅₉, KR 121₃₈₁, KR 122₃₈₂, KR 123₃₈₃, SC 74₄₅₈, SC 98₄₈₂, SC 99₄₈₃, SC 100₄₈₄, TC 78₅₆₃, TC 102₅₈₇, TC 103₅₈₈, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qabababa3fwaa2h2jaa4raa2wxaa3axaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2044: 162 fonts: Symbols 2 4₁₆, Symbols 2 5₁₇, Cuneiform 1₁₉, Cuneiform 2₂₀, Duployan 1₂₂, Duployan 2₂₃, Egyptian Hieroglyphs 1₂₅, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 105₁₃₂, HK 106₁₃₃, HK 107₁₃₄, HK 108₁₃₅, JP 118₂₅₄, JP 120₂₅₆, JP 121₂₅₇, JP 122₂₅₈, JP 123₂₅₉, KR 120₃₈₀, KR 121₃₈₁, KR 122₃₈₂, KR 123₃₈₃, SC 96₄₈₀, SC 97₄₈₁, SC 98₄₈₂, SC 99₄₈₃, SC 100₄₈₄, TC 100₅₈₅, TC 101₅₈₆, TC 102₅₈₇, TC 103₅₈₈, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qabababa4aaaaa4obaaa4qaaa3saaaa3waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2045: 157 fonts: Symbols 2 4₁₆, Symbols 2 5₁₇, Cuneiform 1₁₉, Cuneiform 2₂₀, Duployan 1₂₂, Duployan 2₂₃, Egyptian Hieroglyphs 1₂₅, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 106₁₃₃, HK 107₁₃₄, HK 108₁₃₅, JP 118₂₅₄, JP 121₂₅₇, JP 122₂₅₈, JP 123₂₅₉, KR 121₃₈₁, KR 122₃₈₂, KR 123₃₈₃, SC 96₄₈₀, SC 98₄₈₂, SC 99₄₈₃, SC 100₄₈₄, TC 100₅₈₅, TC 102₅₈₇, TC 103₅₈₈, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qabababa4abaa4ocaa4raa3sbaa3wbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2046: 148 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, HK 82₁₀₉, HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 73₄₅₇, SC 98₄₈₂, SC 99₄₈₃, TC 76₅₆₁, TC 102₅₈₇, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc3fxa2i2ja4sa2wya2zzabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2047: 148 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, HK 84₁₁₁, HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 76₄₆₀, SC 98₄₈₂, SC 99₄₈₃, TC 80₅₆₅, TC 102₅₈₇, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc3hva2i2ja4sa2zva3dvabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2048: 143 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, HK 84₁₁₁, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 96₄₈₀, SC 99₄₈₃, TC 80₅₆₅, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc3hw2i2k4t3tc3dwbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2049: 149 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, HK 104₁₃₁, HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 99₃₅₉, KR 121₃₈₁, KR 122₃₈₂, SC 76₄₆₀, SC 98₄₈₂, SC 99₄₈₃, TC 100₅₈₅, TC 102₅₈₇, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc4bba2i2ja3wva2zva3xbabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2050: 148 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, HK 104₁₃₁, HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 76₄₆₀, SC 98₄₈₂, SC 99₄₈₃, TC 100₅₈₅, TC 102₅₈₇, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc4bba2i2ja4sa2zva3xbabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2051: 148 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, HK 104₁₃₁, HK 106₁₃₃, HK 107₁₃₄, JP 118₂₅₄, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 96₄₈₀, SC 98₄₈₂, SC 99₄₈₃, TC 100₅₈₅, TC 102₅₈₇, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc4bba4pca4sa3tba3xbabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2052: 145 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 98₄₈₂, SC 99₄₈₃, TC 102₅₈₇, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc4da2i2ja4sa3va3zabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2053: 141 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, Duployan 1₂₂, Egyptian Hieroglyphs 1₂₅, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 76₄₆₀, SC 99₄₈₃, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qccc4e2i2k4t2zw4abaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2054: 123 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, HK 104₁₃₁, HK 107₁₃₄, JP 118₂₅₄, JP 122₂₅₈, KR 122₃₈₂, SC 96₄₈₀, SC 99₄₈₃, TC 100₅₈₅, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Indic Siyaq Numbers₆₃₀, Javanese₆₃₃, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Lao₆₄₁, Lepcha₆₄₂, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Manichaean₆₅₂, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old South Arabian₆₇₉, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qc4hc4pd4t3tc3xcbaaaaaaaaaaaaaaaaaaabaaabaaaaabaaaaccbaaaababaabaabbbaaaaababaaaaaaabaabcaaaabbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2055: 120 fonts: Symbols 2 4₁₆, Cuneiform 1₁₉, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 99₄₈₃, TC 103₅₈₈, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Indic Siyaq Numbers₆₃₀, Javanese₆₃₃, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Lao₆₄₁, Lepcha₆₄₂, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Manichaean₆₅₂, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old South Arabian₆₇₉, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'qc4k2i2k4t3w4abaaaaaaaaaaaaaaaaaaabaaabaaaaabaaaaccbaaaababaabaabbbaaaaababaaaaaaabaabcaaaabbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2056: 136 fonts: Symbols 2 4₁₆, HK 83₁₁₀, JP 59₁₉₅, KR 99₃₅₉, SC 74₄₅₈, TC 79₅₆₄, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'q3p3g6h3u4bzaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2057: 133 fonts: Symbols 2 4₁₆, HK 83₁₁₀, JP 59₁₉₅, SC 74₄₅₈, TC 78₅₆₃, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'q3p3g10c4a1aaaaaaaaaaaaaaaaaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2058: 131 fonts: Symbols 2 4₁₆, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'q22baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2059: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 84₁₁₁, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3gx2h2l3vx3sd3cxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2060: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 84₁₁₁, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3gx2h2l4pd2yx3cxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2061: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 84₁₁₁, HK 108₁₃₅, JP 61₁₉₇, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3gx2j2j3vx2yx3cxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2062: 143 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 84₁₁₁, HK 108₁₃₅, JP 71₂₀₇, JP 123₂₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3gx2t1z4t2yx3cxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2063: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 85₁₁₂, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 81₅₆₆, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3hw2h2l3vx2yx3dwaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2064: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 86₁₁₃, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 103₃₆₃, KR 123₃₈₃, SC 72₄₅₆, SC 100₄₈₄, TC 82₅₆₇, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3iv2h2l3zt2u1b3evaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2065: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 86₁₁₃, HK 108₁₃₅, JP 97₂₃₃, JP 123₂₅₉, KR 103₃₆₃, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 82₅₆₇, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3iv3tz3zt2yx3evaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2066: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 91₁₁₈, HK 108₁₃₅, JP 89₂₂₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 86₄₇₀, SC 100₄₈₄, TC 87₅₇₂, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3nq3l1h3vx3in3jqaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2067: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 93₁₂₀, HK 108₁₃₅, JP 87₂₂₃, JP 123₂₅₉, KR 107₃₆₇, KR 123₃₈₃, SC 83₄₆₇, SC 100₄₈₄, TC 89₅₇₄, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3po3j1j4dp3fq3loaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2068: 145 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 97₁₂₄, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 94₄₇₈, SC 100₄₈₄, TC 93₅₇₈, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, Myanmar₆₆₅, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3tk4oe4pd3qf3pkaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2069: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 99₁₂₆, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 95₅₈₀, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3vi4oe4pd3sd3riaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2070: 146 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 100₁₂₇, HK 108₁₃₅, JP 113₂₄₉, JP 123₂₅₉, KR 114₃₇₄, KR 123₃₈₃, SC 91₄₇₅, SC 100₄₈₄, TC 96₅₈₁, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, Myanmar₆₆₅, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phags Pa₆₈₇, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc3wh4jj4ki3ni3shaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2071: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad2h2l3vx3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2072: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 119₃₇₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad2h2l4pd2yx3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2073: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad4oe3vx3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2074: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 100₃₆₀, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad4oe3ww3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2075: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 106₃₆₆, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad4oe4cq3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2076: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 107₃₆₇, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad4oe4dp3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2077: 144 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 111₃₇₁, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4ad4oe4hl3sd3wdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2078: 142 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 100₄₈₄, TC 104₅₈₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, Myanmar₆₆₅, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc4e2h2l3vx3w4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2079: 133 fonts: Symbols 2 5₁₇, Cuneiform 2₂₀, Duployan 2₂₃, Egyptian Hieroglyphs 2₂₆, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 'rccc21raaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #2080: 65 fonts: Cuneiform 1₁₉, HK 104₁₃₁, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 96₄₈₀, SC 99₄₈₃, TC 100₅₈₅, TC 103₅₈₈, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Balinese₅₉₇, Bassa Vah₅₉₉, Bengali₆₀₁, Bhaiksuki₆₀₂, Buginese₆₀₄, Buhid₆₀₅, Chakma₆₀₉, Cherokee₆₁₁, Deseret₆₁₄, Gothic₆₂₁, Hatran₆₂₇, Indic Siyaq Numbers₆₃₀, Kayah Li₆₃₆, Linear B₆₄₅, Lisu₆₄₆, Mahajani₆₄₉, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Miao₆₆₀, Mro₆₆₃, Multani₆₆₄, Nabataean₆₆₇, Ogham₆₇₁, Ol Chiki₆₇₂, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old South Arabian₆₇₉, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Runic₆₉₁, Shavian₆₉₄, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Noto Serif Tibetan₇₂₃. + 't4hc2i2k4t3tc3xccaadbbabadbcgfcfiacebaabcacdacaabcaaaabccbaaafaaiaaaaab,' + // #2081: 62 fonts: Cuneiform 1₁₉, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 99₄₈₃, TC 103₅₈₈, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Balinese₅₉₇, Bassa Vah₅₉₉, Bengali₆₀₁, Bhaiksuki₆₀₂, Buginese₆₀₄, Buhid₆₀₅, Chakma₆₀₉, Cherokee₆₁₁, Deseret₆₁₄, Gothic₆₂₁, Hatran₆₂₇, Indic Siyaq Numbers₆₃₀, Kayah Li₆₃₆, Linear B₆₄₅, Lisu₆₄₆, Mahajani₆₄₉, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Miao₆₆₀, Mro₆₆₃, Multani₆₆₄, Nabataean₆₆₇, Ogham₆₇₁, Ol Chiki₆₇₂, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old South Arabian₆₇₉, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Runic₆₉₁, Shavian₆₉₄, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Noto Serif Tibetan₇₂₃. + 't4k2i2k4t3w4acaadbbabadbcgfcfiacebaabcacdacaabcaaaabccbaaafaaiaaaaab,' + // #2082: 91 fonts: Cuneiform 1₁₉, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Bengali₆₀₁, Bhaiksuki₆₀₂, Buginese₆₀₄, Buhid₆₀₅, Carian₆₀₇, Chakma₆₀₉, Cherokee₆₁₁, Cypriot₆₁₃, Deseret₆₁₄, Elbasan₆₁₆, Ethiopic₆₁₈, Gothic₆₂₁, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Indic Siyaq Numbers₆₃₀, Javanese₆₃₃, Kayah Li₆₃₆, Kharoshthi₆₃₇, Lao₆₄₁, Lepcha₆₄₂, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Mahajani₆₄₉, Malayalam₆₅₀, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Miao₆₆₀, Modi₆₆₁, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old South Arabian₆₇₉, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Tamil₇₁₀, Tamil Supplement₇₁₁, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + 't21yaaacaaabababbbbabbccaaacccadabaacadbaabababaaaaaaabaabcaaaabbaaaaaaaaabaaaaaaabadaaaaaaaa,' + // #2083: 69 fonts: Duployan 0₂₁, Noto Sans₅₉₁, Arabic₅₉₄, Avestan₅₉₆, Balinese₅₉₇, Batak₆₀₀, Bengali₆₀₁, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Chakma₆₀₉, Cham₆₁₀, Devanagari₆₁₅, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Meetei Mayek₆₅₈, Modi₆₆₁, Mongolian₆₆₂, Myanmar₆₆₅, NKo₆₆₆, New Tai Lue₆₆₈, Newa₆₆₉, Oriya₆₈₁, Pahawh Hmong₆₈₄, Phags Pa₆₈₇, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Saurashtra₆₉₂, Sharada₆₉₃, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sundanese₇₀₀, Syloti Nagri₇₀₁, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Warang Citi₇₂₀, Noto Serif Tibetan₇₂₃. + 'v21xcbacabaadaegaaaaaaeaaaaaaaaaafaaafcacabalccbababaacabaaaaaaabaaaadc,' + // #2084: 10 fonts: Duployan 1₂₂, Duployan 2₂₃, Noto Sans₅₉₁, Cherokee₆₁₁, Coptic₆₁₂, Lydian₆₄₈, Malayalam₆₅₀, Runic₆₉₁, Syriac₇₀₃, Tifinagh₇₁₅. + 'wa21vta1jb1oll,' + // #2085: 4 fonts: Duployan 2₂₃, Noto Sans₅₉₁, Adlam₅₉₂, Syriac₇₀₃. + 'x21va4g,' + // #2086: 3 fonts: Egyptian Hieroglyphs 0₂₄, Egyptian Hieroglyphs 1₂₅, Egyptian Hieroglyphs 2₂₆. + 'yaa,' + // #2087: 2 fonts: HK 0₂₇, TC 0₄₈₅. + '1b17p,' + // #2088: 2 fonts: HK 1₂₈, TC 0₄₈₅. + '1c17o,' + // #2089: 2 fonts: HK 2₂₉, TC 0₄₈₅. + '1d17n,' + // #2090: 2 fonts: HK 3₃₀, JP 1₁₃₇. + '1e4c,' + // #2091: 2 fonts: HK 6₃₃, TC 0₄₈₅. + '1h17j,' + // #2092: 2 fonts: HK 7₃₄, JP 1₁₃₇. + '1i3y,' + // #2093: 2 fonts: HK 7₃₄, TC 0₄₈₅. + '1i17i,' + // #2094: 2 fonts: HK 8₃₅, JP 1₁₃₇. + '1j3x,' + // #2095: 2 fonts: HK 10₃₇, JP 1₁₃₇. + '1l3v,' + // #2096: 2 fonts: HK 10₃₇, TC 0₄₈₅. + '1l17f,' + // #2097: 5 fonts: HK 11₃₈, JP 2₁₃₈, KR 0₂₆₀, SC 1₃₈₅, TC 1₄₈₆. + '1m3v4r4u3w,' + // #2098: 5 fonts: HK 11₃₈, JP 3₁₃₉, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m3w4q4u3x,' + // #2099: 3 fonts: HK 11₃₈, JP 3₁₃₉, TC 1₄₈₆. + '1m3w13i,' + // #2100: 3 fonts: HK 11₃₈, JP 70₂₀₆, TC 1₄₈₆. + '1m6l10t,' + // #2101: 5 fonts: HK 11₃₈, JP 71₂₀₇, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m6m2a4u3x,' + // #2102: 4 fonts: HK 11₃₈, JP 72₂₀₈, SC 1₃₈₅, TC 1₄₈₆. + '1m6n6u3w,' + // #2103: 4 fonts: HK 11₃₈, JP 72₂₀₈, SC 1₃₈₅, TC 2₄₈₇. + '1m6n6u3x,' + // #2104: 3 fonts: HK 11₃₈, JP 72₂₀₈, TC 1₄₈₆. + '1m6n10r,' + // #2105: 4 fonts: HK 11₃₈, JP 73₂₀₉, SC 1₃₈₅, TC 2₄₈₇. + '1m6o6t3x,' + // #2106: 5 fonts: HK 11₃₈, JP 75₂₁₁, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m6q1w4u3x,' + // #2107: 3 fonts: HK 11₃₈, JP 75₂₁₁, TC 2₄₈₇. + '1m6q10p,' + // #2108: 4 fonts: HK 11₃₈, JP 76₂₁₂, SC 1₃₈₅, TC 2₄₈₇. + '1m6r6q3x,' + // #2109: 3 fonts: HK 11₃₈, JP 76₂₁₂, TC 1₄₈₆. + '1m6r10n,' + // #2110: 3 fonts: HK 11₃₈, JP 76₂₁₂, TC 2₄₈₇. + '1m6r10o,' + // #2111: 5 fonts: HK 11₃₈, JP 77₂₁₃, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m6s1u4u3x,' + // #2112: 4 fonts: HK 11₃₈, JP 77₂₁₃, SC 1₃₈₅, TC 2₄₈₇. + '1m6s6p3x,' + // #2113: 3 fonts: HK 11₃₈, JP 77₂₁₃, TC 1₄₈₆. + '1m6s10m,' + // #2114: 5 fonts: HK 11₃₈, JP 78₂₁₄, KR 0₂₆₀, SC 1₃₈₅, TC 1₄₈₆. + '1m6t1t4u3w,' + // #2115: 4 fonts: HK 11₃₈, JP 79₂₁₅, SC 1₃₈₅, TC 1₄₈₆. + '1m6u6n3w,' + // #2116: 3 fonts: HK 11₃₈, JP 80₂₁₆, TC 1₄₈₆. + '1m6v10j,' + // #2117: 5 fonts: HK 11₃₈, JP 81₂₁₇, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m6w1q4u3x,' + // #2118: 4 fonts: HK 11₃₈, JP 81₂₁₇, SC 1₃₈₅, TC 1₄₈₆. + '1m6w6l3w,' + // #2119: 4 fonts: HK 11₃₈, JP 82₂₁₈, SC 1₃₈₅, TC 1₄₈₆. + '1m6x6k3w,' + // #2120: 6 fonts: HK 11₃₈, JP 83₂₁₉, SC 1₃₈₅, TC 2₄₈₇, New Tai Lue₆₆₈, Yi₇₂₁. + '1m6y6j3x6y2a,' + // #2121: 4 fonts: HK 11₃₈, JP 84₂₂₀, SC 1₃₈₅, TC 1₄₈₆. + '1m6z6i3w,' + // #2122: 4 fonts: HK 11₃₈, JP 84₂₂₀, SC 1₃₈₅, TC 2₄₈₇. + '1m6z6i3x,' + // #2123: 5 fonts: HK 11₃₈, JP 85₂₂₁, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m7a1m4u3x,' + // #2124: 4 fonts: HK 11₃₈, JP 85₂₂₁, SC 1₃₈₅, TC 2₄₈₇. + '1m7a6h3x,' + // #2125: 5 fonts: HK 11₃₈, JP 86₂₂₂, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m7b1l4u3x,' + // #2126: 4 fonts: HK 11₃₈, JP 86₂₂₂, SC 1₃₈₅, TC 1₄₈₆. + '1m7b6g3w,' + // #2127: 4 fonts: HK 11₃₈, JP 86₂₂₂, SC 1₃₈₅, TC 2₄₈₇. + '1m7b6g3x,' + // #2128: 6 fonts: HK 11₃₈, JP 86₂₂₂, SC 1₃₈₅, TC 2₄₈₇, New Tai Lue₆₆₈, Yi₇₂₁. + '1m7b6g3x6y2a,' + // #2129: 5 fonts: HK 11₃₈, JP 87₂₂₃, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m7c1k4u3x,' + // #2130: 5 fonts: HK 11₃₈, JP 88₂₂₄, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m7d1j4u3x,' + // #2131: 4 fonts: HK 11₃₈, JP 88₂₂₄, SC 1₃₈₅, TC 1₄₈₆. + '1m7d6e3w,' + // #2132: 5 fonts: HK 11₃₈, JP 89₂₂₅, SC 1₃₈₅, TC 2₄₈₇, Yi₇₂₁. + '1m7e6d3x8z,' + // #2133: 5 fonts: HK 11₃₈, JP 90₂₂₆, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m7f1h4u3x,' + // #2134: 4 fonts: HK 11₃₈, JP 92₂₂₈, SC 1₃₈₅, TC 1₄₈₆. + '1m7h6a3w,' + // #2135: 5 fonts: HK 11₃₈, JP 93₂₂₉, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m7i1e4u3x,' + // #2136: 4 fonts: HK 11₃₈, JP 94₂₃₀, SC 1₃₈₅, TC 2₄₈₇. + '1m7j5y3x,' + // #2137: 4 fonts: HK 11₃₈, JP 96₂₃₂, SC 1₃₈₅, TC 1₄₈₆. + '1m7l5w3w,' + // #2138: 5 fonts: HK 11₃₈, JP 98₂₃₄, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1m7nz4u3x,' + // #2139: 5 fonts: HK 11₃₈, JP 103₂₃₉, SC 1₃₈₅, TC 2₄₈₇, Yi₇₂₁. + '1m7s5p3x8z,' + // #2140: 5 fonts: HK 12₃₉, JP 3₁₃₉, SC 2₃₈₆, TC 2₄₈₇, Noto Sans₅₉₁. + '1n3v9m3w3z,' + // #2141: 4 fonts: HK 12₃₉, JP 4₁₄₀, KR 0₂₆₀, SC 2₃₈₆. + '1n3w4p4v,' + // #2142: 5 fonts: HK 12₃₉, JP 4₁₄₀, KR 0₂₆₀, SC 2₃₈₆, TC 2₄₈₇. + '1n3w4p4v3w,' + // #2143: 3 fonts: HK 12₃₉, JP 4₁₄₀, KR 1₂₆₁. + '1n3w4q,' + // #2144: 4 fonts: HK 12₃₉, JP 4₁₄₀, KR 1₂₆₁, SC 2₃₈₆. + '1n3w4q4u,' + // #2145: 4 fonts: HK 12₃₉, JP 70₂₀₆, SC 2₃₈₆, TC 2₄₈₇. + '1n6k6x3w,' + // #2146: 5 fonts: HK 12₃₉, JP 88₂₂₄, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1n7c1j4u3x,' + // #2147: 5 fonts: HK 12₃₉, JP 92₂₂₈, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1n7g1f4u3x,' + // #2148: 5 fonts: HK 12₃₉, JP 101₂₃₇, KR 0₂₆₀, SC 1₃₈₅, TC 2₄₈₇. + '1n7pw4u3x,' + // #2149: 2 fonts: HK 12₃₉, TC 2₄₈₇. + '1n17f,' + // #2150: 4 fonts: HK 13₄₀, JP 4₁₄₀, KR 1₂₆₁, SC 2₃₈₆. + '1o3v4q4u,' + // #2151: 5 fonts: HK 13₄₀, JP 4₁₄₀, KR 1₂₆₁, SC 2₃₈₆, TC 3₄₈₈. + '1o3v4q4u3x,' + // #2152: 5 fonts: HK 13₄₀, JP 4₁₄₀, KR 2₂₆₂, SC 2₃₈₆, TC 3₄₈₈. + '1o3v4r4t3x,' + // #2153: 5 fonts: HK 13₄₀, JP 5₁₄₁, KR 2₂₆₂, SC 2₃₈₆, TC 3₄₈₈. + '1o3w4q4t3x,' + // #2154: 5 fonts: HK 14₄₁, JP 5₁₄₁, KR 65₃₂₅, SC 3₃₈₇, TC 4₄₈₉. + '1p3v7b2j3x,' + // #2155: 5 fonts: HK 14₄₁, JP 5₁₄₁, KR 65₃₂₅, SC 4₃₈₈, TC 4₄₈₉. + '1p3v7b2k3w,' + // #2156: 4 fonts: HK 14₄₁, JP 5₁₄₁, SC 83₄₆₇, TC 4₄₈₉. + '1p3v12nv,' + // #2157: 4 fonts: HK 14₄₁, JP 5₁₄₁, SC 85₄₆₉, TC 4₄₈₉. + '1p3v12pt,' + // #2158: 4 fonts: HK 14₄₁, JP 5₁₄₁, SC 90₄₇₄, TC 4₄₈₉. + '1p3v12uo,' + // #2159: 4 fonts: HK 14₄₁, JP 99₂₃₅, SC 4₃₈₈, TC 4₄₈₉. + '1p7l5w3w,' + // #2160: 3 fonts: HK 14₄₁, SC 3₃₈₇, TC 4₄₈₉. + '1p13h3x,' + // #2161: 5 fonts: HK 15₄₂, JP 5₁₄₁, KR 65₃₂₅, SC 4₃₈₈, TC 4₄₈₉. + '1q3u7b2k3w,' + // #2162: 5 fonts: HK 15₄₂, JP 5₁₄₁, KR 65₃₂₅, SC 80₄₆₄, TC 5₄₉₀. + '1q3u7b5iz,' + // #2163: 5 fonts: HK 15₄₂, JP 6₁₄₂, KR 65₃₂₅, SC 4₃₈₈, TC 5₄₉₀. + '1q3v7a2k3x,' + // #2164: 4 fonts: HK 15₄₂, JP 6₁₄₂, KR 65₃₂₅, TC 5₄₉₀. + '1q3v7a6i,' + // #2165: 5 fonts: HK 15₄₂, JP 65₂₀₁, KR 65₃₂₅, SC 4₃₈₈, TC 5₄₉₀. + '1q6c4t2k3x,' + // #2166: 5 fonts: HK 15₄₂, JP 65₂₀₁, KR 65₃₂₅, SC 5₃₈₉, TC 5₄₉₀. + '1q6c4t2l3w,' + // #2167: 5 fonts: HK 15₄₂, JP 73₂₀₉, KR 65₃₂₅, SC 5₃₈₉, TC 5₄₉₀. + '1q6k4l2l3w,' + // #2168: 4 fonts: HK 15₄₂, JP 88₂₂₄, SC 88₄₇₂, TC 5₄₉₀. + '1q6z9nr,' + // #2169: 2 fonts: HK 15₄₂, TC 4₄₈₉. + '1q17e,' + // #2170: 5 fonts: HK 16₄₃, JP 6₁₄₂, KR 65₃₂₅, SC 5₃₈₉, TC 6₄₉₁. + '1r3u7a2l3x,' + // #2171: 4 fonts: HK 16₄₃, JP 6₁₄₂, KR 65₃₂₅, TC 5₄₉₀. + '1r3u7a6i,' + // #2172: 4 fonts: HK 16₄₃, JP 6₁₄₂, KR 65₃₂₅, TC 6₄₉₁. + '1r3u7a6j,' + // #2173: 3 fonts: HK 16₄₃, JP 6₁₄₂, SC 5₃₈₉. + '1r3u9m,' + // #2174: 3 fonts: HK 16₄₃, JP 6₁₄₂, SC 6₃₉₀. + '1r3u9n,' + // #2175: 4 fonts: HK 16₄₃, JP 6₁₄₂, SC 91₄₇₅, TC 6₄₉₁. + '1r3u12up,' + // #2176: 5 fonts: HK 16₄₃, JP 60₁₉₆, KR 65₃₂₅, SC 6₃₉₀, TC 6₄₉₁. + '1r5w4y2m3w,' + // #2177: 4 fonts: HK 16₄₃, JP 65₂₀₁, SC 5₃₈₉, TC 6₄₉₁. + '1r6b7f3x,' + // #2178: 5 fonts: HK 16₄₃, JP 72₂₀₈, KR 65₃₂₅, SC 5₃₈₉, TC 6₄₉₁. + '1r6i4m2l3x,' + // #2179: 3 fonts: HK 16₄₃, SC 6₃₉₀, TC 6₄₉₁. + '1r13i3w,' + // #2180: 4 fonts: HK 17₄₄, JP 7₁₄₃, KR 65₃₂₅, TC 7₄₉₂. + '1s3u6z6k,' + // #2181: 4 fonts: HK 17₄₄, JP 8₁₄₄, SC 77₄₆₁, TC 7₄₉₂. + '1s3v12e1e,' + // #2182: 5 fonts: HK 17₄₄, JP 60₁₉₆, KR 65₃₂₅, SC 6₃₉₀, TC 7₄₉₂. + '1s5v4y2m3x,' + // #2183: 4 fonts: HK 17₄₄, JP 64₂₀₀, SC 6₃₉₀, TC 7₄₉₂. + '1s5z7h3x,' + // #2184: 5 fonts: HK 17₄₄, JP 67₂₀₃, KR 65₃₂₅, SC 6₃₉₀, TC 7₄₉₂. + '1s6c4r2m3x,' + // #2185: 4 fonts: HK 17₄₄, JP 67₂₀₃, SC 6₃₉₀, TC 7₄₉₂. + '1s6c7e3x,' + // #2186: 4 fonts: HK 17₄₄, JP 69₂₀₅, SC 6₃₉₀, TC 7₄₉₂. + '1s6e7c3x,' + // #2187: 5 fonts: HK 17₄₄, JP 71₂₀₇, KR 65₃₂₅, SC 6₃₉₀, TC 7₄₉₂. + '1s6g4n2m3x,' + // #2188: 2 fonts: HK 17₄₄, SC 6₃₉₀. + '1s13h,' + // #2189: 3 fonts: HK 17₄₄, SC 81₄₆₅, TC 7₄₉₂. + '1s16e1a,' + // #2190: 5 fonts: HK 18₄₅, JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 9₄₉₄. + '1t3u6z2m3y,' + // #2191: 5 fonts: HK 18₄₅, JP 83₂₁₉, KR 66₃₂₆, SC 7₃₉₁, TC 8₄₉₃. + '1t6r4c2m3x,' + // #2192: 5 fonts: HK 18₄₅, JP 85₂₂₁, KR 66₃₂₆, SC 7₃₉₁, TC 8₄₉₃. + '1t6t4a2m3x,' + // #2193: 4 fonts: HK 18₄₅, JP 91₂₂₇, SC 7₃₉₁, TC 8₄₉₃. + '1t6z6h3x,' + // #2194: 4 fonts: HK 18₄₅, JP 108₂₄₄, SC 7₃₉₁, TC 8₄₉₃. + '1t7q5q3x,' + // #2195: 3 fonts: HK 18₄₅, SC 7₃₉₁, TC 9₄₉₄. + '1t13h3y,' + // #2196: 5 fonts: HK 19₄₆, JP 9₁₄₅, KR 66₃₂₆, SC 7₃₉₁, TC 9₄₉₄. + '1u3u6y2m3y,' + // #2197: 3 fonts: HK 19₄₆, JP 9₁₄₅, SC 8₃₉₂. + '1u3u9m,' + // #2198: 4 fonts: HK 19₄₆, JP 9₁₄₅, SC 77₄₆₁, TC 10₄₉₅. + '1u3u12d1h,' + // #2199: 4 fonts: HK 19₄₆, JP 9₁₄₅, SC 84₄₆₈, TC 10₄₉₅. + '1u3u12k1a,' + // #2200: 4 fonts: HK 19₄₆, JP 9₁₄₅, SC 85₄₆₉, TC 9₄₉₄. + '1u3u12ly,' + // #2201: 4 fonts: HK 19₄₆, JP 9₁₄₅, SC 92₄₇₆, TC 9₄₉₄. + '1u3u12sr,' + // #2202: 5 fonts: HK 19₄₆, JP 62₁₉₈, KR 66₃₂₆, SC 8₃₉₂, TC 9₄₉₄. + '1u5v4x2n3x,' + // #2203: 5 fonts: HK 19₄₆, JP 68₂₀₄, KR 66₃₂₆, SC 7₃₉₁, TC 9₄₉₄. + '1u6b4r2m3y,' + // #2204: 5 fonts: HK 19₄₆, JP 69₂₀₅, KR 66₃₂₆, SC 8₃₉₂, TC 9₄₉₄. + '1u6c4q2n3x,' + // #2205: 5 fonts: HK 19₄₆, JP 75₂₁₁, KR 66₃₂₆, SC 7₃₉₁, TC 9₄₉₄. + '1u6i4k2m3y,' + // #2206: 3 fonts: HK 19₄₆, SC 7₃₉₁, TC 9₄₉₄. + '1u13g3y,' + // #2207: 5 fonts: HK 20₄₇, JP 9₁₄₅, KR 106₃₆₆, SC 9₃₉₃, TC 10₄₉₅. + '1v3t8m1a3x,' + // #2208: 5 fonts: HK 20₄₇, JP 10₁₄₆, KR 67₃₂₇, SC 78₄₆₂, TC 10₄₉₅. + '1v3u6y5e1g,' + // #2209: 4 fonts: HK 20₄₇, JP 10₁₄₆, SC 82₄₆₆, TC 10₄₉₅. + '1v3u12h1c,' + // #2210: 4 fonts: HK 20₄₇, JP 10₁₄₆, SC 92₄₇₆, TC 11₄₉₆. + '1v3u12rt,' + // #2211: 4 fonts: HK 20₄₇, JP 60₁₉₆, SC 8₃₉₂, TC 10₄₉₅. + '1v5s7n3y,' + // #2212: 5 fonts: HK 20₄₇, JP 61₁₉₇, KR 66₃₂₆, SC 8₃₉₂, TC 10₄₉₅. + '1v5t4y2n3y,' + // #2213: 5 fonts: HK 20₄₇, JP 65₂₀₁, KR 67₃₂₇, SC 9₃₉₃, TC 11₄₉₆. + '1v5x4v2n3y,' + // #2214: 4 fonts: HK 20₄₇, JP 68₂₀₄, SC 9₃₉₃, TC 10₄₉₅. + '1v6a7g3x,' + // #2215: 4 fonts: HK 20₄₇, JP 69₂₀₅, SC 8₃₉₂, TC 10₄₉₅. + '1v6b7e3y,' + // #2216: 4 fonts: HK 20₄₇, JP 73₂₀₉, SC 9₃₉₃, TC 10₄₉₅. + '1v6f7b3x,' + // #2217: 5 fonts: HK 20₄₇, JP 77₂₁₃, KR 67₃₂₇, SC 9₃₉₃, TC 10₄₉₅. + '1v6j4j2n3x,' + // #2218: 5 fonts: HK 20₄₇, JP 96₂₃₂, KR 66₃₂₆, SC 9₃₉₃, TC 10₄₉₅. + '1v7c3p2o3x,' + // #2219: 5 fonts: HK 20₄₇, JP 97₂₃₃, KR 67₃₂₇, SC 9₃₉₃, TC 10₄₉₅. + '1v7d3p2n3x,' + // #2220: 3 fonts: HK 20₄₇, SC 91₄₇₅, TC 11₄₉₆. + '1v16lu,' + // #2221: 3 fonts: HK 20₄₇, SC 93₄₇₇, TC 11₄₉₆. + '1v16ns,' + // #2222: 2 fonts: HK 21₄₈, JP 10₁₄₆. + '1w3t,' + // #2223: 3 fonts: HK 21₄₈, JP 10₁₄₆, SC 10₃₉₄. + '1w3t9n,' + // #2224: 5 fonts: HK 21₄₈, JP 86₂₂₂, KR 67₃₂₇, SC 9₃₉₃, TC 11₄₉₆. + '1w6r4a2n3y,' + // #2225: 5 fonts: HK 21₄₈, JP 97₂₃₃, KR 67₃₂₇, SC 80₄₆₄, TC 11₄₉₆. + '1w7c3p5g1f,' + // #2226: 4 fonts: HK 21₄₈, JP 105₂₄₁, SC 10₃₉₄, TC 11₄₉₆. + '1w7k5w3x,' + // #2227: 4 fonts: HK 21₄₈, JP 117₂₅₃, SC 10₃₉₄, TC 11₄₉₆. + '1w7w5k3x,' + // #2228: 3 fonts: HK 21₄₈, SC 10₃₉₄, TC 12₄₉₇. + '1w13h3y,' + // #2229: 3 fonts: HK 21₄₈, SC 85₄₆₉, TC 12₄₉₇. + '1w16e1b,' + // #2230: 3 fonts: HK 21₄₈, SC 86₄₇₀, TC 12₄₉₇. + '1w16f1a,' + // #2231: 3 fonts: HK 21₄₈, SC 90₄₇₄, TC 11₄₉₆. + '1w16jv,' + // #2232: 2 fonts: HK 21₄₈, TC 11₄₉₆. + '1w17f,' + // #2233: 5 fonts: HK 22₄₉, JP 10₁₄₆, KR 67₃₂₇, SC 11₃₉₅, TC 12₄₉₇. + '1x3s6y2p3x,' + // #2234: 4 fonts: HK 22₄₉, JP 10₁₄₆, KR 67₃₂₇, TC 12₄₉₇. + '1x3s6y6n,' + // #2235: 3 fonts: HK 22₄₉, JP 10₁₄₆, SC 11₃₉₅. + '1x3s9o,' + // #2236: 3 fonts: HK 22₄₉, JP 11₁₄₇, KR 67₃₂₇. + '1x3t6x,' + // #2237: 4 fonts: HK 22₄₉, JP 11₁₄₇, KR 67₃₂₇, TC 13₄₉₈. + '1x3t6x6o,' + // #2238: 3 fonts: HK 22₄₉, JP 11₁₄₇, SC 12₃₉₆. + '1x3t9o,' + // #2239: 5 fonts: HK 22₄₉, JP 67₂₀₃, KR 67₃₂₇, SC 12₃₉₆, TC 13₄₉₈. + '1x5x4t2q3x,' + // #2240: 5 fonts: HK 22₄₉, JP 82₂₁₈, KR 67₃₂₇, SC 12₃₉₆, TC 13₄₉₈. + '1x6m4e2q3x,' + // #2241: 4 fonts: HK 22₄₉, KR 67₃₂₇, SC 11₃₉₅, TC 12₄₉₇. + '1x10r2p3x,' + // #2242: 2 fonts: HK 22₄₉, SC 12₃₉₆. + '1x13i,' + // #2243: 3 fonts: HK 22₄₉, SC 90₄₇₄, TC 12₄₉₇. + '1x16iw,' + // #2244: 3 fonts: HK 22₄₉, SC 91₄₇₅, TC 12₄₉₇. + '1x16jv,' + // #2245: 4 fonts: HK 23₅₀, JP 11₁₄₇, KR 67₃₂₇, TC 14₄₉₉. + '1y3s6x6p,' + // #2246: 2 fonts: HK 23₅₀, JP 12₁₄₈. + '1y3t,' + // #2247: 3 fonts: HK 23₅₀, JP 12₁₄₈, SC 13₃₉₇. + '1y3t9o,' + // #2248: 3 fonts: HK 23₅₀, JP 62₁₉₈, TC 14₄₉₉. + '1y5r11o,' + // #2249: 5 fonts: HK 23₅₀, JP 64₂₀₀, KR 67₃₂₇, SC 12₃₉₆, TC 13₄₉₈. + '1y5t4w2q3x,' + // #2250: 4 fonts: HK 23₅₀, JP 64₂₀₀, SC 12₃₉₆, TC 14₄₉₉. + '1y5t7n3y,' + // #2251: 4 fonts: HK 23₅₀, JP 68₂₀₄, SC 12₃₉₆, TC 14₄₉₉. + '1y5x7j3y,' + // #2252: 4 fonts: HK 23₅₀, JP 72₂₀₈, KR 67₃₂₇, TC 13₄₉₈. + '1y6b4o6o,' + // #2253: 4 fonts: HK 23₅₀, JP 73₂₀₉, SC 12₃₉₆, TC 13₄₉₈. + '1y6c7e3x,' + // #2254: 4 fonts: HK 23₅₀, JP 76₂₁₂, SC 12₃₉₆, TC 14₄₉₉. + '1y6f7b3y,' + // #2255: 5 fonts: HK 23₅₀, JP 80₂₁₆, KR 67₃₂₇, SC 13₃₉₇, TC 14₄₉₉. + '1y6j4g2r3x,' + // #2256: 5 fonts: HK 24₅₁, JP 12₁₄₈, KR 68₃₂₈, SC 77₄₆₁, TC 15₅₀₀. + '1z3s6x5c1m,' + // #2257: 4 fonts: HK 24₅₁, JP 12₁₄₈, KR 68₃₂₈, TC 15₅₀₀. + '1z3s6x6p,' + // #2258: 3 fonts: HK 24₅₁, JP 12₁₄₈, SC 13₃₉₇. + '1z3s9o,' + // #2259: 4 fonts: HK 24₅₁, JP 12₁₄₈, SC 14₃₉₈, TC 15₅₀₀. + '1z3s9p3x,' + // #2260: 5 fonts: HK 24₅₁, JP 65₂₀₁, KR 68₃₂₈, SC 13₃₉₇, TC 15₅₀₀. + '1z5t4w2q3y,' + // #2261: 4 fonts: HK 24₅₁, JP 65₂₀₁, SC 13₃₉₇, TC 15₅₀₀. + '1z5t7n3y,' + // #2262: 5 fonts: HK 24₅₁, JP 68₂₀₄, KR 68₃₂₈, SC 13₃₉₇, TC 15₅₀₀. + '1z5w4t2q3y,' + // #2263: 5 fonts: HK 24₅₁, JP 70₂₀₆, KR 67₃₂₇, SC 13₃₉₇, TC 14₄₉₉. + '1z5y4q2r3x,' + // #2264: 5 fonts: HK 24₅₁, JP 75₂₁₁, KR 68₃₂₈, SC 14₃₉₈, TC 15₅₀₀. + '1z6d4m2r3x,' + // #2265: 5 fonts: HK 24₅₁, JP 76₂₁₂, KR 67₃₂₇, SC 13₃₉₇, TC 14₄₉₉. + '1z6e4k2r3x,' + // #2266: 4 fonts: HK 24₅₁, JP 106₂₄₂, SC 13₃₉₇, TC 14₄₉₉. + '1z7i5y3x,' + // #2267: 3 fonts: HK 24₅₁, SC 14₃₉₈, TC 15₅₀₀. + '1z13i3x,' + // #2268: 3 fonts: HK 24₅₁, SC 79₄₆₃, TC 15₅₀₀. + '1z15v1k,' + // #2269: 5 fonts: HK 25₅₂, JP 12₁₄₈, KR 68₃₂₈, SC 14₃₉₈, TC 15₅₀₀. + '2a3r6x2r3x,' + // #2270: 5 fonts: HK 25₅₂, JP 13₁₄₉, KR 68₃₂₈, SC 80₄₆₄, TC 15₅₀₀. + '2a3s6w5f1j,' + // #2271: 5 fonts: HK 25₅₂, JP 13₁₄₉, KR 68₃₂₈, SC 80₄₆₄, TC 16₅₀₁. + '2a3s6w5f1k,' + // #2272: 5 fonts: HK 25₅₂, JP 13₁₄₉, KR 68₃₂₈, SC 83₄₆₇, TC 15₅₀₀. + '2a3s6w5i1g,' + // #2273: 5 fonts: HK 25₅₂, JP 13₁₄₉, KR 68₃₂₈, SC 92₄₇₆, TC 16₅₀₁. + '2a3s6w5ry,' + // #2274: 4 fonts: HK 25₅₂, JP 13₁₄₉, KR 68₃₂₈, TC 16₅₀₁. + '2a3s6w6q,' + // #2275: 3 fonts: HK 25₅₂, JP 13₁₄₉, SC 15₃₉₉. + '2a3s9p,' + // #2276: 4 fonts: HK 25₅₂, JP 13₁₄₉, SC 79₄₆₃, TC 16₅₀₁. + '2a3s12b1l,' + // #2277: 5 fonts: HK 25₅₂, JP 61₁₉₇, KR 68₃₂₈, SC 14₃₉₈, TC 16₅₀₁. + '2a5o5a2r3y,' + // #2278: 5 fonts: HK 25₅₂, JP 68₂₀₄, KR 68₃₂₈, SC 14₃₉₈, TC 16₅₀₁. + '2a5v4t2r3y,' + // #2279: 5 fonts: HK 25₅₂, JP 91₂₂₇, KR 68₃₂₈, SC 14₃₉₈, TC 15₅₀₀. + '2a6s3w2r3x,' + // #2280: 2 fonts: HK 25₅₂, SC 14₃₉₈. + '2a13h,' + // #2281: 3 fonts: HK 25₅₂, SC 86₄₇₀, TC 16₅₀₁. + '2a16b1e,' + // #2282: 3 fonts: HK 25₅₂, SC 89₄₇₃, TC 16₅₀₁. + '2a16e1b,' + // #2283: 5 fonts: HK 26₅₃, JP 13₁₄₉, KR 68₃₂₈, SC 15₃₉₉, TC 16₅₀₁. + '2b3r6w2s3x,' + // #2284: 3 fonts: HK 26₅₃, JP 13₁₄₉, SC 15₃₉₉. + '2b3r9p,' + // #2285: 4 fonts: HK 26₅₃, JP 13₁₄₉, SC 83₄₆₇, TC 16₅₀₁. + '2b3r12f1h,' + // #2286: 4 fonts: HK 26₅₃, JP 13₁₄₉, SC 86₄₇₀, TC 16₅₀₁. + '2b3r12i1e,' + // #2287: 3 fonts: HK 26₅₃, JP 13₁₄₉, TC 16₅₀₁. + '2b3r13n,' + // #2288: 3 fonts: HK 26₅₃, JP 14₁₅₀, SC 15₃₉₉. + '2b3s9o,' + // #2289: 4 fonts: HK 26₅₃, JP 14₁₅₀, SC 78₄₆₂, TC 17₅₀₂. + '2b3s11z1n,' + // #2290: 5 fonts: HK 26₅₃, JP 65₂₀₁, KR 69₃₂₉, SC 15₃₉₉, TC 17₅₀₂. + '2b5r4x2r3y,' + // #2291: 4 fonts: HK 26₅₃, JP 65₂₀₁, KR 69₃₂₉, TC 17₅₀₂. + '2b5r4x6q,' + // #2292: 4 fonts: HK 26₅₃, JP 71₂₀₇, SC 15₃₉₉, TC 16₅₀₁. + '2b5x7j3x,' + // #2293: 5 fonts: HK 26₅₃, JP 75₂₁₁, KR 69₃₂₉, SC 15₃₉₉, TC 17₅₀₂. + '2b6b4n2r3y,' + // #2294: 4 fonts: HK 26₅₃, JP 80₂₁₆, SC 15₃₉₉, TC 16₅₀₁. + '2b6g7a3x,' + // #2295: 4 fonts: HK 26₅₃, JP 109₂₄₅, SC 15₃₉₉, TC 16₅₀₁. + '2b7j5x3x,' + // #2296: 4 fonts: HK 26₅₃, JP 112₂₄₈, SC 15₃₉₉, TC 17₅₀₂. + '2b7m5u3y,' + // #2297: 4 fonts: HK 26₅₃, JP 114₂₅₀, SC 15₃₉₉, TC 16₅₀₁. + '2b7o5s3x,' + // #2298: 2 fonts: HK 26₅₃, SC 15₃₉₉. + '2b13h,' + // #2299: 3 fonts: HK 26₅₃, SC 77₄₆₁, TC 17₅₀₂. + '2b15r1o,' + // #2300: 3 fonts: HK 26₅₃, SC 90₄₇₄, TC 17₅₀₂. + '2b16e1b,' + // #2301: 3 fonts: HK 26₅₃, SC 91₄₇₅, TC 16₅₀₁. + '2b16fz,' + // #2302: 2 fonts: HK 26₅₃, TC 16₅₀₁. + '2b17f,' + // #2303: 3 fonts: HK 27₅₄, JP 14₁₅₀, TC 17₅₀₂. + '2c3r13n,' + // #2304: 4 fonts: HK 27₅₄, JP 15₁₅₁, KR 69₃₂₉, TC 18₅₀₃. + '2c3s6v6r,' + // #2305: 4 fonts: HK 27₅₄, JP 15₁₅₁, SC 88₄₇₂, TC 18₅₀₃. + '2c3s12i1e,' + // #2306: 4 fonts: HK 27₅₄, JP 15₁₅₁, SC 90₄₇₄, TC 18₅₀₃. + '2c3s12k1c,' + // #2307: 5 fonts: HK 27₅₄, JP 60₁₉₆, KR 69₃₂₉, SC 87₄₇₁, TC 18₅₀₃. + '2c5l5c5l1f,' + // #2308: 5 fonts: HK 27₅₄, JP 74₂₁₀, KR 69₃₂₉, SC 16₄₀₀, TC 18₅₀₃. + '2c5z4o2s3y,' + // #2309: 5 fonts: HK 27₅₄, JP 86₂₂₂, KR 69₃₂₉, SC 16₄₀₀, TC 18₅₀₃. + '2c6l4c2s3y,' + // #2310: 4 fonts: HK 27₅₄, JP 92₂₂₈, SC 16₄₀₀, TC 19₅₀₄. + '2c6r6p3z,' + // #2311: 2 fonts: HK 27₅₄, SC 16₄₀₀. + '2c13h,' + // #2312: 3 fonts: HK 27₅₄, SC 86₄₇₀, TC 18₅₀₃. + '2c15z1g,' + // #2313: 2 fonts: HK 27₅₄, TC 17₅₀₂. + '2c17f,' + // #2314: 4 fonts: HK 28₅₅, JP 15₁₅₁, KR 69₃₂₉, TC 19₅₀₄. + '2d3r6v6s,' + // #2315: 3 fonts: HK 28₅₅, JP 15₁₅₁, SC 16₄₀₀. + '2d3r9o,' + // #2316: 3 fonts: HK 28₅₅, JP 15₁₅₁, SC 17₄₀₁. + '2d3r9p,' + // #2317: 4 fonts: HK 28₅₅, JP 15₁₅₁, SC 18₄₀₂, TC 20₅₀₅. + '2d3r9q3y,' + // #2318: 5 fonts: HK 28₅₅, JP 71₂₀₇, KR 70₃₃₀, SC 17₄₀₁, TC 20₅₀₅. + '2d5v4s2s3z,' + // #2319: 4 fonts: HK 28₅₅, JP 72₂₀₈, SC 17₄₀₁, TC 20₅₀₅. + '2d5w7k3z,' + // #2320: 5 fonts: HK 28₅₅, JP 74₂₁₀, KR 69₃₂₉, SC 16₄₀₀, TC 19₅₀₄. + '2d5y4o2s3z,' + // #2321: 5 fonts: HK 28₅₅, JP 88₂₂₄, KR 69₃₂₉, SC 17₄₀₁, TC 19₅₀₄. + '2d6m4a2t3y,' + // #2322: 5 fonts: HK 28₅₅, JP 95₂₃₁, KR 69₃₂₉, SC 17₄₀₁, TC 19₅₀₄. + '2d6t3t2t3y,' + // #2323: 5 fonts: HK 28₅₅, JP 104₂₄₀, KR 70₃₃₀, SC 17₄₀₁, TC 19₅₀₄. + '2d7c3l2s3y,' + // #2324: 3 fonts: HK 28₅₅, SC 18₄₀₂, TC 20₅₀₅. + '2d13i3y,' + // #2325: 2 fonts: HK 28₅₅, TC 20₅₀₅. + '2d17h,' + // #2326: 5 fonts: HK 29₅₆, JP 15₁₅₁, KR 70₃₃₀, SC 18₄₀₂, TC 20₅₀₅. + '2e3q6w2t3y,' + // #2327: 4 fonts: HK 29₅₆, JP 15₁₅₁, KR 70₃₃₀, TC 20₅₀₅. + '2e3q6w6s,' + // #2328: 4 fonts: HK 29₅₆, JP 15₁₅₁, SC 18₄₀₂, TC 20₅₀₅. + '2e3q9q3y,' + // #2329: 5 fonts: HK 29₅₆, JP 16₁₅₂, KR 70₃₃₀, SC 18₄₀₂, TC 21₅₀₆. + '2e3r6v2t3z,' + // #2330: 4 fonts: HK 29₅₆, JP 16₁₅₂, KR 70₃₃₀, TC 21₅₀₆. + '2e3r6v6t,' + // #2331: 3 fonts: HK 29₅₆, JP 16₁₅₂, SC 18₄₀₂. + '2e3r9p,' + // #2332: 5 fonts: HK 29₅₆, JP 67₂₀₃, KR 70₃₃₀, SC 18₄₀₂, TC 20₅₀₅. + '2e5q4w2t3y,' + // #2333: 5 fonts: HK 29₅₆, JP 68₂₀₄, KR 70₃₃₀, SC 18₄₀₂, TC 20₅₀₅. + '2e5r4v2t3y,' + // #2334: 5 fonts: HK 29₅₆, JP 69₂₀₅, KR 70₃₃₀, SC 19₄₀₃, TC 21₅₀₆. + '2e5s4u2u3y,' + // #2335: 5 fonts: HK 29₅₆, JP 70₂₀₆, KR 70₃₃₀, SC 18₄₀₂, TC 20₅₀₅. + '2e5t4t2t3y,' + // #2336: 5 fonts: HK 29₅₆, JP 71₂₀₇, KR 70₃₃₀, SC 18₄₀₂, TC 20₅₀₅. + '2e5u4s2t3y,' + // #2337: 4 fonts: HK 29₅₆, JP 72₂₀₈, SC 18₄₀₂, TC 20₅₀₅. + '2e5v7l3y,' + // #2338: 5 fonts: HK 29₅₆, JP 82₂₁₈, KR 70₃₃₀, SC 18₄₀₂, TC 20₅₀₅. + '2e6f4h2t3y,' + // #2339: 5 fonts: HK 29₅₆, JP 94₂₃₀, KR 70₃₃₀, SC 18₄₀₂, TC 20₅₀₅. + '2e6r3v2t3y,' + // #2340: 4 fonts: HK 29₅₆, JP 97₂₃₃, SC 89₄₇₃, TC 21₅₀₆. + '2e6u9f1g,' + // #2341: 4 fonts: HK 29₅₆, JP 116₂₅₂, SC 19₄₀₃, TC 21₅₀₆. + '2e7n5u3y,' + // #2342: 3 fonts: HK 29₅₆, KR 70₃₃₀, TC 20₅₀₅. + '2e10n6s,' + // #2343: 2 fonts: HK 29₅₆, SC 18₄₀₂. + '2e13h,' + // #2344: 3 fonts: HK 29₅₆, SC 18₄₀₂, TC 20₅₀₅. + '2e13h3y,' + // #2345: 2 fonts: HK 29₅₆, TC 21₅₀₆. + '2e17h,' + // #2346: 5 fonts: HK 30₅₇, JP 16₁₅₂, KR 70₃₃₀, SC 19₄₀₃, TC 21₅₀₆. + '2f3q6v2u3y,' + // #2347: 5 fonts: HK 30₅₇, JP 17₁₅₃, KR 70₃₃₀, SC 19₄₀₃, TC 21₅₀₆. + '2f3r6u2u3y,' + // #2348: 4 fonts: HK 30₅₇, JP 17₁₅₃, KR 70₃₃₀, TC 21₅₀₆. + '2f3r6u6t,' + // #2349: 5 fonts: HK 30₅₇, JP 17₁₅₃, KR 71₃₃₁, SC 77₄₆₁, TC 21₅₀₆. + '2f3r6v4z1s,' + // #2350: 4 fonts: HK 30₅₇, JP 17₁₅₃, SC 78₄₆₂, TC 22₅₀₇. + '2f3r11w1s,' + // #2351: 4 fonts: HK 30₅₇, JP 17₁₅₃, SC 86₄₇₀, TC 22₅₀₇. + '2f3r12e1k,' + // #2352: 5 fonts: HK 30₅₇, JP 60₁₉₆, KR 71₃₃₁, SC 19₄₀₃, TC 21₅₀₆. + '2f5i5e2t3y,' + // #2353: 5 fonts: HK 30₅₇, JP 61₁₉₇, KR 71₃₃₁, SC 20₄₀₄, TC 22₅₀₇. + '2f5j5d2u3y,' + // #2354: 5 fonts: HK 30₅₇, JP 66₂₀₂, KR 71₃₃₁, SC 20₄₀₄, TC 22₅₀₇. + '2f5o4y2u3y,' + // #2355: 5 fonts: HK 30₅₇, JP 67₂₀₃, KR 71₃₃₁, SC 19₄₀₃, TC 22₅₀₇. + '2f5p4x2t3z,' + // #2356: 5 fonts: HK 30₅₇, JP 71₂₀₇, KR 70₃₃₀, SC 19₄₀₃, TC 21₅₀₆. + '2f5t4s2u3y,' + // #2357: 4 fonts: HK 30₅₇, JP 74₂₁₀, SC 19₄₀₃, TC 21₅₀₆. + '2f5w7k3y,' + // #2358: 4 fonts: HK 30₅₇, JP 81₂₁₇, SC 19₄₀₃, TC 21₅₀₆. + '2f6d7d3y,' + // #2359: 5 fonts: HK 30₅₇, JP 83₂₁₉, KR 71₃₃₁, SC 19₄₀₃, TC 21₅₀₆. + '2f6f4h2t3y,' + // #2360: 5 fonts: HK 30₅₇, JP 86₂₂₂, KR 70₃₃₀, SC 19₄₀₃, TC 21₅₀₆. + '2f6i4d2u3y,' + // #2361: 5 fonts: HK 30₅₇, JP 87₂₂₃, KR 71₃₃₁, SC 20₄₀₄, TC 22₅₀₇. + '2f6j4d2u3y,' + // #2362: 2 fonts: HK 30₅₇, SC 19₄₀₃. + '2f13h,' + // #2363: 3 fonts: HK 30₅₇, SC 20₄₀₄, TC 22₅₀₇. + '2f13i3y,' + // #2364: 3 fonts: HK 30₅₇, SC 91₄₇₅, TC 22₅₀₇. + '2f16b1f,' + // #2365: 4 fonts: HK 31₅₈, JP 17₁₅₃, KR 71₃₃₁, TC 22₅₀₇. + '2g3q6v6t,' + // #2366: 5 fonts: HK 31₅₈, JP 18₁₅₄, KR 71₃₃₁, SC 80₄₆₄, TC 23₅₀₈. + '2g3r6u5c1r,' + // #2367: 4 fonts: HK 31₅₈, JP 18₁₅₄, SC 79₄₆₃, TC 23₅₀₈. + '2g3r11w1s,' + // #2368: 4 fonts: HK 31₅₈, JP 18₁₅₄, SC 83₄₆₇, TC 23₅₀₈. + '2g3r12a1o,' + // #2369: 3 fonts: HK 31₅₈, SC 77₄₆₁, TC 23₅₀₈. + '2g15m1u,' + // #2370: 2 fonts: HK 32₅₉, JP 18₁₅₄. + '2h3q,' + // #2371: 5 fonts: HK 32₅₉, JP 18₁₅₄, KR 71₃₃₁, SC 78₄₆₂, TC 23₅₀₈. + '2h3q6u5a1t,' + // #2372: 5 fonts: HK 32₅₉, JP 19₁₅₅, KR 71₃₃₁, SC 21₄₀₅, TC 23₅₀₈. + '2h3r6t2v3y,' + // #2373: 4 fonts: HK 32₅₉, JP 60₁₉₆, SC 21₄₀₅, TC 24₅₀₉. + '2h5g8a3z,' + // #2374: 5 fonts: HK 32₅₉, JP 61₁₉₇, KR 71₃₃₁, SC 21₄₀₅, TC 24₅₀₉. + '2h5h5d2v3z,' + // #2375: 5 fonts: HK 32₅₉, JP 61₁₉₇, KR 72₃₃₂, SC 21₄₀₅, TC 24₅₀₉. + '2h5h5e2u3z,' + // #2376: 5 fonts: HK 32₅₉, JP 71₂₀₇, KR 71₃₃₁, SC 21₄₀₅, TC 23₅₀₈. + '2h5r4t2v3y,' + // #2377: 5 fonts: HK 32₅₉, JP 72₂₀₈, KR 71₃₃₁, SC 21₄₀₅, TC 24₅₀₉. + '2h5s4s2v3z,' + // #2378: 5 fonts: HK 32₅₉, JP 77₂₁₃, KR 72₃₃₂, SC 21₄₀₅, TC 24₅₀₉. + '2h5x4o2u3z,' + // #2379: 4 fonts: HK 32₅₉, JP 92₂₂₈, SC 86₄₇₀, TC 23₅₀₈. + '2h6m9h1l,' + // #2380: 2 fonts: HK 32₅₉, SC 21₄₀₅. + '2h13h,' + // #2381: 3 fonts: HK 32₅₉, SC 21₄₀₅, TC 24₅₀₉. + '2h13h3z,' + // #2382: 3 fonts: HK 32₅₉, SC 87₄₇₁, TC 23₅₀₈. + '2h15v1k,' + // #2383: 3 fonts: HK 33₆₀, JP 19₁₅₅, KR 72₃₃₂. + '2i3q6u,' + // #2384: 4 fonts: HK 33₆₀, JP 20₁₅₆, SC 77₄₆₁, TC 25₅₁₀. + '2i3r11s1w,' + // #2385: 4 fonts: HK 33₆₀, JP 60₁₉₆, SC 83₄₆₇, TC 25₅₁₀. + '2i5f10k1q,' + // #2386: 5 fonts: HK 33₆₀, JP 61₁₉₇, KR 72₃₃₂, SC 22₄₀₆, TC 25₅₁₀. + '2i5g5e2v3z,' + // #2387: 5 fonts: HK 33₆₀, JP 63₁₉₉, KR 72₃₃₂, SC 22₄₀₆, TC 24₅₀₉. + '2i5i5c2v3y,' + // #2388: 5 fonts: HK 33₆₀, JP 75₂₁₁, KR 72₃₃₂, SC 22₄₀₆, TC 25₅₁₀. + '2i5u4q2v3z,' + // #2389: 2 fonts: HK 33₆₀, SC 22₄₀₆. + '2i13h,' + // #2390: 5 fonts: HK 34₆₁, JP 20₁₅₆, KR 72₃₃₂, SC 22₄₀₆, TC 25₅₁₀. + '2j3q6t2v3z,' + // #2391: 4 fonts: HK 34₆₁, JP 20₁₅₆, KR 72₃₃₂, SC 23₄₀₇. + '2j3q6t2w,' + // #2392: 4 fonts: HK 34₆₁, JP 20₁₅₆, SC 77₄₆₁, TC 25₅₁₀. + '2j3q11s1w,' + // #2393: 4 fonts: HK 34₆₁, JP 20₁₅₆, SC 78₄₆₂, TC 26₅₁₁. + '2j3q11t1w,' + // #2394: 4 fonts: HK 34₆₁, JP 20₁₅₆, SC 88₄₇₂, TC 26₅₁₁. + '2j3q12d1m,' + // #2395: 4 fonts: HK 34₆₁, JP 20₁₅₆, SC 91₄₇₅, TC 26₅₁₁. + '2j3q12g1j,' + // #2396: 3 fonts: HK 34₆₁, JP 21₁₅₇, KR 72₃₃₂. + '2j3r6s,' + // #2397: 5 fonts: HK 34₆₁, JP 21₁₅₇, KR 72₃₃₂, SC 77₄₆₁, TC 26₅₁₁. + '2j3r6s4y1x,' + // #2398: 4 fonts: HK 34₆₁, JP 21₁₅₇, KR 72₃₃₂, TC 26₅₁₁. + '2j3r6s6w,' + // #2399: 5 fonts: HK 34₆₁, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 26₅₁₁. + '2j3r6t2w3y,' + // #2400: 3 fonts: HK 34₆₁, JP 21₁₅₇, SC 23₄₀₇. + '2j3r9p,' + // #2401: 3 fonts: HK 34₆₁, JP 21₁₅₇, TC 26₅₁₁. + '2j3r13p,' + // #2402: 5 fonts: HK 34₆₁, JP 62₁₉₈, KR 72₃₃₂, SC 77₄₆₁, TC 26₅₁₁. + '2j5g5d4y1x,' + // #2403: 4 fonts: HK 34₆₁, JP 65₂₀₁, SC 23₄₀₇, TC 26₅₁₁. + '2j5j7x3z,' + // #2404: 4 fonts: HK 34₆₁, JP 72₂₀₈, SC 23₄₀₇, TC 26₅₁₁. + '2j5q7q3z,' + // #2405: 5 fonts: HK 34₆₁, JP 73₂₀₉, KR 72₃₃₂, SC 23₄₀₇, TC 26₅₁₁. + '2j5r4s2w3z,' + // #2406: 5 fonts: HK 34₆₁, JP 76₂₁₂, KR 72₃₃₂, SC 84₄₆₈, TC 26₅₁₁. + '2j5u4p5f1q,' + // #2407: 5 fonts: HK 34₆₁, JP 83₂₁₉, KR 72₃₃₂, SC 22₄₀₆, TC 25₅₁₀. + '2j6b4i2v3z,' + // #2408: 3 fonts: HK 34₆₁, SC 24₄₀₈, TC 26₅₁₁. + '2j13i3y,' + // #2409: 3 fonts: HK 34₆₁, SC 83₄₆₇, TC 25₅₁₀. + '2j15p1q,' + // #2410: 5 fonts: HK 35₆₂, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 26₅₁₁. + '2k3q6t2w3y,' + // #2411: 5 fonts: HK 35₆₂, JP 21₁₅₇, KR 73₃₃₃, SC 25₄₀₉, TC 27₅₁₂. + '2k3q6t2x3y,' + // #2412: 4 fonts: HK 35₆₂, JP 21₁₅₇, KR 73₃₃₃, TC 26₅₁₁. + '2k3q6t6v,' + // #2413: 3 fonts: HK 35₆₂, JP 21₁₅₇, SC 24₄₀₈. + '2k3q9q,' + // #2414: 4 fonts: HK 35₆₂, JP 21₁₅₇, SC 77₄₆₁, TC 27₅₁₂. + '2k3q11r1y,' + // #2415: 4 fonts: HK 35₆₂, JP 21₁₅₇, SC 84₄₆₈, TC 27₅₁₂. + '2k3q11y1r,' + // #2416: 3 fonts: HK 35₆₂, JP 21₁₅₇, TC 26₅₁₁. + '2k3q13p,' + // #2417: 5 fonts: HK 35₆₂, JP 63₁₉₉, KR 73₃₃₃, SC 24₄₀₈, TC 27₅₁₂. + '2k5g5d2w3z,' + // #2418: 4 fonts: HK 35₆₂, JP 66₂₀₂, SC 24₄₀₈, TC 27₅₁₂. + '2k5j7x3z,' + // #2419: 5 fonts: HK 35₆₂, JP 67₂₀₃, KR 73₃₃₃, SC 78₄₆₂, TC 27₅₁₂. + '2k5k4z4y1x,' + // #2420: 5 fonts: HK 35₆₂, JP 74₂₁₀, KR 73₃₃₃, SC 24₄₀₈, TC 27₅₁₂. + '2k5r4s2w3z,' + // #2421: 5 fonts: HK 35₆₂, JP 79₂₁₅, KR 73₃₃₃, SC 24₄₀₈, TC 27₅₁₂. + '2k5w4n2w3z,' + // #2422: 4 fonts: HK 35₆₂, JP 81₂₁₇, SC 24₄₀₈, TC 27₅₁₂. + '2k5y7i3z,' + // #2423: 4 fonts: HK 35₆₂, JP 99₂₃₅, SC 90₄₇₄, TC 27₅₁₂. + '2k6q9e1l,' + // #2424: 3 fonts: HK 35₆₂, SC 24₄₀₈, TC 26₅₁₁. + '2k13h3y,' + // #2425: 2 fonts: HK 35₆₂, TC 26₅₁₁. + '2k17g,' + // #2426: 5 fonts: HK 36₆₃, JP 22₁₅₈, KR 74₃₃₄, SC 25₄₀₉, TC 28₅₁₃. + '2l3q6t2w3z,' + // #2427: 5 fonts: HK 36₆₃, JP 22₁₅₈, KR 74₃₃₄, SC 26₄₁₀, TC 29₅₁₄. + '2l3q6t2x3z,' + // #2428: 3 fonts: HK 36₆₃, JP 22₁₅₈, SC 25₄₀₉. + '2l3q9q,' + // #2429: 4 fonts: HK 36₆₃, JP 22₁₅₈, SC 82₄₆₆, TC 28₅₁₃. + '2l3q11v1u,' + // #2430: 4 fonts: HK 36₆₃, JP 22₁₅₈, SC 86₄₇₀, TC 28₅₁₃. + '2l3q11z1q,' + // #2431: 4 fonts: HK 36₆₃, JP 22₁₅₈, SC 88₄₇₂, TC 28₅₁₃. + '2l3q12b1o,' + // #2432: 4 fonts: HK 36₆₃, JP 70₂₀₆, SC 77₄₆₁, TC 28₅₁₃. + '2l5m9u1z,' + // #2433: 5 fonts: HK 36₆₃, JP 71₂₀₇, KR 73₃₃₃, SC 25₄₀₉, TC 28₅₁₃. + '2l5n4v2x3z,' + // #2434: 5 fonts: HK 36₆₃, JP 71₂₀₇, KR 73₃₃₃, SC 77₄₆₁, TC 28₅₁₃. + '2l5n4v4x1z,' + // #2435: 4 fonts: HK 36₆₃, JP 75₂₁₁, SC 25₄₀₉, TC 28₅₁₃. + '2l5r7p3z,' + // #2436: 4 fonts: HK 36₆₃, JP 79₂₁₅, SC 87₄₇₁, TC 28₅₁₃. + '2l5v9v1p,' + // #2437: 5 fonts: HK 36₆₃, JP 86₂₂₂, KR 73₃₃₃, SC 25₄₀₉, TC 28₅₁₃. + '2l6c4g2x3z,' + // #2438: 5 fonts: HK 36₆₃, JP 95₂₃₁, KR 74₃₃₄, SC 26₄₁₀, TC 28₅₁₃. + '2l6l3y2x3y,' + // #2439: 5 fonts: HK 36₆₃, JP 96₂₃₂, KR 73₃₃₃, SC 90₄₇₄, TC 28₅₁₃. + '2l6m3w5k1m,' + // #2440: 2 fonts: HK 36₆₃, SC 26₄₁₀. + '2l13i,' + // #2441: 3 fonts: HK 36₆₃, SC 26₄₁₀, TC 29₅₁₄. + '2l13i3z,' + // #2442: 3 fonts: HK 36₆₃, SC 90₄₇₄, TC 28₅₁₃. + '2l15u1m,' + // #2443: 1 font: HK 37₆₄. + '2m,' + // #2444: 2 fonts: HK 37₆₄, JP 22₁₅₈. + '2m3p,' + // #2445: 4 fonts: HK 37₆₄, JP 22₁₅₈, SC 82₄₆₆, TC 29₅₁₄. + '2m3p11v1v,' + // #2446: 4 fonts: HK 37₆₄, JP 23₁₅₉, KR 74₃₃₄, TC 29₅₁₄. + '2m3q6s6x,' + // #2447: 3 fonts: HK 37₆₄, JP 23₁₅₉, SC 26₄₁₀. + '2m3q9q,' + // #2448: 3 fonts: HK 37₆₄, JP 23₁₅₉, SC 27₄₁₁. + '2m3q9r,' + // #2449: 4 fonts: HK 37₆₄, JP 70₂₀₆, SC 26₄₁₀, TC 29₅₁₄. + '2m5l7v3z,' + // #2450: 5 fonts: HK 37₆₄, JP 84₂₂₀, KR 74₃₃₄, SC 26₄₁₀, TC 29₅₁₄. + '2m5z4j2x3z,' + // #2451: 3 fonts: HK 37₆₄, SC 81₄₆₅, TC 30₅₁₅. + '2m15k1x,' + // #2452: 3 fonts: HK 37₆₄, SC 82₄₆₆, TC 29₅₁₄. + '2m15l1v,' + // #2453: 3 fonts: HK 37₆₄, SC 85₄₆₉, TC 29₅₁₄. + '2m15o1s,' + // #2454: 3 fonts: HK 37₆₄, SC 86₄₇₀, TC 30₅₁₅. + '2m15p1s,' + // #2455: 3 fonts: HK 37₆₄, SC 91₄₇₅, TC 29₅₁₄. + '2m15u1m,' + // #2456: 2 fonts: HK 37₆₄, TC 30₅₁₅. + '2m17i,' + // #2457: 2 fonts: HK 38₆₅, JP 24₁₆₀. + '2n3q,' + // #2458: 4 fonts: HK 38₆₅, JP 24₁₆₀, KR 74₃₃₄, TC 31₅₁₆. + '2n3q6r6z,' + // #2459: 5 fonts: HK 38₆₅, JP 24₁₆₀, KR 75₃₃₅, SC 28₄₁₂, TC 31₅₁₆. + '2n3q6s2y3z,' + // #2460: 5 fonts: HK 38₆₅, JP 65₂₀₁, KR 74₃₃₄, SC 27₄₁₁, TC 30₅₁₅. + '2n5f5c2y3z,' + // #2461: 5 fonts: HK 38₆₅, JP 69₂₀₅, KR 74₃₃₄, SC 28₄₁₂, TC 31₅₁₆. + '2n5j4y2z3z,' + // #2462: 5 fonts: HK 38₆₅, JP 83₂₁₉, KR 74₃₃₄, SC 28₄₁₂, TC 31₅₁₆. + '2n5x4k2z3z,' + // #2463: 5 fonts: HK 38₆₅, JP 92₂₂₈, KR 74₃₃₄, SC 27₄₁₁, TC 30₅₁₅. + '2n6g4b2y3z,' + // #2464: 4 fonts: HK 38₆₅, JP 111₂₄₇, SC 28₄₁₂, TC 30₅₁₅. + '2n6z6i3y,' + // #2465: 3 fonts: HK 38₆₅, KR 74₃₃₄, TC 30₅₁₅. + '2n10i6y,' + // #2466: 3 fonts: HK 38₆₅, SC 28₄₁₂, TC 30₅₁₅. + '2n13i3y,' + // #2467: 3 fonts: HK 39₆₆, JP 24₁₆₀, KR 75₃₃₅. + '2o3p6s,' + // #2468: 5 fonts: HK 39₆₆, JP 24₁₆₀, KR 75₃₃₅, SC 86₄₇₀, TC 31₅₁₆. + '2o3p6s5e1t,' + // #2469: 4 fonts: HK 39₆₆, JP 24₁₆₀, KR 75₃₃₅, TC 31₅₁₆. + '2o3p6s6y,' + // #2470: 4 fonts: HK 39₆₆, JP 24₁₆₀, KR 75₃₃₅, TC 32₅₁₇. + '2o3p6s6z,' + // #2471: 3 fonts: HK 39₆₆, JP 24₁₆₀, SC 28₄₁₂. + '2o3p9r,' + // #2472: 4 fonts: HK 39₆₆, JP 24₁₆₀, SC 30₄₁₄, TC 32₅₁₇. + '2o3p9t3y,' + // #2473: 4 fonts: HK 39₆₆, JP 25₁₆₁, SC 30₄₁₄, TC 32₅₁₇. + '2o3q9s3y,' + // #2474: 5 fonts: HK 39₆₆, JP 62₁₉₈, KR 75₃₃₅, SC 29₄₁₃, TC 31₅₁₆. + '2o5b5g2z3y,' + // #2475: 5 fonts: HK 39₆₆, JP 63₁₉₉, KR 75₃₃₅, SC 28₄₁₂, TC 31₅₁₆. + '2o5c5f2y3z,' + // #2476: 5 fonts: HK 39₆₆, JP 67₂₀₃, KR 75₃₃₅, SC 77₄₆₁, TC 31₅₁₆. + '2o5g5b4v2c,' + // #2477: 5 fonts: HK 39₆₆, JP 80₂₁₆, KR 75₃₃₅, SC 30₄₁₄, TC 32₅₁₇. + '2o5t4o3a3y,' + // #2478: 5 fonts: HK 39₆₆, JP 84₂₂₀, KR 75₃₃₅, SC 29₄₁₃, TC 32₅₁₇. + '2o5x4k2z3z,' + // #2479: 4 fonts: HK 39₆₆, JP 90₂₂₆, SC 28₄₁₂, TC 31₅₁₆. + '2o6d7d3z,' + // #2480: 3 fonts: HK 39₆₆, SC 30₄₁₄, TC 32₅₁₇. + '2o13j3y,' + // #2481: 3 fonts: HK 39₆₆, SC 77₄₆₁, TC 32₅₁₇. + '2o15e2d,' + // #2482: 3 fonts: HK 40₆₇, JP 25₁₆₁, KR 75₃₃₅. + '2p3p6r,' + // #2483: 4 fonts: HK 40₆₇, JP 25₁₆₁, KR 75₃₃₅, SC 30₄₁₄. + '2p3p6r3a,' + // #2484: 5 fonts: HK 40₆₇, JP 25₁₆₁, KR 75₃₃₅, SC 30₄₁₄, TC 33₅₁₈. + '2p3p6r3a3z,' + // #2485: 5 fonts: HK 40₆₇, JP 25₁₆₁, KR 75₃₃₅, SC 80₄₆₄, TC 33₅₁₈. + '2p3p6r4y2b,' + // #2486: 3 fonts: HK 40₆₇, JP 25₁₆₁, SC 31₄₁₅. + '2p3p9t,' + // #2487: 4 fonts: HK 40₆₇, JP 25₁₆₁, SC 78₄₆₂, TC 33₅₁₈. + '2p3p11o2d,' + // #2488: 4 fonts: HK 40₆₇, JP 25₁₆₁, SC 79₄₆₃, TC 32₅₁₇. + '2p3p11p2b,' + // #2489: 4 fonts: HK 40₆₇, JP 25₁₆₁, SC 91₄₇₅, TC 32₅₁₇. + '2p3p12b1p,' + // #2490: 4 fonts: HK 40₆₇, JP 60₁₉₆, SC 30₄₁₄, TC 32₅₁₇. + '2p4y8j3y,' + // #2491: 4 fonts: HK 40₆₇, JP 60₁₉₆, SC 81₄₆₅, TC 33₅₁₈. + '2p4y10i2a,' + // #2492: 5 fonts: HK 40₆₇, JP 68₂₀₄, KR 75₃₃₅, SC 30₄₁₄, TC 32₅₁₇. + '2p5g5a3a3y,' + // #2493: 3 fonts: HK 40₆₇, JP 68₂₀₄, TC 32₅₁₇. + '2p5g12a,' + // #2494: 5 fonts: HK 40₆₇, JP 70₂₀₆, KR 75₃₃₅, SC 30₄₁₄, TC 32₅₁₇. + '2p5i4y3a3y,' + // #2495: 5 fonts: HK 40₆₇, JP 75₂₁₁, KR 75₃₃₅, SC 31₄₁₅, TC 33₅₁₈. + '2p5n4t3b3y,' + // #2496: 5 fonts: HK 40₆₇, JP 76₂₁₂, KR 75₃₃₅, SC 30₄₁₄, TC 32₅₁₇. + '2p5o4s3a3y,' + // #2497: 5 fonts: HK 40₆₇, JP 83₂₁₉, KR 75₃₃₅, SC 30₄₁₄, TC 32₅₁₇. + '2p5v4l3a3y,' + // #2498: 4 fonts: HK 40₆₇, JP 91₂₂₇, SC 31₄₁₅, TC 33₅₁₈. + '2p6d7f3y,' + // #2499: 5 fonts: HK 40₆₇, JP 98₂₃₄, KR 75₃₃₅, SC 31₄₁₅, TC 33₅₁₈. + '2p6k3w3b3y,' + // #2500: 3 fonts: HK 41₆₈, JP 26₁₆₂, KR 76₃₃₆. + '2q3p6r,' + // #2501: 5 fonts: HK 41₆₈, JP 26₁₆₂, KR 76₃₃₆, SC 77₄₆₁, TC 34₅₁₉. + '2q3p6r4u2f,' + // #2502: 5 fonts: HK 41₆₈, JP 26₁₆₂, KR 76₃₃₆, SC 82₄₆₆, TC 34₅₁₉. + '2q3p6r4z2a,' + // #2503: 4 fonts: HK 41₆₈, JP 26₁₆₂, KR 76₃₃₆, TC 34₅₁₉. + '2q3p6r7a,' + // #2504: 4 fonts: HK 41₆₈, JP 26₁₆₂, SC 32₄₁₆, TC 33₅₁₈. + '2q3p9t3x,' + // #2505: 4 fonts: HK 41₆₈, JP 64₂₀₀, SC 32₄₁₆, TC 34₅₁₉. + '2q5b8h3y,' + // #2506: 5 fonts: HK 41₆₈, JP 70₂₀₆, KR 76₃₃₆, SC 32₄₁₆, TC 34₅₁₉. + '2q5h4z3b3y,' + // #2507: 5 fonts: HK 41₆₈, JP 89₂₂₅, KR 76₃₃₆, SC 32₄₁₆, TC 34₅₁₉. + '2q6a4g3b3y,' + // #2508: 5 fonts: HK 41₆₈, JP 91₂₂₇, KR 76₃₃₆, SC 31₄₁₅, TC 33₅₁₈. + '2q6c4e3a3y,' + // #2509: 4 fonts: HK 41₆₈, JP 91₂₂₇, SC 31₄₁₅, TC 33₅₁₈. + '2q6c7f3y,' + // #2510: 4 fonts: HK 41₆₈, JP 107₂₄₃, SC 90₄₇₄, TC 33₅₁₈. + '2q6s8w1r,' + // #2511: 2 fonts: HK 41₆₈, KR 76₃₃₆. + '2q10h,' + // #2512: 5 fonts: HK 42₆₉, JP 26₁₆₂, KR 76₃₃₆, SC 85₄₆₉, TC 34₅₁₉. + '2r3o6r5c1x,' + // #2513: 4 fonts: HK 42₆₉, JP 26₁₆₂, KR 76₃₃₆, TC 34₅₁₉. + '2r3o6r7a,' + // #2514: 4 fonts: HK 42₆₉, JP 26₁₆₂, SC 77₄₆₁, TC 34₅₁₉. + '2r3o11m2f,' + // #2515: 4 fonts: HK 42₆₉, JP 26₁₆₂, SC 78₄₆₂, TC 34₅₁₉. + '2r3o11n2e,' + // #2516: 4 fonts: HK 42₆₉, JP 26₁₆₂, SC 81₄₆₅, TC 34₅₁₉. + '2r3o11q2b,' + // #2517: 5 fonts: HK 42₆₉, JP 27₁₆₃, KR 76₃₃₆, SC 82₄₆₆, TC 35₅₂₀. + '2r3p6q4z2b,' + // #2518: 5 fonts: HK 42₆₉, JP 27₁₆₃, KR 76₃₃₆, SC 83₄₆₇, TC 34₅₁₉. + '2r3p6q5a1z,' + // #2519: 4 fonts: HK 42₆₉, JP 27₁₆₃, KR 76₃₃₆, TC 35₅₂₀. + '2r3p6q7b,' + // #2520: 5 fonts: HK 42₆₉, JP 63₁₉₉, KR 76₃₃₆, SC 78₄₆₂, TC 34₅₁₉. + '2r4z5g4v2e,' + // #2521: 5 fonts: HK 42₆₉, JP 74₂₁₀, KR 76₃₃₆, SC 32₄₁₆, TC 34₅₁₉. + '2r5k4v3b3y,' + // #2522: 3 fonts: HK 42₆₉, SC 33₄₁₇, TC 34₅₁₉. + '2r13j3x,' + // #2523: 5 fonts: HK 43₇₀, JP 27₁₆₃, KR 76₃₃₆, SC 34₄₁₈, TC 35₅₂₀. + '2s3o6q3d3x,' + // #2524: 5 fonts: HK 43₇₀, JP 27₁₆₃, KR 76₃₃₆, SC 34₄₁₈, TC 36₅₂₁. + '2s3o6q3d3y,' + // #2525: 5 fonts: HK 43₇₀, JP 27₁₆₃, KR 76₃₃₆, SC 79₄₆₃, TC 35₅₂₀. + '2s3o6q4w2e,' + // #2526: 5 fonts: HK 43₇₀, JP 27₁₆₃, KR 77₃₃₇, SC 90₄₇₄, TC 36₅₂₁. + '2s3o6r5g1u,' + // #2527: 4 fonts: HK 43₇₀, JP 27₁₆₃, SC 82₄₆₆, TC 36₅₂₁. + '2s3o11q2c,' + // #2528: 2 fonts: HK 43₇₀, JP 28₁₆₄. + '2s3p,' + // #2529: 4 fonts: HK 43₇₀, JP 61₁₉₇, SC 33₄₁₇, TC 35₅₂₀. + '2s4w8l3y,' + // #2530: 5 fonts: HK 43₇₀, JP 68₂₀₄, KR 76₃₃₆, SC 33₄₁₇, TC 35₅₂₀. + '2s5d5b3c3y,' + // #2531: 5 fonts: HK 43₇₀, JP 78₂₁₄, KR 76₃₃₆, SC 34₄₁₈, TC 35₅₂₀. + '2s5n4r3d3x,' + // #2532: 4 fonts: HK 43₇₀, JP 112₂₄₈, SC 34₄₁₈, TC 35₅₂₀. + '2s6v6n3x,' + // #2533: 4 fonts: HK 43₇₀, JP 116₂₅₂, SC 34₄₁₈, TC 36₅₂₁. + '2s6z6j3y,' + // #2534: 2 fonts: HK 43₇₀, SC 34₄₁₈. + '2s13j,' + // #2535: 3 fonts: HK 43₇₀, SC 34₄₁₈, TC 35₅₂₀. + '2s13j3x,' + // #2536: 5 fonts: HK 44₇₁, JP 28₁₆₄, KR 77₃₃₇, SC 77₄₆₁, TC 36₅₂₁. + '2t3o6q4t2h,' + // #2537: 5 fonts: HK 44₇₁, JP 28₁₆₄, KR 77₃₃₇, SC 79₄₆₃, TC 37₅₂₂. + '2t3o6q4v2g,' + // #2538: 4 fonts: HK 44₇₁, JP 28₁₆₄, SC 34₄₁₈, TC 36₅₂₁. + '2t3o9t3y,' + // #2539: 3 fonts: HK 44₇₁, JP 28₁₆₄, SC 35₄₁₉. + '2t3o9u,' + // #2540: 4 fonts: HK 44₇₁, JP 29₁₆₅, SC 35₄₁₉, TC 37₅₂₂. + '2t3p9t3y,' + // #2541: 5 fonts: HK 44₇₁, JP 62₁₉₈, KR 77₃₃₇, SC 36₄₂₀, TC 37₅₂₂. + '2t4w5i3e3x,' + // #2542: 5 fonts: HK 44₇₁, JP 63₁₉₉, KR 77₃₃₇, SC 35₄₁₉, TC 37₅₂₂. + '2t4x5h3d3y,' + // #2543: 5 fonts: HK 44₇₁, JP 64₂₀₀, KR 77₃₃₇, SC 82₄₆₆, TC 36₅₂₁. + '2t4y5g4y2c,' + // #2544: 4 fonts: HK 44₇₁, JP 65₂₀₁, SC 35₄₁₉, TC 37₅₂₂. + '2t4z8j3y,' + // #2545: 5 fonts: HK 44₇₁, JP 66₂₀₂, KR 77₃₃₇, SC 35₄₁₉, TC 36₅₂₁. + '2t5a5e3d3x,' + // #2546: 5 fonts: HK 44₇₁, JP 68₂₀₄, KR 77₃₃₇, SC 35₄₁₉, TC 36₅₂₁. + '2t5c5c3d3x,' + // #2547: 5 fonts: HK 44₇₁, JP 69₂₀₅, KR 77₃₃₇, SC 35₄₁₉, TC 36₅₂₁. + '2t5d5b3d3x,' + // #2548: 5 fonts: HK 44₇₁, JP 72₂₀₈, KR 77₃₃₇, SC 35₄₁₉, TC 36₅₂₁. + '2t5g4y3d3x,' + // #2549: 5 fonts: HK 44₇₁, JP 72₂₀₈, KR 77₃₃₇, SC 35₄₁₉, TC 37₅₂₂. + '2t5g4y3d3y,' + // #2550: 5 fonts: HK 44₇₁, JP 87₂₂₃, KR 77₃₃₇, SC 35₄₁₉, TC 37₅₂₂. + '2t5v4j3d3y,' + // #2551: 5 fonts: HK 44₇₁, JP 89₂₂₅, KR 77₃₃₇, SC 35₄₁₉, TC 37₅₂₂. + '2t5x4h3d3y,' + // #2552: 2 fonts: HK 44₇₁, KR 77₃₃₇. + '2t10f,' + // #2553: 3 fonts: HK 44₇₁, KR 77₃₃₇, SC 35₄₁₉. + '2t10f3d,' + // #2554: 2 fonts: HK 44₇₁, SC 35₄₁₉. + '2t13j,' + // #2555: 3 fonts: HK 44₇₁, SC 36₄₂₀, TC 37₅₂₂. + '2t13k3x,' + // #2556: 4 fonts: HK 45₇₂, JP 29₁₆₅, KR 77₃₃₇, SC 36₄₂₀. + '2u3o6p3e,' + // #2557: 3 fonts: HK 45₇₂, JP 29₁₆₅, KR 78₃₃₈. + '2u3o6q,' + // #2558: 4 fonts: HK 45₇₂, JP 29₁₆₅, KR 78₃₃₈, SC 36₄₂₀. + '2u3o6q3d,' + // #2559: 5 fonts: HK 45₇₂, JP 29₁₆₅, KR 78₃₃₈, SC 77₄₆₁, TC 38₅₂₃. + '2u3o6q4s2j,' + // #2560: 5 fonts: HK 45₇₂, JP 29₁₆₅, KR 78₃₃₈, SC 79₄₆₃, TC 38₅₂₃. + '2u3o6q4u2h,' + // #2561: 4 fonts: HK 45₇₂, JP 29₁₆₅, KR 78₃₃₈, TC 38₅₂₃. + '2u3o6q7c,' + // #2562: 3 fonts: HK 45₇₂, JP 29₁₆₅, TC 37₅₂₂. + '2u3o13s,' + // #2563: 5 fonts: HK 45₇₂, JP 62₁₉₈, KR 78₃₃₈, SC 78₄₆₂, TC 38₅₂₃. + '2u4v5j4t2i,' + // #2564: 4 fonts: HK 45₇₂, JP 74₂₁₀, SC 36₄₂₀, TC 38₅₂₃. + '2u5h8b3y,' + // #2565: 5 fonts: HK 45₇₂, JP 78₂₁₄, KR 78₃₃₈, SC 36₄₂₀, TC 38₅₂₃. + '2u5l4t3d3y,' + // #2566: 5 fonts: HK 45₇₂, JP 82₂₁₈, KR 77₃₃₇, SC 77₄₆₁, TC 37₅₂₂. + '2u5p4o4t2i,' + // #2567: 4 fonts: HK 45₇₂, KR 77₃₃₇, SC 36₄₂₀, TC 38₅₂₃. + '2u10e3e3y,' + // #2568: 3 fonts: HK 45₇₂, KR 78₃₃₈, TC 38₅₂₃. + '2u10f7c,' + // #2569: 3 fonts: HK 45₇₂, SC 83₄₆₇, TC 38₅₂₃. + '2u15e2d,' + // #2570: 2 fonts: HK 46₇₃, JP 29₁₆₅. + '2v3n,' + // #2571: 4 fonts: HK 46₇₃, JP 29₁₆₅, KR 78₃₃₈, TC 39₅₂₄. + '2v3n6q7d,' + // #2572: 4 fonts: HK 46₇₃, JP 29₁₆₅, SC 80₄₆₄, TC 38₅₂₃. + '2v3n11m2g,' + // #2573: 2 fonts: HK 46₇₃, JP 30₁₆₆. + '2v3o,' + // #2574: 5 fonts: HK 46₇₃, JP 63₁₉₉, KR 78₃₃₈, SC 37₄₂₁, TC 38₅₂₃. + '2v4v5i3e3x,' + // #2575: 5 fonts: HK 46₇₃, JP 73₂₀₉, KR 78₃₃₈, SC 88₄₇₂, TC 39₅₂₄. + '2v5f4y5d1z,' + // #2576: 4 fonts: HK 46₇₃, JP 92₂₂₈, SC 90₄₇₄, TC 39₅₂₄. + '2v5y9l1x,' + // #2577: 5 fonts: HK 46₇₃, JP 97₂₃₃, KR 78₃₃₈, SC 90₄₇₄, TC 39₅₂₄. + '2v6d4a5f1x,' + // #2578: 4 fonts: HK 46₇₃, KR 78₃₃₈, SC 37₄₂₁, TC 39₅₂₄. + '2v10e3e3y,' + // #2579: 2 fonts: HK 46₇₃, SC 37₄₂₁. + '2v13j,' + // #2580: 3 fonts: HK 46₇₃, SC 37₄₂₁, TC 38₅₂₃. + '2v13j3x,' + // #2581: 2 fonts: HK 46₇₃, SC 38₄₂₂. + '2v13k,' + // #2582: 3 fonts: HK 47₇₄, JP 30₁₆₆, KR 79₃₃₉. + '2w3n6q,' + // #2583: 5 fonts: HK 47₇₄, JP 30₁₆₆, KR 79₃₃₉, SC 80₄₆₄, TC 40₅₂₅. + '2w3n6q4u2i,' + // #2584: 3 fonts: HK 47₇₄, JP 30₁₆₆, SC 38₄₂₂. + '2w3n9v,' + // #2585: 5 fonts: HK 47₇₄, JP 61₁₉₇, KR 78₃₃₈, SC 38₄₂₂, TC 40₅₂₅. + '2w4s5k3f3y,' + // #2586: 5 fonts: HK 47₇₄, JP 74₂₁₀, KR 79₃₃₉, SC 39₄₂₃, TC 40₅₂₅. + '2w5f4y3f3x,' + // #2587: 5 fonts: HK 48₇₅, JP 31₁₆₇, KR 79₃₃₉, SC 78₄₆₂, TC 41₅₂₆. + '2x3n6p4s2l,' + // #2588: 5 fonts: HK 48₇₅, JP 31₁₆₇, KR 79₃₃₉, SC 89₄₇₃, TC 41₅₂₆. + '2x3n6p5d2a,' + // #2589: 4 fonts: HK 48₇₅, JP 31₁₆₇, KR 79₃₃₉, TC 42₅₂₇. + '2x3n6p7f,' + // #2590: 4 fonts: HK 48₇₅, JP 31₁₆₇, SC 40₄₂₄, TC 42₅₂₇. + '2x3n9w3y,' + // #2591: 4 fonts: HK 48₇₅, JP 31₁₆₇, SC 77₄₆₁, TC 41₅₂₆. + '2x3n11h2m,' + // #2592: 4 fonts: HK 48₇₅, JP 31₁₆₇, SC 80₄₆₄, TC 41₅₂₆. + '2x3n11k2j,' + // #2593: 4 fonts: HK 48₇₅, JP 31₁₆₇, SC 86₄₇₀, TC 41₅₂₆. + '2x3n11q2d,' + // #2594: 4 fonts: HK 48₇₅, JP 31₁₆₇, SC 91₄₇₅, TC 41₅₂₆. + '2x3n11v1y,' + // #2595: 5 fonts: HK 48₇₅, JP 63₁₉₉, KR 79₃₃₉, SC 39₄₂₃, TC 41₅₂₆. + '2x4t5j3f3y,' + // #2596: 5 fonts: HK 48₇₅, JP 69₂₀₅, KR 79₃₃₉, SC 39₄₂₃, TC 41₅₂₆. + '2x4z5d3f3y,' + // #2597: 5 fonts: HK 48₇₅, JP 71₂₀₇, KR 79₃₃₉, SC 78₄₆₂, TC 41₅₂₆. + '2x5b5b4s2l,' + // #2598: 4 fonts: HK 48₇₅, JP 76₂₁₂, SC 88₄₇₂, TC 41₅₂₆. + '2x5g9z2b,' + // #2599: 5 fonts: HK 48₇₅, JP 83₂₁₉, KR 79₃₃₉, SC 40₄₂₄, TC 42₅₂₇. + '2x5n4p3g3y,' + // #2600: 4 fonts: HK 48₇₅, JP 89₂₂₅, SC 90₄₇₄, TC 41₅₂₆. + '2x5t9o1z,' + // #2601: 4 fonts: HK 48₇₅, JP 99₂₃₅, SC 39₄₂₃, TC 41₅₂₆. + '2x6d7f3y,' + // #2602: 2 fonts: HK 48₇₅, KR 79₃₃₉. + '2x10d,' + // #2603: 3 fonts: HK 48₇₅, KR 79₃₃₉, TC 41₅₂₆. + '2x10d7e,' + // #2604: 2 fonts: HK 48₇₅, SC 39₄₂₃. + '2x13j,' + // #2605: 2 fonts: HK 48₇₅, SC 40₄₂₄. + '2x13k,' + // #2606: 3 fonts: HK 48₇₅, SC 77₄₆₁, TC 41₅₂₆. + '2x14v2m,' + // #2607: 3 fonts: HK 48₇₅, SC 80₄₆₄, TC 41₅₂₆. + '2x14y2j,' + // #2608: 3 fonts: HK 48₇₅, SC 83₄₆₇, TC 41₅₂₆. + '2x15b2g,' + // #2609: 3 fonts: HK 48₇₅, SC 87₄₇₁, TC 41₅₂₆. + '2x15f2c,' + // #2610: 2 fonts: HK 48₇₅, TC 42₅₂₇. + '2x17j,' + // #2611: 3 fonts: HK 49₇₆, JP 31₁₆₇, KR 79₃₃₉. + '2y3m6p,' + // #2612: 4 fonts: HK 49₇₆, JP 31₁₆₇, KR 79₃₃₉, TC 42₅₂₇. + '2y3m6p7f,' + // #2613: 5 fonts: HK 49₇₆, JP 32₁₆₈, KR 80₃₄₀, SC 77₄₆₁, TC 42₅₂₇. + '2y3n6p4q2n,' + // #2614: 4 fonts: HK 49₇₆, JP 32₁₆₈, KR 80₃₄₀, TC 42₅₂₇. + '2y3n6p7e,' + // #2615: 4 fonts: HK 49₇₆, JP 32₁₆₈, KR 80₃₄₀, TC 43₅₂₈. + '2y3n6p7f,' + // #2616: 3 fonts: HK 49₇₆, JP 32₁₆₈, SC 40₄₂₄. + '2y3n9v,' + // #2617: 3 fonts: HK 49₇₆, JP 32₁₆₈, SC 41₄₂₅. + '2y3n9w,' + // #2618: 4 fonts: HK 49₇₆, JP 32₁₆₈, SC 78₄₆₂, TC 43₅₂₈. + '2y3n11h2n,' + // #2619: 4 fonts: HK 49₇₆, JP 32₁₆₈, SC 79₄₆₃, TC 43₅₂₈. + '2y3n11i2m,' + // #2620: 4 fonts: HK 49₇₆, JP 32₁₆₈, SC 87₄₇₁, TC 42₅₂₇. + '2y3n11q2d,' + // #2621: 5 fonts: HK 49₇₆, JP 60₁₉₆, KR 80₃₄₀, SC 40₄₂₄, TC 42₅₂₇. + '2y4p5n3f3y,' + // #2622: 4 fonts: HK 49₇₆, JP 62₁₉₈, SC 40₄₂₄, TC 42₅₂₇. + '2y4r8r3y,' + // #2623: 5 fonts: HK 49₇₆, JP 63₁₉₉, KR 80₃₄₀, SC 40₄₂₄, TC 42₅₂₇. + '2y4s5k3f3y,' + // #2624: 4 fonts: HK 49₇₆, JP 85₂₂₁, SC 40₄₂₄, TC 42₅₂₇. + '2y5o7u3y,' + // #2625: 4 fonts: HK 49₇₆, JP 85₂₂₁, SC 88₄₇₂, TC 42₅₂₇. + '2y5o9q2c,' + // #2626: 5 fonts: HK 49₇₆, JP 98₂₃₄, KR 80₃₄₀, SC 40₄₂₄, TC 42₅₂₇. + '2y6b4b3f3y,' + // #2627: 3 fonts: HK 49₇₆, SC 85₄₆₉, TC 42₅₂₇. + '2y15c2f,' + // #2628: 3 fonts: HK 49₇₆, SC 87₄₇₁, TC 42₅₂₇. + '2y15e2d,' + // #2629: 5 fonts: HK 50₇₇, JP 32₁₆₈, KR 80₃₄₀, SC 77₄₆₁, TC 43₅₂₈. + '2z3m6p4q2o,' + // #2630: 5 fonts: HK 50₇₇, JP 32₁₆₈, KR 80₃₄₀, SC 78₄₆₂, TC 43₅₂₈. + '2z3m6p4r2n,' + // #2631: 5 fonts: HK 50₇₇, JP 32₁₆₈, KR 80₃₄₀, SC 78₄₆₂, TC 44₅₂₉. + '2z3m6p4r2o,' + // #2632: 5 fonts: HK 50₇₇, JP 32₁₆₈, KR 80₃₄₀, SC 80₄₆₄, TC 44₅₂₉. + '2z3m6p4t2m,' + // #2633: 3 fonts: HK 50₇₇, JP 32₁₆₈, SC 41₄₂₅. + '2z3m9w,' + // #2634: 3 fonts: HK 50₇₇, JP 32₁₆₈, SC 42₄₂₆. + '2z3m9x,' + // #2635: 4 fonts: HK 50₇₇, JP 32₁₆₈, SC 82₄₆₆, TC 43₅₂₈. + '2z3m11l2j,' + // #2636: 4 fonts: HK 50₇₇, JP 32₁₆₈, SC 85₄₆₉, TC 43₅₂₈. + '2z3m11o2g,' + // #2637: 4 fonts: HK 50₇₇, JP 32₁₆₈, SC 87₄₇₁, TC 44₅₂₉. + '2z3m11q2f,' + // #2638: 4 fonts: HK 50₇₇, JP 33₁₆₉, KR 80₃₄₀, TC 44₅₂₉. + '2z3n6o7g,' + // #2639: 5 fonts: HK 50₇₇, JP 68₂₀₄, KR 80₃₄₀, SC 41₄₂₅, TC 43₅₂₈. + '2z4w5f3g3y,' + // #2640: 4 fonts: HK 50₇₇, JP 70₂₀₆, SC 41₄₂₅, TC 43₅₂₈. + '2z4y8k3y,' + // #2641: 5 fonts: HK 50₇₇, JP 73₂₀₉, KR 80₃₄₀, SC 84₄₆₈, TC 44₅₂₉. + '2z5b5a4x2i,' + // #2642: 4 fonts: HK 50₇₇, KR 80₃₄₀, SC 41₄₂₅, TC 43₅₂₈. + '2z10c3g3y,' + // #2643: 3 fonts: HK 50₇₇, KR 80₃₄₀, TC 44₅₂₉. + '2z10c7g,' + // #2644: 2 fonts: HK 50₇₇, SC 42₄₂₆. + '2z13k,' + // #2645: 3 fonts: HK 50₇₇, SC 42₄₂₆, TC 44₅₂₉. + '2z13k3y,' + // #2646: 3 fonts: HK 50₇₇, SC 79₄₆₃, TC 44₅₂₉. + '2z14v2n,' + // #2647: 5 fonts: HK 51₇₈, JP 33₁₆₉, KR 81₃₄₁, SC 77₄₆₁, TC 45₅₃₀. + '3a3m6p4p2q,' + // #2648: 4 fonts: HK 51₇₈, JP 33₁₆₉, SC 77₄₆₁, TC 44₅₂₉. + '3a3m11f2p,' + // #2649: 4 fonts: HK 51₇₈, JP 33₁₆₉, SC 87₄₇₁, TC 44₅₂₉. + '3a3m11p2f,' + // #2650: 4 fonts: HK 51₇₈, JP 33₁₆₉, SC 89₄₇₃, TC 44₅₂₉. + '3a3m11r2d,' + // #2651: 5 fonts: HK 51₇₈, JP 62₁₉₈, KR 81₃₄₁, SC 43₄₂₇, TC 45₅₃₀. + '3a4p5m3h3y,' + // #2652: 5 fonts: HK 51₇₈, JP 65₂₀₁, KR 80₃₄₀, SC 42₄₂₆, TC 44₅₂₉. + '3a4s5i3h3y,' + // #2653: 4 fonts: HK 51₇₈, JP 83₂₁₉, SC 84₄₆₈, TC 44₅₂₉. + '3a5k9o2i,' + // #2654: 5 fonts: HK 51₇₈, JP 90₂₂₆, KR 81₃₄₁, SC 42₄₂₆, TC 45₅₃₀. + '3a5r4k3g3z,' + // #2655: 5 fonts: HK 51₇₈, JP 93₂₂₉, KR 81₃₄₁, SC 43₄₂₇, TC 45₅₃₀. + '3a5u4h3h3y,' + // #2656: 5 fonts: HK 51₇₈, JP 103₂₃₉, KR 80₃₄₀, SC 42₄₂₆, TC 44₅₂₉. + '3a6e3w3h3y,' + // #2657: 4 fonts: HK 51₇₈, JP 106₂₄₂, SC 43₄₂₇, TC 45₅₃₀. + '3a6h7c3y,' + // #2658: 3 fonts: HK 51₇₈, SC 92₄₇₆, TC 44₅₂₉. + '3a15h2a,' + // #2659: 5 fonts: HK 52₇₉, JP 33₁₆₉, KR 81₃₄₁, SC 79₄₆₃, TC 45₅₃₀. + '3b3l6p4r2o,' + // #2660: 4 fonts: HK 52₇₉, JP 33₁₆₉, KR 81₃₄₁, TC 46₅₃₁. + '3b3l6p7h,' + // #2661: 4 fonts: HK 52₇₉, JP 33₁₆₉, SC 78₄₆₂, TC 45₅₃₀. + '3b3l11g2p,' + // #2662: 4 fonts: HK 52₇₉, JP 33₁₆₉, SC 79₄₆₃, TC 45₅₃₀. + '3b3l11h2o,' + // #2663: 4 fonts: HK 52₇₉, JP 33₁₆₉, SC 82₄₆₆, TC 45₅₃₀. + '3b3l11k2l,' + // #2664: 3 fonts: HK 52₇₉, JP 33₁₆₉, TC 46₅₃₁. + '3b3l13x,' + // #2665: 5 fonts: HK 52₇₉, JP 34₁₇₀, KR 81₃₄₁, SC 43₄₂₇, TC 46₅₃₁. + '3b3m6o3h3z,' + // #2666: 4 fonts: HK 52₇₉, JP 73₂₀₉, SC 44₄₂₈, TC 46₅₃₁. + '3b4z8k3y,' + // #2667: 5 fonts: HK 52₇₉, JP 76₂₁₂, KR 81₃₄₁, SC 43₄₂₇, TC 46₅₃₁. + '3b5c4y3h3z,' + // #2668: 5 fonts: HK 52₇₉, JP 77₂₁₃, KR 81₃₄₁, SC 43₄₂₇, TC 45₅₃₀. + '3b5d4x3h3y,' + // #2669: 5 fonts: HK 52₇₉, JP 88₂₂₄, KR 81₃₄₁, SC 43₄₂₇, TC 45₅₃₀. + '3b5o4m3h3y,' + // #2670: 4 fonts: HK 52₇₉, JP 96₂₃₂, SC 44₄₂₈, TC 46₅₃₁. + '3b5w7n3y,' + // #2671: 4 fonts: HK 52₇₉, JP 110₂₄₆, SC 44₄₂₈, TC 46₅₃₁. + '3b6k6z3y,' + // #2672: 2 fonts: HK 52₇₉, KR 81₃₄₁. + '3b10b,' + // #2673: 4 fonts: HK 52₇₉, KR 81₃₄₁, SC 86₄₇₀, TC 45₅₃₀. + '3b10b4y2h,' + // #2674: 3 fonts: HK 52₇₉, SC 43₄₂₇, TC 46₅₃₁. + '3b13j3z,' + // #2675: 3 fonts: HK 52₇₉, SC 79₄₆₃, TC 45₅₃₀. + '3b14t2o,' + // #2676: 3 fonts: HK 52₇₉, SC 91₄₇₅, TC 45₅₃₀. + '3b15f2c,' + // #2677: 5 fonts: HK 53₈₀, JP 34₁₇₀, KR 82₃₄₂, SC 78₄₆₂, TC 47₅₃₂. + '3c3l6p4p2r,' + // #2678: 4 fonts: HK 53₈₀, JP 34₁₇₀, KR 82₃₄₂, TC 47₅₃₂. + '3c3l6p7h,' + // #2679: 4 fonts: HK 53₈₀, JP 34₁₇₀, SC 81₄₆₅, TC 46₅₃₁. + '3c3l11i2n,' + // #2680: 4 fonts: HK 53₈₀, JP 35₁₇₁, KR 82₃₄₂, TC 47₅₃₂. + '3c3m6o7h,' + // #2681: 4 fonts: HK 53₈₀, JP 35₁₇₁, SC 79₄₆₃, TC 47₅₃₂. + '3c3m11f2q,' + // #2682: 5 fonts: HK 53₈₀, JP 62₁₉₈, KR 82₃₄₂, SC 78₄₆₂, TC 46₅₃₁. + '3c4n5n4p2q,' + // #2683: 5 fonts: HK 53₈₀, JP 63₁₉₉, KR 82₃₄₂, SC 45₄₂₉, TC 47₅₃₂. + '3c4o5m3i3y,' + // #2684: 5 fonts: HK 53₈₀, JP 64₂₀₀, KR 82₃₄₂, SC 45₄₂₉, TC 47₅₃₂. + '3c4p5l3i3y,' + // #2685: 4 fonts: HK 53₈₀, JP 64₂₀₀, SC 45₄₂₉, TC 47₅₃₂. + '3c4p8u3y,' + // #2686: 4 fonts: HK 53₈₀, JP 65₂₀₁, SC 44₄₂₈, TC 47₅₃₂. + '3c4q8s3z,' + // #2687: 4 fonts: HK 53₈₀, JP 66₂₀₂, SC 45₄₂₉, TC 47₅₃₂. + '3c4r8s3y,' + // #2688: 4 fonts: HK 53₈₀, JP 70₂₀₆, SC 44₄₂₈, TC 47₅₃₂. + '3c4v8n3z,' + // #2689: 5 fonts: HK 53₈₀, JP 80₂₁₆, KR 82₃₄₂, SC 45₄₂₉, TC 47₅₃₂. + '3c5f4v3i3y,' + // #2690: 4 fonts: HK 53₈₀, JP 106₂₄₂, SC 44₄₂₈, TC 47₅₃₂. + '3c6f7d3z,' + // #2691: 2 fonts: HK 53₈₀, KR 82₃₄₂. + '3c10b,' + // #2692: 2 fonts: HK 53₈₀, SC 44₄₂₈. + '3c13j,' + // #2693: 3 fonts: HK 53₈₀, SC 44₄₂₈, TC 47₅₃₂. + '3c13j3z,' + // #2694: 3 fonts: HK 54₈₁, JP 35₁₇₁, KR 82₃₄₂. + '3d3l6o,' + // #2695: 5 fonts: HK 54₈₁, JP 35₁₇₁, KR 82₃₄₂, SC 77₄₆₁, TC 47₅₃₂. + '3d3l6o4o2s,' + // #2696: 4 fonts: HK 54₈₁, JP 35₁₇₁, KR 82₃₄₂, TC 47₅₃₂. + '3d3l6o7h,' + // #2697: 4 fonts: HK 54₈₁, JP 35₁₇₁, KR 82₃₄₂, TC 48₅₃₃. + '3d3l6o7i,' + // #2698: 3 fonts: HK 54₈₁, JP 35₁₇₁, SC 45₄₂₉. + '3d3l9x,' + // #2699: 3 fonts: HK 54₈₁, JP 35₁₇₁, SC 46₄₃₀. + '3d3l9y,' + // #2700: 4 fonts: HK 54₈₁, JP 35₁₇₁, SC 77₄₆₁, TC 48₅₃₃. + '3d3l11d2t,' + // #2701: 4 fonts: HK 54₈₁, JP 35₁₇₁, SC 91₄₇₅, TC 48₅₃₃. + '3d3l11r2f,' + // #2702: 4 fonts: HK 54₈₁, JP 36₁₇₂, KR 82₃₄₂, TC 48₅₃₃. + '3d3m6n7i,' + // #2703: 4 fonts: HK 54₈₁, JP 60₁₉₆, KR 82₃₄₂, TC 47₅₃₂. + '3d4k5p7h,' + // #2704: 4 fonts: HK 54₈₁, JP 66₂₀₂, SC 46₄₃₀, TC 48₅₃₃. + '3d4q8t3y,' + // #2705: 5 fonts: HK 54₈₁, JP 70₂₀₆, KR 82₃₄₂, SC 46₄₃₀, TC 48₅₃₃. + '3d4u5f3j3y,' + // #2706: 5 fonts: HK 54₈₁, JP 71₂₀₇, KR 82₃₄₂, SC 80₄₆₄, TC 48₅₃₃. + '3d4v5e4r2q,' + // #2707: 5 fonts: HK 54₈₁, JP 74₂₁₀, KR 82₃₄₂, SC 45₄₂₉, TC 47₅₃₂. + '3d4y5b3i3y,' + // #2708: 5 fonts: HK 54₈₁, JP 84₂₂₀, KR 82₃₄₂, SC 46₄₃₀, TC 48₅₃₃. + '3d5i4r3j3y,' + // #2709: 5 fonts: HK 54₈₁, JP 90₂₂₆, KR 82₃₄₂, SC 46₄₃₀, TC 48₅₃₃. + '3d5o4l3j3y,' + // #2710: 4 fonts: HK 54₈₁, KR 82₃₄₂, SC 46₄₃₀, TC 48₅₃₃. + '3d10a3j3y,' + // #2711: 3 fonts: HK 54₈₁, KR 82₃₄₂, TC 48₅₃₃. + '3d10a7i,' + // #2712: 2 fonts: HK 54₈₁, SC 45₄₂₉. + '3d13j,' + // #2713: 3 fonts: HK 54₈₁, SC 89₄₇₃, TC 48₅₃₃. + '3d15b2h,' + // #2714: 4 fonts: HK 55₈₂, JP 36₁₇₂, KR 82₃₄₂, TC 48₅₃₃. + '3e3l6n7i,' + // #2715: 3 fonts: HK 55₈₂, JP 36₁₇₂, KR 83₃₄₃. + '3e3l6o,' + // #2716: 5 fonts: HK 55₈₂, JP 36₁₇₂, KR 83₃₄₃, SC 78₄₆₂, TC 49₅₃₄. + '3e3l6o4o2t,' + // #2717: 4 fonts: HK 55₈₂, JP 36₁₇₂, KR 83₃₄₃, TC 49₅₃₄. + '3e3l6o7i,' + // #2718: 3 fonts: HK 55₈₂, JP 36₁₇₂, SC 46₄₃₀. + '3e3l9x,' + // #2719: 4 fonts: HK 55₈₂, JP 36₁₇₂, SC 81₄₆₅, TC 49₅₃₄. + '3e3l11g2q,' + // #2720: 4 fonts: HK 55₈₂, JP 36₁₇₂, SC 87₄₇₁, TC 48₅₃₃. + '3e3l11m2j,' + // #2721: 5 fonts: HK 55₈₂, JP 60₁₉₆, KR 83₃₄₃, SC 47₄₃₁, TC 49₅₃₄. + '3e4j5q3j3y,' + // #2722: 5 fonts: HK 55₈₂, JP 62₁₉₈, KR 83₃₄₃, SC 47₄₃₁, TC 49₅₃₄. + '3e4l5o3j3y,' + // #2723: 5 fonts: HK 55₈₂, JP 62₁₉₈, KR 83₃₄₃, SC 77₄₆₁, TC 49₅₃₄. + '3e4l5o4n2u,' + // #2724: 4 fonts: HK 55₈₂, JP 63₁₉₉, SC 46₄₃₀, TC 49₅₃₄. + '3e4m8w3z,' + // #2725: 5 fonts: HK 55₈₂, JP 77₂₁₃, KR 83₃₄₃, SC 47₄₃₁, TC 49₅₃₄. + '3e5a4z3j3y,' + // #2726: 4 fonts: HK 55₈₂, JP 87₂₂₃, SC 46₄₃₀, TC 49₅₃₄. + '3e5k7y3z,' + // #2727: 4 fonts: HK 55₈₂, JP 96₂₃₂, SC 46₄₃₀, TC 48₅₃₃. + '3e5t7p3y,' + // #2728: 4 fonts: HK 55₈₂, JP 99₂₃₅, SC 46₄₃₀, TC 48₅₃₃. + '3e5w7m3y,' + // #2729: 2 fonts: HK 55₈₂, TC 48₅₃₃. + '3e17i,' + // #2730: 4 fonts: HK 56₈₃, JP 36₁₇₂, KR 83₃₄₃, TC 49₅₃₄. + '3f3k6o7i,' + // #2731: 3 fonts: HK 56₈₃, JP 36₁₇₂, SC 47₄₃₁. + '3f3k9y,' + // #2732: 4 fonts: HK 56₈₃, JP 36₁₇₂, SC 77₄₆₁, TC 50₅₃₅. + '3f3k11c2v,' + // #2733: 4 fonts: HK 56₈₃, JP 37₁₇₃, KR 83₃₄₃, SC 47₄₃₁. + '3f3l6n3j,' + // #2734: 5 fonts: HK 56₈₃, JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂, TC 51₅₃₆. + '3f3l6o3j3z,' + // #2735: 3 fonts: HK 56₈₃, JP 37₁₇₃, SC 47₄₃₁. + '3f3l9x,' + // #2736: 4 fonts: HK 56₈₃, JP 37₁₇₃, SC 82₄₆₆, TC 50₅₃₅. + '3f3l11g2q,' + // #2737: 4 fonts: HK 56₈₃, JP 37₁₇₃, SC 85₄₆₉, TC 50₅₃₅. + '3f3l11j2n,' + // #2738: 5 fonts: HK 56₈₃, JP 63₁₉₉, KR 83₃₄₃, SC 47₄₃₁, TC 50₅₃₅. + '3f4l5n3j3z,' + // #2739: 5 fonts: HK 56₈₃, JP 80₂₁₆, KR 83₃₄₃, SC 86₄₇₀, TC 50₅₃₅. + '3f5c4w4w2m,' + // #2740: 4 fonts: HK 56₈₃, JP 83₂₁₉, SC 48₄₃₂, TC 50₅₃₅. + '3f5f8e3y,' + // #2741: 4 fonts: HK 56₈₃, KR 83₃₄₃, SC 48₄₃₂, TC 50₅₃₅. + '3f9z3k3y,' + // #2742: 4 fonts: HK 56₈₃, KR 84₃₄₄, SC 48₄₃₂, TC 50₅₃₅. + '3f10a3j3y,' + // #2743: 3 fonts: HK 56₈₃, KR 84₃₄₄, TC 50₅₃₅. + '3f10a7i,' + // #2744: 2 fonts: HK 56₈₃, SC 47₄₃₁. + '3f13j,' + // #2745: 3 fonts: HK 56₈₃, SC 47₄₃₁, TC 50₅₃₅. + '3f13j3z,' + // #2746: 3 fonts: HK 56₈₃, SC 48₄₃₂, TC 51₅₃₆. + '3f13k3z,' + // #2747: 3 fonts: HK 56₈₃, SC 81₄₆₅, TC 50₅₃₅. + '3f14r2r,' + // #2748: 3 fonts: HK 56₈₃, SC 89₄₇₃, TC 50₅₃₅. + '3f14z2j,' + // #2749: 2 fonts: HK 56₈₃, TC 49₅₃₄. + '3f17i,' + // #2750: 3 fonts: HK 57₈₄, JP 37₁₇₃, KR 84₃₄₄. + '3g3k6o,' + // #2751: 5 fonts: HK 57₈₄, JP 37₁₇₃, KR 84₃₄₄, SC 77₄₆₁, TC 51₅₃₆. + '3g3k6o4m2w,' + // #2752: 2 fonts: HK 57₈₄, JP 38₁₇₄. + '3g3l,' + // #2753: 5 fonts: HK 57₈₄, JP 38₁₇₄, KR 84₃₄₄, SC 86₄₇₀, TC 51₅₃₆. + '3g3l6n4v2n,' + // #2754: 4 fonts: HK 57₈₄, JP 38₁₇₄, KR 84₃₄₄, TC 51₅₃₆. + '3g3l6n7j,' + // #2755: 5 fonts: HK 57₈₄, JP 60₁₉₆, KR 84₃₄₄, SC 49₄₃₃, TC 51₅₃₆. + '3g4h5r3k3y,' + // #2756: 5 fonts: HK 57₈₄, JP 72₂₀₈, KR 84₃₄₄, SC 48₄₃₂, TC 51₅₃₆. + '3g4t5f3j3z,' + // #2757: 5 fonts: HK 57₈₄, JP 73₂₀₉, KR 84₃₄₄, SC 49₄₃₃, TC 51₅₃₆. + '3g4u5e3k3y,' + // #2758: 5 fonts: HK 57₈₄, JP 108₂₄₄, KR 84₃₄₄, SC 88₄₇₂, TC 51₅₃₆. + '3g6d3v4x2l,' + // #2759: 5 fonts: HK 58₈₅, JP 38₁₇₄, KR 84₃₄₄, SC 50₄₃₄, TC 52₅₃₇. + '3h3k6n3l3y,' + // #2760: 3 fonts: HK 58₈₅, JP 38₁₇₄, KR 85₃₄₅. + '3h3k6o,' + // #2761: 4 fonts: HK 58₈₅, JP 38₁₇₄, SC 77₄₆₁, TC 52₅₃₇. + '3h3k11a2x,' + // #2762: 4 fonts: HK 58₈₅, JP 38₁₇₄, SC 78₄₆₂, TC 52₅₃₇. + '3h3k11b2w,' + // #2763: 4 fonts: HK 58₈₅, JP 38₁₇₄, SC 80₄₆₄, TC 52₅₃₇. + '3h3k11d2u,' + // #2764: 4 fonts: HK 58₈₅, JP 38₁₇₄, SC 80₄₆₄, TC 53₅₃₈. + '3h3k11d2v,' + // #2765: 4 fonts: HK 58₈₅, JP 38₁₇₄, SC 89₄₇₃, TC 53₅₃₈. + '3h3k11m2m,' + // #2766: 3 fonts: HK 58₈₅, JP 38₁₇₄, TC 53₅₃₈. + '3h3k13z,' + // #2767: 4 fonts: HK 58₈₅, JP 61₁₉₇, SC 50₄₃₄, TC 52₅₃₇. + '3h4h9c3y,' + // #2768: 5 fonts: HK 58₈₅, JP 64₂₀₀, KR 85₃₄₅, SC 50₄₃₄, TC 53₅₃₈. + '3h4k5o3k3z,' + // #2769: 5 fonts: HK 58₈₅, JP 65₂₀₁, KR 85₃₄₅, SC 50₄₃₄, TC 53₅₃₈. + '3h4l5n3k3z,' + // #2770: 5 fonts: HK 58₈₅, JP 66₂₀₂, KR 85₃₄₅, SC 50₄₃₄, TC 52₅₃₇. + '3h4m5m3k3y,' + // #2771: 5 fonts: HK 58₈₅, JP 68₂₀₄, KR 85₃₄₅, SC 78₄₆₂, TC 53₅₃₈. + '3h4o5k4m2x,' + // #2772: 3 fonts: HK 58₈₅, JP 68₂₀₄, TC 52₅₃₇. + '3h4o12u,' + // #2773: 4 fonts: HK 58₈₅, JP 89₂₂₅, SC 50₄₃₄, TC 53₅₃₈. + '3h5j8a3z,' + // #2774: 4 fonts: HK 58₈₅, JP 97₂₃₃, SC 49₄₃₃, TC 52₅₃₇. + '3h5r7r3z,' + // #2775: 4 fonts: HK 58₈₅, JP 104₂₄₀, SC 91₄₇₅, TC 53₅₃₈. + '3h5y9a2k,' + // #2776: 3 fonts: HK 58₈₅, SC 50₄₃₄, TC 53₅₃₈. + '3h13k3z,' + // #2777: 3 fonts: HK 58₈₅, SC 77₄₆₁, TC 52₅₃₇. + '3h14l2x,' + // #2778: 3 fonts: HK 58₈₅, SC 89₄₇₃, TC 53₅₃₈. + '3h14x2m,' + // #2779: 3 fonts: HK 58₈₅, SC 90₄₇₄, TC 53₅₃₈. + '3h14y2l,' + // #2780: 3 fonts: HK 59₈₆, JP 39₁₇₅, KR 86₃₄₆. + '3i3k6o,' + // #2781: 3 fonts: HK 59₈₆, JP 39₁₇₅, SC 51₄₃₅. + '3i3k9z,' + // #2782: 4 fonts: HK 59₈₆, JP 39₁₇₅, SC 78₄₆₂, TC 54₅₃₉. + '3i3k11a2y,' + // #2783: 4 fonts: HK 59₈₆, JP 39₁₇₅, SC 85₄₆₉, TC 53₅₃₈. + '3i3k11h2q,' + // #2784: 5 fonts: HK 59₈₆, JP 62₁₉₈, KR 85₃₄₅, SC 51₄₃₅, TC 53₅₃₈. + '3i4h5q3l3y,' + // #2785: 5 fonts: HK 59₈₆, JP 71₂₀₇, KR 85₃₄₅, SC 51₄₃₅, TC 54₅₃₉. + '3i4q5h3l3z,' + // #2786: 5 fonts: HK 59₈₆, JP 81₂₁₇, KR 85₃₄₅, SC 50₄₃₄, TC 53₅₃₈. + '3i5a4x3k3z,' + // #2787: 5 fonts: HK 59₈₆, JP 86₂₂₂, KR 85₃₄₅, SC 51₄₃₅, TC 54₅₃₉. + '3i5f4s3l3z,' + // #2788: 4 fonts: HK 59₈₆, JP 91₂₂₇, SC 51₄₃₅, TC 53₅₃₈. + '3i5k7z3y,' + // #2789: 5 fonts: HK 59₈₆, JP 106₂₄₂, KR 85₃₄₅, SC 50₄₃₄, TC 53₅₃₈. + '3i5z3y3k3z,' + // #2790: 3 fonts: HK 59₈₆, SC 87₄₇₁, TC 53₅₃₈. + '3i14u2o,' + // #2791: 5 fonts: HK 60₈₇, JP 39₁₇₅, KR 86₃₄₆, SC 90₄₇₄, TC 54₅₃₉. + '3j3j6o4x2m,' + // #2792: 4 fonts: HK 60₈₇, JP 39₁₇₅, KR 86₃₄₆, TC 54₅₃₉. + '3j3j6o7k,' + // #2793: 3 fonts: HK 60₈₇, JP 40₁₇₆, KR 86₃₄₆. + '3j3k6n,' + // #2794: 4 fonts: HK 60₈₇, JP 40₁₇₆, KR 86₃₄₆, TC 54₅₃₉. + '3j3k6n7k,' + // #2795: 4 fonts: HK 60₈₇, JP 40₁₇₆, SC 83₄₆₇, TC 54₅₃₉. + '3j3k11e2t,' + // #2796: 4 fonts: HK 60₈₇, JP 40₁₇₆, SC 91₄₇₅, TC 54₅₃₉. + '3j3k11m2l,' + // #2797: 5 fonts: HK 60₈₇, JP 66₂₀₂, KR 86₃₄₆, SC 52₄₃₆, TC 55₅₄₀. + '3j4k5n3l3z,' + // #2798: 5 fonts: HK 60₈₇, JP 69₂₀₅, KR 86₃₄₆, SC 82₄₆₆, TC 55₅₄₀. + '3j4n5k4p2v,' + // #2799: 4 fonts: HK 60₈₇, JP 69₂₀₅, SC 83₄₆₇, TC 54₅₃₉. + '3j4n10b2t,' + // #2800: 5 fonts: HK 60₈₇, JP 72₂₀₈, KR 86₃₄₆, SC 52₄₃₆, TC 55₅₄₀. + '3j4q5h3l3z,' + // #2801: 4 fonts: HK 60₈₇, JP 72₂₀₈, SC 87₄₇₁, TC 55₅₄₀. + '3j4q10c2q,' + // #2802: 4 fonts: HK 60₈₇, JP 73₂₀₉, SC 52₄₃₆, TC 54₅₃₉. + '3j4r8s3y,' + // #2803: 5 fonts: HK 60₈₇, JP 78₂₁₄, KR 86₃₄₆, SC 51₄₃₅, TC 54₅₃₉. + '3j4w5b3k3z,' + // #2804: 4 fonts: HK 60₈₇, JP 79₂₁₅, SC 86₄₇₀, TC 54₅₃₉. + '3j4x9u2q,' + // #2805: 5 fonts: HK 60₈₇, JP 81₂₁₇, KR 86₃₄₆, SC 52₄₃₆, TC 54₅₃₉. + '3j4z4y3l3y,' + // #2806: 4 fonts: HK 60₈₇, JP 104₂₄₀, SC 52₄₃₆, TC 55₅₄₀. + '3j5w7n3z,' + // #2807: 4 fonts: HK 60₈₇, JP 104₂₄₀, SC 90₄₇₄, TC 55₅₄₀. + '3j5w8z2n,' + // #2808: 2 fonts: HK 60₈₇, SC 52₄₃₆. + '3j13k,' + // #2809: 3 fonts: HK 60₈₇, SC 82₄₆₆, TC 54₅₃₉. + '3j14o2u,' + // #2810: 3 fonts: HK 60₈₇, SC 90₄₇₄, TC 55₅₄₀. + '3j14w2n,' + // #2811: 2 fonts: HK 61₈₈, JP 40₁₇₆. + '3k3j,' + // #2812: 3 fonts: HK 61₈₈, JP 40₁₇₆, TC 56₅₄₁. + '3k3j14a,' + // #2813: 5 fonts: HK 61₈₈, JP 41₁₇₇, KR 86₃₄₆, SC 77₄₆₁, TC 56₅₄₁. + '3k3k6m4k3b,' + // #2814: 4 fonts: HK 61₈₈, JP 41₁₇₇, KR 87₃₄₇, TC 56₅₄₁. + '3k3k6n7l,' + // #2815: 4 fonts: HK 61₈₈, JP 41₁₇₇, SC 79₄₆₃, TC 56₅₄₁. + '3k3k10z2z,' + // #2816: 4 fonts: HK 61₈₈, JP 41₁₇₇, SC 83₄₆₇, TC 56₅₄₁. + '3k3k11d2v,' + // #2817: 4 fonts: HK 61₈₈, JP 41₁₇₇, SC 91₄₇₅, TC 56₅₄₁. + '3k3k11l2n,' + // #2818: 5 fonts: HK 61₈₈, JP 63₁₉₉, KR 86₃₄₆, SC 53₄₃₇, TC 56₅₄₁. + '3k4g5q3m3z,' + // #2819: 5 fonts: HK 61₈₈, JP 67₂₀₃, KR 86₃₄₆, SC 52₄₃₆, TC 55₅₄₀. + '3k4k5m3l3z,' + // #2820: 5 fonts: HK 61₈₈, JP 68₂₀₄, KR 87₃₄₇, SC 53₄₃₇, TC 56₅₄₁. + '3k4l5m3l3z,' + // #2821: 4 fonts: HK 61₈₈, JP 74₂₁₀, SC 53₄₃₇, TC 56₅₄₁. + '3k4r8s3z,' + // #2822: 5 fonts: HK 61₈₈, JP 76₂₁₂, KR 87₃₄₇, SC 53₄₃₇, TC 56₅₄₁. + '3k4t5e3l3z,' + // #2823: 5 fonts: HK 61₈₈, JP 77₂₁₃, KR 87₃₄₇, SC 53₄₃₇, TC 56₅₄₁. + '3k4u5d3l3z,' + // #2824: 4 fonts: HK 61₈₈, JP 84₂₂₀, SC 84₄₆₈, TC 56₅₄₁. + '3k5b9n2u,' + // #2825: 5 fonts: HK 61₈₈, JP 85₂₂₁, KR 86₃₄₆, SC 53₄₃₇, TC 56₅₄₁. + '3k5c4u3m3z,' + // #2826: 5 fonts: HK 61₈₈, JP 100₂₃₆, KR 87₃₄₇, SC 53₄₃₇, TC 56₅₄₁. + '3k5r4g3l3z,' + // #2827: 4 fonts: HK 61₈₈, JP 115₂₅₁, SC 54₄₃₈, TC 56₅₄₁. + '3k6g7e3y,' + // #2828: 2 fonts: HK 61₈₈, SC 52₄₃₆. + '3k13j,' + // #2829: 3 fonts: HK 61₈₈, SC 53₄₃₇, TC 55₅₄₀. + '3k13k3y,' + // #2830: 3 fonts: HK 61₈₈, SC 54₄₃₈, TC 56₅₄₁. + '3k13l3y,' + // #2831: 3 fonts: HK 61₈₈, SC 87₄₇₁, TC 56₅₄₁. + '3k14s2r,' + // #2832: 3 fonts: HK 61₈₈, SC 91₄₇₅, TC 56₅₄₁. + '3k14w2n,' + // #2833: 5 fonts: HK 62₈₉, JP 41₁₇₇, KR 87₃₄₇, SC 77₄₆₁, TC 57₅₄₂. + '3l3j6n4j3c,' + // #2834: 5 fonts: HK 62₈₉, JP 41₁₇₇, KR 87₃₄₇, SC 79₄₆₃, TC 56₅₄₁. + '3l3j6n4l2z,' + // #2835: 3 fonts: HK 62₈₉, JP 41₁₇₇, SC 54₄₃₈. + '3l3j10a,' + // #2836: 4 fonts: HK 62₈₉, JP 41₁₇₇, SC 78₄₆₂, TC 57₅₄₂. + '3l3j10y3b,' + // #2837: 4 fonts: HK 62₈₉, JP 41₁₇₇, SC 82₄₆₆, TC 57₅₄₂. + '3l3j11c2x,' + // #2838: 2 fonts: HK 62₈₉, JP 42₁₇₈. + '3l3k,' + // #2839: 5 fonts: HK 62₈₉, JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉, TC 57₅₄₂. + '3l3k6m3n3y,' + // #2840: 3 fonts: HK 62₈₉, JP 42₁₇₈, SC 54₄₃₈. + '3l3k9z,' + // #2841: 5 fonts: HK 62₈₉, JP 69₂₀₅, KR 87₃₄₇, SC 54₄₃₈, TC 57₅₄₂. + '3l4l5l3m3z,' + // #2842: 5 fonts: HK 62₈₉, JP 82₂₁₈, KR 87₃₄₇, SC 54₄₃₈, TC 56₅₄₁. + '3l4y4y3m3y,' + // #2843: 5 fonts: HK 62₈₉, JP 89₂₂₅, KR 87₃₄₇, SC 54₄₃₈, TC 57₅₄₂. + '3l5f4r3m3z,' + // #2844: 3 fonts: HK 62₈₉, KR 87₃₄₇, TC 57₅₄₂. + '3l9x7m,' + // #2845: 3 fonts: HK 62₈₉, SC 54₄₃₈, TC 56₅₄₁. + '3l13k3y,' + // #2846: 3 fonts: HK 62₈₉, SC 84₄₆₈, TC 56₅₄₁. + '3l14o2u,' + // #2847: 5 fonts: HK 63₉₀, JP 42₁₇₈, KR 88₃₄₈, SC 85₄₆₉, TC 58₅₄₃. + '3m3j6n4q2v,' + // #2848: 4 fonts: HK 63₉₀, JP 42₁₇₈, SC 84₄₆₈, TC 58₅₄₃. + '3m3j11d2w,' + // #2849: 3 fonts: HK 63₉₀, JP 43₁₇₉, KR 88₃₄₈. + '3m3k6m,' + // #2850: 5 fonts: HK 63₉₀, JP 43₁₇₉, KR 88₃₄₈, SC 78₄₆₂, TC 59₅₄₄. + '3m3k6m4j3d,' + // #2851: 4 fonts: HK 63₉₀, JP 43₁₇₉, KR 88₃₄₈, TC 58₅₄₃. + '3m3k6m7m,' + // #2852: 4 fonts: HK 63₉₀, JP 43₁₇₉, SC 56₄₄₀, TC 58₅₄₃. + '3m3k10a3y,' + // #2853: 4 fonts: HK 63₉₀, JP 43₁₇₉, SC 78₄₆₂, TC 59₅₄₄. + '3m3k10w3d,' + // #2854: 4 fonts: HK 63₉₀, JP 43₁₇₉, SC 91₄₇₅, TC 59₅₄₄. + '3m3k11j2q,' + // #2855: 3 fonts: HK 63₉₀, JP 43₁₇₉, TC 59₅₄₄. + '3m3k14a,' + // #2856: 5 fonts: HK 63₉₀, JP 62₁₉₈, KR 88₃₄₈, SC 56₄₄₀, TC 58₅₄₃. + '3m4d5t3n3y,' + // #2857: 5 fonts: HK 63₉₀, JP 71₂₀₇, KR 87₃₄₇, SC 55₄₃₉, TC 58₅₄₃. + '3m4m5j3n3z,' + // #2858: 4 fonts: HK 63₉₀, JP 80₂₁₆, SC 56₄₄₀, TC 58₅₄₃. + '3m4v8p3y,' + // #2859: 4 fonts: HK 63₉₀, JP 97₂₃₃, SC 87₄₇₁, TC 58₅₄₃. + '3m5m9d2t,' + // #2860: 4 fonts: HK 63₉₀, JP 109₂₄₅, SC 89₄₇₃, TC 58₅₄₃. + '3m5y8t2r,' + // #2861: 4 fonts: HK 63₉₀, JP 115₂₅₁, SC 56₄₄₀, TC 58₅₄₃. + '3m6e7g3y,' + // #2862: 3 fonts: HK 63₉₀, SC 56₄₄₀, TC 59₅₄₄. + '3m13l3z,' + // #2863: 3 fonts: HK 63₉₀, SC 78₄₆₂, TC 58₅₄₃. + '3m14h3c,' + // #2864: 3 fonts: HK 63₉₀, SC 80₄₆₄, TC 58₅₄₃. + '3m14j3a,' + // #2865: 3 fonts: HK 63₉₀, SC 83₄₆₇, TC 58₅₄₃. + '3m14m2x,' + // #2866: 2 fonts: HK 63₉₀, TC 59₅₄₄. + '3m17l,' + // #2867: 5 fonts: HK 64₉₁, JP 43₁₇₉, KR 88₃₄₈, SC 57₄₄₁, TC 59₅₄₄. + '3n3j6m3o3y,' + // #2868: 5 fonts: HK 64₉₁, JP 43₁₇₉, KR 88₃₄₈, SC 57₄₄₁, TC 60₅₄₅. + '3n3j6m3o3z,' + // #2869: 5 fonts: HK 64₉₁, JP 43₁₇₉, KR 88₃₄₈, SC 77₄₆₁, TC 59₅₄₄. + '3n3j6m4i3e,' + // #2870: 3 fonts: HK 64₉₁, JP 43₁₇₉, SC 57₄₄₁. + '3n3j10b,' + // #2871: 4 fonts: HK 64₉₁, JP 43₁₇₉, SC 77₄₆₁, TC 59₅₄₄. + '3n3j10v3e,' + // #2872: 4 fonts: HK 64₉₁, JP 43₁₇₉, SC 82₄₆₆, TC 59₅₄₄. + '3n3j11a2z,' + // #2873: 3 fonts: HK 64₉₁, JP 43₁₇₉, TC 60₅₄₅. + '3n3j14b,' + // #2874: 5 fonts: HK 64₉₁, JP 60₁₉₆, KR 88₃₄₈, SC 57₄₄₁, TC 60₅₄₅. + '3n4a5v3o3z,' + // #2875: 5 fonts: HK 64₉₁, JP 72₂₀₈, KR 88₃₄₈, SC 78₄₆₂, TC 59₅₄₄. + '3n4m5j4j3d,' + // #2876: 2 fonts: HK 64₉₁, SC 57₄₄₁. + '3n13l,' + // #2877: 5 fonts: HK 65₉₂, JP 43₁₇₉, KR 89₃₄₉, SC 57₄₄₁, TC 60₅₄₅. + '3o3i6n3n3z,' + // #2878: 5 fonts: HK 65₉₂, JP 44₁₈₀, KR 89₃₄₉, SC 57₄₄₁, TC 60₅₄₅. + '3o3j6m3n3z,' + // #2879: 5 fonts: HK 65₉₂, JP 44₁₈₀, KR 89₃₄₉, SC 79₄₆₃, TC 60₅₄₅. + '3o3j6m4j3d,' + // #2880: 4 fonts: HK 65₉₂, JP 44₁₈₀, KR 89₃₄₉, TC 61₅₄₆. + '3o3j6m7o,' + // #2881: 3 fonts: HK 65₉₂, JP 44₁₈₀, SC 58₄₄₂. + '3o3j10b,' + // #2882: 4 fonts: HK 65₉₂, JP 44₁₈₀, SC 86₄₇₀, TC 60₅₄₅. + '3o3j11d2w,' + // #2883: 4 fonts: HK 65₉₂, JP 44₁₈₀, SC 86₄₇₀, TC 61₅₄₆. + '3o3j11d2x,' + // #2884: 4 fonts: HK 65₉₂, JP 44₁₈₀, SC 91₄₇₅, TC 60₅₄₅. + '3o3j11i2r,' + // #2885: 5 fonts: HK 65₉₂, JP 64₂₀₀, KR 89₃₄₉, SC 58₄₄₂, TC 60₅₄₅. + '3o4d5s3o3y,' + // #2886: 5 fonts: HK 65₉₂, JP 67₂₀₃, KR 88₃₄₈, SC 57₄₄₁, TC 60₅₄₅. + '3o4g5o3o3z,' + // #2887: 5 fonts: HK 65₉₂, JP 73₂₀₉, KR 88₃₄₈, SC 77₄₆₁, TC 60₅₄₅. + '3o4m5i4i3f,' + // #2888: 5 fonts: HK 65₉₂, JP 74₂₁₀, KR 89₃₄₉, SC 57₄₄₁, TC 60₅₄₅. + '3o4n5i3n3z,' + // #2889: 5 fonts: HK 65₉₂, JP 90₂₂₆, KR 88₃₄₈, SC 77₄₆₁, TC 60₅₄₅. + '3o5d4r4i3f,' + // #2890: 5 fonts: HK 65₉₂, JP 93₂₂₉, KR 89₃₄₉, SC 78₄₆₂, TC 60₅₄₅. + '3o5g4p4i3e,' + // #2891: 4 fonts: HK 65₉₂, JP 95₂₃₁, SC 57₄₄₁, TC 60₅₄₅. + '3o5i8b3z,' + // #2892: 5 fonts: HK 65₉₂, JP 98₂₃₄, KR 88₃₄₈, SC 57₄₄₁, TC 60₅₄₅. + '3o5l4j3o3z,' + // #2893: 4 fonts: HK 65₉₂, JP 114₂₅₀, SC 58₄₄₂, TC 61₅₄₆. + '3o6b7j3z,' + // #2894: 3 fonts: HK 65₉₂, SC 58₄₄₂, TC 60₅₄₅. + '3o13l3y,' + // #2895: 5 fonts: HK 66₉₃, JP 44₁₈₀, KR 89₃₄₉, SC 79₄₆₃, TC 61₅₄₆. + '3p3i6m4j3e,' + // #2896: 4 fonts: HK 66₉₃, JP 45₁₈₁, SC 77₄₆₁, TC 62₅₄₇. + '3p3j10t3h,' + // #2897: 4 fonts: HK 66₉₃, JP 45₁₈₁, SC 79₄₆₃, TC 62₅₄₇. + '3p3j10v3f,' + // #2898: 3 fonts: HK 66₉₃, JP 45₁₈₁, TC 62₅₄₇. + '3p3j14b,' + // #2899: 5 fonts: HK 66₉₃, JP 80₂₁₆, KR 89₃₄₉, SC 58₄₄₂, TC 61₅₄₆. + '3p4s5c3o3z,' + // #2900: 5 fonts: HK 66₉₃, JP 90₂₂₆, KR 89₃₄₉, SC 58₄₄₂, TC 61₅₄₆. + '3p5c4s3o3z,' + // #2901: 5 fonts: HK 66₉₃, JP 91₂₂₇, KR 89₃₄₉, SC 59₄₄₃, TC 61₅₄₆. + '3p5d4r3p3y,' + // #2902: 4 fonts: HK 66₉₃, JP 93₂₂₉, SC 58₄₄₂, TC 61₅₄₆. + '3p5f8e3z,' + // #2903: 5 fonts: HK 66₉₃, JP 96₂₃₂, KR 89₃₄₉, SC 58₄₄₂, TC 61₅₄₆. + '3p5i4m3o3z,' + // #2904: 5 fonts: HK 66₉₃, JP 100₂₃₆, KR 89₃₄₉, SC 59₄₄₃, TC 61₅₄₆. + '3p5m4i3p3y,' + // #2905: 3 fonts: HK 66₉₃, SC 59₄₄₃, TC 62₅₄₇. + '3p13l3z,' + // #2906: 3 fonts: HK 66₉₃, SC 89₄₇₃, TC 61₅₄₆. + '3p14p2u,' + // #2907: 3 fonts: HK 66₉₃, SC 92₄₇₆, TC 62₅₄₇. + '3p14s2s,' + // #2908: 5 fonts: HK 67₉₄, JP 45₁₈₁, KR 90₃₅₀, SC 59₄₄₃, TC 62₅₄₇. + '3q3i6m3o3z,' + // #2909: 3 fonts: HK 67₉₄, JP 45₁₈₁, SC 60₄₄₄. + '3q3i10c,' + // #2910: 4 fonts: HK 67₉₄, JP 45₁₈₁, SC 78₄₆₂, TC 63₅₄₈. + '3q3i10u3h,' + // #2911: 4 fonts: HK 67₉₄, JP 45₁₈₁, SC 89₄₇₃, TC 62₅₄₇. + '3q3i11f2v,' + // #2912: 4 fonts: HK 67₉₄, JP 45₁₈₁, SC 90₄₇₄, TC 62₅₄₇. + '3q3i11g2u,' + // #2913: 2 fonts: HK 67₉₄, JP 46₁₈₂. + '3q3j,' + // #2914: 4 fonts: HK 67₉₄, JP 46₁₈₂, SC 60₄₄₄, TC 63₅₄₈. + '3q3j10b3z,' + // #2915: 3 fonts: HK 67₉₄, JP 46₁₈₂, TC 63₅₄₈. + '3q3j14b,' + // #2916: 4 fonts: HK 67₉₄, JP 61₁₉₇, SC 60₄₄₄, TC 62₅₄₇. + '3q3y9m3y,' + // #2917: 4 fonts: HK 67₉₄, JP 79₂₁₅, SC 60₄₄₄, TC 63₅₄₈. + '3q4q8u3z,' + // #2918: 4 fonts: HK 67₉₄, JP 84₂₂₀, SC 60₄₄₄, TC 63₅₄₈. + '3q4v8p3z,' + // #2919: 2 fonts: HK 67₉₄, SC 59₄₄₃. + '3q13k,' + // #2920: 3 fonts: HK 67₉₄, SC 59₄₄₃, TC 62₅₄₇. + '3q13k3z,' + // #2921: 2 fonts: HK 67₉₄, SC 60₄₄₄. + '3q13l,' + // #2922: 3 fonts: HK 67₉₄, SC 93₄₇₇, TC 62₅₄₇. + '3q14s2r,' + // #2923: 5 fonts: HK 68₉₅, JP 46₁₈₂, KR 90₃₅₀, SC 60₄₄₄, TC 63₅₄₈. + '3r3i6l3p3z,' + // #2924: 5 fonts: HK 68₉₅, JP 46₁₈₂, KR 90₃₅₀, SC 62₄₄₆, TC 64₅₄₉. + '3r3i6l3r3y,' + // #2925: 4 fonts: HK 68₉₅, JP 46₁₈₂, KR 90₃₅₀, TC 63₅₄₈. + '3r3i6l7p,' + // #2926: 5 fonts: HK 68₉₅, JP 65₂₀₁, KR 90₃₅₀, SC 62₄₄₆, TC 64₅₄₉. + '3r4b5s3r3y,' + // #2927: 5 fonts: HK 68₉₅, JP 65₂₀₁, KR 90₃₅₀, SC 77₄₆₁, TC 63₅₄₈. + '3r4b5s4g3i,' + // #2928: 5 fonts: HK 68₉₅, JP 67₂₀₃, KR 90₃₅₀, SC 61₄₄₅, TC 63₅₄₈. + '3r4d5q3q3y,' + // #2929: 5 fonts: HK 68₉₅, JP 68₂₀₄, KR 90₃₅₀, SC 61₄₄₅, TC 64₅₄₉. + '3r4e5p3q3z,' + // #2930: 5 fonts: HK 68₉₅, JP 72₂₀₈, KR 90₃₅₀, SC 62₄₄₆, TC 64₅₄₉. + '3r4i5l3r3y,' + // #2931: 4 fonts: HK 68₉₅, JP 75₂₁₁, SC 61₄₄₅, TC 63₅₄₈. + '3r4l8z3y,' + // #2932: 4 fonts: HK 68₉₅, JP 81₂₁₇, SC 61₄₄₅, TC 63₅₄₈. + '3r4r8t3y,' + // #2933: 5 fonts: HK 68₉₅, JP 82₂₁₈, KR 90₃₅₀, SC 61₄₄₅, TC 64₅₄₉. + '3r4s5b3q3z,' + // #2934: 5 fonts: HK 68₉₅, JP 94₂₃₀, KR 90₃₅₀, SC 61₄₄₅, TC 63₅₄₈. + '3r5e4p3q3y,' + // #2935: 2 fonts: HK 68₉₅, SC 60₄₄₄. + '3r13k,' + // #2936: 2 fonts: HK 69₉₆, JP 46₁₈₂. + '3s3h,' + // #2937: 5 fonts: HK 69₉₆, JP 46₁₈₂, KR 90₃₅₀, SC 78₄₆₂, TC 64₅₄₉. + '3s3h6l4h3i,' + // #2938: 4 fonts: HK 69₉₆, JP 46₁₈₂, SC 62₄₄₆, TC 65₅₅₀. + '3s3h10d3z,' + // #2939: 5 fonts: HK 69₉₆, JP 47₁₈₃, KR 90₃₅₀, SC 88₄₇₂, TC 65₅₅₀. + '3s3i6k4r2z,' + // #2940: 3 fonts: HK 69₉₆, JP 47₁₈₃, SC 63₄₄₇. + '3s3i10d,' + // #2941: 4 fonts: HK 69₉₆, JP 47₁₈₃, SC 81₄₆₅, TC 65₅₅₀. + '3s3i10v3g,' + // #2942: 4 fonts: HK 69₉₆, JP 47₁₈₃, SC 83₄₆₇, TC 65₅₅₀. + '3s3i10x3e,' + // #2943: 4 fonts: HK 69₉₆, JP 47₁₈₃, SC 85₄₆₉, TC 65₅₅₀. + '3s3i10z3c,' + // #2944: 4 fonts: HK 69₉₆, JP 47₁₈₃, SC 86₄₇₀, TC 65₅₅₀. + '3s3i11a3b,' + // #2945: 4 fonts: HK 69₉₆, JP 47₁₈₃, SC 89₄₇₃, TC 65₅₅₀. + '3s3i11d2y,' + // #2946: 4 fonts: HK 69₉₆, JP 47₁₈₃, SC 92₄₇₆, TC 65₅₅₀. + '3s3i11g2v,' + // #2947: 4 fonts: HK 69₉₆, JP 61₁₉₇, SC 83₄₆₇, TC 65₅₅₀. + '3s3w10j3e,' + // #2948: 5 fonts: HK 69₉₆, JP 62₁₉₈, KR 90₃₅₀, SC 62₄₄₆, TC 65₅₅₀. + '3s3x5v3r3z,' + // #2949: 5 fonts: HK 69₉₆, JP 66₂₀₂, KR 90₃₅₀, SC 63₄₄₇, TC 65₅₅₀. + '3s4b5r3s3y,' + // #2950: 5 fonts: HK 69₉₆, JP 67₂₀₃, KR 90₃₅₀, SC 62₄₄₆, TC 65₅₅₀. + '3s4c5q3r3z,' + // #2951: 5 fonts: HK 69₉₆, JP 67₂₀₃, KR 91₃₅₁, SC 63₄₄₇, TC 65₅₅₀. + '3s4c5r3r3y,' + // #2952: 4 fonts: HK 69₉₆, JP 67₂₀₃, SC 62₄₄₆, TC 64₅₄₉. + '3s4c9i3y,' + // #2953: 5 fonts: HK 69₉₆, JP 76₂₁₂, KR 90₃₅₀, SC 62₄₄₆, TC 64₅₄₉. + '3s4l5h3r3y,' + // #2954: 4 fonts: HK 69₉₆, JP 78₂₁₄, SC 88₄₇₂, TC 65₅₅₀. + '3s4n9x2z,' + // #2955: 5 fonts: HK 69₉₆, JP 84₂₂₀, KR 90₃₅₀, SC 62₄₄₆, TC 65₅₅₀. + '3s4t4z3r3z,' + // #2956: 4 fonts: HK 69₉₆, JP 86₂₂₂, SC 90₄₇₄, TC 65₅₅₀. + '3s4v9r2x,' + // #2957: 5 fonts: HK 69₉₆, JP 90₂₂₆, KR 91₃₅₁, SC 91₄₇₅, TC 65₅₅₀. + '3s4z4u4t2w,' + // #2958: 3 fonts: HK 69₉₆, SC 77₄₆₁, TC 64₅₄₉. + '3s14a3j,' + // #2959: 3 fonts: HK 69₉₆, SC 80₄₆₄, TC 65₅₅₀. + '3s14d3h,' + // #2960: 3 fonts: HK 70₉₇, JP 47₁₈₃, KR 91₃₅₁. + '3t3h6l,' + // #2961: 5 fonts: HK 70₉₇, JP 47₁₈₃, KR 91₃₅₁, SC 79₄₆₃, TC 65₅₅₀. + '3t3h6l4h3i,' + // #2962: 3 fonts: HK 70₉₇, JP 47₁₈₃, SC 63₄₄₇. + '3t3h10d,' + // #2963: 4 fonts: HK 70₉₇, JP 47₁₈₃, SC 79₄₆₃, TC 65₅₅₀. + '3t3h10t3i,' + // #2964: 5 fonts: HK 70₉₇, JP 48₁₈₄, KR 91₃₅₁, SC 88₄₇₂, TC 66₅₅₁. + '3t3i6k4q3a,' + // #2965: 4 fonts: HK 70₉₇, JP 48₁₈₄, SC 77₄₆₁, TC 66₅₅₁. + '3t3i10q3l,' + // #2966: 4 fonts: HK 70₉₇, JP 48₁₈₄, SC 83₄₆₇, TC 66₅₅₁. + '3t3i10w3f,' + // #2967: 4 fonts: HK 70₉₇, JP 48₁₈₄, SC 87₄₇₁, TC 66₅₅₁. + '3t3i11a3b,' + // #2968: 4 fonts: HK 70₉₇, JP 48₁₈₄, SC 91₄₇₅, TC 66₅₅₁. + '3t3i11e2x,' + // #2969: 5 fonts: HK 70₉₇, JP 64₂₀₀, KR 91₃₅₁, SC 63₄₄₇, TC 65₅₅₀. + '3t3y5u3r3y,' + // #2970: 5 fonts: HK 70₉₇, JP 66₂₀₂, KR 91₃₅₁, SC 64₄₄₈, TC 66₅₅₁. + '3t4a5s3s3y,' + // #2971: 5 fonts: HK 70₉₇, JP 76₂₁₂, KR 91₃₅₁, SC 64₄₄₈, TC 66₅₅₁. + '3t4k5i3s3y,' + // #2972: 4 fonts: HK 70₉₇, JP 78₂₁₄, SC 64₄₄₈, TC 66₅₅₁. + '3t4m8z3y,' + // #2973: 4 fonts: HK 70₉₇, JP 108₂₄₄, SC 92₄₇₆, TC 65₅₅₀. + '3t5q8x2v,' + // #2974: 4 fonts: HK 70₉₇, JP 111₂₄₇, SC 63₄₄₇, TC 66₅₅₁. + '3t5t7r3z,' + // #2975: 4 fonts: HK 70₉₇, KR 91₃₅₁, SC 63₄₄₇, TC 65₅₅₀. + '3t9t3r3y,' + // #2976: 2 fonts: HK 70₉₇, SC 63₄₄₇. + '3t13l,' + // #2977: 3 fonts: HK 70₉₇, SC 63₄₄₇, TC 66₅₅₁. + '3t13l3z,' + // #2978: 3 fonts: HK 70₉₇, SC 77₄₆₁, TC 66₅₅₁. + '3t13z3l,' + // #2979: 3 fonts: HK 70₉₇, SC 89₄₇₃, TC 66₅₅₁. + '3t14l2z,' + // #2980: 3 fonts: HK 70₉₇, SC 90₄₇₄, TC 66₅₅₁. + '3t14m2y,' + // #2981: 5 fonts: HK 71₉₈, JP 48₁₈₄, KR 91₃₅₁, SC 64₄₄₈, TC 66₅₅₁. + '3u3h6k3s3y,' + // #2982: 5 fonts: HK 71₉₈, JP 48₁₈₄, KR 91₃₅₁, SC 64₄₄₈, TC 67₅₅₂. + '3u3h6k3s3z,' + // #2983: 5 fonts: HK 71₉₈, JP 48₁₈₄, KR 91₃₅₁, SC 79₄₆₃, TC 67₅₅₂. + '3u3h6k4h3k,' + // #2984: 4 fonts: HK 71₉₈, JP 48₁₈₄, SC 79₄₆₃, TC 67₅₅₂. + '3u3h10s3k,' + // #2985: 4 fonts: HK 71₉₈, JP 48₁₈₄, SC 83₄₆₇, TC 67₅₅₂. + '3u3h10w3g,' + // #2986: 4 fonts: HK 71₉₈, JP 48₁₈₄, SC 90₄₇₄, TC 66₅₅₁. + '3u3h11d2y,' + // #2987: 4 fonts: HK 71₉₈, JP 48₁₈₄, SC 92₄₇₆, TC 67₅₅₂. + '3u3h11f2x,' + // #2988: 4 fonts: HK 71₉₈, JP 49₁₈₅, KR 92₃₅₂, TC 67₅₅₂. + '3u3i6k7r,' + // #2989: 5 fonts: HK 71₉₈, JP 66₂₀₂, KR 91₃₅₁, SC 81₄₆₅, TC 66₅₅₁. + '3u3z5s4j3h,' + // #2990: 5 fonts: HK 71₉₈, JP 70₂₀₆, KR 91₃₅₁, SC 64₄₄₈, TC 66₅₅₁. + '3u4d5o3s3y,' + // #2991: 4 fonts: HK 71₉₈, JP 70₂₀₆, SC 64₄₄₈, TC 66₅₅₁. + '3u4d9h3y,' + // #2992: 4 fonts: HK 71₉₈, JP 71₂₀₇, SC 64₄₄₈, TC 66₅₅₁. + '3u4e9g3y,' + // #2993: 4 fonts: HK 71₉₈, JP 88₂₂₄, SC 89₄₇₃, TC 67₅₅₂. + '3u4v9o3a,' + // #2994: 5 fonts: HK 71₉₈, JP 95₂₃₁, KR 91₃₅₁, SC 78₄₆₂, TC 66₅₅₁. + '3u5c4p4g3k,' + // #2995: 4 fonts: HK 71₉₈, JP 103₂₃₉, SC 65₄₄₉, TC 67₅₅₂. + '3u5k8b3y,' + // #2996: 5 fonts: HK 71₉₈, JP 114₂₅₀, KR 91₃₅₁, SC 64₄₄₈, TC 67₅₅₂. + '3u5v3w3s3z,' + // #2997: 2 fonts: HK 71₉₈, SC 65₄₄₉. + '3u13m,' + // #2998: 3 fonts: HK 71₉₈, SC 82₄₆₆, TC 67₅₅₂. + '3u14d3h,' + // #2999: 3 fonts: HK 71₉₈, SC 85₄₆₉, TC 67₅₅₂. + '3u14g3e,' + // #3000: 3 fonts: HK 71₉₈, SC 91₄₇₅, TC 66₅₅₁. + '3u14m2x,' + // #3001: 2 fonts: HK 71₉₈, TC 66₅₅₁. + '3u17k,' + // #3002: 5 fonts: HK 72₉₉, JP 49₁₈₅, KR 92₃₅₂, SC 78₄₆₂, TC 68₅₅₃. + '3v3h6k4f3m,' + // #3003: 4 fonts: HK 72₉₉, JP 49₁₈₅, KR 92₃₅₂, TC 68₅₅₃. + '3v3h6k7s,' + // #3004: 3 fonts: HK 72₉₉, JP 49₁₈₅, SC 66₄₅₀. + '3v3h10e,' + // #3005: 4 fonts: HK 72₉₉, JP 49₁₈₅, SC 78₄₆₂, TC 68₅₅₃. + '3v3h10q3m,' + // #3006: 4 fonts: HK 72₉₉, JP 49₁₈₅, SC 79₄₆₃, TC 68₅₅₃. + '3v3h10r3l,' + // #3007: 4 fonts: HK 72₉₉, JP 49₁₈₅, SC 81₄₆₅, TC 68₅₅₃. + '3v3h10t3j,' + // #3008: 3 fonts: HK 72₉₉, JP 49₁₈₅, TC 67₅₅₂. + '3v3h14c,' + // #3009: 2 fonts: HK 72₉₉, JP 50₁₈₆. + '3v3i,' + // #3010: 5 fonts: HK 72₉₉, JP 50₁₈₆, KR 92₃₅₂, SC 66₄₅₀, TC 68₅₅₃. + '3v3i6j3t3y,' + // #3011: 4 fonts: HK 72₉₉, JP 50₁₈₆, SC 66₄₅₀, TC 69₅₅₄. + '3v3i10d3z,' + // #3012: 4 fonts: HK 72₉₉, JP 50₁₈₆, SC 93₄₇₇, TC 69₅₅₄. + '3v3i11e2y,' + // #3013: 5 fonts: HK 72₉₉, JP 60₁₉₆, KR 92₃₅₂, SC 65₄₄₉, TC 68₅₅₃. + '3v3s5z3s3z,' + // #3014: 5 fonts: HK 72₉₉, JP 64₂₀₀, KR 92₃₅₂, SC 66₄₅₀, TC 68₅₅₃. + '3v3w5v3t3y,' + // #3015: 5 fonts: HK 72₉₉, JP 66₂₀₂, KR 92₃₅₂, SC 66₄₅₀, TC 68₅₅₃. + '3v3y5t3t3y,' + // #3016: 5 fonts: HK 72₉₉, JP 67₂₀₃, KR 92₃₅₂, SC 65₄₄₉, TC 68₅₅₃. + '3v3z5s3s3z,' + // #3017: 5 fonts: HK 72₉₉, JP 75₂₁₁, KR 93₃₅₃, SC 66₄₅₀, TC 69₅₅₄. + '3v4h5l3s3z,' + // #3018: 5 fonts: HK 72₉₉, JP 88₂₂₄, KR 92₃₅₂, SC 65₄₄₉, TC 68₅₅₃. + '3v4u4x3s3z,' + // #3019: 5 fonts: HK 72₉₉, JP 97₂₃₃, KR 92₃₅₂, SC 78₄₆₂, TC 68₅₅₃. + '3v5d4o4f3m,' + // #3020: 4 fonts: HK 72₉₉, JP 110₂₄₆, SC 66₄₅₀, TC 69₅₅₄. + '3v5q7v3z,' + // #3021: 2 fonts: HK 72₉₉, SC 65₄₄₉. + '3v13l,' + // #3022: 2 fonts: HK 72₉₉, SC 66₄₅₀. + '3v13m,' + // #3023: 3 fonts: HK 72₉₉, SC 82₄₆₆, TC 68₅₅₃. + '3v14c3i,' + // #3024: 2 fonts: HK 72₉₉, TC 67₅₅₂. + '3v17k,' + // #3025: 4 fonts: HK 73₁₀₀, JP 50₁₈₆, SC 77₄₆₁, TC 69₅₅₄. + '3w3h10o3o,' + // #3026: 4 fonts: HK 73₁₀₀, JP 50₁₈₆, SC 80₄₆₄, TC 69₅₅₄. + '3w3h10r3l,' + // #3027: 4 fonts: HK 73₁₀₀, JP 50₁₈₆, SC 81₄₆₅, TC 69₅₅₄. + '3w3h10s3k,' + // #3028: 4 fonts: HK 73₁₀₀, JP 89₂₂₅, SC 67₄₅₁, TC 69₅₅₄. + '3w4u8r3y,' + // #3029: 4 fonts: HK 73₁₀₀, JP 93₂₂₉, SC 91₄₇₅, TC 69₅₅₄. + '3w4y9l3a,' + // #3030: 4 fonts: HK 73₁₀₀, JP 97₂₃₃, SC 66₄₅₀, TC 69₅₅₄. + '3w5c8i3z,' + // #3031: 4 fonts: HK 73₁₀₀, JP 104₂₄₀, SC 67₄₅₁, TC 69₅₅₄. + '3w5j8c3y,' + // #3032: 3 fonts: HK 73₁₀₀, SC 78₄₆₂, TC 69₅₅₄. + '3w13x3n,' + // #3033: 3 fonts: HK 73₁₀₀, SC 80₄₆₄, TC 69₅₅₄. + '3w13z3l,' + // #3034: 3 fonts: HK 73₁₀₀, SC 86₄₇₀, TC 69₅₅₄. + '3w14f3f,' + // #3035: 3 fonts: HK 73₁₀₀, SC 89₄₇₃, TC 69₅₅₄. + '3w14i3c,' + // #3036: 3 fonts: HK 73₁₀₀, SC 90₄₇₄, TC 69₅₅₄. + '3w14j3b,' + // #3037: 2 fonts: HK 74₁₀₁, SC 67₄₅₁. + '3x13l,' + // #3038: 2 fonts: HK 75₁₀₂, JP 50₁₈₆. + '3y3f,' + // #3039: 2 fonts: HK 76₁₀₃, SC 67₄₅₁. + '3z13j,' + // #3040: 5 fonts: HK 77₁₀₄, JP 51₁₈₇, KR 93₃₅₃, SC 67₄₅₁, TC 70₅₅₅. + '4a3e6j3t3z,' + // #3041: 5 fonts: HK 77₁₀₄, JP 51₁₈₇, KR 103₃₆₃, SC 67₄₅₁, TC 70₅₅₅. + '4a3e6t3j3z,' + // #3042: 3 fonts: HK 77₁₀₄, JP 51₁₈₇, SC 67₄₅₁. + '4a3e10d,' + // #3043: 4 fonts: HK 77₁₀₄, JP 51₁₈₇, SC 67₄₅₁, TC 70₅₅₅. + '4a3e10d3z,' + // #3044: 5 fonts: HK 77₁₀₄, JP 52₁₈₈, KR 93₃₅₃, SC 67₄₅₁, TC 70₅₅₅. + '4a3f6i3t3z,' + // #3045: 5 fonts: HK 77₁₀₄, JP 53₁₈₉, KR 94₃₅₄, SC 67₄₅₁, TC 70₅₅₅. + '4a3g6i3s3z,' + // #3046: 4 fonts: HK 77₁₀₄, JP 53₁₈₉, KR 94₃₅₄, TC 70₅₅₅. + '4a3g6i7s,' + // #3047: 4 fonts: HK 77₁₀₄, JP 54₁₉₀, KR 94₃₅₄, TC 70₅₅₅. + '4a3h6h7s,' + // #3048: 5 fonts: HK 77₁₀₄, JP 54₁₉₀, KR 95₃₅₅, SC 68₄₅₂, TC 70₅₅₅. + '4a3h6i3s3y,' + // #3049: 4 fonts: HK 77₁₀₄, JP 54₁₉₀, KR 102₃₆₂, TC 70₅₅₅. + '4a3h6p7k,' + // #3050: 4 fonts: HK 77₁₀₄, JP 54₁₉₀, KR 105₃₆₅, TC 70₅₅₅. + '4a3h6s7h,' + // #3051: 5 fonts: HK 77₁₀₄, JP 54₁₉₀, KR 107₃₆₇, SC 67₄₅₁, TC 70₅₅₅. + '4a3h6u3f3z,' + // #3052: 5 fonts: HK 77₁₀₄, JP 54₁₉₀, KR 112₃₇₂, SC 67₄₅₁, TC 70₅₅₅. + '4a3h6z3a3z,' + // #3053: 5 fonts: HK 77₁₀₄, JP 54₁₉₀, KR 113₃₇₃, SC 67₄₅₁, TC 70₅₅₅. + '4a3h7a2z3z,' + // #3054: 5 fonts: HK 77₁₀₄, JP 54₁₉₀, KR 114₃₇₄, SC 67₄₅₁, TC 70₅₅₅. + '4a3h7b2y3z,' + // #3055: 7 fonts: HK 77₁₀₄, JP 55₁₉₁, KR 95₃₅₅, SC 78₄₆₂, TC 71₅₅₆, Phags Pa₆₈₇, Yi₇₂₁. + '4a3i6h4c3p5a1h,' + // #3056: 4 fonts: HK 77₁₀₄, JP 55₁₉₁, KR 95₃₅₅, TC 71₅₅₆. + '4a3i6h7s,' + // #3057: 6 fonts: HK 77₁₀₄, JP 55₁₉₁, KR 95₃₅₅, TC 71₅₅₆, Phags Pa₆₈₇, Yi₇₂₁. + '4a3i6h7s5a1h,' + // #3058: 5 fonts: HK 77₁₀₄, JP 61₁₉₇, KR 93₃₅₃, SC 67₄₅₁, TC 70₅₅₅. + '4a3o5z3t3z,' + // #3059: 4 fonts: HK 77₁₀₄, JP 65₂₀₁, SC 68₄₅₂, TC 70₅₅₅. + '4a3s9q3y,' + // #3060: 5 fonts: HK 77₁₀₄, JP 66₂₀₂, KR 94₃₅₄, SC 67₄₅₁, TC 70₅₅₅. + '4a3t5v3s3z,' + // #3061: 5 fonts: HK 77₁₀₄, JP 66₂₀₂, KR 95₃₅₅, SC 68₄₅₂, TC 71₅₅₆. + '4a3t5w3s3z,' + // #3062: 4 fonts: HK 77₁₀₄, JP 70₂₀₆, SC 68₄₅₂, TC 71₅₅₆. + '4a3x9l3z,' + // #3063: 5 fonts: HK 77₁₀₄, JP 71₂₀₇, KR 106₃₆₆, SC 80₄₆₄, TC 70₅₅₅. + '4a3y6c3t3m,' + // #3064: 5 fonts: HK 77₁₀₄, JP 75₂₁₁, KR 100₃₆₀, SC 67₄₅₁, TC 70₅₅₅. + '4a4c5s3m3z,' + // #3065: 4 fonts: HK 77₁₀₄, JP 77₂₁₃, SC 80₄₆₄, TC 70₅₅₅. + '4a4e9q3m,' + // #3066: 5 fonts: HK 77₁₀₄, JP 79₂₁₅, KR 95₃₅₅, SC 68₄₅₂, TC 70₅₅₅. + '4a4g5j3s3y,' + // #3067: 5 fonts: HK 77₁₀₄, JP 85₂₂₁, SC 68₄₅₂, TC 71₅₅₆, Phags Pa₆₈₇. + '4a4m8w3z5a,' + // #3068: 5 fonts: HK 77₁₀₄, JP 101₂₃₇, KR 95₃₅₅, SC 68₄₅₂, TC 71₅₅₆. + '4a5c4n3s3z,' + // #3069: 5 fonts: HK 77₁₀₄, JP 106₂₄₂, KR 95₃₅₅, SC 68₄₅₂, TC 70₅₅₅. + '4a5h4i3s3y,' + // #3070: 4 fonts: HK 77₁₀₄, JP 114₂₅₀, SC 68₄₅₂, TC 71₅₅₆. + '4a5p7t3z,' + // #3071: 5 fonts: HK 77₁₀₄, JP 115₂₅₁, KR 95₃₅₅, SC 68₄₅₂, TC 70₅₅₅. + '4a5q3z3s3y,' + // #3072: 5 fonts: HK 77₁₀₄, JP 119₂₅₅, KR 95₃₅₅, SC 96₄₈₀, TC 70₅₅₅. + '4a5u3v4u2w,' + // #3073: 5 fonts: HK 77₁₀₄, JP 119₂₅₅, KR 100₃₆₀, SC 96₄₈₀, TC 70₅₅₅. + '4a5u4a4p2w,' + // #3074: 4 fonts: HK 78₁₀₅, JP 55₁₉₁, KR 96₃₅₆, TC 71₅₅₆. + '4b3h6i7r,' + // #3075: 4 fonts: HK 78₁₀₅, JP 56₁₉₂, KR 96₃₅₆, TC 71₅₅₆. + '4b3i6h7r,' + // #3076: 5 fonts: HK 78₁₀₅, JP 56₁₉₂, SC 69₄₅₃, TC 71₅₅₆, Symbols₇₀₂. + '4b3i10a3y5p,' + // #3077: 8 fonts: HK 79₁₀₆, JP 56₁₉₂, KR 96₃₅₆, SC 69₄₅₃, TC 72₅₅₇, Noto Music₅₉₀, Math₆₅₅, Symbols₇₀₂. + '4c3h6h3s3z1g2m1u,' + // #3078: 6 fonts: HK 79₁₀₆, JP 56₁₉₂, TC 72₅₅₇, Noto Music₅₉₀, Math₆₅₅, Symbols₇₀₂. + '4c3h14a1g2m1u,' + // #3079: 4 fonts: HK 79₁₀₆, JP 56₁₉₂, TC 73₅₅₈, Symbols₇₀₂. + '4c3h14b5n,' + // #3080: 8 fonts: HK 79₁₀₆, JP 63₁₉₉, KR 96₃₅₆, SC 69₄₅₃, TC 72₅₅₇, Noto Music₅₉₀, Math₆₅₅, Symbols₇₀₂. + '4c3o6a3s3z1g2m1u,' + // #3081: 7 fonts: HK 79₁₀₆, JP 63₁₉₉, KR 96₃₅₆, SC 69₄₅₃, TC 72₅₅₇, Noto Music₅₉₀, Symbols₇₀₂. + '4c3o6a3s3z1g4h,' + // #3082: 7 fonts: HK 79₁₀₆, JP 69₂₀₅, KR 105₃₆₅, SC 69₄₅₃, TC 72₅₅₇, Noto Music₅₉₀, Symbols₇₀₂. + '4c3u6d3j3z1g4h,' + // #3083: 6 fonts: HK 79₁₀₆, JP 70₂₀₆, SC 69₄₅₃, TC 72₅₅₇, Noto Music₅₉₀, Symbols₇₀₂. + '4c3v9m3z1g4h,' + // #3084: 5 fonts: HK 80₁₀₇, JP 57₁₉₃, KR 103₃₆₃, SC 70₄₅₄, TC 73₅₅₈. + '4d3h6n3m3z,' + // #3085: 6 fonts: HK 80₁₀₇, JP 57₁₉₃, KR 103₃₆₃, SC 71₄₅₅, TC 74₅₅₉, Symbols₇₀₂. + '4d3h6n3n3z5m,' + // #3086: 6 fonts: HK 80₁₀₇, JP 57₁₉₃, KR 114₃₇₄, SC 71₄₅₅, TC 74₅₅₉, Symbols₇₀₂. + '4d3h6y3c3z5m,' + // #3087: 4 fonts: HK 80₁₀₇, JP 57₁₉₃, SC 82₄₆₆, TC 74₅₅₉. + '4d3h10m3o,' + // #3088: 6 fonts: HK 80₁₀₇, JP 58₁₉₄, KR 98₃₅₈, SC 71₄₅₅, TC 74₅₅₉, Symbols₇₀₂. + '4d3i6h3s3z5m,' + // #3089: 5 fonts: HK 80₁₀₇, JP 58₁₉₄, SC 71₄₅₅, TC 74₅₅₉, Symbols₇₀₂. + '4d3i10a3z5m,' + // #3090: 5 fonts: HK 80₁₀₇, JP 71₂₀₇, KR 104₃₆₄, SC 71₄₅₅, TC 74₅₅₉. + '4d3v6a3m3z,' + // #3091: 5 fonts: HK 80₁₀₇, JP 73₂₀₉, KR 103₃₆₃, SC 71₄₅₅, TC 74₅₅₉. + '4d3x5x3n3z,' + // #3092: 5 fonts: HK 80₁₀₇, JP 75₂₁₁, KR 97₃₅₇, SC 71₄₅₅, TC 74₅₅₉. + '4d3z5p3t3z,' + // #3093: 12 fonts: HK 81₁₀₈, HK 105₁₃₂, JP 58₁₉₄, JP 120₂₅₆, KR 99₃₅₉, KR 120₃₈₀, SC 72₄₅₆, SC 97₄₈₁, TC 76₅₆₁, TC 101₅₈₆, Noto Sans₅₉₁, Georgian₆₁₉. + '4ex2j2j3yu2xy3bye1b,' + // #3094: 15 fonts: HK 81₁₀₈, HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 72₄₅₆, SC 98₄₈₂, SC 99₄₈₃, TC 76₅₆₁, TC 102₅₈₇, TC 103₅₈₈, Noto Sans₅₉₁. + '4eya2i2ja4sa2vza2zzac,' + // #3095: 11 fonts: HK 81₁₀₈, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 103₃₆₃, KR 122₃₈₂, SC 72₄₅₆, SC 99₄₈₃, TC 76₅₆₁, TC 103₅₈₈, Noto Sans₅₉₁. + '4ez2i2k4as2v1a2z1ac,' + // #3096: 11 fonts: HK 81₁₀₈, HK 107₁₃₄, JP 118₂₅₄, JP 122₂₅₈, KR 99₃₅₉, KR 122₃₈₂, SC 72₄₅₆, SC 99₄₈₃, TC 76₅₆₁, TC 103₅₈₈, Noto Sans₅₉₁. + '4ez4pd3ww2v1a2z1ac,' + // #3097: 10 fonts: HK 81₁₀₈, HK 108₁₃₅, JP 58₁₉₄, JP 123₂₅₉, KR 123₃₈₃, SC 72₄₅₆, SC 100₄₈₄, TC 75₅₆₀, TC 104₅₈₉, Math₆₅₅. + '4e1a2g2m4t2u1b2x1c2n,' + // #3098: 7 fonts: HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, SC 71₄₅₅, TC 74₅₅₉, Math₆₅₅, Symbols₇₀₂. + '4e3h6h3s3z3r1u,' + // #3099: 6 fonts: HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, SC 71₄₅₅, TC 74₅₅₉, Symbols₇₀₂. + '4e3h6h3s3z5m,' + // #3100: 7 fonts: HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, SC 71₄₅₅, TC 75₅₆₀, Math₆₅₅, Symbols₇₀₂. + '4e3h6h3s4a3q1u,' + // #3101: 6 fonts: HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, SC 71₄₅₅, TC 75₅₆₀, Symbols₇₀₂. + '4e3h6h3s4a5l,' + // #3102: 7 fonts: HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, SC 77₄₆₁, TC 75₅₆₀, Arabic₅₉₄, Math₆₅₅. + '4e3h6h3y3u1h2i,' + // #3103: 6 fonts: HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, SC 77₄₆₁, TC 75₅₆₀, Math₆₅₅. + '4e3h6h3y3u3q,' + // #3104: 6 fonts: HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, SC 79₄₆₃, TC 75₅₆₀, Math₆₅₅. + '4e3h6h4a3s3q,' + // #3105: 5 fonts: HK 81₁₀₈, JP 58₁₉₄, KR 98₃₅₈, TC 75₅₆₀, Math₆₅₅. + '4e3h6h7t3q,' + // #3106: 6 fonts: HK 81₁₀₈, JP 58₁₉₄, KR 104₃₆₄, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e3h6n3n3z3q,' + // #3107: 5 fonts: HK 81₁₀₈, JP 58₁₉₄, SC 72₄₅₆, TC 76₅₆₁, Noto Sans₅₉₁. + '4e3h10b4a1d,' + // #3108: 5 fonts: HK 81₁₀₈, JP 59₁₉₅, SC 72₄₅₆, TC 76₅₆₁, Symbols₇₀₂. + '4e3i10a4a5k,' + // #3109: 6 fonts: HK 81₁₀₈, JP 60₁₉₆, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e3j6f3t3z3q,' + // #3110: 6 fonts: HK 81₁₀₈, JP 62₁₉₈, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e3l6d3t3z3q,' + // #3111: 6 fonts: HK 81₁₀₈, JP 63₁₉₉, KR 98₃₅₈, SC 71₄₅₅, TC 75₅₆₀, Symbols₇₀₂. + '4e3m6c3s4a5l,' + // #3112: 6 fonts: HK 81₁₀₈, JP 63₁₉₉, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e3m6c3t3z3q,' + // #3113: 6 fonts: HK 81₁₀₈, JP 64₂₀₀, KR 98₃₅₈, SC 71₄₅₅, TC 75₅₆₀, Symbols₇₀₂. + '4e3n6b3s4a5l,' + // #3114: 6 fonts: HK 81₁₀₈, JP 67₂₀₃, KR 101₃₆₁, SC 71₄₅₅, TC 75₅₆₀, Symbols₇₀₂. + '4e3q6b3p4a5l,' + // #3115: 6 fonts: HK 81₁₀₈, JP 69₂₀₅, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Symbols₇₀₂. + '4e3s5w3t3z5l,' + // #3116: 6 fonts: HK 81₁₀₈, JP 71₂₀₇, KR 102₃₆₂, SC 71₄₅₅, TC 75₅₆₀, Symbols₇₀₂. + '4e3u5y3o4a5l,' + // #3117: 6 fonts: HK 81₁₀₈, JP 73₂₀₉, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e3w5s3t3z3q,' + // #3118: 6 fonts: HK 81₁₀₈, JP 75₂₁₁, KR 104₃₆₄, SC 77₄₆₁, TC 75₅₆₀, Symbols₇₀₂. + '4e3y5w3s3u5l,' + // #3119: 6 fonts: HK 81₁₀₈, JP 77₂₁₃, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e4a5o3t3z3q,' + // #3120: 6 fonts: HK 81₁₀₈, JP 80₂₁₆, KR 105₃₆₅, SC 79₄₆₃, TC 75₅₆₀, Symbols₇₀₂. + '4e4d5s3t3s5l,' + // #3121: 6 fonts: HK 81₁₀₈, JP 86₂₂₂, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e4j5f3t3z3q,' + // #3122: 6 fonts: HK 81₁₀₈, JP 87₂₂₃, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e4k5e3t3z3q,' + // #3123: 6 fonts: HK 81₁₀₈, JP 87₂₂₃, KR 107₃₆₇, SC 81₄₆₅, TC 75₅₆₀, Symbols₇₀₂. + '4e4k5n3t3q5l,' + // #3124: 6 fonts: HK 81₁₀₈, JP 91₂₂₇, KR 108₃₆₈, SC 83₄₆₇, TC 75₅₆₀, Symbols₇₀₂. + '4e4o5k3u3o5l,' + // #3125: 6 fonts: HK 81₁₀₈, JP 92₂₂₈, KR 108₃₆₈, SC 83₄₆₇, TC 75₅₆₀, Symbols₇₀₂. + '4e4p5j3u3o5l,' + // #3126: 6 fonts: HK 81₁₀₈, JP 93₂₂₉, KR 98₃₅₈, SC 72₄₅₆, TC 75₅₆₀, Math₆₅₅. + '4e4q4y3t3z3q,' + // #3127: 9 fonts: HK 82₁₀₉, HK 106₁₃₃, JP 59₁₉₅, JP 121₂₅₇, KR 121₃₈₁, SC 98₄₈₂, TC 77₅₆₂, TC 102₅₈₇, Noto Sans₅₉₁. + '4fx2j2j4t3w3byd,' + // #3128: 12 fonts: HK 82₁₀₉, HK 107₁₃₄, JP 66₂₀₂, JP 122₂₅₈, KR 99₃₅₉, KR 122₃₈₂, SC 73₄₅₇, SC 99₄₈₃, TC 76₅₆₁, TC 103₅₈₈, Noto Sans₅₉₁, Adlam₅₉₂. + '4fy2p2d3ww2wz2z1aca,' + // #3129: 12 fonts: HK 82₁₀₉, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 73₄₅₇, SC 100₄₈₄, TC 76₅₆₁, TC 104₅₈₉, Noto Sans₅₉₁, Math₆₅₅. + '4fz2h2l3vx2v1a2y1bb2l,' + // #3130: 12 fonts: HK 82₁₀₉, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 73₄₅₇, SC 100₄₈₄, TC 76₅₆₁, TC 104₅₈₉, Noto Sans₅₉₁, Tamil₇₁₀. + '4fz2h2l3vx2v1a2y1bb4o,' + // #3131: 12 fonts: HK 82₁₀₉, HK 108₁₃₅, JP 61₁₉₇, JP 123₂₅₉, KR 102₃₆₂, KR 123₃₈₃, SC 73₄₅₇, SC 100₄₈₄, TC 76₅₆₁, TC 104₅₈₉, Noto Sans₅₉₁, Math₆₅₅. + '4fz2j2j3yu2v1a2y1bb2l,' + // #3132: 10 fonts: HK 82₁₀₉, HK 108₁₃₅, JP 74₂₁₀, JP 123₂₅₉, KR 123₃₈₃, SC 73₄₅₇, SC 100₄₈₄, TC 76₅₆₁, TC 104₅₈₉, Noto Sans₅₉₁. + '4fz2w1w4t2v1a2y1bb,' + // #3133: 6 fonts: HK 82₁₀₉, JP 59₁₉₅, KR 99₃₅₉, SC 73₄₅₇, TC 76₅₆₁, Noto Sans₅₉₁. + '4f3h6h3t3z1d,' + // #3134: 7 fonts: HK 82₁₀₉, JP 59₁₉₅, KR 99₃₅₉, SC 73₄₅₇, TC 76₅₆₁, Noto Sans₅₉₁, Math₆₅₅. + '4f3h6h3t3z1d2l,' + // #3135: 5 fonts: HK 82₁₀₉, JP 59₁₉₅, KR 99₃₅₉, TC 76₅₆₁, Noto Sans₅₉₁. + '4f3h6h7t1d,' + // #3136: 5 fonts: HK 82₁₀₉, JP 59₁₉₅, SC 73₄₅₇, TC 76₅₆₁, Noto Sans₅₉₁. + '4f3h10b3z1d,' + // #3137: 7 fonts: HK 82₁₀₉, JP 59₁₉₅, SC 73₄₅₇, TC 76₅₆₁, Noto Sans₅₉₁, Arabic₅₉₄, Syloti Nagri₇₀₁. + '4f3h10b3z1dc4c,' + // #3138: 5 fonts: HK 82₁₀₉, JP 69₂₀₅, SC 81₄₆₅, TC 76₅₆₁, Noto Sans₅₉₁. + '4f3r9z3r1d,' + // #3139: 7 fonts: HK 82₁₀₉, JP 74₂₁₀, KR 99₃₅₉, SC 73₄₅₇, TC 76₅₆₁, Noto Sans₅₉₁, Phags Pa₆₈₇. + '4f3w5s3t3z1d3r,' + // #3140: 31 fonts: HK 82₁₀₉, JP 81₂₁₇, KR 99₃₅₉, SC 73₄₅₇, TC 76₅₆₁, Noto Sans₅₉₁, Arabic₅₉₄, Armenian₅₉₅, Bengali₆₀₁, Coptic₆₁₂, Devanagari₆₁₅, Ethiopic₆₁₈, Georgian₆₁₉, Gujarati₆₂₃, Gurmukhi₆₂₅, Hebrew₆₂₈, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Lisu₆₄₆, Malayalam₆₅₀, Oriya₆₈₁, Sora Sompeng₆₉₈, Sundanese₇₀₀, Syloti Nagri₇₀₁, Tamil₇₁₀, Telugu₇₁₂, Thai₇₁₄, Noto Serif Tibetan₇₂₃. + '4f4d5l3t3z1dcafkccadbcfaaaahd1eqbaibbi,' + // #3141: 7 fonts: HK 82₁₀₉, JP 94₂₃₀, KR 99₃₅₉, SC 73₄₅₇, TC 76₅₆₁, Noto Sans₅₉₁, Adlam₅₉₂. + '4f4q4y3t3z1da,' + // #3142: 10 fonts: HK 83₁₁₀, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 123₃₈₃, SC 74₄₅₈, SC 100₄₈₄, TC 79₅₆₄, TC 104₅₈₉, Noto Sans₅₉₁. + '4gy2h2l4t2wz3byb,' + // #3143: 134 fonts: HK 83₁₁₀, JP 59₁₉₅, SC 74₄₅₈, TC 78₅₆₃, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + '4g3g10c4a1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #3144: 5 fonts: HK 83₁₁₀, JP 59₁₉₅, SC 74₄₅₈, TC 78₅₆₃, Noto Sans₅₉₁. + '4g3g10c4a1b,' + // #3145: 15 fonts: HK 84₁₁₁, HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 75₄₅₉, SC 98₄₈₂, SC 99₄₈₃, TC 79₅₆₄, TC 102₅₈₇, TC 103₅₈₈, Noto Sans₅₉₁. + '4hva2i2ja4sa2ywa3cwac,' + // #3146: 15 fonts: HK 84₁₁₁, HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 75₄₅₉, SC 98₄₈₂, SC 99₄₈₃, TC 80₅₆₅, TC 102₅₈₇, TC 103₅₈₈, Noto Sans₅₉₁. + '4hva2i2ja4sa2ywa3dvac,' + // #3147: 10 fonts: HK 84₁₁₁, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 75₄₅₉, SC 99₄₈₃, TC 80₅₆₅, TC 103₅₈₈, Noto Sans₅₉₁. + '4hw2i2k4t2yx3dwc,' + // #3148: 10 fonts: HK 84₁₁₁, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 76₄₆₀, SC 99₄₈₃, TC 80₅₆₅, TC 103₅₈₈, Noto Sans₅₉₁. + '4hw2i2k4t2zw3dwc,' + // #3149: 10 fonts: HK 84₁₁₁, HK 107₁₃₄, JP 59₁₉₅, JP 122₂₅₈, KR 122₃₈₂, SC 96₄₈₀, SC 99₄₈₃, TC 79₅₆₄, TC 103₅₈₈, Noto Sans₅₉₁. + '4hw2i2k4t3tc3cxc,' + // #3150: 12 fonts: HK 84₁₁₁, HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Sans₅₉₁, Math₆₅₅. + '4hx2h2l3vx2yx3cxb2l,' + // #3151: 12 fonts: HK 84₁₁₁, HK 108₁₃₅, JP 65₂₀₁, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 80₅₆₅, TC 104₅₈₉, Noto Sans₅₉₁, Math₆₅₅. + '4hx2n2f3vx2yx3cxb2l,' + // #3152: 4 fonts: HK 85₁₁₂, JP 5₁₄₁, SC 4₃₈₈, TC 81₅₆₆. + '4i1c9m6v,' + // #3153: 4 fonts: HK 85₁₁₂, JP 5₁₄₁, SC 93₄₇₇, TC 81₅₆₆. + '4i1c12x3k,' + // #3154: 4 fonts: HK 85₁₁₂, JP 8₁₄₄, SC 7₃₉₁, TC 81₅₆₆. + '4i1f9m6s,' + // #3155: 4 fonts: HK 85₁₁₂, JP 8₁₄₄, SC 77₄₆₁, TC 81₅₆₆. + '4i1f12e4a,' + // #3156: 5 fonts: HK 85₁₁₂, JP 9₁₄₅, KR 66₃₂₆, SC 77₄₆₁, TC 81₅₆₆. + '4i1g6y5e4a,' + // #3157: 4 fonts: HK 85₁₁₂, JP 9₁₄₅, SC 81₄₆₅, TC 81₅₆₆. + '4i1g12h3w,' + // #3158: 5 fonts: HK 85₁₁₂, JP 10₁₄₆, KR 66₃₂₆, SC 78₄₆₂, TC 81₅₆₆. + '4i1h6x5f3z,' + // #3159: 5 fonts: HK 85₁₁₂, JP 10₁₄₆, KR 66₃₂₆, SC 80₄₆₄, TC 81₅₆₆. + '4i1h6x5h3x,' + // #3160: 5 fonts: HK 85₁₁₂, JP 10₁₄₆, KR 67₃₂₇, SC 78₄₆₂, TC 81₅₆₆. + '4i1h6y5e3z,' + // #3161: 5 fonts: HK 85₁₁₂, JP 11₁₄₇, KR 67₃₂₇, SC 12₃₉₆, TC 81₅₆₆. + '4i1i6x2q6n,' + // #3162: 4 fonts: HK 85₁₁₂, JP 11₁₄₇, SC 13₃₉₇, TC 81₅₆₆. + '4i1i9p6m,' + // #3163: 5 fonts: HK 85₁₁₂, JP 12₁₄₈, KR 68₃₂₈, SC 78₄₆₂, TC 81₅₆₆. + '4i1j6x5d3z,' + // #3164: 5 fonts: HK 85₁₁₂, JP 14₁₅₀, KR 69₃₂₉, SC 15₃₉₉, TC 81₅₆₆. + '4i1l6w2r6k,' + // #3165: 4 fonts: HK 85₁₁₂, JP 14₁₅₀, SC 16₄₀₀, TC 81₅₆₆. + '4i1l9p6j,' + // #3166: 5 fonts: HK 85₁₁₂, JP 15₁₅₁, KR 69₃₂₉, SC 77₄₆₁, TC 81₅₆₆. + '4i1m6v5b4a,' + // #3167: 4 fonts: HK 85₁₁₂, JP 16₁₅₂, SC 18₄₀₂, TC 81₅₆₆. + '4i1n9p6h,' + // #3168: 4 fonts: HK 85₁₁₂, JP 17₁₅₃, SC 20₄₀₄, TC 81₅₆₆. + '4i1o9q6f,' + // #3169: 4 fonts: HK 85₁₁₂, JP 18₁₅₄, SC 77₄₆₁, TC 81₅₆₆. + '4i1p11u4a,' + // #3170: 5 fonts: HK 85₁₁₂, JP 19₁₅₅, KR 72₃₃₂, SC 22₄₀₆, TC 81₅₆₆. + '4i1q6u2v6d,' + // #3171: 4 fonts: HK 85₁₁₂, JP 19₁₅₅, SC 82₄₆₆, TC 81₅₆₆. + '4i1q11y3v,' + // #3172: 5 fonts: HK 85₁₁₂, JP 20₁₅₆, KR 72₃₃₂, SC 81₄₆₅, TC 81₅₆₆. + '4i1r6t5c3w,' + // #3173: 5 fonts: HK 85₁₁₂, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 81₅₆₆. + '4i1s6t2w6b,' + // #3174: 5 fonts: HK 85₁₁₂, JP 21₁₅₇, KR 73₃₃₃, SC 77₄₆₁, TC 81₅₆₆. + '4i1s6t4x4a,' + // #3175: 4 fonts: HK 85₁₁₂, JP 21₁₅₇, SC 79₄₆₃, TC 81₅₆₆. + '4i1s11t3y,' + // #3176: 5 fonts: HK 85₁₁₂, JP 22₁₅₈, KR 73₃₃₃, SC 25₄₀₉, TC 81₅₆₆. + '4i1t6s2x6a,' + // #3177: 5 fonts: HK 85₁₁₂, JP 22₁₅₈, KR 74₃₃₄, SC 26₄₁₀, TC 81₅₆₆. + '4i1t6t2x5z,' + // #3178: 5 fonts: HK 85₁₁₂, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 81₅₆₆. + '4i1u6s2y5y,' + // #3179: 4 fonts: HK 85₁₁₂, JP 24₁₆₀, SC 29₄₁₃, TC 81₅₆₆. + '4i1v9s5w,' + // #3180: 4 fonts: HK 85₁₁₂, JP 24₁₆₀, SC 79₄₆₃, TC 81₅₆₆. + '4i1v11q3y,' + // #3181: 5 fonts: HK 85₁₁₂, JP 25₁₆₁, KR 75₃₃₅, SC 30₄₁₄, TC 81₅₆₆. + '4i1w6r3a5v,' + // #3182: 5 fonts: HK 85₁₁₂, JP 25₁₆₁, KR 75₃₃₅, SC 31₄₁₅, TC 81₅₆₆. + '4i1w6r3b5u,' + // #3183: 5 fonts: HK 85₁₁₂, JP 26₁₆₂, KR 76₃₃₆, SC 77₄₆₁, TC 81₅₆₆. + '4i1x6r4u4a,' + // #3184: 5 fonts: HK 85₁₁₂, JP 26₁₆₂, KR 76₃₃₆, SC 78₄₆₂, TC 81₅₆₆. + '4i1x6r4v3z,' + // #3185: 5 fonts: HK 85₁₁₂, JP 26₁₆₂, KR 76₃₃₆, SC 83₄₆₇, TC 81₅₆₆. + '4i1x6r5a3u,' + // #3186: 4 fonts: HK 85₁₁₂, JP 26₁₆₂, SC 92₄₇₆, TC 81₅₆₆. + '4i1x12b3l,' + // #3187: 5 fonts: HK 85₁₁₂, JP 27₁₆₃, KR 77₃₃₇, SC 34₄₁₈, TC 81₅₆₆. + '4i1y6r3c5r,' + // #3188: 4 fonts: HK 85₁₁₂, JP 27₁₆₃, SC 34₄₁₈, TC 81₅₆₆. + '4i1y9u5r,' + // #3189: 4 fonts: HK 85₁₁₂, JP 27₁₆₃, SC 77₄₆₁, TC 81₅₆₆. + '4i1y11l4a,' + // #3190: 4 fonts: HK 85₁₁₂, JP 27₁₆₃, SC 78₄₆₂, TC 81₅₆₆. + '4i1y11m3z,' + // #3191: 4 fonts: HK 85₁₁₂, JP 28₁₆₄, SC 34₄₁₈, TC 81₅₆₆. + '4i1z9t5r,' + // #3192: 5 fonts: HK 85₁₁₂, JP 29₁₆₅, KR 77₃₃₇, SC 36₄₂₀, TC 81₅₆₆. + '4i2a6p3e5p,' + // #3193: 5 fonts: HK 85₁₁₂, JP 29₁₆₅, KR 78₃₃₈, SC 36₄₂₀, TC 81₅₆₆. + '4i2a6q3d5p,' + // #3194: 5 fonts: HK 85₁₁₂, JP 29₁₆₅, KR 78₃₃₈, SC 77₄₆₁, TC 81₅₆₆. + '4i2a6q4s4a,' + // #3195: 5 fonts: HK 85₁₁₂, JP 30₁₆₆, KR 79₃₃₉, SC 80₄₆₄, TC 81₅₆₆. + '4i2b6q4u3x,' + // #3196: 4 fonts: HK 85₁₁₂, JP 30₁₆₆, SC 38₄₂₂, TC 81₅₆₆. + '4i2b9v5n,' + // #3197: 4 fonts: HK 85₁₁₂, JP 31₁₆₇, SC 80₄₆₄, TC 81₅₆₆. + '4i2c11k3x,' + // #3198: 5 fonts: HK 85₁₁₂, JP 32₁₆₈, KR 80₃₄₀, SC 41₄₂₅, TC 81₅₆₆. + '4i2d6p3g5k,' + // #3199: 5 fonts: HK 85₁₁₂, JP 32₁₆₈, KR 80₃₄₀, SC 77₄₆₁, TC 81₅₆₆. + '4i2d6p4q4a,' + // #3200: 4 fonts: HK 85₁₁₂, JP 32₁₆₈, SC 40₄₂₄, TC 81₅₆₆. + '4i2d9v5l,' + // #3201: 5 fonts: HK 85₁₁₂, JP 33₁₆₉, KR 81₃₄₁, SC 42₄₂₆, TC 81₅₆₆. + '4i2e6p3g5j,' + // #3202: 5 fonts: HK 85₁₁₂, JP 33₁₆₉, KR 81₃₄₁, SC 79₄₆₃, TC 81₅₆₆. + '4i2e6p4r3y,' + // #3203: 5 fonts: HK 85₁₁₂, JP 34₁₇₀, KR 82₃₄₂, SC 44₄₂₈, TC 81₅₆₆. + '4i2f6p3h5h,' + // #3204: 4 fonts: HK 85₁₁₂, JP 34₁₇₀, SC 44₄₂₈, TC 81₅₆₆. + '4i2f9x5h,' + // #3205: 5 fonts: HK 85₁₁₂, JP 35₁₇₁, KR 82₃₄₂, SC 78₄₆₂, TC 81₅₆₆. + '4i2g6o4p3z,' + // #3206: 5 fonts: HK 85₁₁₂, JP 36₁₇₂, KR 82₃₄₂, SC 78₄₆₂, TC 81₅₆₆. + '4i2h6n4p3z,' + // #3207: 5 fonts: HK 85₁₁₂, JP 36₁₇₂, KR 83₃₄₃, SC 77₄₆₁, TC 81₅₆₆. + '4i2h6o4n4a,' + // #3208: 4 fonts: HK 85₁₁₂, JP 36₁₇₂, SC 93₄₇₇, TC 81₅₆₆. + '4i2h11s3k,' + // #3209: 5 fonts: HK 85₁₁₂, JP 37₁₇₃, KR 83₃₄₃, SC 81₄₆₅, TC 81₅₆₆. + '4i2i6n4r3w,' + // #3210: 5 fonts: HK 85₁₁₂, JP 37₁₇₃, KR 84₃₄₄, SC 78₄₆₂, TC 81₅₆₆. + '4i2i6o4n3z,' + // #3211: 4 fonts: HK 85₁₁₂, JP 37₁₇₃, SC 48₄₃₂, TC 81₅₆₆. + '4i2i9y5d,' + // #3212: 4 fonts: HK 85₁₁₂, JP 38₁₇₄, SC 49₄₃₃, TC 81₅₆₆. + '4i2j9y5c,' + // #3213: 4 fonts: HK 85₁₁₂, JP 38₁₇₄, SC 77₄₆₁, TC 81₅₆₆. + '4i2j11a4a,' + // #3214: 5 fonts: HK 85₁₁₂, JP 39₁₇₅, KR 85₃₄₅, SC 78₄₆₂, TC 81₅₆₆. + '4i2k6n4m3z,' + // #3215: 5 fonts: HK 85₁₁₂, JP 39₁₇₅, KR 85₃₄₅, SC 79₄₆₃, TC 81₅₆₆. + '4i2k6n4n3y,' + // #3216: 5 fonts: HK 85₁₁₂, JP 39₁₇₅, KR 85₃₄₅, SC 80₄₆₄, TC 81₅₆₆. + '4i2k6n4o3x,' + // #3217: 5 fonts: HK 85₁₁₂, JP 39₁₇₅, KR 86₃₄₆, SC 51₄₃₅, TC 81₅₆₆. + '4i2k6o3k5a,' + // #3218: 4 fonts: HK 85₁₁₂, JP 39₁₇₅, SC 51₄₃₅, TC 81₅₆₆. + '4i2k9z5a,' + // #3219: 5 fonts: HK 85₁₁₂, JP 40₁₇₆, KR 86₃₄₆, SC 77₄₆₁, TC 81₅₆₆. + '4i2l6n4k4a,' + // #3220: 4 fonts: HK 85₁₁₂, JP 42₁₇₈, SC 92₄₇₆, TC 81₅₆₆. + '4i2n11l3l,' + // #3221: 5 fonts: HK 85₁₁₂, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 81₅₆₆. + '4i2p6m3o4t,' + // #3222: 5 fonts: HK 85₁₁₂, JP 44₁₈₀, KR 89₃₄₉, SC 79₄₆₃, TC 81₅₆₆. + '4i2p6m4j3y,' + // #3223: 4 fonts: HK 85₁₁₂, JP 44₁₈₀, SC 59₄₄₃, TC 81₅₆₆. + '4i2p10c4s,' + // #3224: 4 fonts: HK 85₁₁₂, JP 45₁₈₁, SC 60₄₄₄, TC 81₅₆₆. + '4i2q10c4r,' + // #3225: 4 fonts: HK 85₁₁₂, JP 45₁₈₁, SC 79₄₆₃, TC 81₅₆₆. + '4i2q10v3y,' + // #3226: 5 fonts: HK 85₁₁₂, JP 46₁₈₂, KR 90₃₅₀, SC 79₄₆₃, TC 81₅₆₆. + '4i2r6l4i3y,' + // #3227: 4 fonts: HK 85₁₁₂, JP 46₁₈₂, SC 60₄₄₄, TC 81₅₆₆. + '4i2r10b4r,' + // #3228: 4 fonts: HK 85₁₁₂, JP 46₁₈₂, SC 61₄₄₅, TC 81₅₆₆. + '4i2r10c4q,' + // #3229: 4 fonts: HK 85₁₁₂, JP 46₁₈₂, SC 77₄₆₁, TC 81₅₆₆. + '4i2r10s4a,' + // #3230: 4 fonts: HK 85₁₁₂, JP 46₁₈₂, SC 78₄₆₂, TC 81₅₆₆. + '4i2r10t3z,' + // #3231: 4 fonts: HK 85₁₁₂, JP 46₁₈₂, SC 82₄₆₆, TC 81₅₆₆. + '4i2r10x3v,' + // #3232: 4 fonts: HK 85₁₁₂, JP 46₁₈₂, SC 92₄₇₆, TC 81₅₆₆. + '4i2r11h3l,' + // #3233: 5 fonts: HK 85₁₁₂, JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇, TC 81₅₆₆. + '4i2s6l3r4o,' + // #3234: 5 fonts: HK 85₁₁₂, JP 49₁₈₅, KR 92₃₅₂, SC 81₄₆₅, TC 81₅₆₆. + '4i2u6k4i3w,' + // #3235: 4 fonts: HK 85₁₁₂, JP 49₁₈₅, SC 66₄₅₀, TC 81₅₆₆. + '4i2u10e4l,' + // #3236: 4 fonts: HK 85₁₁₂, JP 50₁₈₆, SC 91₄₇₅, TC 81₅₆₆. + '4i2v11c3m,' + // #3237: 5 fonts: HK 85₁₁₂, JP 58₁₉₄, SC 72₄₅₆, TC 81₅₆₆, Math₆₅₅. + '4i3d10b4f3k,' + // #3238: 5 fonts: HK 85₁₁₂, JP 61₁₉₇, KR 73₃₃₃, SC 77₄₆₁, TC 81₅₆₆. + '4i3g5f4x4a,' + // #3239: 5 fonts: HK 85₁₁₂, JP 61₁₉₇, KR 76₃₃₆, SC 31₄₁₅, TC 81₅₆₆. + '4i3g5i3a5u,' + // #3240: 5 fonts: HK 85₁₁₂, JP 61₁₉₇, KR 79₃₃₉, SC 38₄₂₂, TC 81₅₆₆. + '4i3g5l3e5n,' + // #3241: 5 fonts: HK 85₁₁₂, JP 61₁₉₇, KR 82₃₄₂, SC 78₄₆₂, TC 81₅₆₆. + '4i3g5o4p3z,' + // #3242: 4 fonts: HK 85₁₁₂, JP 61₁₉₇, SC 77₄₆₁, TC 81₅₆₆. + '4i3g10d4a,' + // #3243: 5 fonts: HK 85₁₁₂, JP 62₁₉₈, KR 68₃₂₈, SC 77₄₆₁, TC 81₅₆₆. + '4i3h4z5c4a,' + // #3244: 4 fonts: HK 85₁₁₂, JP 62₁₉₈, SC 68₄₅₂, TC 81₅₆₆. + '4i3h9t4j,' + // #3245: 5 fonts: HK 85₁₁₂, JP 63₁₉₉, KR 75₃₃₅, SC 30₄₁₄, TC 81₅₆₆. + '4i3i5f3a5v,' + // #3246: 5 fonts: HK 85₁₁₂, JP 63₁₉₉, KR 75₃₃₅, SC 78₄₆₂, TC 81₅₆₆. + '4i3i5f4w3z,' + // #3247: 5 fonts: HK 85₁₁₂, JP 63₁₉₉, KR 76₃₃₆, SC 81₄₆₅, TC 81₅₆₆. + '4i3i5g4y3w,' + // #3248: 5 fonts: HK 85₁₁₂, JP 63₁₉₉, SC 74₄₅₈, TC 81₅₆₆, Noto Sans₅₉₁. + '4i3i9y4dy,' + // #3249: 5 fonts: HK 85₁₁₂, JP 64₂₀₀, KR 65₃₂₅, SC 6₃₉₀, TC 81₅₆₆. + '4i3j4u2m6t,' + // #3250: 5 fonts: HK 85₁₁₂, JP 64₂₀₀, KR 75₃₃₅, SC 77₄₆₁, TC 81₅₆₆. + '4i3j5e4v4a,' + // #3251: 5 fonts: HK 85₁₁₂, JP 65₂₀₁, KR 83₃₄₃, SC 78₄₆₂, TC 81₅₆₆. + '4i3k5l4o3z,' + // #3252: 5 fonts: HK 85₁₁₂, JP 65₂₀₁, KR 93₃₅₃, SC 79₄₆₃, TC 81₅₆₆. + '4i3k5v4f3y,' + // #3253: 5 fonts: HK 85₁₁₂, JP 66₂₀₂, KR 78₃₃₈, SC 36₄₂₀, TC 81₅₆₆. + '4i3l5f3d5p,' + // #3254: 5 fonts: HK 85₁₁₂, JP 67₂₀₃, KR 66₃₂₆, SC 78₄₆₂, TC 81₅₆₆. + '4i3m4s5f3z,' + // #3255: 5 fonts: HK 85₁₁₂, JP 67₂₀₃, KR 72₃₃₂, SC 78₄₆₂, TC 81₅₆₆. + '4i3m4y4z3z,' + // #3256: 5 fonts: HK 85₁₁₂, JP 69₂₀₅, KR 79₃₃₉, SC 40₄₂₄, TC 81₅₆₆. + '4i3o5d3g5l,' + // #3257: 5 fonts: HK 85₁₁₂, JP 69₂₀₅, KR 91₃₅₁, SC 80₄₆₄, TC 81₅₆₆. + '4i3o5p4i3x,' + // #3258: 5 fonts: HK 85₁₁₂, JP 72₂₀₈, KR 66₃₂₆, SC 9₃₉₃, TC 81₅₆₆. + '4i3r4n2o6q,' + // #3259: 5 fonts: HK 85₁₁₂, JP 74₂₁₀, KR 82₃₄₂, SC 80₄₆₄, TC 81₅₆₆. + '4i3t5b4r3x,' + // #3260: 4 fonts: HK 85₁₁₂, JP 74₂₁₀, SC 79₄₆₃, TC 81₅₆₆. + '4i3t9s3y,' + // #3261: 5 fonts: HK 85₁₁₂, JP 75₂₁₁, KR 76₃₃₆, SC 32₄₁₆, TC 81₅₆₆. + '4i3u4u3b5t,' + // #3262: 4 fonts: HK 85₁₁₂, JP 77₂₁₃, SC 60₄₄₄, TC 81₅₆₆. + '4i3w8w4r,' + // #3263: 5 fonts: HK 85₁₁₂, JP 78₂₁₄, KR 74₃₃₄, SC 28₄₁₂, TC 81₅₆₆. + '4i3x4p2z5x,' + // #3264: 5 fonts: HK 85₁₁₂, JP 78₂₁₄, KR 75₃₃₅, SC 29₄₁₃, TC 81₅₆₆. + '4i3x4q2z5w,' + // #3265: 5 fonts: HK 85₁₁₂, JP 78₂₁₄, KR 75₃₃₅, SC 89₄₇₃, TC 81₅₆₆. + '4i3x4q5h3o,' + // #3266: 5 fonts: HK 85₁₁₂, JP 82₂₁₈, KR 75₃₃₅, SC 30₄₁₄, TC 81₅₆₆. + '4i4b4m3a5v,' + // #3267: 5 fonts: HK 85₁₁₂, JP 83₂₁₉, KR 69₃₂₉, SC 16₄₀₀, TC 81₅₆₆. + '4i4c4f2s6j,' + // #3268: 5 fonts: HK 85₁₁₂, JP 85₂₂₁, KR 77₃₃₇, SC 34₄₁₈, TC 81₅₆₆. + '4i4e4l3c5r,' + // #3269: 5 fonts: HK 85₁₁₂, JP 87₂₂₃, KR 92₃₅₂, SC 65₄₄₉, TC 81₅₆₆. + '4i4g4y3s4m,' + // #3270: 5 fonts: HK 85₁₁₂, JP 91₂₂₇, KR 87₃₄₇, SC 54₄₃₈, TC 81₅₆₆. + '4i4k4p3m4x,' + // #3271: 4 fonts: HK 85₁₁₂, JP 94₂₃₀, SC 77₄₆₁, TC 81₅₆₆. + '4i4n8w4a,' + // #3272: 5 fonts: HK 85₁₁₂, JP 95₂₃₁, KR 0₂₆₀, SC 86₄₇₀, TC 81₅₆₆. + '4i4o1c8b3r,' + // #3273: 5 fonts: HK 85₁₁₂, JP 95₂₃₁, KR 88₃₄₈, SC 55₄₃₉, TC 81₅₆₆. + '4i4o4m3m4w,' + // #3274: 5 fonts: HK 85₁₁₂, JP 98₂₃₄, KR 0₂₆₀, SC 1₃₈₅, TC 81₅₆₆. + '4i4rz4u6y,' + // #3275: 4 fonts: HK 85₁₁₂, JP 98₂₃₄, SC 92₄₇₆, TC 81₅₆₆. + '4i4r9h3l,' + // #3276: 5 fonts: HK 85₁₁₂, JP 101₂₃₇, KR 0₂₆₀, SC 1₃₈₅, TC 81₅₆₆. + '4i4uw4u6y,' + // #3277: 4 fonts: HK 85₁₁₂, JP 101₂₃₇, SC 91₄₇₅, TC 81₅₆₆. + '4i4u9d3m,' + // #3278: 5 fonts: HK 85₁₁₂, JP 103₂₃₉, KR 91₃₅₁, SC 77₄₆₁, TC 81₅₆₆. + '4i4w4h4f4a,' + // #3279: 5 fonts: HK 85₁₁₂, JP 104₂₄₀, KR 66₃₂₆, SC 8₃₉₂, TC 81₅₆₆. + '4i4x3h2n6r,' + // #3280: 4 fonts: HK 85₁₁₂, JP 105₂₄₁, SC 92₄₇₆, TC 81₅₆₆. + '4i4y9a3l,' + // #3281: 5 fonts: HK 85₁₁₂, JP 106₂₄₂, KR 84₃₄₄, SC 77₄₆₁, TC 81₅₆₆. + '4i4z3x4m4a,' + // #3282: 5 fonts: HK 85₁₁₂, JP 108₂₄₄, KR 95₃₅₅, SC 68₄₅₂, TC 81₅₆₆. + '4i5b4g3s4j,' + // #3283: 3 fonts: HK 85₁₁₂, SC 43₄₂₇, TC 81₅₆₆. + '4i12c5i,' + // #3284: 3 fonts: HK 85₁₁₂, SC 78₄₆₂, TC 81₅₆₆. + '4i13l3z,' + // #3285: 3 fonts: HK 85₁₁₂, SC 79₄₆₃, TC 81₅₆₆. + '4i13m3y,' + // #3286: 3 fonts: HK 85₁₁₂, SC 94₄₇₈, TC 81₅₆₆. + '4i14b3j,' + // #3287: 5 fonts: HK 86₁₁₃, JP 3₁₃₉, KR 100₃₆₀, SC 78₄₆₂, TC 82₅₆₇. + '4jz8m3x4a,' + // #3288: 4 fonts: HK 86₁₁₃, JP 3₁₃₉, SC 2₃₈₆, TC 82₅₆₇. + '4jz9m6y,' + // #3289: 4 fonts: HK 86₁₁₃, JP 7₁₄₃, SC 6₃₉₀, TC 82₅₆₇. + '4j1d9m6u,' + // #3290: 4 fonts: HK 86₁₁₃, JP 8₁₄₄, SC 7₃₉₁, TC 82₅₆₇. + '4j1e9m6t,' + // #3291: 5 fonts: HK 86₁₁₃, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 82₅₆₇. + '4j1f6y2n6s,' + // #3292: 5 fonts: HK 86₁₁₃, JP 9₁₄₅, KR 66₃₂₆, SC 81₄₆₅, TC 82₅₆₇. + '4j1f6y5i3x,' + // #3293: 4 fonts: HK 86₁₁₃, JP 9₁₄₅, SC 8₃₉₂, TC 82₅₆₇. + '4j1f9m6s,' + // #3294: 5 fonts: HK 86₁₁₃, JP 10₁₄₆, KR 66₃₂₆, SC 78₄₆₂, TC 82₅₆₇. + '4j1g6x5f4a,' + // #3295: 5 fonts: HK 86₁₁₃, JP 10₁₄₆, KR 67₃₂₇, SC 77₄₆₁, TC 82₅₆₇. + '4j1g6y5d4b,' + // #3296: 4 fonts: HK 86₁₁₃, JP 10₁₄₆, SC 10₃₉₄, TC 82₅₆₇. + '4j1g9n6q,' + // #3297: 4 fonts: HK 86₁₁₃, JP 10₁₄₆, SC 79₄₆₃, TC 82₅₆₇. + '4j1g12e3z,' + // #3298: 5 fonts: HK 86₁₁₃, JP 11₁₄₇, KR 67₃₂₇, SC 12₃₉₆, TC 82₅₆₇. + '4j1h6x2q6o,' + // #3299: 4 fonts: HK 86₁₁₃, JP 11₁₄₇, SC 12₃₉₆, TC 82₅₆₇. + '4j1h9o6o,' + // #3300: 5 fonts: HK 86₁₁₃, JP 12₁₄₈, KR 67₃₂₇, SC 13₃₉₇, TC 82₅₆₇. + '4j1i6w2r6n,' + // #3301: 5 fonts: HK 86₁₁₃, JP 13₁₄₉, KR 68₃₂₈, SC 90₄₇₄, TC 82₅₆₇. + '4j1j6w5p3o,' + // #3302: 4 fonts: HK 86₁₁₃, JP 13₁₄₉, SC 14₃₉₈, TC 82₅₆₇. + '4j1j9o6m,' + // #3303: 5 fonts: HK 86₁₁₃, JP 14₁₅₀, KR 69₃₂₉, SC 15₃₉₉, TC 82₅₆₇. + '4j1k6w2r6l,' + // #3304: 5 fonts: HK 86₁₁₃, JP 14₁₅₀, KR 69₃₂₉, SC 79₄₆₃, TC 82₅₆₇. + '4j1k6w5d3z,' + // #3305: 5 fonts: HK 86₁₁₃, JP 15₁₅₁, KR 69₃₂₉, SC 16₄₀₀, TC 82₅₆₇. + '4j1l6v2s6k,' + // #3306: 5 fonts: HK 86₁₁₃, JP 15₁₅₁, KR 70₃₃₀, SC 18₄₀₂, TC 82₅₆₇. + '4j1l6w2t6i,' + // #3307: 4 fonts: HK 86₁₁₃, JP 15₁₅₁, SC 17₄₀₁, TC 82₅₆₇. + '4j1l9p6j,' + // #3308: 5 fonts: HK 86₁₁₃, JP 17₁₅₃, KR 70₃₃₀, SC 80₄₆₄, TC 82₅₆₇. + '4j1n6u5d3y,' + // #3309: 5 fonts: HK 86₁₁₃, JP 17₁₅₃, KR 71₃₃₁, SC 20₄₀₄, TC 82₅₆₇. + '4j1n6v2u6g,' + // #3310: 4 fonts: HK 86₁₁₃, JP 17₁₅₃, SC 20₄₀₄, TC 82₅₆₇. + '4j1n9q6g,' + // #3311: 5 fonts: HK 86₁₁₃, JP 18₁₅₄, KR 71₃₃₁, SC 21₄₀₅, TC 82₅₆₇. + '4j1o6u2v6f,' + // #3312: 5 fonts: HK 86₁₁₃, JP 18₁₅₄, KR 71₃₃₁, SC 78₄₆₂, TC 82₅₆₇. + '4j1o6u5a4a,' + // #3313: 5 fonts: HK 86₁₁₃, JP 18₁₅₄, KR 71₃₃₁, SC 84₄₆₈, TC 82₅₆₇. + '4j1o6u5g3u,' + // #3314: 4 fonts: HK 86₁₁₃, JP 18₁₅₄, SC 20₄₀₄, TC 82₅₆₇. + '4j1o9p6g,' + // #3315: 5 fonts: HK 86₁₁₃, JP 19₁₅₅, KR 72₃₃₂, SC 21₄₀₅, TC 82₅₆₇. + '4j1p6u2u6f,' + // #3316: 4 fonts: HK 86₁₁₃, JP 19₁₅₅, SC 77₄₆₁, TC 82₅₆₇. + '4j1p11t4b,' + // #3317: 5 fonts: HK 86₁₁₃, JP 20₁₅₆, KR 72₃₃₂, SC 23₄₀₇, TC 82₅₆₇. + '4j1q6t2w6d,' + // #3318: 5 fonts: HK 86₁₁₃, JP 20₁₅₆, KR 72₃₃₂, SC 77₄₆₁, TC 82₅₆₇. + '4j1q6t4y4b,' + // #3319: 4 fonts: HK 86₁₁₃, JP 20₁₅₆, SC 83₄₆₇, TC 82₅₆₇. + '4j1q11y3v,' + // #3320: 5 fonts: HK 86₁₁₃, JP 21₁₅₇, KR 72₃₃₂, SC 77₄₆₁, TC 82₅₆₇. + '4j1r6s4y4b,' + // #3321: 4 fonts: HK 86₁₁₃, JP 21₁₅₇, SC 77₄₆₁, TC 82₅₆₇. + '4j1r11r4b,' + // #3322: 5 fonts: HK 86₁₁₃, JP 22₁₅₈, KR 73₃₃₃, SC 81₄₆₅, TC 82₅₆₇. + '4j1s6s5b3x,' + // #3323: 5 fonts: HK 86₁₁₃, JP 22₁₅₈, KR 74₃₃₄, SC 26₄₁₀, TC 82₅₆₇. + '4j1s6t2x6a,' + // #3324: 5 fonts: HK 86₁₁₃, JP 22₁₅₈, KR 74₃₃₄, SC 77₄₆₁, TC 82₅₆₇. + '4j1s6t4w4b,' + // #3325: 5 fonts: HK 86₁₁₃, JP 25₁₆₁, KR 76₃₃₆, SC 77₄₆₁, TC 82₅₆₇. + '4j1v6s4u4b,' + // #3326: 4 fonts: HK 86₁₁₃, JP 25₁₆₁, SC 30₄₁₄, TC 82₅₆₇. + '4j1v9s5w,' + // #3327: 4 fonts: HK 86₁₁₃, JP 25₁₆₁, SC 77₄₆₁, TC 82₅₆₇. + '4j1v11n4b,' + // #3328: 5 fonts: HK 86₁₁₃, JP 26₁₆₂, KR 76₃₃₆, SC 32₄₁₆, TC 82₅₆₇. + '4j1w6r3b5u,' + // #3329: 4 fonts: HK 86₁₁₃, JP 26₁₆₂, SC 32₄₁₆, TC 82₅₆₇. + '4j1w9t5u,' + // #3330: 4 fonts: HK 86₁₁₃, JP 26₁₆₂, SC 80₄₆₄, TC 82₅₆₇. + '4j1w11p3y,' + // #3331: 5 fonts: HK 86₁₁₃, JP 28₁₆₄, KR 77₃₃₇, SC 35₄₁₉, TC 82₅₆₇. + '4j1y6q3d5r,' + // #3332: 4 fonts: HK 86₁₁₃, JP 28₁₆₄, SC 34₄₁₈, TC 82₅₆₇. + '4j1y9t5s,' + // #3333: 4 fonts: HK 86₁₁₃, JP 28₁₆₄, SC 35₄₁₉, TC 82₅₆₇. + '4j1y9u5r,' + // #3334: 5 fonts: HK 86₁₁₃, JP 29₁₆₅, KR 77₃₃₇, SC 78₄₆₂, TC 82₅₆₇. + '4j1z6p4u4a,' + // #3335: 5 fonts: HK 86₁₁₃, JP 29₁₆₅, KR 78₃₃₈, SC 77₄₆₁, TC 82₅₆₇. + '4j1z6q4s4b,' + // #3336: 4 fonts: HK 86₁₁₃, JP 29₁₆₅, SC 36₄₂₀, TC 82₅₆₇. + '4j1z9u5q,' + // #3337: 4 fonts: HK 86₁₁₃, JP 29₁₆₅, SC 78₄₆₂, TC 82₅₆₇. + '4j1z11k4a,' + // #3338: 5 fonts: HK 86₁₁₃, JP 30₁₆₆, KR 78₃₃₈, SC 37₄₂₁, TC 82₅₆₇. + '4j2a6p3e5p,' + // #3339: 5 fonts: HK 86₁₁₃, JP 31₁₆₇, KR 80₃₄₀, SC 78₄₆₂, TC 82₅₆₇. + '4j2b6q4r4a,' + // #3340: 5 fonts: HK 86₁₁₃, JP 32₁₆₈, KR 80₃₄₀, SC 83₄₆₇, TC 82₅₆₇. + '4j2c6p4w3v,' + // #3341: 5 fonts: HK 86₁₁₃, JP 33₁₆₉, KR 81₃₄₁, SC 43₄₂₇, TC 82₅₆₇. + '4j2d6p3h5j,' + // #3342: 5 fonts: HK 86₁₁₃, JP 33₁₆₉, KR 81₃₄₁, SC 77₄₆₁, TC 82₅₆₇. + '4j2d6p4p4b,' + // #3343: 5 fonts: HK 86₁₁₃, JP 34₁₇₀, KR 81₃₄₁, SC 44₄₂₈, TC 82₅₆₇. + '4j2e6o3i5i,' + // #3344: 5 fonts: HK 86₁₁₃, JP 34₁₇₀, KR 82₃₄₂, SC 77₄₆₁, TC 82₅₆₇. + '4j2e6p4o4b,' + // #3345: 4 fonts: HK 86₁₁₃, JP 34₁₇₀, SC 44₄₂₈, TC 82₅₆₇. + '4j2e9x5i,' + // #3346: 5 fonts: HK 86₁₁₃, JP 35₁₇₁, KR 82₃₄₂, SC 79₄₆₃, TC 82₅₆₇. + '4j2f6o4q3z,' + // #3347: 5 fonts: HK 86₁₁₃, JP 36₁₇₂, KR 83₃₄₃, SC 80₄₆₄, TC 82₅₆₇. + '4j2g6o4q3y,' + // #3348: 4 fonts: HK 86₁₁₃, JP 36₁₇₂, SC 92₄₇₆, TC 82₅₆₇. + '4j2g11r3m,' + // #3349: 5 fonts: HK 86₁₁₃, JP 37₁₇₃, KR 83₃₄₃, SC 80₄₆₄, TC 82₅₆₇. + '4j2h6n4q3y,' + // #3350: 4 fonts: HK 86₁₁₃, JP 37₁₇₃, SC 48₄₃₂, TC 82₅₆₇. + '4j2h9y5e,' + // #3351: 4 fonts: HK 86₁₁₃, JP 38₁₇₄, SC 50₄₃₄, TC 82₅₆₇. + '4j2i9z5c,' + // #3352: 4 fonts: HK 86₁₁₃, JP 38₁₇₄, SC 79₄₆₃, TC 82₅₆₇. + '4j2i11c3z,' + // #3353: 5 fonts: HK 86₁₁₃, JP 39₁₇₅, KR 85₃₄₅, SC 77₄₆₁, TC 82₅₆₇. + '4j2j6n4l4b,' + // #3354: 4 fonts: HK 86₁₁₃, JP 39₁₇₅, SC 82₄₆₆, TC 82₅₆₇. + '4j2j11e3w,' + // #3355: 5 fonts: HK 86₁₁₃, JP 41₁₇₇, KR 86₃₄₆, SC 80₄₆₄, TC 82₅₆₇. + '4j2l6m4n3y,' + // #3356: 5 fonts: HK 86₁₁₃, JP 41₁₇₇, KR 87₃₄₇, SC 79₄₆₃, TC 82₅₆₇. + '4j2l6n4l3z,' + // #3357: 4 fonts: HK 86₁₁₃, JP 41₁₇₇, SC 54₄₃₈, TC 82₅₆₇. + '4j2l10a4y,' + // #3358: 5 fonts: HK 86₁₁₃, JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉, TC 82₅₆₇. + '4j2m6m3n4x,' + // #3359: 5 fonts: HK 86₁₁₃, JP 42₁₇₈, KR 88₃₄₈, SC 77₄₆₁, TC 82₅₆₇. + '4j2m6n4i4b,' + // #3360: 4 fonts: HK 86₁₁₃, JP 43₁₇₉, SC 78₄₆₂, TC 82₅₆₇. + '4j2n10w4a,' + // #3361: 5 fonts: HK 86₁₁₃, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 82₅₆₇. + '4j2o6m3o4u,' + // #3362: 5 fonts: HK 86₁₁₃, JP 44₁₈₀, KR 89₃₄₉, SC 80₄₆₄, TC 82₅₆₇. + '4j2o6m4k3y,' + // #3363: 4 fonts: HK 86₁₁₃, JP 45₁₈₁, SC 60₄₄₄, TC 82₅₆₇. + '4j2p10c4s,' + // #3364: 5 fonts: HK 86₁₁₃, JP 46₁₈₂, KR 90₃₅₀, SC 78₄₆₂, TC 82₅₆₇. + '4j2q6l4h4a,' + // #3365: 5 fonts: HK 86₁₁₃, JP 47₁₈₃, KR 90₃₅₀, SC 62₄₄₆, TC 82₅₆₇. + '4j2r6k3r4q,' + // #3366: 4 fonts: HK 86₁₁₃, JP 47₁₈₃, SC 62₄₄₆, TC 82₅₆₇. + '4j2r10c4q,' + // #3367: 4 fonts: HK 86₁₁₃, JP 47₁₈₃, SC 79₄₆₃, TC 82₅₆₇. + '4j2r10t3z,' + // #3368: 4 fonts: HK 86₁₁₃, JP 47₁₈₃, SC 86₄₇₀, TC 82₅₆₇. + '4j2r11a3s,' + // #3369: 5 fonts: HK 86₁₁₃, JP 48₁₈₄, KR 91₃₅₁, SC 64₄₄₈, TC 82₅₆₇. + '4j2s6k3s4o,' + // #3370: 5 fonts: HK 86₁₁₃, JP 48₁₈₄, KR 91₃₅₁, SC 83₄₆₇, TC 82₅₆₇. + '4j2s6k4l3v,' + // #3371: 5 fonts: HK 86₁₁₃, JP 49₁₈₅, KR 92₃₅₂, SC 65₄₄₉, TC 82₅₆₇. + '4j2t6k3s4n,' + // #3372: 5 fonts: HK 86₁₁₃, JP 55₁₉₁, KR 95₃₅₅, SC 68₄₅₂, TC 82₅₆₇. + '4j2z6h3s4k,' + // #3373: 5 fonts: HK 86₁₁₃, JP 60₁₉₆, KR 67₃₂₇, SC 10₃₉₄, TC 82₅₆₇. + '4j3e5a2o6q,' + // #3374: 5 fonts: HK 86₁₁₃, JP 60₁₉₆, KR 89₃₄₉, SC 79₄₆₃, TC 82₅₆₇. + '4j3e5w4j3z,' + // #3375: 5 fonts: HK 86₁₁₃, JP 62₁₉₈, KR 69₃₂₉, SC 15₃₉₉, TC 82₅₆₇. + '4j3g5a2r6l,' + // #3376: 5 fonts: HK 86₁₁₃, JP 62₁₉₈, KR 73₃₃₃, SC 77₄₆₁, TC 82₅₆₇. + '4j3g5e4x4b,' + // #3377: 5 fonts: HK 86₁₁₃, JP 62₁₉₈, KR 79₃₃₉, SC 78₄₆₂, TC 82₅₆₇. + '4j3g5k4s4a,' + // #3378: 5 fonts: HK 86₁₁₃, JP 62₁₉₈, KR 82₃₄₂, SC 46₄₃₀, TC 82₅₆₇. + '4j3g5n3j5g,' + // #3379: 135 fonts: HK 86₁₁₃, JP 62₁₉₈, KR 99₃₅₉, SC 74₄₅₈, TC 82₅₆₇, Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Canadian Aboriginal₆₀₆, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + '4j3g6e3u4ewaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #3380: 5 fonts: HK 86₁₁₃, JP 63₁₉₉, KR 66₃₂₆, SC 7₃₉₁, TC 82₅₆₇. + '4j3h4w2m6t,' + // #3381: 5 fonts: HK 86₁₁₃, JP 64₂₀₀, KR 65₃₂₅, SC 79₄₆₃, TC 82₅₆₇. + '4j3i4u5h3z,' + // #3382: 5 fonts: HK 86₁₁₃, JP 64₂₀₀, KR 79₃₃₉, SC 39₄₂₃, TC 82₅₆₇. + '4j3i5i3f5n,' + // #3383: 4 fonts: HK 86₁₁₃, JP 64₂₀₀, SC 94₄₇₈, TC 82₅₆₇. + '4j3i10r3k,' + // #3384: 5 fonts: HK 86₁₁₃, JP 65₂₀₁, KR 65₃₂₅, SC 78₄₆₂, TC 82₅₆₇. + '4j3j4t5g4a,' + // #3385: 4 fonts: HK 86₁₁₃, JP 65₂₀₁, SC 6₃₉₀, TC 82₅₆₇. + '4j3j7g6u,' + // #3386: 5 fonts: HK 86₁₁₃, JP 66₂₀₂, KR 78₃₃₈, SC 77₄₆₁, TC 82₅₆₇. + '4j3k5f4s4b,' + // #3387: 5 fonts: HK 86₁₁₃, JP 66₂₀₂, KR 83₃₄₃, SC 80₄₆₄, TC 82₅₆₇. + '4j3k5k4q3y,' + // #3388: 5 fonts: HK 86₁₁₃, JP 66₂₀₂, KR 86₃₄₆, SC 79₄₆₃, TC 82₅₆₇. + '4j3k5n4m3z,' + // #3389: 5 fonts: HK 86₁₁₃, JP 66₂₀₂, KR 88₃₄₈, SC 78₄₆₂, TC 82₅₆₇. + '4j3k5p4j4a,' + // #3390: 5 fonts: HK 86₁₁₃, JP 67₂₀₃, KR 72₃₃₂, SC 77₄₆₁, TC 82₅₆₇. + '4j3l4y4y4b,' + // #3391: 5 fonts: HK 86₁₁₃, JP 70₂₀₆, KR 75₃₃₅, SC 31₄₁₅, TC 82₅₆₇. + '4j3o4y3b5v,' + // #3392: 5 fonts: HK 86₁₁₃, JP 71₂₀₇, KR 74₃₃₄, SC 28₄₁₂, TC 82₅₆₇. + '4j3p4w2z5y,' + // #3393: 5 fonts: HK 86₁₁₃, JP 71₂₀₇, KR 75₃₃₅, SC 29₄₁₃, TC 82₅₆₇. + '4j3p4x2z5x,' + // #3394: 5 fonts: HK 86₁₁₃, JP 72₂₀₈, KR 78₃₃₈, SC 80₄₆₄, TC 82₅₆₇. + '4j3q4z4v3y,' + // #3395: 6 fonts: HK 86₁₁₃, JP 76₂₁₂, KR 98₃₅₈, SC 72₄₅₆, TC 82₅₆₇, Math₆₅₅. + '4j3u5p3t4g3j,' + // #3396: 4 fonts: HK 86₁₁₃, JP 77₂₁₃, SC 77₄₆₁, TC 82₅₆₇. + '4j3v9n4b,' + // #3397: 5 fonts: HK 86₁₁₃, JP 78₂₁₄, KR 68₃₂₈, SC 14₃₉₈, TC 82₅₆₇. + '4j3w4j2r6m,' + // #3398: 5 fonts: HK 86₁₁₃, JP 79₂₁₅, KR 90₃₅₀, SC 79₄₆₃, TC 82₅₆₇. + '4j3x5e4i3z,' + // #3399: 5 fonts: HK 86₁₁₃, JP 83₂₁₉, KR 74₃₃₄, SC 27₄₁₁, TC 82₅₆₇. + '4j4b4k2y5z,' + // #3400: 5 fonts: HK 86₁₁₃, JP 83₂₁₉, KR 76₃₃₆, SC 33₄₁₇, TC 82₅₆₇. + '4j4b4m3c5t,' + // #3401: 5 fonts: HK 86₁₁₃, JP 83₂₁₉, KR 77₃₃₇, SC 78₄₆₂, TC 82₅₆₇. + '4j4b4n4u4a,' + // #3402: 5 fonts: HK 86₁₁₃, JP 83₂₁₉, KR 89₃₄₉, SC 77₄₆₁, TC 82₅₆₇. + '4j4b4z4h4b,' + // #3403: 5 fonts: HK 86₁₁₃, JP 85₂₂₁, KR 78₃₃₈, SC 78₄₆₂, TC 82₅₆₇. + '4j4d4m4t4a,' + // #3404: 5 fonts: HK 86₁₁₃, JP 87₂₂₃, KR 74₃₃₄, SC 28₄₁₂, TC 82₅₆₇. + '4j4f4g2z5y,' + // #3405: 5 fonts: HK 86₁₁₃, JP 88₂₂₄, KR 89₃₄₉, SC 59₄₄₃, TC 82₅₆₇. + '4j4g4u3p4t,' + // #3406: 5 fonts: HK 86₁₁₃, JP 90₂₂₆, KR 83₃₄₃, SC 78₄₆₂, TC 82₅₆₇. + '4j4i4m4o4a,' + // #3407: 5 fonts: HK 86₁₁₃, JP 91₂₂₇, KR 78₃₃₈, SC 80₄₆₄, TC 82₅₆₇. + '4j4j4g4v3y,' + // #3408: 5 fonts: HK 86₁₁₃, JP 91₂₂₇, KR 79₃₃₉, SC 40₄₂₄, TC 82₅₆₇. + '4j4j4h3g5m,' + // #3409: 5 fonts: HK 86₁₁₃, JP 95₂₃₁, KR 74₃₃₄, SC 28₄₁₂, TC 82₅₆₇. + '4j4n3y2z5y,' + // #3410: 4 fonts: HK 86₁₁₃, JP 95₂₃₁, SC 92₄₇₆, TC 82₅₆₇. + '4j4n9k3m,' + // #3411: 5 fonts: HK 86₁₁₃, JP 100₂₃₆, KR 72₃₃₂, SC 22₄₀₆, TC 82₅₆₇. + '4j4s3r2v6e,' + // #3412: 5 fonts: HK 86₁₁₃, JP 111₂₄₇, KR 93₃₅₃, SC 94₄₇₈, TC 82₅₆₇. + '4j5d4b4u3k,' + // #3413: 4 fonts: HK 86₁₁₃, JP 112₂₄₈, SC 18₄₀₂, TC 82₅₆₇. + '4j5e5x6i,' + // #3414: 5 fonts: HK 86₁₁₃, JP 119₂₅₅, KR 100₃₆₀, SC 68₄₅₂, TC 82₅₆₇. + '4j5l4a3n4k,' + // #3415: 3 fonts: HK 86₁₁₃, SC 20₄₀₄, TC 82₅₆₇. + '4j11e6g,' + // #3416: 3 fonts: HK 86₁₁₃, SC 61₄₄₅, TC 82₅₆₇. + '4j12t4r,' + // #3417: 3 fonts: HK 86₁₁₃, SC 80₄₆₄, TC 82₅₆₇. + '4j13m3y,' + // #3418: 3 fonts: HK 86₁₁₃, SC 92₄₇₆, TC 82₅₆₇. + '4j13y3m,' + // #3419: 3 fonts: HK 86₁₁₃, SC 93₄₇₇, TC 82₅₆₇. + '4j13z3l,' + // #3420: 3 fonts: HK 86₁₁₃, SC 94₄₇₈, TC 82₅₆₇. + '4j14a3k,' + // #3421: 4 fonts: HK 87₁₁₄, JP 3₁₃₉, SC 2₃₈₆, TC 83₅₆₈. + '4ky9m6z,' + // #3422: 5 fonts: HK 87₁₁₄, JP 5₁₄₁, KR 65₃₂₅, SC 79₄₆₃, TC 83₅₆₈. + '4k1a7b5h4a,' + // #3423: 5 fonts: HK 87₁₁₄, JP 6₁₄₂, KR 65₃₂₅, SC 5₃₈₉, TC 83₅₆₈. + '4k1b7a2l6w,' + // #3424: 4 fonts: HK 87₁₁₄, JP 6₁₄₂, SC 4₃₈₈, TC 83₅₆₈. + '4k1b9l6x,' + // #3425: 4 fonts: HK 87₁₁₄, JP 6₁₄₂, SC 5₃₈₉, TC 83₅₆₈. + '4k1b9m6w,' + // #3426: 5 fonts: HK 87₁₁₄, JP 7₁₄₃, KR 65₃₂₅, SC 6₃₉₀, TC 83₅₆₈. + '4k1c6z2m6v,' + // #3427: 5 fonts: HK 87₁₁₄, JP 7₁₄₃, KR 65₃₂₅, SC 80₄₆₄, TC 83₅₆₈. + '4k1c6z5i3z,' + // #3428: 4 fonts: HK 87₁₁₄, JP 7₁₄₃, SC 81₄₆₅, TC 83₅₆₈. + '4k1c12j3y,' + // #3429: 5 fonts: HK 87₁₁₄, JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 83₅₆₈. + '4k1d6z2m6u,' + // #3430: 5 fonts: HK 87₁₁₄, JP 9₁₄₅, KR 66₃₂₆, SC 9₃₉₃, TC 83₅₆₈. + '4k1e6y2o6s,' + // #3431: 5 fonts: HK 87₁₁₄, JP 9₁₄₅, KR 66₃₂₆, SC 77₄₆₁, TC 83₅₆₈. + '4k1e6y5e4c,' + // #3432: 5 fonts: HK 87₁₁₄, JP 9₁₄₅, KR 66₃₂₆, SC 80₄₆₄, TC 83₅₆₈. + '4k1e6y5h3z,' + // #3433: 5 fonts: HK 87₁₁₄, JP 10₁₄₆, KR 67₃₂₇, SC 10₃₉₄, TC 83₅₆₈. + '4k1f6y2o6r,' + // #3434: 4 fonts: HK 87₁₁₄, JP 10₁₄₆, SC 11₃₉₅, TC 83₅₆₈. + '4k1f9o6q,' + // #3435: 5 fonts: HK 87₁₁₄, JP 11₁₄₇, KR 67₃₂₇, SC 12₃₉₆, TC 83₅₆₈. + '4k1g6x2q6p,' + // #3436: 5 fonts: HK 87₁₁₄, JP 12₁₄₈, KR 67₃₂₇, SC 13₃₉₇, TC 83₅₆₈. + '4k1h6w2r6o,' + // #3437: 4 fonts: HK 87₁₁₄, JP 12₁₄₈, SC 13₃₉₇, TC 83₅₆₈. + '4k1h9o6o,' + // #3438: 5 fonts: HK 87₁₁₄, JP 13₁₄₉, KR 68₃₂₈, SC 81₄₆₅, TC 83₅₆₈. + '4k1i6w5g3y,' + // #3439: 4 fonts: HK 87₁₁₄, JP 13₁₄₉, SC 93₄₇₇, TC 83₅₆₈. + '4k1i12p3m,' + // #3440: 5 fonts: HK 87₁₁₄, JP 15₁₅₁, KR 70₃₃₀, SC 17₄₀₁, TC 83₅₆₈. + '4k1k6w2s6k,' + // #3441: 5 fonts: HK 87₁₁₄, JP 15₁₅₁, KR 70₃₃₀, SC 78₄₆₂, TC 83₅₆₈. + '4k1k6w5b4b,' + // #3442: 4 fonts: HK 87₁₁₄, JP 15₁₅₁, SC 18₄₀₂, TC 83₅₆₈. + '4k1k9q6j,' + // #3443: 4 fonts: HK 87₁₁₄, JP 16₁₅₂, SC 94₄₇₈, TC 83₅₆₈. + '4k1l12n3l,' + // #3444: 5 fonts: HK 87₁₁₄, JP 17₁₅₃, KR 71₃₃₁, SC 19₄₀₃, TC 83₅₆₈. + '4k1m6v2t6i,' + // #3445: 5 fonts: HK 87₁₁₄, JP 17₁₅₃, KR 71₃₃₁, SC 20₄₀₄, TC 83₅₆₈. + '4k1m6v2u6h,' + // #3446: 4 fonts: HK 87₁₁₄, JP 18₁₅₄, SC 21₄₀₅, TC 83₅₆₈. + '4k1n9q6g,' + // #3447: 4 fonts: HK 87₁₁₄, JP 19₁₅₅, SC 21₄₀₅, TC 83₅₆₈. + '4k1o9p6g,' + // #3448: 5 fonts: HK 87₁₁₄, JP 20₁₅₆, KR 72₃₃₂, SC 23₄₀₇, TC 83₅₆₈. + '4k1p6t2w6e,' + // #3449: 5 fonts: HK 87₁₁₄, JP 20₁₅₆, KR 72₃₃₂, SC 81₄₆₅, TC 83₅₆₈. + '4k1p6t5c3y,' + // #3450: 4 fonts: HK 87₁₁₄, JP 20₁₅₆, SC 81₄₆₅, TC 83₅₆₈. + '4k1p11w3y,' + // #3451: 5 fonts: HK 87₁₁₄, JP 22₁₅₈, KR 73₃₃₃, SC 78₄₆₂, TC 83₅₆₈. + '4k1r6s4y4b,' + // #3452: 4 fonts: HK 87₁₁₄, JP 22₁₅₈, SC 79₄₆₃, TC 83₅₆₈. + '4k1r11s4a,' + // #3453: 5 fonts: HK 87₁₁₄, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 83₅₆₈. + '4k1s6s2y6a,' + // #3454: 5 fonts: HK 87₁₁₄, JP 24₁₆₀, KR 75₃₃₅, SC 79₄₆₃, TC 83₅₆₈. + '4k1t6s4x4a,' + // #3455: 4 fonts: HK 87₁₁₄, JP 24₁₆₀, SC 79₄₆₃, TC 83₅₆₈. + '4k1t11q4a,' + // #3456: 4 fonts: HK 87₁₁₄, JP 25₁₆₁, SC 31₄₁₅, TC 83₅₆₈. + '4k1u9t5w,' + // #3457: 4 fonts: HK 87₁₁₄, JP 25₁₆₁, SC 78₄₆₂, TC 83₅₆₈. + '4k1u11o4b,' + // #3458: 5 fonts: HK 87₁₁₄, JP 26₁₆₂, KR 76₃₃₆, SC 31₄₁₅, TC 83₅₆₈. + '4k1v6r3a5w,' + // #3459: 4 fonts: HK 87₁₁₄, JP 26₁₆₂, SC 94₄₇₈, TC 83₅₆₈. + '4k1v12d3l,' + // #3460: 5 fonts: HK 87₁₁₄, JP 27₁₆₃, KR 76₃₃₆, SC 33₄₁₇, TC 83₅₆₈. + '4k1w6q3c5u,' + // #3461: 4 fonts: HK 87₁₁₄, JP 28₁₆₄, SC 35₄₁₉, TC 83₅₆₈. + '4k1x9u5s,' + // #3462: 4 fonts: HK 87₁₁₄, JP 28₁₆₄, SC 79₄₆₃, TC 83₅₆₈. + '4k1x11m4a,' + // #3463: 5 fonts: HK 87₁₁₄, JP 29₁₆₅, KR 77₃₃₇, SC 79₄₆₃, TC 83₅₆₈. + '4k1y6p4v4a,' + // #3464: 5 fonts: HK 87₁₁₄, JP 29₁₆₅, KR 78₃₃₈, SC 36₄₂₀, TC 83₅₆₈. + '4k1y6q3d5r,' + // #3465: 5 fonts: HK 87₁₁₄, JP 29₁₆₅, KR 78₃₃₈, SC 78₄₆₂, TC 83₅₆₈. + '4k1y6q4t4b,' + // #3466: 4 fonts: HK 87₁₁₄, JP 29₁₆₅, SC 37₄₂₁, TC 83₅₆₈. + '4k1y9v5q,' + // #3467: 5 fonts: HK 87₁₁₄, JP 31₁₆₇, KR 79₃₃₉, SC 39₄₂₃, TC 83₅₆₈. + '4k2a6p3f5o,' + // #3468: 5 fonts: HK 87₁₁₄, JP 31₁₆₇, KR 79₃₃₉, SC 77₄₆₁, TC 83₅₆₈. + '4k2a6p4r4c,' + // #3469: 5 fonts: HK 87₁₁₄, JP 32₁₆₈, KR 80₃₄₀, SC 80₄₆₄, TC 83₅₆₈. + '4k2b6p4t3z,' + // #3470: 5 fonts: HK 87₁₁₄, JP 32₁₆₈, KR 80₃₄₀, SC 82₄₆₆, TC 83₅₆₈. + '4k2b6p4v3x,' + // #3471: 4 fonts: HK 87₁₁₄, JP 32₁₆₈, SC 42₄₂₆, TC 83₅₆₈. + '4k2b9x5l,' + // #3472: 5 fonts: HK 87₁₁₄, JP 33₁₆₉, KR 81₃₄₁, SC 42₄₂₆, TC 83₅₆₈. + '4k2c6p3g5l,' + // #3473: 5 fonts: HK 87₁₁₄, JP 33₁₆₉, KR 81₃₄₁, SC 43₄₂₇, TC 83₅₆₈. + '4k2c6p3h5k,' + // #3474: 5 fonts: HK 87₁₁₄, JP 33₁₆₉, KR 81₃₄₁, SC 80₄₆₄, TC 83₅₆₈. + '4k2c6p4s3z,' + // #3475: 5 fonts: HK 87₁₁₄, JP 34₁₇₀, KR 82₃₄₂, SC 44₄₂₈, TC 83₅₆₈. + '4k2d6p3h5j,' + // #3476: 5 fonts: HK 87₁₁₄, JP 35₁₇₁, KR 82₃₄₂, SC 79₄₆₃, TC 83₅₆₈. + '4k2e6o4q4a,' + // #3477: 5 fonts: HK 87₁₁₄, JP 35₁₇₁, KR 82₃₄₂, SC 80₄₆₄, TC 83₅₆₈. + '4k2e6o4r3z,' + // #3478: 5 fonts: HK 87₁₁₄, JP 35₁₇₁, KR 82₃₄₂, SC 81₄₆₅, TC 83₅₆₈. + '4k2e6o4s3y,' + // #3479: 4 fonts: HK 87₁₁₄, JP 35₁₇₁, SC 45₄₂₉, TC 83₅₆₈. + '4k2e9x5i,' + // #3480: 4 fonts: HK 87₁₁₄, JP 35₁₇₁, SC 79₄₆₃, TC 83₅₆₈. + '4k2e11f4a,' + // #3481: 4 fonts: HK 87₁₁₄, JP 35₁₇₁, SC 80₄₆₄, TC 83₅₆₈. + '4k2e11g3z,' + // #3482: 5 fonts: HK 87₁₁₄, JP 36₁₇₂, KR 83₃₄₃, SC 47₄₃₁, TC 83₅₆₈. + '4k2f6o3j5g,' + // #3483: 5 fonts: HK 87₁₁₄, JP 36₁₇₂, KR 83₃₄₃, SC 86₄₇₀, TC 83₅₆₈. + '4k2f6o4w3t,' + // #3484: 5 fonts: HK 87₁₁₄, JP 37₁₇₃, KR 83₃₄₃, SC 78₄₆₂, TC 83₅₆₈. + '4k2g6n4o4b,' + // #3485: 5 fonts: HK 87₁₁₄, JP 38₁₇₄, KR 84₃₄₄, SC 80₄₆₄, TC 83₅₆₈. + '4k2h6n4p3z,' + // #3486: 5 fonts: HK 87₁₁₄, JP 38₁₇₄, KR 85₃₄₅, SC 80₄₆₄, TC 83₅₆₈. + '4k2h6o4o3z,' + // #3487: 4 fonts: HK 87₁₁₄, JP 38₁₇₄, SC 49₄₃₃, TC 83₅₆₈. + '4k2h9y5e,' + // #3488: 4 fonts: HK 87₁₁₄, JP 38₁₇₄, SC 78₄₆₂, TC 83₅₆₈. + '4k2h11b4b,' + // #3489: 4 fonts: HK 87₁₁₄, JP 39₁₇₅, SC 79₄₆₃, TC 83₅₆₈. + '4k2i11b4a,' + // #3490: 5 fonts: HK 87₁₁₄, JP 40₁₇₆, KR 86₃₄₆, SC 52₄₃₆, TC 83₅₆₈. + '4k2j6n3l5b,' + // #3491: 4 fonts: HK 87₁₁₄, JP 42₁₇₈, SC 54₄₃₈, TC 83₅₆₈. + '4k2l9z4z,' + // #3492: 5 fonts: HK 87₁₁₄, JP 43₁₇₉, KR 88₃₄₈, SC 56₄₄₀, TC 83₅₆₈. + '4k2m6m3n4x,' + // #3493: 5 fonts: HK 87₁₁₄, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 83₅₆₈. + '4k2n6m3o4v,' + // #3494: 4 fonts: HK 87₁₁₄, JP 44₁₈₀, SC 59₄₄₃, TC 83₅₆₈. + '4k2n10c4u,' + // #3495: 5 fonts: HK 87₁₁₄, JP 45₁₈₁, KR 90₃₅₀, SC 78₄₆₂, TC 83₅₆₈. + '4k2o6m4h4b,' + // #3496: 4 fonts: HK 87₁₁₄, JP 45₁₈₁, SC 78₄₆₂, TC 83₅₆₈. + '4k2o10u4b,' + // #3497: 4 fonts: HK 87₁₁₄, JP 45₁₈₁, SC 81₄₆₅, TC 83₅₆₈. + '4k2o10x3y,' + // #3498: 4 fonts: HK 87₁₁₄, JP 45₁₈₁, SC 93₄₇₇, TC 83₅₆₈. + '4k2o11j3m,' + // #3499: 5 fonts: HK 87₁₁₄, JP 46₁₈₂, KR 90₃₅₀, SC 61₄₄₅, TC 83₅₆₈. + '4k2p6l3q4s,' + // #3500: 5 fonts: HK 87₁₁₄, JP 46₁₈₂, KR 90₃₅₀, SC 80₄₆₄, TC 83₅₆₈. + '4k2p6l4j3z,' + // #3501: 5 fonts: HK 87₁₁₄, JP 46₁₈₂, KR 90₃₅₀, SC 92₄₇₆, TC 83₅₆₈. + '4k2p6l4v3n,' + // #3502: 4 fonts: HK 87₁₁₄, JP 46₁₈₂, SC 78₄₆₂, TC 83₅₆₈. + '4k2p10t4b,' + // #3503: 4 fonts: HK 87₁₁₄, JP 46₁₈₂, SC 79₄₆₃, TC 83₅₆₈. + '4k2p10u4a,' + // #3504: 5 fonts: HK 87₁₁₄, JP 47₁₈₃, KR 91₃₅₁, SC 79₄₆₃, TC 83₅₆₈. + '4k2q6l4h4a,' + // #3505: 5 fonts: HK 87₁₁₄, JP 48₁₈₄, KR 91₃₅₁, SC 64₄₄₈, TC 83₅₆₈. + '4k2r6k3s4p,' + // #3506: 5 fonts: HK 87₁₁₄, JP 48₁₈₄, KR 91₃₅₁, SC 79₄₆₃, TC 83₅₆₈. + '4k2r6k4h4a,' + // #3507: 5 fonts: HK 87₁₁₄, JP 48₁₈₄, KR 92₃₅₂, SC 64₄₄₈, TC 83₅₆₈. + '4k2r6l3r4p,' + // #3508: 4 fonts: HK 87₁₁₄, JP 48₁₈₄, SC 80₄₆₄, TC 83₅₆₈. + '4k2r10t3z,' + // #3509: 4 fonts: HK 87₁₁₄, JP 48₁₈₄, SC 93₄₇₇, TC 83₅₆₈. + '4k2r11g3m,' + // #3510: 5 fonts: HK 87₁₁₄, JP 49₁₈₅, KR 92₃₅₂, SC 78₄₆₂, TC 83₅₆₈. + '4k2s6k4f4b,' + // #3511: 5 fonts: HK 87₁₁₄, JP 49₁₈₅, KR 92₃₅₂, SC 79₄₆₃, TC 83₅₆₈. + '4k2s6k4g4a,' + // #3512: 5 fonts: HK 87₁₁₄, JP 49₁₈₅, KR 92₃₅₂, SC 80₄₆₄, TC 83₅₆₈. + '4k2s6k4h3z,' + // #3513: 5 fonts: HK 87₁₁₄, JP 49₁₈₅, KR 92₃₅₂, SC 82₄₆₆, TC 83₅₆₈. + '4k2s6k4j3x,' + // #3514: 4 fonts: HK 87₁₁₄, JP 49₁₈₅, SC 65₄₄₉, TC 83₅₆₈. + '4k2s10d4o,' + // #3515: 5 fonts: HK 87₁₁₄, JP 50₁₈₆, KR 93₃₅₃, SC 78₄₆₂, TC 83₅₆₈. + '4k2t6k4e4b,' + // #3516: 4 fonts: HK 87₁₁₄, JP 50₁₈₆, SC 84₄₆₈, TC 83₅₆₈. + '4k2t10v3v,' + // #3517: 4 fonts: HK 87₁₁₄, JP 57₁₉₃, SC 79₄₆₃, TC 83₅₆₈. + '4k3a10j4a,' + // #3518: 5 fonts: HK 87₁₁₄, JP 60₁₉₆, KR 71₃₃₁, SC 80₄₆₄, TC 83₅₆₈. + '4k3d5e5c3z,' + // #3519: 5 fonts: HK 87₁₁₄, JP 60₁₉₆, KR 75₃₃₅, SC 31₄₁₅, TC 83₅₆₈. + '4k3d5i3b5w,' + // #3520: 5 fonts: HK 87₁₁₄, JP 60₁₉₆, KR 84₃₄₄, SC 78₄₆₂, TC 83₅₆₈. + '4k3d5r4n4b,' + // #3521: 5 fonts: HK 87₁₁₄, JP 61₁₉₇, KR 89₃₄₉, SC 77₄₆₁, TC 83₅₆₈. + '4k3e5v4h4c,' + // #3522: 5 fonts: HK 87₁₁₄, JP 63₁₉₉, KR 78₃₃₈, SC 80₄₆₄, TC 83₅₆₈. + '4k3g5i4v3z,' + // #3523: 5 fonts: HK 87₁₁₄, JP 63₁₉₉, KR 85₃₄₅, SC 81₄₆₅, TC 83₅₆₈. + '4k3g5p4p3y,' + // #3524: 5 fonts: HK 87₁₁₄, JP 63₁₉₉, SC 74₄₅₈, TC 83₅₆₈, Noto Sans₅₉₁. + '4k3g9y4fw,' + // #3525: 5 fonts: HK 87₁₁₄, JP 64₂₀₀, KR 71₃₃₁, SC 21₄₀₅, TC 83₅₆₈. + '4k3h5a2v6g,' + // #3526: 5 fonts: HK 87₁₁₄, JP 64₂₀₀, KR 81₃₄₁, SC 86₄₇₀, TC 83₅₆₈. + '4k3h5k4y3t,' + // #3527: 5 fonts: HK 87₁₁₄, JP 65₂₀₁, KR 87₃₄₇, SC 80₄₆₄, TC 83₅₆₈. + '4k3i5p4m3z,' + // #3528: 5 fonts: HK 87₁₁₄, JP 66₂₀₂, KR 72₃₃₂, SC 22₄₀₆, TC 83₅₆₈. + '4k3j4z2v6f,' + // #3529: 5 fonts: HK 87₁₁₄, JP 66₂₀₂, KR 72₃₃₂, SC 79₄₆₃, TC 83₅₆₈. + '4k3j4z5a4a,' + // #3530: 5 fonts: HK 87₁₁₄, JP 67₂₀₃, KR 75₃₃₅, SC 30₄₁₄, TC 83₅₆₈. + '4k3k5b3a5x,' + // #3531: 5 fonts: HK 87₁₁₄, JP 67₂₀₃, KR 76₃₃₆, SC 77₄₆₁, TC 83₅₆₈. + '4k3k5c4u4c,' + // #3532: 5 fonts: HK 87₁₁₄, JP 67₂₀₃, KR 80₃₄₀, SC 41₄₂₅, TC 83₅₆₈. + '4k3k5g3g5m,' + // #3533: 5 fonts: HK 87₁₁₄, JP 67₂₀₃, KR 83₃₄₃, SC 47₄₃₁, TC 83₅₆₈. + '4k3k5j3j5g,' + // #3534: 5 fonts: HK 87₁₁₄, JP 67₂₀₃, KR 83₃₄₃, SC 77₄₆₁, TC 83₅₆₈. + '4k3k5j4n4c,' + // #3535: 5 fonts: HK 87₁₁₄, JP 67₂₀₃, KR 87₃₄₇, SC 55₄₃₉, TC 83₅₆₈. + '4k3k5n3n4y,' + // #3536: 5 fonts: HK 87₁₁₄, JP 67₂₀₃, KR 87₃₄₇, SC 80₄₆₄, TC 83₅₆₈. + '4k3k5n4m3z,' + // #3537: 5 fonts: HK 87₁₁₄, JP 68₂₀₄, KR 71₃₃₁, SC 80₄₆₄, TC 83₅₆₈. + '4k3l4w5c3z,' + // #3538: 5 fonts: HK 87₁₁₄, JP 68₂₀₄, KR 79₃₃₉, SC 80₄₆₄, TC 83₅₆₈. + '4k3l5e4u3z,' + // #3539: 5 fonts: HK 87₁₁₄, JP 68₂₀₄, KR 90₃₅₀, SC 81₄₆₅, TC 83₅₆₈. + '4k3l5p4k3y,' + // #3540: 5 fonts: HK 87₁₁₄, JP 69₂₀₅, KR 65₃₂₅, SC 6₃₉₀, TC 83₅₆₈. + '4k3m4p2m6v,' + // #3541: 5 fonts: HK 87₁₁₄, JP 70₂₀₆, KR 75₃₃₅, SC 78₄₆₂, TC 83₅₆₈. + '4k3n4y4w4b,' + // #3542: 5 fonts: HK 87₁₁₄, JP 70₂₀₆, KR 76₃₃₆, SC 78₄₆₂, TC 83₅₆₈. + '4k3n4z4v4b,' + // #3543: 5 fonts: HK 87₁₁₄, JP 70₂₀₆, KR 76₃₃₆, SC 80₄₆₄, TC 83₅₆₈. + '4k3n4z4x3z,' + // #3544: 5 fonts: HK 87₁₁₄, JP 70₂₀₆, KR 80₃₄₀, SC 79₄₆₃, TC 83₅₆₈. + '4k3n5d4s4a,' + // #3545: 5 fonts: HK 87₁₁₄, JP 70₂₀₆, KR 90₃₅₀, SC 60₄₄₄, TC 83₅₆₈. + '4k3n5n3p4t,' + // #3546: 5 fonts: HK 87₁₁₄, JP 71₂₀₇, KR 91₃₅₁, SC 79₄₆₃, TC 83₅₆₈. + '4k3o5n4h4a,' + // #3547: 5 fonts: HK 87₁₁₄, JP 72₂₀₈, KR 83₃₄₃, SC 46₄₃₀, TC 83₅₆₈. + '4k3p5e3i5h,' + // #3548: 5 fonts: HK 87₁₁₄, JP 73₂₀₉, KR 73₃₃₃, SC 80₄₆₄, TC 83₅₆₈. + '4k3q4t5a3z,' + // #3549: 5 fonts: HK 87₁₁₄, JP 73₂₀₉, KR 82₃₄₂, SC 44₄₂₈, TC 83₅₆₈. + '4k3q5c3h5j,' + // #3550: 5 fonts: HK 87₁₁₄, JP 74₂₁₀, KR 72₃₃₂, SC 78₄₆₂, TC 83₅₆₈. + '4k3r4r4z4b,' + // #3551: 5 fonts: HK 87₁₁₄, JP 76₂₁₂, KR 68₃₂₈, SC 77₄₆₁, TC 83₅₆₈. + '4k3t4l5c4c,' + // #3552: 6 fonts: HK 87₁₁₄, JP 76₂₁₂, KR 98₃₅₈, SC 72₄₅₆, TC 83₅₆₈, Math₆₅₅. + '4k3t5p3t4h3i,' + // #3553: 5 fonts: HK 87₁₁₄, JP 77₂₁₃, KR 78₃₃₈, SC 77₄₆₁, TC 83₅₆₈. + '4k3u4u4s4c,' + // #3554: 5 fonts: HK 87₁₁₄, JP 80₂₁₆, KR 85₃₄₅, SC 51₄₃₅, TC 83₅₆₈. + '4k3x4y3l5c,' + // #3555: 5 fonts: HK 87₁₁₄, JP 80₂₁₆, KR 90₃₅₀, SC 62₄₄₆, TC 83₅₆₈. + '4k3x5d3r4r,' + // #3556: 5 fonts: HK 87₁₁₄, JP 82₂₁₈, KR 89₃₄₉, SC 79₄₆₃, TC 83₅₆₈. + '4k3z5a4j4a,' + // #3557: 4 fonts: HK 87₁₁₄, JP 84₂₂₀, SC 67₄₅₁, TC 83₅₆₈. + '4k4b8w4m,' + // #3558: 5 fonts: HK 87₁₁₄, JP 86₂₂₂, KR 91₃₅₁, SC 79₄₆₃, TC 83₅₆₈. + '4k4d4y4h4a,' + // #3559: 5 fonts: HK 87₁₁₄, JP 87₂₂₃, KR 66₃₂₆, SC 82₄₆₆, TC 83₅₆₈. + '4k4e3y5j3x,' + // #3560: 5 fonts: HK 87₁₁₄, JP 88₂₂₄, KR 87₃₄₇, SC 81₄₆₅, TC 83₅₆₈. + '4k4f4s4n3y,' + // #3561: 5 fonts: HK 87₁₁₄, JP 91₂₂₇, KR 75₃₃₅, SC 29₄₁₃, TC 83₅₆₈. + '4k4i4d2z5y,' + // #3562: 5 fonts: HK 87₁₁₄, JP 92₂₂₈, KR 68₃₂₈, SC 79₄₆₃, TC 83₅₆₈. + '4k4j3v5e4a,' + // #3563: 4 fonts: HK 87₁₁₄, JP 93₂₂₉, SC 93₄₇₇, TC 83₅₆₈. + '4k4k9n3m,' + // #3564: 5 fonts: HK 87₁₁₄, JP 97₂₃₃, KR 0₂₆₀, SC 1₃₈₅, TC 83₅₆₈. + '4k4o1a4u7a,' + // #3565: 5 fonts: HK 87₁₁₄, JP 98₂₃₄, KR 0₂₆₀, SC 1₃₈₅, TC 83₅₆₈. + '4k4pz4u7a,' + // #3566: 5 fonts: HK 87₁₁₄, JP 98₂₃₄, KR 72₃₃₂, SC 82₄₆₆, TC 83₅₆₈. + '4k4p3t5d3x,' + // #3567: 5 fonts: HK 87₁₁₄, JP 99₂₃₅, KR 87₃₄₇, SC 78₄₆₂, TC 83₅₆₈. + '4k4q4h4k4b,' + // #3568: 5 fonts: HK 87₁₁₄, JP 99₂₃₅, KR 88₃₄₈, SC 77₄₆₁, TC 83₅₆₈. + '4k4q4i4i4c,' + // #3569: 5 fonts: HK 87₁₁₄, JP 101₂₃₇, KR 0₂₆₀, SC 1₃₈₅, TC 83₅₆₈. + '4k4sw4u7a,' + // #3570: 5 fonts: HK 87₁₁₄, JP 102₂₃₈, KR 0₂₆₀, SC 1₃₈₅, TC 83₅₆₈. + '4k4tv4u7a,' + // #3571: 5 fonts: HK 87₁₁₄, JP 103₂₃₉, KR 83₃₄₃, SC 80₄₆₄, TC 83₅₆₈. + '4k4u3z4q3z,' + // #3572: 4 fonts: HK 87₁₁₄, JP 105₂₄₁, SC 92₄₇₆, TC 83₅₆₈. + '4k4w9a3n,' + // #3573: 5 fonts: HK 87₁₁₄, JP 116₂₅₂, KR 100₃₆₀, SC 68₄₅₂, TC 83₅₆₈. + '4k5h4d3n4l,' + // #3574: 3 fonts: HK 87₁₁₄, SC 26₄₁₀, TC 83₅₆₈. + '4k11j6b,' + // #3575: 3 fonts: HK 87₁₁₄, SC 54₄₃₈, TC 83₅₆₈. + '4k12l4z,' + // #3576: 3 fonts: HK 87₁₁₄, SC 60₄₄₄, TC 83₅₆₈. + '4k12r4t,' + // #3577: 3 fonts: HK 87₁₁₄, SC 62₄₄₆, TC 83₅₆₈. + '4k12t4r,' + // #3578: 3 fonts: HK 87₁₁₄, SC 77₄₆₁, TC 83₅₆₈. + '4k13i4c,' + // #3579: 3 fonts: HK 87₁₁₄, SC 79₄₆₃, TC 83₅₆₈. + '4k13k4a,' + // #3580: 3 fonts: HK 87₁₁₄, SC 82₄₆₆, TC 83₅₆₈. + '4k13n3x,' + // #3581: 3 fonts: HK 87₁₁₄, SC 94₄₇₈, TC 83₅₆₈. + '4k13z3l,' + // #3582: 4 fonts: HK 88₁₁₅, JP 6₁₄₂, SC 6₃₉₀, TC 84₅₆₉. + '4l1a9n6w,' + // #3583: 4 fonts: HK 88₁₁₅, JP 6₁₄₂, SC 88₄₇₂, TC 84₅₆₉. + '4l1a12r3s,' + // #3584: 4 fonts: HK 88₁₁₅, JP 7₁₄₃, SC 6₃₉₀, TC 84₅₆₉. + '4l1b9m6w,' + // #3585: 5 fonts: HK 88₁₁₅, JP 8₁₄₄, KR 65₃₂₅, SC 7₃₉₁, TC 84₅₆₉. + '4l1c6y2n6v,' + // #3586: 5 fonts: HK 88₁₁₅, JP 8₁₄₄, KR 65₃₂₅, SC 81₄₆₅, TC 84₅₆₉. + '4l1c6y5j3z,' + // #3587: 5 fonts: HK 88₁₁₅, JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 84₅₆₉. + '4l1c6z2m6v,' + // #3588: 5 fonts: HK 88₁₁₅, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 84₅₆₉. + '4l1d6y2n6u,' + // #3589: 4 fonts: HK 88₁₁₅, JP 9₁₄₅, SC 94₄₇₈, TC 84₅₆₉. + '4l1d12u3m,' + // #3590: 5 fonts: HK 88₁₁₅, JP 10₁₄₆, KR 67₃₂₇, SC 9₃₉₃, TC 84₅₆₉. + '4l1e6y2n6t,' + // #3591: 5 fonts: HK 88₁₁₅, JP 10₁₄₆, KR 67₃₂₇, SC 11₃₉₅, TC 84₅₆₉. + '4l1e6y2p6r,' + // #3592: 4 fonts: HK 88₁₁₅, JP 10₁₄₆, SC 9₃₉₃, TC 84₅₆₉. + '4l1e9m6t,' + // #3593: 4 fonts: HK 88₁₁₅, JP 10₁₄₆, SC 81₄₆₅, TC 84₅₆₉. + '4l1e12g3z,' + // #3594: 4 fonts: HK 88₁₁₅, JP 10₁₄₆, SC 83₄₆₇, TC 84₅₆₉. + '4l1e12i3x,' + // #3595: 4 fonts: HK 88₁₁₅, JP 12₁₄₈, SC 13₃₉₇, TC 84₅₆₉. + '4l1g9o6p,' + // #3596: 5 fonts: HK 88₁₁₅, JP 13₁₄₉, KR 69₃₂₉, SC 80₄₆₄, TC 84₅₆₉. + '4l1h6x5e4a,' + // #3597: 4 fonts: HK 88₁₁₅, JP 13₁₄₉, SC 94₄₇₈, TC 84₅₆₉. + '4l1h12q3m,' + // #3598: 5 fonts: HK 88₁₁₅, JP 14₁₅₀, KR 69₃₂₉, SC 15₃₉₉, TC 84₅₆₉. + '4l1i6w2r6n,' + // #3599: 4 fonts: HK 88₁₁₅, JP 14₁₅₀, SC 80₄₆₄, TC 84₅₆₉. + '4l1i12b4a,' + // #3600: 5 fonts: HK 88₁₁₅, JP 16₁₅₂, KR 70₃₃₀, SC 19₄₀₃, TC 84₅₆₉. + '4l1k6v2u6j,' + // #3601: 4 fonts: HK 88₁₁₅, JP 16₁₅₂, SC 18₄₀₂, TC 84₅₆₉. + '4l1k9p6k,' + // #3602: 5 fonts: HK 88₁₁₅, JP 17₁₅₃, KR 71₃₃₁, SC 80₄₆₄, TC 84₅₆₉. + '4l1l6v5c4a,' + // #3603: 5 fonts: HK 88₁₁₅, JP 19₁₅₅, KR 71₃₃₁, SC 21₄₀₅, TC 84₅₆₉. + '4l1n6t2v6h,' + // #3604: 5 fonts: HK 88₁₁₅, JP 20₁₅₆, KR 72₃₃₂, SC 79₄₆₃, TC 84₅₆₉. + '4l1o6t5a4b,' + // #3605: 4 fonts: HK 88₁₁₅, JP 20₁₅₆, SC 79₄₆₃, TC 84₅₆₉. + '4l1o11u4b,' + // #3606: 4 fonts: HK 88₁₁₅, JP 20₁₅₆, SC 94₄₇₈, TC 84₅₆₉. + '4l1o12j3m,' + // #3607: 5 fonts: HK 88₁₁₅, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 84₅₆₉. + '4l1p6t2w6e,' + // #3608: 4 fonts: HK 88₁₁₅, JP 21₁₅₇, SC 81₄₆₅, TC 84₅₆₉. + '4l1p11v3z,' + // #3609: 5 fonts: HK 88₁₁₅, JP 22₁₅₈, KR 73₃₃₃, SC 80₄₆₄, TC 84₅₆₉. + '4l1q6s5a4a,' + // #3610: 4 fonts: HK 88₁₁₅, JP 22₁₅₈, SC 82₄₆₆, TC 84₅₆₉. + '4l1q11v3y,' + // #3611: 5 fonts: HK 88₁₁₅, JP 23₁₅₉, KR 74₃₃₄, SC 28₄₁₂, TC 84₅₆₉. + '4l1r6s2z6a,' + // #3612: 4 fonts: HK 88₁₁₅, JP 23₁₅₉, SC 28₄₁₂, TC 84₅₆₉. + '4l1r9s6a,' + // #3613: 5 fonts: HK 88₁₁₅, JP 25₁₆₁, KR 75₃₃₅, SC 31₄₁₅, TC 84₅₆₉. + '4l1t6r3b5x,' + // #3614: 5 fonts: HK 88₁₁₅, JP 26₁₆₂, KR 76₃₃₆, SC 31₄₁₅, TC 84₅₆₉. + '4l1u6r3a5x,' + // #3615: 5 fonts: HK 88₁₁₅, JP 26₁₆₂, KR 76₃₃₆, SC 32₄₁₆, TC 84₅₆₉. + '4l1u6r3b5w,' + // #3616: 5 fonts: HK 88₁₁₅, JP 26₁₆₂, KR 76₃₃₆, SC 79₄₆₃, TC 84₅₆₉. + '4l1u6r4w4b,' + // #3617: 5 fonts: HK 88₁₁₅, JP 26₁₆₂, KR 76₃₃₆, SC 82₄₆₆, TC 84₅₆₉. + '4l1u6r4z3y,' + // #3618: 5 fonts: HK 88₁₁₅, JP 27₁₆₃, KR 76₃₃₆, SC 78₄₆₂, TC 84₅₆₉. + '4l1v6q4v4c,' + // #3619: 5 fonts: HK 88₁₁₅, JP 27₁₆₃, KR 76₃₃₆, SC 81₄₆₅, TC 84₅₆₉. + '4l1v6q4y3z,' + // #3620: 5 fonts: HK 88₁₁₅, JP 27₁₆₃, KR 76₃₃₆, SC 83₄₆₇, TC 84₅₆₉. + '4l1v6q5a3x,' + // #3621: 5 fonts: HK 88₁₁₅, JP 27₁₆₃, KR 77₃₃₇, SC 79₄₆₃, TC 84₅₆₉. + '4l1v6r4v4b,' + // #3622: 4 fonts: HK 88₁₁₅, JP 27₁₆₃, SC 33₄₁₇, TC 84₅₆₉. + '4l1v9t5v,' + // #3623: 4 fonts: HK 88₁₁₅, JP 27₁₆₃, SC 79₄₆₃, TC 84₅₆₉. + '4l1v11n4b,' + // #3624: 5 fonts: HK 88₁₁₅, JP 28₁₆₄, KR 77₃₃₇, SC 78₄₆₂, TC 84₅₆₉. + '4l1w6q4u4c,' + // #3625: 4 fonts: HK 88₁₁₅, JP 28₁₆₄, SC 34₄₁₈, TC 84₅₆₉. + '4l1w9t5u,' + // #3626: 5 fonts: HK 88₁₁₅, JP 29₁₆₅, KR 78₃₃₈, SC 80₄₆₄, TC 84₅₆₉. + '4l1x6q4v4a,' + // #3627: 5 fonts: HK 88₁₁₅, JP 30₁₆₆, KR 78₃₃₈, SC 37₄₂₁, TC 84₅₆₉. + '4l1y6p3e5r,' + // #3628: 5 fonts: HK 88₁₁₅, JP 30₁₆₆, KR 79₃₃₉, SC 39₄₂₃, TC 84₅₆₉. + '4l1y6q3f5p,' + // #3629: 5 fonts: HK 88₁₁₅, JP 31₁₆₇, KR 79₃₃₉, SC 78₄₆₂, TC 84₅₆₉. + '4l1z6p4s4c,' + // #3630: 5 fonts: HK 88₁₁₅, JP 31₁₆₇, KR 79₃₃₉, SC 80₄₆₄, TC 84₅₆₉. + '4l1z6p4u4a,' + // #3631: 5 fonts: HK 88₁₁₅, JP 32₁₆₈, KR 80₃₄₀, SC 80₄₆₄, TC 84₅₆₉. + '4l2a6p4t4a,' + // #3632: 5 fonts: HK 88₁₁₅, JP 32₁₆₈, KR 80₃₄₀, SC 85₄₆₉, TC 84₅₆₉. + '4l2a6p4y3v,' + // #3633: 4 fonts: HK 88₁₁₅, JP 32₁₆₈, SC 40₄₂₄, TC 84₅₆₉. + '4l2a9v5o,' + // #3634: 4 fonts: HK 88₁₁₅, JP 32₁₆₈, SC 41₄₂₅, TC 84₅₆₉. + '4l2a9w5n,' + // #3635: 5 fonts: HK 88₁₁₅, JP 33₁₆₉, KR 81₃₄₁, SC 78₄₆₂, TC 84₅₆₉. + '4l2b6p4q4c,' + // #3636: 5 fonts: HK 88₁₁₅, JP 33₁₆₉, KR 81₃₄₁, SC 79₄₆₃, TC 84₅₆₉. + '4l2b6p4r4b,' + // #3637: 4 fonts: HK 88₁₁₅, JP 33₁₆₉, SC 94₄₇₈, TC 84₅₆₉. + '4l2b11w3m,' + // #3638: 5 fonts: HK 88₁₁₅, JP 34₁₇₀, KR 82₃₄₂, SC 79₄₆₃, TC 84₅₆₉. + '4l2c6p4q4b,' + // #3639: 5 fonts: HK 88₁₁₅, JP 36₁₇₂, KR 83₃₄₃, SC 80₄₆₄, TC 84₅₆₉. + '4l2e6o4q4a,' + // #3640: 5 fonts: HK 88₁₁₅, JP 38₁₇₄, KR 84₃₄₄, SC 49₄₃₃, TC 84₅₆₉. + '4l2g6n3k5f,' + // #3641: 4 fonts: HK 88₁₁₅, JP 38₁₇₄, SC 50₄₃₄, TC 84₅₆₉. + '4l2g9z5e,' + // #3642: 4 fonts: HK 88₁₁₅, JP 38₁₇₄, SC 80₄₆₄, TC 84₅₆₉. + '4l2g11d4a,' + // #3643: 4 fonts: HK 88₁₁₅, JP 39₁₇₅, SC 77₄₆₁, TC 84₅₆₉. + '4l2h10z4d,' + // #3644: 4 fonts: HK 88₁₁₅, JP 39₁₇₅, SC 82₄₆₆, TC 84₅₆₉. + '4l2h11e3y,' + // #3645: 5 fonts: HK 88₁₁₅, JP 41₁₇₇, KR 86₃₄₆, SC 80₄₆₄, TC 84₅₆₉. + '4l2j6m4n4a,' + // #3646: 5 fonts: HK 88₁₁₅, JP 41₁₇₇, KR 86₃₄₆, SC 93₄₇₇, TC 84₅₆₉. + '4l2j6m5a3n,' + // #3647: 5 fonts: HK 88₁₁₅, JP 41₁₇₇, KR 87₃₄₇, SC 54₄₃₈, TC 84₅₆₉. + '4l2j6n3m5a,' + // #3648: 4 fonts: HK 88₁₁₅, JP 41₁₇₇, SC 53₄₃₇, TC 84₅₆₉. + '4l2j9z5b,' + // #3649: 4 fonts: HK 88₁₁₅, JP 41₁₇₇, SC 82₄₆₆, TC 84₅₆₉. + '4l2j11c3y,' + // #3650: 5 fonts: HK 88₁₁₅, JP 42₁₇₈, KR 87₃₄₇, SC 80₄₆₄, TC 84₅₆₉. + '4l2k6m4m4a,' + // #3651: 4 fonts: HK 88₁₁₅, JP 42₁₇₈, SC 55₄₃₉, TC 84₅₆₉. + '4l2k10a4z,' + // #3652: 4 fonts: HK 88₁₁₅, JP 42₁₇₈, SC 82₄₆₆, TC 84₅₆₉. + '4l2k11b3y,' + // #3653: 5 fonts: HK 88₁₁₅, JP 43₁₇₉, KR 88₃₄₈, SC 77₄₆₁, TC 84₅₆₉. + '4l2l6m4i4d,' + // #3654: 5 fonts: HK 88₁₁₅, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 84₅₆₉. + '4l2m6m3o4w,' + // #3655: 5 fonts: HK 88₁₁₅, JP 44₁₈₀, KR 89₃₄₉, SC 81₄₆₅, TC 84₅₆₉. + '4l2m6m4l3z,' + // #3656: 5 fonts: HK 88₁₁₅, JP 44₁₈₀, KR 89₃₄₉, SC 86₄₇₀, TC 84₅₆₉. + '4l2m6m4q3u,' + // #3657: 4 fonts: HK 88₁₁₅, JP 44₁₈₀, SC 59₄₄₃, TC 84₅₆₉. + '4l2m10c4v,' + // #3658: 4 fonts: HK 88₁₁₅, JP 44₁₈₀, SC 94₄₇₈, TC 84₅₆₉. + '4l2m11l3m,' + // #3659: 4 fonts: HK 88₁₁₅, JP 45₁₈₁, SC 78₄₆₂, TC 84₅₆₉. + '4l2n10u4c,' + // #3660: 5 fonts: HK 88₁₁₅, JP 46₁₈₂, KR 90₃₅₀, SC 61₄₄₅, TC 84₅₆₉. + '4l2o6l3q4t,' + // #3661: 5 fonts: HK 88₁₁₅, JP 46₁₈₂, KR 90₃₅₀, SC 84₄₆₈, TC 84₅₆₉. + '4l2o6l4n3w,' + // #3662: 4 fonts: HK 88₁₁₅, JP 46₁₈₂, SC 61₄₄₅, TC 84₅₆₉. + '4l2o10c4t,' + // #3663: 4 fonts: HK 88₁₁₅, JP 46₁₈₂, SC 83₄₆₇, TC 84₅₆₉. + '4l2o10y3x,' + // #3664: 4 fonts: HK 88₁₁₅, JP 46₁₈₂, SC 84₄₆₈, TC 84₅₆₉. + '4l2o10z3w,' + // #3665: 5 fonts: HK 88₁₁₅, JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇, TC 84₅₆₉. + '4l2p6l3r4r,' + // #3666: 5 fonts: HK 88₁₁₅, JP 48₁₈₄, KR 91₃₅₁, SC 79₄₆₃, TC 84₅₆₉. + '4l2q6k4h4b,' + // #3667: 4 fonts: HK 88₁₁₅, JP 48₁₈₄, SC 64₄₄₈, TC 84₅₆₉. + '4l2q10d4q,' + // #3668: 4 fonts: HK 88₁₁₅, JP 48₁₈₄, SC 65₄₄₉, TC 84₅₆₉. + '4l2q10e4p,' + // #3669: 4 fonts: HK 88₁₁₅, JP 48₁₈₄, SC 83₄₆₇, TC 84₅₆₉. + '4l2q10w3x,' + // #3670: 5 fonts: HK 88₁₁₅, JP 49₁₈₅, KR 92₃₅₂, SC 80₄₆₄, TC 84₅₆₉. + '4l2r6k4h4a,' + // #3671: 4 fonts: HK 88₁₁₅, JP 49₁₈₅, SC 66₄₅₀, TC 84₅₆₉. + '4l2r10e4o,' + // #3672: 4 fonts: HK 88₁₁₅, JP 49₁₈₅, SC 82₄₆₆, TC 84₅₆₉. + '4l2r10u3y,' + // #3673: 5 fonts: HK 88₁₁₅, JP 50₁₈₆, KR 93₃₅₃, SC 66₄₅₀, TC 84₅₆₉. + '4l2s6k3s4o,' + // #3674: 5 fonts: HK 88₁₁₅, JP 50₁₈₆, KR 93₃₅₃, SC 86₄₇₀, TC 84₅₆₉. + '4l2s6k4m3u,' + // #3675: 5 fonts: HK 88₁₁₅, JP 50₁₈₆, KR 93₃₅₃, SC 94₄₇₈, TC 84₅₆₉. + '4l2s6k4u3m,' + // #3676: 4 fonts: HK 88₁₁₅, JP 50₁₈₆, SC 81₄₆₅, TC 84₅₆₉. + '4l2s10s3z,' + // #3677: 4 fonts: HK 88₁₁₅, JP 50₁₈₆, SC 94₄₇₈, TC 84₅₆₉. + '4l2s11f3m,' + // #3678: 4 fonts: HK 88₁₁₅, JP 54₁₉₀, SC 68₄₅₂, TC 84₅₆₉. + '4l2w10b4m,' + // #3679: 5 fonts: HK 88₁₁₅, JP 60₁₉₆, KR 71₃₃₁, SC 81₄₆₅, TC 84₅₆₉. + '4l3c5e5d3z,' + // #3680: 5 fonts: HK 88₁₁₅, JP 60₁₉₆, KR 84₃₄₄, SC 49₄₃₃, TC 84₅₆₉. + '4l3c5r3k5f,' + // #3681: 5 fonts: HK 88₁₁₅, JP 61₁₉₇, KR 68₃₂₈, SC 77₄₆₁, TC 84₅₆₉. + '4l3d5a5c4d,' + // #3682: 5 fonts: HK 88₁₁₅, JP 61₁₉₇, KR 76₃₃₆, SC 32₄₁₆, TC 84₅₆₉. + '4l3d5i3b5w,' + // #3683: 4 fonts: HK 88₁₁₅, JP 61₁₉₇, SC 22₄₀₆, TC 84₅₆₉. + '4l3d8a6g,' + // #3684: 5 fonts: HK 88₁₁₅, JP 63₁₉₉, KR 77₃₃₇, SC 80₄₆₄, TC 84₅₆₉. + '4l3f5h4w4a,' + // #3685: 5 fonts: HK 88₁₁₅, JP 64₂₀₀, KR 66₃₂₆, SC 81₄₆₅, TC 84₅₆₉. + '4l3g4v5i3z,' + // #3686: 5 fonts: HK 88₁₁₅, JP 64₂₀₀, KR 68₃₂₈, SC 14₃₉₈, TC 84₅₆₉. + '4l3g4x2r6o,' + // #3687: 5 fonts: HK 88₁₁₅, JP 64₂₀₀, KR 85₃₄₅, SC 80₄₆₄, TC 84₅₆₉. + '4l3g5o4o4a,' + // #3688: 5 fonts: HK 88₁₁₅, JP 64₂₀₀, KR 90₃₅₀, SC 81₄₆₅, TC 84₅₆₉. + '4l3g5t4k3z,' + // #3689: 5 fonts: HK 88₁₁₅, JP 65₂₀₁, KR 83₃₄₃, SC 47₄₃₁, TC 84₅₆₉. + '4l3h5l3j5h,' + // #3690: 5 fonts: HK 88₁₁₅, JP 66₂₀₂, KR 72₃₃₂, SC 79₄₆₃, TC 84₅₆₉. + '4l3i4z5a4b,' + // #3691: 5 fonts: HK 88₁₁₅, JP 66₂₀₂, KR 74₃₃₄, SC 28₄₁₂, TC 84₅₆₉. + '4l3i5b2z6a,' + // #3692: 5 fonts: HK 88₁₁₅, JP 67₂₀₃, KR 91₃₅₁, SC 80₄₆₄, TC 84₅₆₉. + '4l3j5r4i4a,' + // #3693: 5 fonts: HK 88₁₁₅, JP 68₂₀₄, KR 74₃₃₄, SC 27₄₁₁, TC 84₅₆₉. + '4l3k4z2y6b,' + // #3694: 5 fonts: HK 88₁₁₅, JP 68₂₀₄, KR 77₃₃₇, SC 80₄₆₄, TC 84₅₆₉. + '4l3k5c4w4a,' + // #3695: 5 fonts: HK 88₁₁₅, JP 68₂₀₄, KR 90₃₅₀, SC 78₄₆₂, TC 84₅₆₉. + '4l3k5p4h4c,' + // #3696: 5 fonts: HK 88₁₁₅, JP 69₂₀₅, KR 65₃₂₅, SC 6₃₉₀, TC 84₅₆₉. + '4l3l4p2m6w,' + // #3697: 5 fonts: HK 88₁₁₅, JP 70₂₀₆, KR 70₃₃₀, SC 18₄₀₂, TC 84₅₆₉. + '4l3m4t2t6k,' + // #3698: 5 fonts: HK 88₁₁₅, JP 70₂₀₆, KR 72₃₃₂, SC 77₄₆₁, TC 84₅₆₉. + '4l3m4v4y4d,' + // #3699: 4 fonts: HK 88₁₁₅, JP 70₂₀₆, SC 81₄₆₅, TC 84₅₆₉. + '4l3m9y3z,' + // #3700: 5 fonts: HK 88₁₁₅, JP 71₂₀₇, KR 67₃₂₇, SC 10₃₉₄, TC 84₅₆₉. + '4l3n4p2o6s,' + // #3701: 5 fonts: HK 88₁₁₅, JP 71₂₀₇, KR 75₃₃₅, SC 80₄₆₄, TC 84₅₆₉. + '4l3n4x4y4a,' + // #3702: 5 fonts: HK 88₁₁₅, JP 72₂₀₈, KR 70₃₃₀, SC 18₄₀₂, TC 84₅₆₉. + '4l3o4r2t6k,' + // #3703: 5 fonts: HK 88₁₁₅, JP 72₂₀₈, KR 92₃₅₂, SC 79₄₆₃, TC 84₅₆₉. + '4l3o5n4g4b,' + // #3704: 5 fonts: HK 88₁₁₅, JP 72₂₀₈, KR 92₃₅₂, SC 81₄₆₅, TC 84₅₆₉. + '4l3o5n4i3z,' + // #3705: 5 fonts: HK 88₁₁₅, JP 73₂₀₉, KR 75₃₃₅, SC 31₄₁₅, TC 84₅₆₉. + '4l3p4v3b5x,' + // #3706: 5 fonts: HK 88₁₁₅, JP 73₂₀₉, KR 83₃₄₃, SC 81₄₆₅, TC 84₅₆₉. + '4l3p5d4r3z,' + // #3707: 5 fonts: HK 88₁₁₅, JP 74₂₁₀, KR 72₃₃₂, SC 82₄₆₆, TC 84₅₆₉. + '4l3q4r5d3y,' + // #3708: 5 fonts: HK 88₁₁₅, JP 75₂₁₁, KR 81₃₄₁, SC 81₄₆₅, TC 84₅₆₉. + '4l3r4z4t3z,' + // #3709: 5 fonts: HK 88₁₁₅, JP 75₂₁₁, KR 84₃₄₄, SC 80₄₆₄, TC 84₅₆₉. + '4l3r5c4p4a,' + // #3710: 5 fonts: HK 88₁₁₅, JP 77₂₁₃, KR 80₃₄₀, SC 79₄₆₃, TC 84₅₆₉. + '4l3t4w4s4b,' + // #3711: 5 fonts: HK 88₁₁₅, JP 78₂₁₄, KR 85₃₄₅, SC 77₄₆₁, TC 84₅₆₉. + '4l3u5a4l4d,' + // #3712: 5 fonts: HK 88₁₁₅, JP 79₂₁₅, KR 74₃₃₄, SC 28₄₁₂, TC 84₅₆₉. + '4l3v4o2z6a,' + // #3713: 5 fonts: HK 88₁₁₅, JP 79₂₁₅, KR 76₃₃₆, SC 79₄₆₃, TC 84₅₆₉. + '4l3v4q4w4b,' + // #3714: 5 fonts: HK 88₁₁₅, JP 79₂₁₅, KR 91₃₅₁, SC 82₄₆₆, TC 84₅₆₉. + '4l3v5f4k3y,' + // #3715: 4 fonts: HK 88₁₁₅, JP 79₂₁₅, SC 81₄₆₅, TC 84₅₆₉. + '4l3v9p3z,' + // #3716: 5 fonts: HK 88₁₁₅, JP 80₂₁₆, KR 0₂₆₀, SC 1₃₈₅, TC 84₅₆₉. + '4l3w1r4u7b,' + // #3717: 5 fonts: HK 88₁₁₅, JP 80₂₁₆, KR 73₃₃₃, SC 81₄₆₅, TC 84₅₆₉. + '4l3w4m5b3z,' + // #3718: 5 fonts: HK 88₁₁₅, JP 81₂₁₇, KR 76₃₃₆, SC 33₄₁₇, TC 84₅₆₉. + '4l3x4o3c5v,' + // #3719: 5 fonts: HK 88₁₁₅, JP 81₂₁₇, KR 80₃₄₀, SC 40₄₂₄, TC 84₅₆₉. + '4l3x4s3f5o,' + // #3720: 5 fonts: HK 88₁₁₅, JP 82₂₁₈, KR 85₃₄₅, SC 51₄₃₅, TC 84₅₆₉. + '4l3y4w3l5d,' + // #3721: 5 fonts: HK 88₁₁₅, JP 83₂₁₉, KR 81₃₄₁, SC 81₄₆₅, TC 84₅₆₉. + '4l3z4r4t3z,' + // #3722: 5 fonts: HK 88₁₁₅, JP 84₂₂₀, KR 89₃₄₉, SC 79₄₆₃, TC 84₅₆₉. + '4l4a4y4j4b,' + // #3723: 5 fonts: HK 88₁₁₅, JP 86₂₂₂, KR 68₃₂₈, SC 81₄₆₅, TC 84₅₆₉. + '4l4c4b5g3z,' + // #3724: 5 fonts: HK 88₁₁₅, JP 94₂₃₀, KR 81₃₄₁, SC 80₄₆₄, TC 84₅₆₉. + '4l4k4g4s4a,' + // #3725: 4 fonts: HK 88₁₁₅, JP 95₂₃₁, SC 92₄₇₆, TC 84₅₆₉. + '4l4l9k3o,' + // #3726: 5 fonts: HK 88₁₁₅, JP 96₂₃₂, KR 76₃₃₆, SC 85₄₆₉, TC 84₅₆₉. + '4l4m3z5c3v,' + // #3727: 5 fonts: HK 88₁₁₅, JP 97₂₃₃, KR 0₂₆₀, SC 1₃₈₅, TC 84₅₆₉. + '4l4n1a4u7b,' + // #3728: 5 fonts: HK 88₁₁₅, JP 98₂₃₄, KR 66₃₂₆, SC 8₃₉₂, TC 84₅₆₉. + '4l4o3n2n6u,' + // #3729: 5 fonts: HK 88₁₁₅, JP 98₂₃₄, KR 74₃₃₄, SC 27₄₁₁, TC 84₅₆₉. + '4l4o3v2y6b,' + // #3730: 5 fonts: HK 88₁₁₅, JP 98₂₃₄, KR 92₃₅₂, SC 65₄₄₉, TC 84₅₆₉. + '4l4o4n3s4p,' + // #3731: 5 fonts: HK 88₁₁₅, JP 101₂₃₇, KR 89₃₄₉, SC 78₄₆₂, TC 84₅₆₉. + '4l4r4h4i4c,' + // #3732: 5 fonts: HK 88₁₁₅, JP 105₂₄₁, KR 83₃₄₃, SC 47₄₃₁, TC 84₅₆₉. + '4l4v3x3j5h,' + // #3733: 4 fonts: HK 88₁₁₅, JP 108₂₄₄, SC 94₄₇₈, TC 84₅₆₉. + '4l4y8z3m,' + // #3734: 4 fonts: HK 88₁₁₅, JP 112₂₄₈, SC 93₄₇₇, TC 84₅₆₉. + '4l5c8u3n,' + // #3735: 5 fonts: HK 88₁₁₅, JP 119₂₅₅, KR 102₃₆₂, SC 96₄₈₀, TC 84₅₆₉. + '4l5j4c4n3k,' + // #3736: 3 fonts: HK 88₁₁₅, SC 39₄₂₃, TC 84₅₆₉. + '4l11v5p,' + // #3737: 3 fonts: HK 88₁₁₅, SC 61₄₄₅, TC 84₅₆₉. + '4l12r4t,' + // #3738: 3 fonts: HK 88₁₁₅, SC 64₄₄₈, TC 84₅₆₉. + '4l12u4q,' + // #3739: 3 fonts: HK 88₁₁₅, SC 81₄₆₅, TC 84₅₆₉. + '4l13l3z,' + // #3740: 3 fonts: HK 88₁₁₅, SC 93₄₇₇, TC 84₅₆₉. + '4l13x3n,' + // #3741: 3 fonts: HK 88₁₁₅, SC 95₄₇₉, TC 84₅₆₉. + '4l13z3l,' + // #3742: 4 fonts: HK 89₁₁₆, JP 5₁₄₁, SC 4₃₈₈, TC 85₅₇₀. + '4my9m6z,' + // #3743: 5 fonts: HK 89₁₁₆, JP 7₁₄₃, KR 65₃₂₅, SC 6₃₉₀, TC 85₅₇₀. + '4m1a6z2m6x,' + // #3744: 5 fonts: HK 89₁₁₆, JP 8₁₄₄, KR 65₃₂₅, SC 7₃₉₁, TC 85₅₇₀. + '4m1b6y2n6w,' + // #3745: 5 fonts: HK 89₁₁₆, JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 85₅₇₀. + '4m1b6z2m6w,' + // #3746: 4 fonts: HK 89₁₁₆, JP 8₁₄₄, SC 7₃₉₁, TC 85₅₇₀. + '4m1b9m6w,' + // #3747: 4 fonts: HK 89₁₁₆, JP 9₁₄₅, SC 7₃₉₁, TC 85₅₇₀. + '4m1c9l6w,' + // #3748: 4 fonts: HK 89₁₁₆, JP 9₁₄₅, SC 8₃₉₂, TC 85₅₇₀. + '4m1c9m6v,' + // #3749: 5 fonts: HK 89₁₁₆, JP 10₁₄₆, KR 66₃₂₆, SC 80₄₆₄, TC 85₅₇₀. + '4m1d6x5h4b,' + // #3750: 4 fonts: HK 89₁₁₆, JP 10₁₄₆, SC 95₄₇₉, TC 85₅₇₀. + '4m1d12u3m,' + // #3751: 5 fonts: HK 89₁₁₆, JP 11₁₄₇, KR 67₃₂₇, SC 11₃₉₅, TC 85₅₇₀. + '4m1e6x2p6s,' + // #3752: 5 fonts: HK 89₁₁₆, JP 11₁₄₇, KR 67₃₂₇, SC 12₃₉₆, TC 85₅₇₀. + '4m1e6x2q6r,' + // #3753: 5 fonts: HK 89₁₁₆, JP 12₁₄₈, KR 68₃₂₈, SC 14₃₉₈, TC 85₅₇₀. + '4m1f6x2r6p,' + // #3754: 5 fonts: HK 89₁₁₆, JP 13₁₄₉, KR 68₃₂₈, SC 80₄₆₄, TC 85₅₇₀. + '4m1g6w5f4b,' + // #3755: 4 fonts: HK 89₁₁₆, JP 13₁₄₉, SC 94₄₇₈, TC 85₅₇₀. + '4m1g12q3n,' + // #3756: 5 fonts: HK 89₁₁₆, JP 14₁₅₀, KR 69₃₂₉, SC 82₄₆₆, TC 85₅₇₀. + '4m1h6w5g3z,' + // #3757: 5 fonts: HK 89₁₁₆, JP 15₁₅₁, KR 69₃₂₉, SC 16₄₀₀, TC 85₅₇₀. + '4m1i6v2s6n,' + // #3758: 5 fonts: HK 89₁₁₆, JP 16₁₅₂, KR 70₃₃₀, SC 18₄₀₂, TC 85₅₇₀. + '4m1j6v2t6l,' + // #3759: 4 fonts: HK 89₁₁₆, JP 16₁₅₂, SC 18₄₀₂, TC 85₅₇₀. + '4m1j9p6l,' + // #3760: 5 fonts: HK 89₁₁₆, JP 17₁₅₃, KR 71₃₃₁, SC 20₄₀₄, TC 85₅₇₀. + '4m1k6v2u6j,' + // #3761: 5 fonts: HK 89₁₁₆, JP 17₁₅₃, KR 71₃₃₁, SC 82₄₆₆, TC 85₅₇₀. + '4m1k6v5e3z,' + // #3762: 5 fonts: HK 89₁₁₆, JP 19₁₅₅, KR 71₃₃₁, SC 82₄₆₆, TC 85₅₇₀. + '4m1m6t5e3z,' + // #3763: 5 fonts: HK 89₁₁₆, JP 19₁₅₅, KR 72₃₃₂, SC 22₄₀₆, TC 85₅₇₀. + '4m1m6u2v6h,' + // #3764: 5 fonts: HK 89₁₁₆, JP 19₁₅₅, KR 72₃₃₂, SC 80₄₆₄, TC 85₅₇₀. + '4m1m6u5b4b,' + // #3765: 5 fonts: HK 89₁₁₆, JP 20₁₅₆, KR 72₃₃₂, SC 83₄₆₇, TC 85₅₇₀. + '4m1n6t5e3y,' + // #3766: 5 fonts: HK 89₁₁₆, JP 20₁₅₆, KR 72₃₃₂, SC 85₄₆₉, TC 85₅₇₀. + '4m1n6t5g3w,' + // #3767: 4 fonts: HK 89₁₁₆, JP 20₁₅₆, SC 23₄₀₇, TC 85₅₇₀. + '4m1n9q6g,' + // #3768: 5 fonts: HK 89₁₁₆, JP 21₁₅₇, KR 73₃₃₃, SC 79₄₆₃, TC 85₅₇₀. + '4m1o6t4z4c,' + // #3769: 5 fonts: HK 89₁₁₆, JP 21₁₅₇, KR 73₃₃₃, SC 81₄₆₅, TC 85₅₇₀. + '4m1o6t5b4a,' + // #3770: 4 fonts: HK 89₁₁₆, JP 21₁₅₇, SC 23₄₀₇, TC 85₅₇₀. + '4m1o9p6g,' + // #3771: 5 fonts: HK 89₁₁₆, JP 22₁₅₈, KR 73₃₃₃, SC 25₄₀₉, TC 85₅₇₀. + '4m1p6s2x6e,' + // #3772: 5 fonts: HK 89₁₁₆, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 85₅₇₀. + '4m1q6s2y6c,' + // #3773: 4 fonts: HK 89₁₁₆, JP 23₁₅₉, SC 27₄₁₁, TC 85₅₇₀. + '4m1q9r6c,' + // #3774: 4 fonts: HK 89₁₁₆, JP 24₁₆₀, SC 28₄₁₂, TC 85₅₇₀. + '4m1r9r6b,' + // #3775: 4 fonts: HK 89₁₁₆, JP 24₁₆₀, SC 79₄₆₃, TC 85₅₇₀. + '4m1r11q4c,' + // #3776: 4 fonts: HK 89₁₁₆, JP 24₁₆₀, SC 95₄₇₉, TC 85₅₇₀. + '4m1r12g3m,' + // #3777: 5 fonts: HK 89₁₁₆, JP 25₁₆₁, KR 75₃₃₅, SC 30₄₁₄, TC 85₅₇₀. + '4m1s6r3a5z,' + // #3778: 5 fonts: HK 89₁₁₆, JP 25₁₆₁, KR 75₃₃₅, SC 31₄₁₅, TC 85₅₇₀. + '4m1s6r3b5y,' + // #3779: 5 fonts: HK 89₁₁₆, JP 25₁₆₁, KR 75₃₃₅, SC 79₄₆₃, TC 85₅₇₀. + '4m1s6r4x4c,' + // #3780: 5 fonts: HK 89₁₁₆, JP 25₁₆₁, KR 75₃₃₅, SC 83₄₆₇, TC 85₅₇₀. + '4m1s6r5b3y,' + // #3781: 5 fonts: HK 89₁₁₆, JP 25₁₆₁, KR 76₃₃₆, SC 82₄₆₆, TC 85₅₇₀. + '4m1s6s4z3z,' + // #3782: 5 fonts: HK 89₁₁₆, JP 27₁₆₃, KR 76₃₃₆, SC 79₄₆₃, TC 85₅₇₀. + '4m1u6q4w4c,' + // #3783: 5 fonts: HK 89₁₁₆, JP 27₁₆₃, KR 76₃₃₆, SC 82₄₆₆, TC 85₅₇₀. + '4m1u6q4z3z,' + // #3784: 4 fonts: HK 89₁₁₆, JP 27₁₆₃, SC 34₄₁₈, TC 85₅₇₀. + '4m1u9u5v,' + // #3785: 4 fonts: HK 89₁₁₆, JP 27₁₆₃, SC 80₄₆₄, TC 85₅₇₀. + '4m1u11o4b,' + // #3786: 4 fonts: HK 89₁₁₆, JP 27₁₆₃, SC 81₄₆₅, TC 85₅₇₀. + '4m1u11p4a,' + // #3787: 5 fonts: HK 89₁₁₆, JP 29₁₆₅, KR 78₃₃₈, SC 36₄₂₀, TC 85₅₇₀. + '4m1w6q3d5t,' + // #3788: 5 fonts: HK 89₁₁₆, JP 30₁₆₆, KR 78₃₃₈, SC 38₄₂₂, TC 85₅₇₀. + '4m1x6p3f5r,' + // #3789: 5 fonts: HK 89₁₁₆, JP 30₁₆₆, KR 78₃₃₈, SC 81₄₆₅, TC 85₅₇₀. + '4m1x6p4w4a,' + // #3790: 4 fonts: HK 89₁₁₆, JP 30₁₆₆, SC 83₄₆₇, TC 85₅₇₀. + '4m1x11o3y,' + // #3791: 5 fonts: HK 89₁₁₆, JP 31₁₆₇, KR 79₃₃₉, SC 39₄₂₃, TC 85₅₇₀. + '4m1y6p3f5q,' + // #3792: 5 fonts: HK 89₁₁₆, JP 31₁₆₇, KR 79₃₃₉, SC 40₄₂₄, TC 85₅₇₀. + '4m1y6p3g5p,' + // #3793: 5 fonts: HK 89₁₁₆, JP 31₁₆₇, KR 79₃₃₉, SC 82₄₆₆, TC 85₅₇₀. + '4m1y6p4w3z,' + // #3794: 5 fonts: HK 89₁₁₆, JP 32₁₆₈, KR 80₃₄₀, SC 40₄₂₄, TC 85₅₇₀. + '4m1z6p3f5p,' + // #3795: 5 fonts: HK 89₁₁₆, JP 32₁₆₈, KR 80₃₄₀, SC 41₄₂₅, TC 85₅₇₀. + '4m1z6p3g5o,' + // #3796: 5 fonts: HK 89₁₁₆, JP 32₁₆₈, KR 80₃₄₀, SC 78₄₆₂, TC 85₅₇₀. + '4m1z6p4r4d,' + // #3797: 5 fonts: HK 89₁₁₆, JP 32₁₆₈, KR 80₃₄₀, SC 84₄₆₈, TC 85₅₇₀. + '4m1z6p4x3x,' + // #3798: 5 fonts: HK 89₁₁₆, JP 33₁₆₉, KR 81₃₄₁, SC 43₄₂₇, TC 85₅₇₀. + '4m2a6p3h5m,' + // #3799: 5 fonts: HK 89₁₁₆, JP 33₁₆₉, KR 81₃₄₁, SC 83₄₆₇, TC 85₅₇₀. + '4m2a6p4v3y,' + // #3800: 5 fonts: HK 89₁₁₆, JP 34₁₇₀, KR 82₃₄₂, SC 44₄₂₈, TC 85₅₇₀. + '4m2b6p3h5l,' + // #3801: 4 fonts: HK 89₁₁₆, JP 34₁₇₀, SC 44₄₂₈, TC 85₅₇₀. + '4m2b9x5l,' + // #3802: 5 fonts: HK 89₁₁₆, JP 35₁₇₁, KR 82₃₄₂, SC 81₄₆₅, TC 85₅₇₀. + '4m2c6o4s4a,' + // #3803: 5 fonts: HK 89₁₁₆, JP 37₁₇₃, KR 84₃₄₄, SC 80₄₆₄, TC 85₅₇₀. + '4m2e6o4p4b,' + // #3804: 5 fonts: HK 89₁₁₆, JP 37₁₇₃, KR 84₃₄₄, SC 84₄₆₈, TC 85₅₇₀. + '4m2e6o4t3x,' + // #3805: 4 fonts: HK 89₁₁₆, JP 37₁₇₃, SC 48₄₃₂, TC 85₅₇₀. + '4m2e9y5h,' + // #3806: 5 fonts: HK 89₁₁₆, JP 38₁₇₄, KR 84₃₄₄, SC 82₄₆₆, TC 85₅₇₀. + '4m2f6n4r3z,' + // #3807: 5 fonts: HK 89₁₁₆, JP 38₁₇₄, KR 85₃₄₅, SC 82₄₆₆, TC 85₅₇₀. + '4m2f6o4q3z,' + // #3808: 4 fonts: HK 89₁₁₆, JP 38₁₇₄, SC 83₄₆₇, TC 85₅₇₀. + '4m2f11g3y,' + // #3809: 5 fonts: HK 89₁₁₆, JP 39₁₇₅, KR 85₃₄₅, SC 78₄₆₂, TC 85₅₇₀. + '4m2g6n4m4d,' + // #3810: 5 fonts: HK 89₁₁₆, JP 39₁₇₅, KR 85₃₄₅, SC 82₄₆₆, TC 85₅₇₀. + '4m2g6n4q3z,' + // #3811: 5 fonts: HK 89₁₁₆, JP 39₁₇₅, KR 86₃₄₆, SC 81₄₆₅, TC 85₅₇₀. + '4m2g6o4o4a,' + // #3812: 4 fonts: HK 89₁₁₆, JP 39₁₇₅, SC 83₄₆₇, TC 85₅₇₀. + '4m2g11f3y,' + // #3813: 5 fonts: HK 89₁₁₆, JP 40₁₇₆, KR 86₃₄₆, SC 81₄₆₅, TC 85₅₇₀. + '4m2h6n4o4a,' + // #3814: 4 fonts: HK 89₁₁₆, JP 41₁₇₇, SC 94₄₇₈, TC 85₅₇₀. + '4m2i11o3n,' + // #3815: 4 fonts: HK 89₁₁₆, JP 41₁₇₇, SC 95₄₇₉, TC 85₅₇₀. + '4m2i11p3m,' + // #3816: 5 fonts: HK 89₁₁₆, JP 42₁₇₈, KR 87₃₄₇, SC 82₄₆₆, TC 85₅₇₀. + '4m2j6m4o3z,' + // #3817: 5 fonts: HK 89₁₁₆, JP 42₁₇₈, KR 88₃₄₈, SC 80₄₆₄, TC 85₅₇₀. + '4m2j6n4l4b,' + // #3818: 5 fonts: HK 89₁₁₆, JP 42₁₇₈, KR 88₃₄₈, SC 81₄₆₅, TC 85₅₇₀. + '4m2j6n4m4a,' + // #3819: 5 fonts: HK 89₁₁₆, JP 42₁₇₈, KR 88₃₄₈, SC 82₄₆₆, TC 85₅₇₀. + '4m2j6n4n3z,' + // #3820: 5 fonts: HK 89₁₁₆, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 85₅₇₀. + '4m2l6m3o4x,' + // #3821: 4 fonts: HK 89₁₁₆, JP 44₁₈₀, SC 81₄₆₅, TC 85₅₇₀. + '4m2l10y4a,' + // #3822: 5 fonts: HK 89₁₁₆, JP 45₁₈₁, KR 89₃₄₉, SC 81₄₆₅, TC 85₅₇₀. + '4m2m6l4l4a,' + // #3823: 5 fonts: HK 89₁₁₆, JP 45₁₈₁, KR 90₃₅₀, SC 60₄₄₄, TC 85₅₇₀. + '4m2m6m3p4v,' + // #3824: 5 fonts: HK 89₁₁₆, JP 45₁₈₁, KR 90₃₅₀, SC 81₄₆₅, TC 85₅₇₀. + '4m2m6m4k4a,' + // #3825: 4 fonts: HK 89₁₁₆, JP 45₁₈₁, SC 80₄₆₄, TC 85₅₇₀. + '4m2m10w4b,' + // #3826: 4 fonts: HK 89₁₁₆, JP 45₁₈₁, SC 82₄₆₆, TC 85₅₇₀. + '4m2m10y3z,' + // #3827: 4 fonts: HK 89₁₁₆, JP 46₁₈₂, SC 62₄₄₆, TC 85₅₇₀. + '4m2n10d4t,' + // #3828: 4 fonts: HK 89₁₁₆, JP 46₁₈₂, SC 77₄₆₁, TC 85₅₇₀. + '4m2n10s4e,' + // #3829: 5 fonts: HK 89₁₁₆, JP 47₁₈₃, KR 90₃₅₀, SC 79₄₆₃, TC 85₅₇₀. + '4m2o6k4i4c,' + // #3830: 5 fonts: HK 89₁₁₆, JP 47₁₈₃, KR 91₃₅₁, SC 84₄₆₈, TC 85₅₇₀. + '4m2o6l4m3x,' + // #3831: 4 fonts: HK 89₁₁₆, JP 47₁₈₃, SC 63₄₄₇, TC 85₅₇₀. + '4m2o10d4s,' + // #3832: 4 fonts: HK 89₁₁₆, JP 48₁₈₄, SC 81₄₆₅, TC 85₅₇₀. + '4m2p10u4a,' + // #3833: 4 fonts: HK 89₁₁₆, JP 48₁₈₄, SC 93₄₇₇, TC 85₅₇₀. + '4m2p11g3o,' + // #3834: 5 fonts: HK 89₁₁₆, JP 49₁₈₅, KR 92₃₅₂, SC 83₄₆₇, TC 85₅₇₀. + '4m2q6k4k3y,' + // #3835: 4 fonts: HK 89₁₁₆, JP 49₁₈₅, SC 65₄₄₉, TC 85₅₇₀. + '4m2q10d4q,' + // #3836: 5 fonts: HK 89₁₁₆, JP 50₁₈₆, KR 93₃₅₃, SC 79₄₆₃, TC 85₅₇₀. + '4m2r6k4f4c,' + // #3837: 4 fonts: HK 89₁₁₆, JP 50₁₈₆, SC 67₄₅₁, TC 85₅₇₀. + '4m2r10e4o,' + // #3838: 5 fonts: HK 89₁₁₆, JP 61₁₉₇, KR 66₃₂₆, SC 80₄₆₄, TC 85₅₇₀. + '4m3c4y5h4b,' + // #3839: 5 fonts: HK 89₁₁₆, JP 61₁₉₇, KR 72₃₃₂, SC 80₄₆₄, TC 85₅₇₀. + '4m3c5e5b4b,' + // #3840: 4 fonts: HK 89₁₁₆, JP 61₁₉₇, SC 40₄₂₄, TC 85₅₇₀. + '4m3c8s5p,' + // #3841: 5 fonts: HK 89₁₁₆, JP 62₁₉₈, KR 72₃₃₂, SC 83₄₆₇, TC 85₅₇₀. + '4m3d5d5e3y,' + // #3842: 5 fonts: HK 89₁₁₆, JP 62₁₉₈, KR 83₃₄₃, SC 88₄₇₂, TC 85₅₇₀. + '4m3d5o4y3t,' + // #3843: 5 fonts: HK 89₁₁₆, JP 62₁₉₈, KR 85₃₄₅, SC 78₄₆₂, TC 85₅₇₀. + '4m3d5q4m4d,' + // #3844: 5 fonts: HK 89₁₁₆, JP 62₁₉₈, KR 92₃₅₂, SC 80₄₆₄, TC 85₅₇₀. + '4m3d5x4h4b,' + // #3845: 5 fonts: HK 89₁₁₆, JP 63₁₉₉, KR 80₃₄₀, SC 41₄₂₅, TC 85₅₇₀. + '4m3e5k3g5o,' + // #3846: 5 fonts: HK 89₁₁₆, JP 63₁₉₉, KR 83₃₄₃, SC 47₄₃₁, TC 85₅₇₀. + '4m3e5n3j5i,' + // #3847: 5 fonts: HK 89₁₁₆, JP 64₂₀₀, KR 69₃₂₉, SC 15₃₉₉, TC 85₅₇₀. + '4m3f4y2r6o,' + // #3848: 5 fonts: HK 89₁₁₆, JP 64₂₀₀, KR 74₃₃₄, SC 28₄₁₂, TC 85₅₇₀. + '4m3f5d2z6b,' + // #3849: 5 fonts: HK 89₁₁₆, JP 65₂₀₁, KR 78₃₃₈, SC 81₄₆₅, TC 85₅₇₀. + '4m3g5g4w4a,' + // #3850: 5 fonts: HK 89₁₁₆, JP 66₂₀₂, KR 85₃₄₅, SC 82₄₆₆, TC 85₅₇₀. + '4m3h5m4q3z,' + // #3851: 5 fonts: HK 89₁₁₆, JP 68₂₀₄, KR 90₃₅₀, SC 80₄₆₄, TC 85₅₇₀. + '4m3j5p4j4b,' + // #3852: 5 fonts: HK 89₁₁₆, JP 69₂₀₅, KR 76₃₃₆, SC 83₄₆₇, TC 85₅₇₀. + '4m3k5a5a3y,' + // #3853: 5 fonts: HK 89₁₁₆, JP 69₂₀₅, KR 90₃₅₀, SC 82₄₆₆, TC 85₅₇₀. + '4m3k5o4l3z,' + // #3854: 5 fonts: HK 89₁₁₆, JP 71₂₀₇, KR 71₃₃₁, SC 79₄₆₃, TC 85₅₇₀. + '4m3m4t5b4c,' + // #3855: 5 fonts: HK 89₁₁₆, JP 71₂₀₇, KR 85₃₄₅, SC 51₄₃₅, TC 85₅₇₀. + '4m3m5h3l5e,' + // #3856: 5 fonts: HK 89₁₁₆, JP 71₂₀₇, KR 88₃₄₈, SC 82₄₆₆, TC 85₅₇₀. + '4m3m5k4n3z,' + // #3857: 5 fonts: HK 89₁₁₆, JP 72₂₀₈, KR 65₃₂₅, SC 4₃₈₈, TC 85₅₇₀. + '4m3n4m2k6z,' + // #3858: 5 fonts: HK 89₁₁₆, JP 73₂₀₉, KR 67₃₂₇, SC 12₃₉₆, TC 85₅₇₀. + '4m3o4n2q6r,' + // #3859: 5 fonts: HK 89₁₁₆, JP 73₂₀₉, KR 105₃₆₅, SC 85₄₆₉, TC 85₅₇₀. + '4m3o5z3z3w,' + // #3860: 5 fonts: HK 89₁₁₆, JP 74₂₁₀, KR 78₃₃₈, SC 81₄₆₅, TC 85₅₇₀. + '4m3p4x4w4a,' + // #3861: 5 fonts: HK 89₁₁₆, JP 74₂₁₀, KR 82₃₄₂, SC 45₄₂₉, TC 85₅₇₀. + '4m3p5b3i5k,' + // #3862: 5 fonts: HK 89₁₁₆, JP 74₂₁₀, KR 83₃₄₃, SC 80₄₆₄, TC 85₅₇₀. + '4m3p5c4q4b,' + // #3863: 5 fonts: HK 89₁₁₆, JP 74₂₁₀, KR 90₃₅₀, SC 81₄₆₅, TC 85₅₇₀. + '4m3p5j4k4a,' + // #3864: 5 fonts: HK 89₁₁₆, JP 74₂₁₀, KR 92₃₅₂, SC 80₄₆₄, TC 85₅₇₀. + '4m3p5l4h4b,' + // #3865: 4 fonts: HK 89₁₁₆, JP 74₂₁₀, SC 37₄₂₁, TC 85₅₇₀. + '4m3p8c5s,' + // #3866: 5 fonts: HK 89₁₁₆, JP 76₂₁₂, KR 67₃₂₇, SC 13₃₉₇, TC 85₅₇₀. + '4m3r4k2r6q,' + // #3867: 5 fonts: HK 89₁₁₆, JP 76₂₁₂, KR 74₃₃₄, SC 28₄₁₂, TC 85₅₇₀. + '4m3r4r2z6b,' + // #3868: 5 fonts: HK 89₁₁₆, JP 77₂₁₃, KR 83₃₄₃, SC 80₄₆₄, TC 85₅₇₀. + '4m3s4z4q4b,' + // #3869: 5 fonts: HK 89₁₁₆, JP 77₂₁₃, KR 86₃₄₆, SC 81₄₆₅, TC 85₅₇₀. + '4m3s5c4o4a,' + // #3870: 4 fonts: HK 89₁₁₆, JP 78₂₁₄, SC 80₄₆₄, TC 85₅₇₀. + '4m3t9p4b,' + // #3871: 5 fonts: HK 89₁₁₆, JP 79₂₁₅, KR 80₃₄₀, SC 86₄₇₀, TC 85₅₇₀. + '4m3u4u4z3v,' + // #3872: 5 fonts: HK 89₁₁₆, JP 80₂₁₆, KR 76₃₃₆, SC 79₄₆₃, TC 85₅₇₀. + '4m3v4p4w4c,' + // #3873: 5 fonts: HK 89₁₁₆, JP 80₂₁₆, KR 77₃₃₇, SC 84₄₆₈, TC 85₅₇₀. + '4m3v4q5a3x,' + // #3874: 5 fonts: HK 89₁₁₆, JP 81₂₁₇, KR 79₃₃₉, SC 39₄₂₃, TC 85₅₇₀. + '4m3w4r3f5q,' + // #3875: 5 fonts: HK 89₁₁₆, JP 81₂₁₇, KR 82₃₄₂, SC 81₄₆₅, TC 85₅₇₀. + '4m3w4u4s4a,' + // #3876: 5 fonts: HK 89₁₁₆, JP 82₂₁₈, KR 67₃₂₇, SC 12₃₉₆, TC 85₅₇₀. + '4m3x4e2q6r,' + // #3877: 5 fonts: HK 89₁₁₆, JP 88₂₂₄, KR 70₃₃₀, SC 79₄₆₃, TC 85₅₇₀. + '4m4d4b5c4c,' + // #3878: 4 fonts: HK 89₁₁₆, JP 89₂₂₅, SC 80₄₆₄, TC 85₅₇₀. + '4m4e9e4b,' + // #3879: 5 fonts: HK 89₁₁₆, JP 91₂₂₇, KR 0₂₆₀, SC 1₃₈₅, TC 85₅₇₀. + '4m4g1g4u7c,' + // #3880: 5 fonts: HK 89₁₁₆, JP 93₂₂₉, KR 69₃₂₉, SC 16₄₀₀, TC 85₅₇₀. + '4m4i3v2s6n,' + // #3881: 5 fonts: HK 89₁₁₆, JP 95₂₃₁, KR 0₂₆₀, SC 1₃₈₅, TC 85₅₇₀. + '4m4k1c4u7c,' + // #3882: 5 fonts: HK 89₁₁₆, JP 95₂₃₁, KR 68₃₂₈, SC 80₄₆₄, TC 85₅₇₀. + '4m4k3s5f4b,' + // #3883: 4 fonts: HK 89₁₁₆, JP 96₂₃₂, SC 1₃₈₅, TC 85₅₇₀. + '4m4l5w7c,' + // #3884: 5 fonts: HK 89₁₁₆, JP 98₂₃₄, KR 83₃₄₃, SC 81₄₆₅, TC 85₅₇₀. + '4m4n4e4r4a,' + // #3885: 5 fonts: HK 89₁₁₆, JP 101₂₃₇, KR 100₃₆₀, SC 1₃₈₅, TC 85₅₇₀. + '4m4q4sy7c,' + // #3886: 5 fonts: HK 89₁₁₆, JP 104₂₄₀, KR 0₂₆₀, SC 1₃₈₅, TC 85₅₇₀. + '4m4tt4u7c,' + // #3887: 5 fonts: HK 89₁₁₆, JP 109₂₄₅, KR 72₃₃₂, SC 78₄₆₂, TC 85₅₇₀. + '4m4y3i4z4d,' + // #3888: 4 fonts: HK 89₁₁₆, JP 109₂₄₅, SC 94₄₇₈, TC 85₅₇₀. + '4m4y8y3n,' + // #3889: 4 fonts: HK 89₁₁₆, JP 117₂₅₃, SC 92₄₇₆, TC 85₅₇₀. + '4m5g8o3p,' + // #3890: 5 fonts: HK 89₁₁₆, JP 119₂₅₅, KR 103₃₆₃, SC 96₄₈₀, TC 85₅₇₀. + '4m5i4d4m3l,' + // #3891: 3 fonts: HK 89₁₁₆, SC 56₄₄₀, TC 85₅₇₀. + '4m12l4z,' + // #3892: 3 fonts: HK 89₁₁₆, SC 60₄₄₄, TC 85₅₇₀. + '4m12p4v,' + // #3893: 3 fonts: HK 89₁₁₆, SC 77₄₆₁, TC 85₅₇₀. + '4m13g4e,' + // #3894: 3 fonts: HK 89₁₁₆, SC 80₄₆₄, TC 85₅₇₀. + '4m13j4b,' + // #3895: 3 fonts: HK 89₁₁₆, SC 82₄₆₆, TC 85₅₇₀. + '4m13l3z,' + // #3896: 5 fonts: HK 90₁₁₇, JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 86₅₇₁. + '4n1a6z2m6x,' + // #3897: 5 fonts: HK 90₁₁₇, JP 10₁₄₆, KR 66₃₂₆, SC 83₄₆₇, TC 86₅₇₁. + '4n1c6x5k3z,' + // #3898: 4 fonts: HK 90₁₁₇, JP 10₁₄₆, SC 11₃₉₅, TC 86₅₇₁. + '4n1c9o6t,' + // #3899: 4 fonts: HK 90₁₁₇, JP 14₁₅₀, SC 83₄₆₇, TC 86₅₇₁. + '4n1g12e3z,' + // #3900: 5 fonts: HK 90₁₁₇, JP 17₁₅₃, KR 71₃₃₁, SC 82₄₆₆, TC 86₅₇₁. + '4n1j6v5e4a,' + // #3901: 5 fonts: HK 90₁₁₇, JP 17₁₅₃, KR 71₃₃₁, SC 84₄₆₈, TC 86₅₇₁. + '4n1j6v5g3y,' + // #3902: 4 fonts: HK 90₁₁₇, JP 17₁₅₃, SC 83₄₆₇, TC 86₅₇₁. + '4n1j12b3z,' + // #3903: 5 fonts: HK 90₁₁₇, JP 18₁₅₄, KR 71₃₃₁, SC 20₄₀₄, TC 86₅₇₁. + '4n1k6u2u6k,' + // #3904: 5 fonts: HK 90₁₁₇, JP 19₁₅₅, KR 72₃₃₂, SC 22₄₀₆, TC 86₅₇₁. + '4n1l6u2v6i,' + // #3905: 5 fonts: HK 90₁₁₇, JP 20₁₅₆, KR 72₃₃₂, SC 78₄₆₂, TC 86₅₇₁. + '4n1m6t4z4e,' + // #3906: 5 fonts: HK 90₁₁₇, JP 20₁₅₆, KR 72₃₃₂, SC 80₄₆₄, TC 86₅₇₁. + '4n1m6t5b4c,' + // #3907: 5 fonts: HK 90₁₁₇, JP 22₁₅₈, KR 73₃₃₃, SC 25₄₀₉, TC 86₅₇₁. + '4n1o6s2x6f,' + // #3908: 5 fonts: HK 90₁₁₇, JP 22₁₅₈, KR 73₃₃₃, SC 81₄₆₅, TC 86₅₇₁. + '4n1o6s5b4b,' + // #3909: 5 fonts: HK 90₁₁₇, JP 22₁₅₈, KR 73₃₃₃, SC 82₄₆₆, TC 86₅₇₁. + '4n1o6s5c4a,' + // #3910: 5 fonts: HK 90₁₁₇, JP 22₁₅₈, KR 74₃₃₄, SC 26₄₁₀, TC 86₅₇₁. + '4n1o6t2x6e,' + // #3911: 5 fonts: HK 90₁₁₇, JP 24₁₆₀, KR 75₃₃₅, SC 28₄₁₂, TC 86₅₇₁. + '4n1q6s2y6c,' + // #3912: 5 fonts: HK 90₁₁₇, JP 24₁₆₀, KR 75₃₃₅, SC 29₄₁₃, TC 86₅₇₁. + '4n1q6s2z6b,' + // #3913: 5 fonts: HK 90₁₁₇, JP 24₁₆₀, KR 75₃₃₅, SC 82₄₆₆, TC 86₅₇₁. + '4n1q6s5a4a,' + // #3914: 4 fonts: HK 90₁₁₇, JP 24₁₆₀, SC 80₄₆₄, TC 86₅₇₁. + '4n1q11r4c,' + // #3915: 5 fonts: HK 90₁₁₇, JP 25₁₆₁, KR 75₃₃₅, SC 31₄₁₅, TC 86₅₇₁. + '4n1r6r3b5z,' + // #3916: 5 fonts: HK 90₁₁₇, JP 26₁₆₂, KR 76₃₃₆, SC 79₄₆₃, TC 86₅₇₁. + '4n1s6r4w4d,' + // #3917: 4 fonts: HK 90₁₁₇, JP 26₁₆₂, SC 93₄₇₇, TC 86₅₇₁. + '4n1s12c3p,' + // #3918: 5 fonts: HK 90₁₁₇, JP 28₁₆₄, KR 77₃₃₇, SC 34₄₁₈, TC 86₅₇₁. + '4n1u6q3c5w,' + // #3919: 5 fonts: HK 90₁₁₇, JP 28₁₆₄, KR 77₃₃₇, SC 35₄₁₉, TC 86₅₇₁. + '4n1u6q3d5v,' + // #3920: 5 fonts: HK 90₁₁₇, JP 28₁₆₄, KR 77₃₃₇, SC 82₄₆₆, TC 86₅₇₁. + '4n1u6q4y4a,' + // #3921: 5 fonts: HK 90₁₁₇, JP 28₁₆₄, KR 77₃₃₇, SC 83₄₆₇, TC 86₅₇₁. + '4n1u6q4z3z,' + // #3922: 5 fonts: HK 90₁₁₇, JP 28₁₆₄, KR 77₃₃₇, SC 87₄₇₁, TC 86₅₇₁. + '4n1u6q5d3v,' + // #3923: 4 fonts: HK 90₁₁₇, JP 28₁₆₄, SC 34₄₁₈, TC 86₅₇₁. + '4n1u9t5w,' + // #3924: 5 fonts: HK 90₁₁₇, JP 29₁₆₅, KR 77₃₃₇, SC 36₄₂₀, TC 86₅₇₁. + '4n1v6p3e5u,' + // #3925: 5 fonts: HK 90₁₁₇, JP 29₁₆₅, KR 77₃₃₇, SC 89₄₇₃, TC 86₅₇₁. + '4n1v6p5f3t,' + // #3926: 4 fonts: HK 90₁₁₇, JP 30₁₆₆, SC 38₄₂₂, TC 86₅₇₁. + '4n1w9v5s,' + // #3927: 5 fonts: HK 90₁₁₇, JP 31₁₆₇, KR 79₃₃₉, SC 84₄₆₈, TC 86₅₇₁. + '4n1x6p4y3y,' + // #3928: 4 fonts: HK 90₁₁₇, JP 31₁₆₇, SC 40₄₂₄, TC 86₅₇₁. + '4n1x9w5q,' + // #3929: 4 fonts: HK 90₁₁₇, JP 32₁₆₈, SC 40₄₂₄, TC 86₅₇₁. + '4n1y9v5q,' + // #3930: 4 fonts: HK 90₁₁₇, JP 32₁₆₈, SC 77₄₆₁, TC 86₅₇₁. + '4n1y11g4f,' + // #3931: 5 fonts: HK 90₁₁₇, JP 33₁₆₉, KR 81₃₄₁, SC 85₄₆₉, TC 86₅₇₁. + '4n1z6p4x3x,' + // #3932: 4 fonts: HK 90₁₁₇, JP 33₁₆₉, SC 43₄₂₇, TC 86₅₇₁. + '4n1z9x5n,' + // #3933: 4 fonts: HK 90₁₁₇, JP 34₁₇₀, SC 81₄₆₅, TC 86₅₇₁. + '4n2a11i4b,' + // #3934: 5 fonts: HK 90₁₁₇, JP 35₁₇₁, KR 82₃₄₂, SC 79₄₆₃, TC 86₅₇₁. + '4n2b6o4q4d,' + // #3935: 5 fonts: HK 90₁₁₇, JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂, TC 86₅₇₁. + '4n2d6o3j5i,' + // #3936: 5 fonts: HK 90₁₁₇, JP 37₁₇₃, KR 84₃₄₄, SC 77₄₆₁, TC 86₅₇₁. + '4n2d6o4m4f,' + // #3937: 5 fonts: HK 90₁₁₇, JP 37₁₇₃, KR 84₃₄₄, SC 83₄₆₇, TC 86₅₇₁. + '4n2d6o4s3z,' + // #3938: 5 fonts: HK 90₁₁₇, JP 37₁₇₃, KR 84₃₄₄, SC 95₄₇₉, TC 86₅₇₁. + '4n2d6o5e3n,' + // #3939: 5 fonts: HK 90₁₁₇, JP 38₁₇₄, KR 84₃₄₄, SC 49₄₃₃, TC 86₅₇₁. + '4n2e6n3k5h,' + // #3940: 4 fonts: HK 90₁₁₇, JP 38₁₇₄, SC 82₄₆₆, TC 86₅₇₁. + '4n2e11f4a,' + // #3941: 4 fonts: HK 90₁₁₇, JP 39₁₇₅, SC 83₄₆₇, TC 86₅₇₁. + '4n2f11f3z,' + // #3942: 5 fonts: HK 90₁₁₇, JP 40₁₇₆, KR 86₃₄₆, SC 53₄₃₇, TC 86₅₇₁. + '4n2g6n3m5d,' + // #3943: 5 fonts: HK 90₁₁₇, JP 40₁₇₆, KR 86₃₄₆, SC 80₄₆₄, TC 86₅₇₁. + '4n2g6n4n4c,' + // #3944: 5 fonts: HK 90₁₁₇, JP 40₁₇₆, KR 86₃₄₆, SC 81₄₆₅, TC 86₅₇₁. + '4n2g6n4o4b,' + // #3945: 5 fonts: HK 90₁₁₇, JP 40₁₇₆, KR 86₃₄₆, SC 84₄₆₈, TC 86₅₇₁. + '4n2g6n4r3y,' + // #3946: 5 fonts: HK 90₁₁₇, JP 40₁₇₆, KR 86₃₄₆, SC 88₄₇₂, TC 86₅₇₁. + '4n2g6n4v3u,' + // #3947: 5 fonts: HK 90₁₁₇, JP 41₁₇₇, KR 86₃₄₆, SC 84₄₆₈, TC 86₅₇₁. + '4n2h6m4r3y,' + // #3948: 5 fonts: HK 90₁₁₇, JP 41₁₇₇, KR 87₃₄₇, SC 53₄₃₇, TC 86₅₇₁. + '4n2h6n3l5d,' + // #3949: 5 fonts: HK 90₁₁₇, JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉, TC 86₅₇₁. + '4n2i6m3n5b,' + // #3950: 5 fonts: HK 90₁₁₇, JP 42₁₇₈, KR 87₃₄₇, SC 80₄₆₄, TC 86₅₇₁. + '4n2i6m4m4c,' + // #3951: 5 fonts: HK 90₁₁₇, JP 42₁₇₈, KR 87₃₄₇, SC 83₄₆₇, TC 86₅₇₁. + '4n2i6m4p3z,' + // #3952: 5 fonts: HK 90₁₁₇, JP 42₁₇₈, KR 88₃₄₈, SC 84₄₆₈, TC 86₅₇₁. + '4n2i6n4p3y,' + // #3953: 5 fonts: HK 90₁₁₇, JP 43₁₇₉, KR 88₃₄₈, SC 81₄₆₅, TC 86₅₇₁. + '4n2j6m4m4b,' + // #3954: 5 fonts: HK 90₁₁₇, JP 43₁₇₉, KR 88₃₄₈, SC 83₄₆₇, TC 86₅₇₁. + '4n2j6m4o3z,' + // #3955: 4 fonts: HK 90₁₁₇, JP 43₁₇₉, SC 83₄₆₇, TC 86₅₇₁. + '4n2j11b3z,' + // #3956: 5 fonts: HK 90₁₁₇, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 86₅₇₁. + '4n2k6m3o4y,' + // #3957: 4 fonts: HK 90₁₁₇, JP 46₁₈₂, SC 61₄₄₅, TC 86₅₇₁. + '4n2m10c4v,' + // #3958: 4 fonts: HK 90₁₁₇, JP 46₁₈₂, SC 81₄₆₅, TC 86₅₇₁. + '4n2m10w4b,' + // #3959: 5 fonts: HK 90₁₁₇, JP 47₁₈₃, KR 90₃₅₀, SC 82₄₆₆, TC 86₅₇₁. + '4n2n6k4l4a,' + // #3960: 5 fonts: HK 90₁₁₇, JP 47₁₈₃, KR 91₃₅₁, SC 83₄₆₇, TC 86₅₇₁. + '4n2n6l4l3z,' + // #3961: 4 fonts: HK 90₁₁₇, JP 47₁₈₃, SC 80₄₆₄, TC 86₅₇₁. + '4n2n10u4c,' + // #3962: 5 fonts: HK 90₁₁₇, JP 48₁₈₄, KR 91₃₅₁, SC 64₄₄₈, TC 86₅₇₁. + '4n2o6k3s4s,' + // #3963: 5 fonts: HK 90₁₁₇, JP 49₁₈₅, KR 92₃₅₂, SC 78₄₆₂, TC 86₅₇₁. + '4n2p6k4f4e,' + // #3964: 5 fonts: HK 90₁₁₇, JP 60₁₉₆, KR 66₃₂₆, SC 7₃₉₁, TC 86₅₇₁. + '4n3a4z2m6x,' + // #3965: 5 fonts: HK 90₁₁₇, JP 60₁₉₆, KR 72₃₃₂, SC 81₄₆₅, TC 86₅₇₁. + '4n3a5f5c4b,' + // #3966: 5 fonts: HK 90₁₁₇, JP 60₁₉₆, KR 89₃₄₉, SC 81₄₆₅, TC 86₅₇₁. + '4n3a5w4l4b,' + // #3967: 5 fonts: HK 90₁₁₇, JP 60₁₉₆, KR 90₃₅₀, SC 80₄₆₄, TC 86₅₇₁. + '4n3a5x4j4c,' + // #3968: 5 fonts: HK 90₁₁₇, JP 61₁₉₇, KR 68₃₂₈, SC 77₄₆₁, TC 86₅₇₁. + '4n3b5a5c4f,' + // #3969: 5 fonts: HK 90₁₁₇, JP 62₁₉₈, KR 82₃₄₂, SC 44₄₂₈, TC 86₅₇₁. + '4n3c5n3h5m,' + // #3970: 5 fonts: HK 90₁₁₇, JP 63₁₉₉, KR 72₃₃₂, SC 82₄₆₆, TC 86₅₇₁. + '4n3d5c5d4a,' + // #3971: 5 fonts: HK 90₁₁₇, JP 63₁₉₉, KR 87₃₄₇, SC 83₄₆₇, TC 86₅₇₁. + '4n3d5r4p3z,' + // #3972: 5 fonts: HK 90₁₁₇, JP 64₂₀₀, KR 86₃₄₆, SC 53₄₃₇, TC 86₅₇₁. + '4n3e5p3m5d,' + // #3973: 5 fonts: HK 90₁₁₇, JP 64₂₀₀, KR 86₃₄₆, SC 83₄₆₇, TC 86₅₇₁. + '4n3e5p4q3z,' + // #3974: 5 fonts: HK 90₁₁₇, JP 65₂₀₁, KR 70₃₃₀, SC 18₄₀₂, TC 86₅₇₁. + '4n3f4y2t6m,' + // #3975: 5 fonts: HK 90₁₁₇, JP 65₂₀₁, KR 85₃₄₅, SC 86₄₇₀, TC 86₅₇₁. + '4n3f5n4u3w,' + // #3976: 5 fonts: HK 90₁₁₇, JP 66₂₀₂, KR 73₃₃₃, SC 81₄₆₅, TC 86₅₇₁. + '4n3g5a5b4b,' + // #3977: 5 fonts: HK 90₁₁₇, JP 66₂₀₂, KR 82₃₄₂, SC 80₄₆₄, TC 86₅₇₁. + '4n3g5j4r4c,' + // #3978: 5 fonts: HK 90₁₁₇, JP 67₂₀₃, KR 85₃₄₅, SC 81₄₆₅, TC 86₅₇₁. + '4n3h5l4p4b,' + // #3979: 5 fonts: HK 90₁₁₇, JP 67₂₀₃, KR 91₃₅₁, SC 86₄₇₀, TC 86₅₇₁. + '4n3h5r4o3w,' + // #3980: 5 fonts: HK 90₁₁₇, JP 68₂₀₄, KR 69₃₂₉, SC 16₄₀₀, TC 86₅₇₁. + '4n3i4u2s6o,' + // #3981: 5 fonts: HK 90₁₁₇, JP 68₂₀₄, KR 70₃₃₀, SC 18₄₀₂, TC 86₅₇₁. + '4n3i4v2t6m,' + // #3982: 5 fonts: HK 90₁₁₇, JP 68₂₀₄, KR 75₃₃₅, SC 28₄₁₂, TC 86₅₇₁. + '4n3i5a2y6c,' + // #3983: 5 fonts: HK 90₁₁₇, JP 68₂₀₄, KR 82₃₄₂, SC 82₄₆₆, TC 86₅₇₁. + '4n3i5h4t4a,' + // #3984: 5 fonts: HK 90₁₁₇, JP 69₂₀₅, KR 73₃₃₃, SC 83₄₆₇, TC 86₅₇₁. + '4n3j4x5d3z,' + // #3985: 5 fonts: HK 90₁₁₇, JP 69₂₀₅, KR 85₃₄₅, SC 80₄₆₄, TC 86₅₇₁. + '4n3j5j4o4c,' + // #3986: 5 fonts: HK 90₁₁₇, JP 69₂₀₅, KR 88₃₄₈, SC 85₄₆₉, TC 86₅₇₁. + '4n3j5m4q3x,' + // #3987: 5 fonts: HK 90₁₁₇, JP 69₂₀₅, KR 91₃₅₁, SC 82₄₆₆, TC 86₅₇₁. + '4n3j5p4k4a,' + // #3988: 5 fonts: HK 90₁₁₇, JP 70₂₀₆, KR 66₃₂₆, SC 84₄₆₈, TC 86₅₇₁. + '4n3k4p5l3y,' + // #3989: 5 fonts: HK 90₁₁₇, JP 70₂₀₆, KR 78₃₃₈, SC 81₄₆₅, TC 86₅₇₁. + '4n3k5b4w4b,' + // #3990: 5 fonts: HK 90₁₁₇, JP 70₂₀₆, KR 87₃₄₇, SC 82₄₆₆, TC 86₅₇₁. + '4n3k5k4o4a,' + // #3991: 5 fonts: HK 90₁₁₇, JP 71₂₀₇, KR 82₃₄₂, SC 81₄₆₅, TC 86₅₇₁. + '4n3l5e4s4b,' + // #3992: 5 fonts: HK 90₁₁₇, JP 71₂₀₇, KR 93₃₅₃, SC 83₄₆₇, TC 86₅₇₁. + '4n3l5p4j3z,' + // #3993: 5 fonts: HK 90₁₁₇, JP 72₂₀₈, KR 70₃₃₀, SC 18₄₀₂, TC 86₅₇₁. + '4n3m4r2t6m,' + // #3994: 5 fonts: HK 90₁₁₇, JP 72₂₀₈, KR 76₃₃₆, SC 31₄₁₅, TC 86₅₇₁. + '4n3m4x3a5z,' + // #3995: 5 fonts: HK 90₁₁₇, JP 72₂₀₈, KR 80₃₄₀, SC 41₄₂₅, TC 86₅₇₁. + '4n3m5b3g5p,' + // #3996: 5 fonts: HK 90₁₁₇, JP 72₂₀₈, KR 81₃₄₁, SC 83₄₆₇, TC 86₅₇₁. + '4n3m5c4v3z,' + // #3997: 5 fonts: HK 90₁₁₇, JP 72₂₀₈, KR 85₃₄₅, SC 82₄₆₆, TC 86₅₇₁. + '4n3m5g4q4a,' + // #3998: 5 fonts: HK 90₁₁₇, JP 72₂₀₈, KR 91₃₅₁, SC 85₄₆₉, TC 86₅₇₁. + '4n3m5m4n3x,' + // #3999: 5 fonts: HK 90₁₁₇, JP 73₂₀₉, KR 70₃₃₀, SC 82₄₆₆, TC 86₅₇₁. + '4n3n4q5f4a,' + // #4000: 5 fonts: HK 90₁₁₇, JP 73₂₀₉, KR 73₃₃₃, SC 83₄₆₇, TC 86₅₇₁. + '4n3n4t5d3z,' + // #4001: 5 fonts: HK 90₁₁₇, JP 73₂₀₉, KR 85₃₄₅, SC 82₄₆₆, TC 86₅₇₁. + '4n3n5f4q4a,' + // #4002: 5 fonts: HK 90₁₁₇, JP 73₂₀₉, KR 91₃₅₁, SC 82₄₆₆, TC 86₅₇₁. + '4n3n5l4k4a,' + // #4003: 5 fonts: HK 90₁₁₇, JP 74₂₁₀, KR 66₃₂₆, SC 84₄₆₈, TC 86₅₇₁. + '4n3o4l5l3y,' + // #4004: 5 fonts: HK 90₁₁₇, JP 74₂₁₀, KR 73₃₃₃, SC 80₄₆₄, TC 86₅₇₁. + '4n3o4s5a4c,' + // #4005: 5 fonts: HK 90₁₁₇, JP 74₂₁₀, KR 81₃₄₁, SC 82₄₆₆, TC 86₅₇₁. + '4n3o5a4u4a,' + // #4006: 5 fonts: HK 90₁₁₇, JP 75₂₁₁, KR 68₃₂₈, SC 82₄₆₆, TC 86₅₇₁. + '4n3p4m5h4a,' + // #4007: 5 fonts: HK 90₁₁₇, JP 75₂₁₁, KR 73₃₃₃, SC 84₄₆₈, TC 86₅₇₁. + '4n3p4r5e3y,' + // #4008: 5 fonts: HK 90₁₁₇, JP 76₂₁₂, KR 83₃₄₃, SC 81₄₆₅, TC 86₅₇₁. + '4n3q5a4r4b,' + // #4009: 5 fonts: HK 90₁₁₇, JP 76₂₁₂, KR 88₃₄₈, SC 80₄₆₄, TC 86₅₇₁. + '4n3q5f4l4c,' + // #4010: 5 fonts: HK 90₁₁₇, JP 76₂₁₂, KR 88₃₄₈, SC 83₄₆₇, TC 86₅₇₁. + '4n3q5f4o3z,' + // #4011: 5 fonts: HK 90₁₁₇, JP 77₂₁₃, KR 66₃₂₆, SC 7₃₉₁, TC 86₅₇₁. + '4n3r4i2m6x,' + // #4012: 5 fonts: HK 90₁₁₇, JP 77₂₁₃, KR 91₃₅₁, SC 81₄₆₅, TC 86₅₇₁. + '4n3r5h4j4b,' + // #4013: 5 fonts: HK 90₁₁₇, JP 78₂₁₄, KR 65₃₂₅, SC 7₃₉₁, TC 86₅₇₁. + '4n3s4g2n6x,' + // #4014: 5 fonts: HK 90₁₁₇, JP 78₂₁₄, KR 72₃₃₂, SC 81₄₆₅, TC 86₅₇₁. + '4n3s4n5c4b,' + // #4015: 5 fonts: HK 90₁₁₇, JP 78₂₁₄, KR 86₃₄₆, SC 83₄₆₇, TC 86₅₇₁. + '4n3s5b4q3z,' + // #4016: 5 fonts: HK 90₁₁₇, JP 79₂₁₅, KR 68₃₂₈, SC 82₄₆₆, TC 86₅₇₁. + '4n3t4i5h4a,' + // #4017: 5 fonts: HK 90₁₁₇, JP 80₂₁₆, KR 79₃₃₉, SC 81₄₆₅, TC 86₅₇₁. + '4n3u4s4v4b,' + // #4018: 5 fonts: HK 90₁₁₇, JP 80₂₁₆, KR 86₃₄₆, SC 82₄₆₆, TC 86₅₇₁. + '4n3u4z4p4a,' + // #4019: 5 fonts: HK 90₁₁₇, JP 80₂₁₆, KR 86₃₄₆, SC 88₄₇₂, TC 86₅₇₁. + '4n3u4z4v3u,' + // #4020: 5 fonts: HK 90₁₁₇, JP 81₂₁₇, KR 75₃₃₅, SC 85₄₆₉, TC 86₅₇₁. + '4n3v4n5d3x,' + // #4021: 5 fonts: HK 90₁₁₇, JP 81₂₁₇, KR 87₃₄₇, SC 88₄₇₂, TC 86₅₇₁. + '4n3v4z4u3u,' + // #4022: 5 fonts: HK 90₁₁₇, JP 82₂₁₈, KR 68₃₂₈, SC 78₄₆₂, TC 86₅₇₁. + '4n3w4f5d4e,' + // #4023: 5 fonts: HK 90₁₁₇, JP 82₂₁₈, KR 72₃₃₂, SC 83₄₆₇, TC 86₅₇₁. + '4n3w4j5e3z,' + // #4024: 6 fonts: HK 90₁₁₇, JP 82₂₁₈, KR 105₃₆₅, SC 84₄₆₈, TC 86₅₇₁, Noto Sans₅₉₁. + '4n3w5q3y3yt,' + // #4025: 5 fonts: HK 90₁₁₇, JP 84₂₂₀, KR 72₃₃₂, SC 82₄₆₆, TC 86₅₇₁. + '4n3y4h5d4a,' + // #4026: 7 fonts: HK 90₁₁₇, JP 84₂₂₀, KR 95₃₅₅, SC 80₄₆₄, TC 86₅₇₁, Phags Pa₆₈₇, Yi₇₂₁. + '4n3y5e4e4c4l1h,' + // #4027: 5 fonts: HK 90₁₁₇, JP 85₂₂₁, KR 76₃₃₆, SC 32₄₁₆, TC 86₅₇₁. + '4n3z4k3b5y,' + // #4028: 5 fonts: HK 90₁₁₇, JP 85₂₂₁, KR 82₃₄₂, SC 82₄₆₆, TC 86₅₇₁. + '4n3z4q4t4a,' + // #4029: 5 fonts: HK 90₁₁₇, JP 86₂₂₂, KR 66₃₂₆, SC 7₃₉₁, TC 86₅₇₁. + '4n4a3z2m6x,' + // #4030: 5 fonts: HK 90₁₁₇, JP 87₂₂₃, KR 87₃₄₇, SC 54₄₃₈, TC 86₅₇₁. + '4n4b4t3m5c,' + // #4031: 5 fonts: HK 90₁₁₇, JP 88₂₂₄, KR 91₃₅₁, SC 82₄₆₆, TC 86₅₇₁. + '4n4c4w4k4a,' + // #4032: 5 fonts: HK 90₁₁₇, JP 90₂₂₆, KR 65₃₂₅, SC 5₃₈₉, TC 86₅₇₁. + '4n4e3u2l6z,' + // #4033: 5 fonts: HK 90₁₁₇, JP 91₂₂₇, KR 86₃₄₆, SC 52₄₃₆, TC 86₅₇₁. + '4n4f4o3l5e,' + // #4034: 5 fonts: HK 90₁₁₇, JP 95₂₃₁, KR 84₃₄₄, SC 83₄₆₇, TC 86₅₇₁. + '4n4j4i4s3z,' + // #4035: 5 fonts: HK 90₁₁₇, JP 95₂₃₁, KR 88₃₄₈, SC 82₄₆₆, TC 86₅₇₁. + '4n4j4m4n4a,' + // #4036: 5 fonts: HK 90₁₁₇, JP 95₂₃₁, KR 91₃₅₁, SC 80₄₆₄, TC 86₅₇₁. + '4n4j4p4i4c,' + // #4037: 5 fonts: HK 90₁₁₇, JP 96₂₃₂, KR 74₃₃₄, SC 28₄₁₂, TC 86₅₇₁. + '4n4k3x2z6c,' + // #4038: 5 fonts: HK 90₁₁₇, JP 103₂₃₉, KR 0₂₆₀, SC 1₃₈₅, TC 86₅₇₁. + '4n4ru4u7d,' + // #4039: 5 fonts: HK 90₁₁₇, JP 104₂₄₀, KR 0₂₆₀, SC 1₃₈₅, TC 86₅₇₁. + '4n4st4u7d,' + // #4040: 5 fonts: HK 90₁₁₇, JP 105₂₄₁, KR 0₂₆₀, SC 1₃₈₅, TC 86₅₇₁. + '4n4ts4u7d,' + // #4041: 5 fonts: HK 90₁₁₇, JP 106₂₄₂, KR 0₂₆₀, SC 77₄₆₁, TC 86₅₇₁. + '4n4ur7s4f,' + // #4042: 5 fonts: HK 90₁₁₇, JP 106₂₄₂, KR 83₃₄₃, SC 84₄₆₈, TC 86₅₇₁. + '4n4u3w4u3y,' + // #4043: 7 fonts: HK 90₁₁₇, JP 109₂₄₅, KR 106₃₆₆, SC 69₄₅₃, TC 86₅₇₁, Noto Music₅₉₀, Symbols₇₀₂. + '4n4x4q3i4ns4h,' + // #4044: 4 fonts: HK 90₁₁₇, JP 109₂₄₅, SC 18₄₀₂, TC 86₅₇₁. + '4n4x6a6m,' + // #4045: 4 fonts: HK 90₁₁₇, JP 113₂₄₉, SC 95₄₇₉, TC 86₅₇₁. + '4n5b8v3n,' + // #4046: 3 fonts: HK 90₁₁₇, SC 55₄₃₉, TC 86₅₇₁. + '4n12j5b,' + // #4047: 3 fonts: HK 90₁₁₇, SC 83₄₆₇, TC 86₅₇₁. + '4n13l3z,' + // #4048: 3 fonts: HK 90₁₁₇, SC 91₄₇₅, TC 86₅₇₁. + '4n13t3r,' + // #4049: 3 fonts: HK 90₁₁₇, SC 94₄₇₈, TC 86₅₇₁. + '4n13w3o,' + // #4050: 12 fonts: HK 91₁₁₈, HK 108₁₃₅, JP 95₂₃₁, JP 123₂₅₉, KR 107₃₆₇, KR 123₃₈₃, SC 81₄₆₅, SC 100₄₈₄, TC 87₅₇₂, TC 104₅₈₉, Math₆₅₅, Symbols₇₀₂. + '4oq3r1b4dp3ds3jq2n1u,' + // #4051: 12 fonts: HK 91₁₁₈, HK 108₁₃₅, JP 99₂₃₅, JP 123₂₅₉, KR 109₃₆₉, KR 123₃₈₃, SC 83₄₆₇, SC 100₄₈₄, TC 87₅₇₂, TC 104₅₈₉, Math₆₅₅, Symbols₇₀₂. + '4oq3vx4fn3fq3jq2n1u,' + // #4052: 5 fonts: HK 91₁₁₈, JP 5₁₄₁, KR 65₃₂₅, SC 3₃₈₇, TC 87₅₇₂. + '4ow7b2j7c,' + // #4053: 5 fonts: HK 91₁₁₈, JP 5₁₄₁, KR 65₃₂₅, SC 81₄₆₅, TC 87₅₇₂. + '4ow7b5j4c,' + // #4054: 5 fonts: HK 91₁₁₈, JP 6₁₄₂, KR 65₃₂₅, SC 5₃₈₉, TC 87₅₇₂. + '4ox7a2l7a,' + // #4055: 5 fonts: HK 91₁₁₈, JP 7₁₄₃, KR 65₃₂₅, SC 84₄₆₈, TC 87₅₇₂. + '4oy6z5m3z,' + // #4056: 5 fonts: HK 91₁₁₈, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 87₅₇₂. + '4o1a6y2n6x,' + // #4057: 4 fonts: HK 91₁₁₈, JP 9₁₄₅, SC 8₃₉₂, TC 87₅₇₂. + '4o1a9m6x,' + // #4058: 5 fonts: HK 91₁₁₈, JP 10₁₄₆, KR 66₃₂₆, SC 80₄₆₄, TC 87₅₇₂. + '4o1b6x5h4d,' + // #4059: 5 fonts: HK 91₁₁₈, JP 10₁₄₆, KR 66₃₂₆, SC 82₄₆₆, TC 87₅₇₂. + '4o1b6x5j4b,' + // #4060: 4 fonts: HK 91₁₁₈, JP 10₁₄₆, SC 11₃₉₅, TC 87₅₇₂. + '4o1b9o6u,' + // #4061: 4 fonts: HK 91₁₁₈, JP 10₁₄₆, SC 84₄₆₈, TC 87₅₇₂. + '4o1b12j3z,' + // #4062: 4 fonts: HK 91₁₁₈, JP 12₁₄₈, SC 13₃₉₇, TC 87₅₇₂. + '4o1d9o6s,' + // #4063: 5 fonts: HK 91₁₁₈, JP 13₁₄₉, KR 68₃₂₈, SC 15₃₉₉, TC 87₅₇₂. + '4o1e6w2s6q,' + // #4064: 5 fonts: HK 91₁₁₈, JP 14₁₅₀, KR 69₃₂₉, SC 16₄₀₀, TC 87₅₇₂. + '4o1f6w2s6p,' + // #4065: 5 fonts: HK 91₁₁₈, JP 14₁₅₀, KR 69₃₂₉, SC 86₄₇₀, TC 87₅₇₂. + '4o1f6w5k3x,' + // #4066: 5 fonts: HK 91₁₁₈, JP 15₁₅₁, KR 69₃₂₉, SC 16₄₀₀, TC 87₅₇₂. + '4o1g6v2s6p,' + // #4067: 4 fonts: HK 91₁₁₈, JP 15₁₅₁, SC 79₄₆₃, TC 87₅₇₂. + '4o1g11z4e,' + // #4068: 5 fonts: HK 91₁₁₈, JP 16₁₅₂, KR 70₃₃₀, SC 82₄₆₆, TC 87₅₇₂. + '4o1h6v5f4b,' + // #4069: 5 fonts: HK 91₁₁₈, JP 17₁₅₃, KR 71₃₃₁, SC 20₄₀₄, TC 87₅₇₂. + '4o1i6v2u6l,' + // #4070: 5 fonts: HK 91₁₁₈, JP 18₁₅₄, KR 71₃₃₁, SC 84₄₆₈, TC 87₅₇₂. + '4o1j6u5g3z,' + // #4071: 5 fonts: HK 91₁₁₈, JP 19₁₅₅, KR 71₃₃₁, SC 21₄₀₅, TC 87₅₇₂. + '4o1k6t2v6k,' + // #4072: 5 fonts: HK 91₁₁₈, JP 19₁₅₅, KR 71₃₃₁, SC 79₄₆₃, TC 87₅₇₂. + '4o1k6t5b4e,' + // #4073: 5 fonts: HK 91₁₁₈, JP 19₁₅₅, KR 72₃₃₂, SC 21₄₀₅, TC 87₅₇₂. + '4o1k6u2u6k,' + // #4074: 5 fonts: HK 91₁₁₈, JP 19₁₅₅, KR 72₃₃₂, SC 22₄₀₆, TC 87₅₇₂. + '4o1k6u2v6j,' + // #4075: 5 fonts: HK 91₁₁₈, JP 19₁₅₅, KR 72₃₃₂, SC 83₄₆₇, TC 87₅₇₂. + '4o1k6u5e4a,' + // #4076: 5 fonts: HK 91₁₁₈, JP 19₁₅₅, KR 72₃₃₂, SC 84₄₆₈, TC 87₅₇₂. + '4o1k6u5f3z,' + // #4077: 5 fonts: HK 91₁₁₈, JP 20₁₅₆, KR 72₃₃₂, SC 81₄₆₅, TC 87₅₇₂. + '4o1l6t5c4c,' + // #4078: 4 fonts: HK 91₁₁₈, JP 20₁₅₆, SC 22₄₀₆, TC 87₅₇₂. + '4o1l9p6j,' + // #4079: 5 fonts: HK 91₁₁₈, JP 21₁₅₇, KR 73₃₃₃, SC 83₄₆₇, TC 87₅₇₂. + '4o1m6t5d4a,' + // #4080: 5 fonts: HK 91₁₁₈, JP 21₁₅₇, KR 73₃₃₃, SC 85₄₆₉, TC 87₅₇₂. + '4o1m6t5f3y,' + // #4081: 5 fonts: HK 91₁₁₈, JP 22₁₅₈, KR 74₃₃₄, SC 80₄₆₄, TC 87₅₇₂. + '4o1n6t4z4d,' + // #4082: 5 fonts: HK 91₁₁₈, JP 22₁₅₈, KR 74₃₃₄, SC 83₄₆₇, TC 87₅₇₂. + '4o1n6t5c4a,' + // #4083: 4 fonts: HK 91₁₁₈, JP 22₁₅₈, SC 83₄₆₇, TC 87₅₇₂. + '4o1n11w4a,' + // #4084: 5 fonts: HK 91₁₁₈, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 87₅₇₂. + '4o1o6s2y6e,' + // #4085: 4 fonts: HK 91₁₁₈, JP 23₁₅₉, SC 27₄₁₁, TC 87₅₇₂. + '4o1o9r6e,' + // #4086: 5 fonts: HK 91₁₁₈, JP 25₁₆₁, KR 75₃₃₅, SC 31₄₁₅, TC 87₅₇₂. + '4o1q6r3b6a,' + // #4087: 5 fonts: HK 91₁₁₈, JP 25₁₆₁, KR 75₃₃₅, SC 83₄₆₇, TC 87₅₇₂. + '4o1q6r5b4a,' + // #4088: 5 fonts: HK 91₁₁₈, JP 26₁₆₂, KR 76₃₃₆, SC 32₄₁₆, TC 87₅₇₂. + '4o1r6r3b5z,' + // #4089: 5 fonts: HK 91₁₁₈, JP 26₁₆₂, KR 76₃₃₆, SC 81₄₆₅, TC 87₅₇₂. + '4o1r6r4y4c,' + // #4090: 5 fonts: HK 91₁₁₈, JP 26₁₆₂, KR 76₃₃₆, SC 82₄₆₆, TC 87₅₇₂. + '4o1r6r4z4b,' + // #4091: 5 fonts: HK 91₁₁₈, JP 26₁₆₂, KR 76₃₃₆, SC 84₄₆₈, TC 87₅₇₂. + '4o1r6r5b3z,' + // #4092: 5 fonts: HK 91₁₁₈, JP 27₁₆₃, KR 77₃₃₇, SC 34₄₁₈, TC 87₅₇₂. + '4o1s6r3c5x,' + // #4093: 4 fonts: HK 91₁₁₈, JP 27₁₆₃, SC 83₄₆₇, TC 87₅₇₂. + '4o1s11r4a,' + // #4094: 4 fonts: HK 91₁₁₈, JP 28₁₆₄, SC 80₄₆₄, TC 87₅₇₂. + '4o1t11n4d,' + // #4095: 5 fonts: HK 91₁₁₈, JP 29₁₆₅, KR 77₃₃₇, SC 81₄₆₅, TC 87₅₇₂. + '4o1u6p4x4c,' + // #4096: 5 fonts: HK 91₁₁₈, JP 29₁₆₅, KR 78₃₃₈, SC 36₄₂₀, TC 87₅₇₂. + '4o1u6q3d5v,' + // #4097: 5 fonts: HK 91₁₁₈, JP 29₁₆₅, KR 78₃₃₈, SC 82₄₆₆, TC 87₅₇₂. + '4o1u6q4x4b,' + // #4098: 5 fonts: HK 91₁₁₈, JP 30₁₆₆, KR 79₃₃₉, SC 82₄₆₆, TC 87₅₇₂. + '4o1v6q4w4b,' + // #4099: 5 fonts: HK 91₁₁₈, JP 30₁₆₆, KR 79₃₃₉, SC 84₄₆₈, TC 87₅₇₂. + '4o1v6q4y3z,' + // #4100: 5 fonts: HK 91₁₁₈, JP 31₁₆₇, KR 79₃₃₉, SC 39₄₂₃, TC 87₅₇₂. + '4o1w6p3f5s,' + // #4101: 5 fonts: HK 91₁₁₈, JP 31₁₆₇, KR 79₃₃₉, SC 40₄₂₄, TC 87₅₇₂. + '4o1w6p3g5r,' + // #4102: 5 fonts: HK 91₁₁₈, JP 31₁₆₇, KR 79₃₃₉, SC 79₄₆₃, TC 87₅₇₂. + '4o1w6p4t4e,' + // #4103: 5 fonts: HK 91₁₁₈, JP 32₁₆₈, KR 80₃₄₀, SC 83₄₆₇, TC 87₅₇₂. + '4o1x6p4w4a,' + // #4104: 5 fonts: HK 91₁₁₈, JP 32₁₆₈, KR 80₃₄₀, SC 84₄₆₈, TC 87₅₇₂. + '4o1x6p4x3z,' + // #4105: 5 fonts: HK 91₁₁₈, JP 33₁₆₉, KR 81₃₄₁, SC 79₄₆₃, TC 87₅₇₂. + '4o1y6p4r4e,' + // #4106: 5 fonts: HK 91₁₁₈, JP 34₁₇₀, KR 81₃₄₁, SC 82₄₆₆, TC 87₅₇₂. + '4o1z6o4u4b,' + // #4107: 5 fonts: HK 91₁₁₈, JP 34₁₇₀, KR 82₃₄₂, SC 84₄₆₈, TC 87₅₇₂. + '4o1z6p4v3z,' + // #4108: 5 fonts: HK 91₁₁₈, JP 35₁₇₁, KR 82₃₄₂, SC 45₄₂₉, TC 87₅₇₂. + '4o2a6o3i5m,' + // #4109: 5 fonts: HK 91₁₁₈, JP 35₁₇₁, KR 82₃₄₂, SC 80₄₆₄, TC 87₅₇₂. + '4o2a6o4r4d,' + // #4110: 5 fonts: HK 91₁₁₈, JP 35₁₇₁, KR 82₃₄₂, SC 82₄₆₆, TC 87₅₇₂. + '4o2a6o4t4b,' + // #4111: 5 fonts: HK 91₁₁₈, JP 38₁₇₄, KR 84₃₄₄, SC 83₄₆₇, TC 87₅₇₂. + '4o2d6n4s4a,' + // #4112: 4 fonts: HK 91₁₁₈, JP 38₁₇₄, SC 84₄₆₈, TC 87₅₇₂. + '4o2d11h3z,' + // #4113: 5 fonts: HK 91₁₁₈, JP 39₁₇₅, KR 85₃₄₅, SC 51₄₃₅, TC 87₅₇₂. + '4o2e6n3l5g,' + // #4114: 5 fonts: HK 91₁₁₈, JP 39₁₇₅, KR 85₃₄₅, SC 83₄₆₇, TC 87₅₇₂. + '4o2e6n4r4a,' + // #4115: 4 fonts: HK 91₁₁₈, JP 39₁₇₅, SC 84₄₆₈, TC 87₅₇₂. + '4o2e11g3z,' + // #4116: 5 fonts: HK 91₁₁₈, JP 40₁₇₆, KR 86₃₄₆, SC 80₄₆₄, TC 87₅₇₂. + '4o2f6n4n4d,' + // #4117: 5 fonts: HK 91₁₁₈, JP 40₁₇₆, KR 86₃₄₆, SC 83₄₆₇, TC 87₅₇₂. + '4o2f6n4q4a,' + // #4118: 5 fonts: HK 91₁₁₈, JP 41₁₇₇, KR 87₃₄₇, SC 54₄₃₈, TC 87₅₇₂. + '4o2g6n3m5d,' + // #4119: 4 fonts: HK 91₁₁₈, JP 41₁₇₇, SC 93₄₇₇, TC 87₅₇₂. + '4o2g11n3q,' + // #4120: 5 fonts: HK 91₁₁₈, JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉, TC 87₅₇₂. + '4o2h6m3n5c,' + // #4121: 5 fonts: HK 91₁₁₈, JP 43₁₇₉, KR 88₃₄₈, SC 83₄₆₇, TC 87₅₇₂. + '4o2i6m4o4a,' + // #4122: 5 fonts: HK 91₁₁₈, JP 43₁₇₉, KR 88₃₄₈, SC 84₄₆₈, TC 87₅₇₂. + '4o2i6m4p3z,' + // #4123: 4 fonts: HK 91₁₁₈, JP 43₁₇₉, SC 82₄₆₆, TC 87₅₇₂. + '4o2i11a4b,' + // #4124: 5 fonts: HK 91₁₁₈, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 87₅₇₂. + '4o2j6m3o4z,' + // #4125: 5 fonts: HK 91₁₁₈, JP 44₁₈₀, KR 89₃₄₉, SC 81₄₆₅, TC 87₅₇₂. + '4o2j6m4l4c,' + // #4126: 5 fonts: HK 91₁₁₈, JP 45₁₈₁, KR 89₃₄₉, SC 84₄₆₈, TC 87₅₇₂. + '4o2k6l4o3z,' + // #4127: 5 fonts: HK 91₁₁₈, JP 45₁₈₁, KR 90₃₅₀, SC 60₄₄₄, TC 87₅₇₂. + '4o2k6m3p4x,' + // #4128: 4 fonts: HK 91₁₁₈, JP 45₁₈₁, SC 60₄₄₄, TC 87₅₇₂. + '4o2k10c4x,' + // #4129: 4 fonts: HK 91₁₁₈, JP 45₁₈₁, SC 82₄₆₆, TC 87₅₇₂. + '4o2k10y4b,' + // #4130: 5 fonts: HK 91₁₁₈, JP 46₁₈₂, KR 90₃₅₀, SC 62₄₄₆, TC 87₅₇₂. + '4o2l6l3r4v,' + // #4131: 4 fonts: HK 91₁₁₈, JP 46₁₈₂, SC 61₄₄₅, TC 87₅₇₂. + '4o2l10c4w,' + // #4132: 4 fonts: HK 91₁₁₈, JP 46₁₈₂, SC 78₄₆₂, TC 87₅₇₂. + '4o2l10t4f,' + // #4133: 4 fonts: HK 91₁₁₈, JP 46₁₈₂, SC 81₄₆₅, TC 87₅₇₂. + '4o2l10w4c,' + // #4134: 5 fonts: HK 91₁₁₈, JP 47₁₈₃, KR 90₃₅₀, SC 63₄₄₇, TC 87₅₇₂. + '4o2m6k3s4u,' + // #4135: 5 fonts: HK 91₁₁₈, JP 47₁₈₃, KR 91₃₅₁, SC 84₄₆₈, TC 87₅₇₂. + '4o2m6l4m3z,' + // #4136: 5 fonts: HK 91₁₁₈, JP 48₁₈₄, KR 92₃₅₂, SC 81₄₆₅, TC 87₅₇₂. + '4o2n6l4i4c,' + // #4137: 4 fonts: HK 91₁₁₈, JP 50₁₈₆, SC 94₄₇₈, TC 87₅₇₂. + '4o2p11f3p,' + // #4138: 4 fonts: HK 91₁₁₈, JP 60₁₉₆, SC 83₄₆₇, TC 87₅₇₂. + '4o2z10k4a,' + // #4139: 5 fonts: HK 91₁₁₈, JP 61₁₉₇, KR 71₃₃₁, SC 81₄₆₅, TC 87₅₇₂. + '4o3a5d5d4c,' + // #4140: 5 fonts: HK 91₁₁₈, JP 61₁₉₇, KR 78₃₃₈, SC 85₄₆₉, TC 87₅₇₂. + '4o3a5k5a3y,' + // #4141: 5 fonts: HK 91₁₁₈, JP 63₁₉₉, KR 65₃₂₅, SC 82₄₆₆, TC 87₅₇₂. + '4o3c4v5k4b,' + // #4142: 5 fonts: HK 91₁₁₈, JP 63₁₉₉, KR 67₃₂₇, SC 82₄₆₆, TC 87₅₇₂. + '4o3c4x5i4b,' + // #4143: 4 fonts: HK 91₁₁₈, JP 63₁₉₉, SC 84₄₆₈, TC 87₅₇₂. + '4o3c10i3z,' + // #4144: 5 fonts: HK 91₁₁₈, JP 64₂₀₀, KR 68₃₂₈, SC 84₄₆₈, TC 87₅₇₂. + '4o3d4x5j3z,' + // #4145: 5 fonts: HK 91₁₁₈, JP 64₂₀₀, KR 72₃₃₂, SC 83₄₆₇, TC 87₅₇₂. + '4o3d5b5e4a,' + // #4146: 5 fonts: HK 91₁₁₈, JP 64₂₀₀, KR 82₃₄₂, SC 84₄₆₈, TC 87₅₇₂. + '4o3d5l4v3z,' + // #4147: 5 fonts: HK 91₁₁₈, JP 64₂₀₀, KR 85₃₄₅, SC 51₄₃₅, TC 87₅₇₂. + '4o3d5o3l5g,' + // #4148: 5 fonts: HK 91₁₁₈, JP 65₂₀₁, KR 69₃₂₉, SC 84₄₆₈, TC 87₅₇₂. + '4o3e4x5i3z,' + // #4149: 5 fonts: HK 91₁₁₈, JP 65₂₀₁, KR 70₃₃₀, SC 18₄₀₂, TC 87₅₇₂. + '4o3e4y2t6n,' + // #4150: 5 fonts: HK 91₁₁₈, JP 65₂₀₁, KR 75₃₃₅, SC 29₄₁₃, TC 87₅₇₂. + '4o3e5d2z6c,' + // #4151: 5 fonts: HK 91₁₁₈, JP 66₂₀₂, KR 79₃₃₉, SC 83₄₆₇, TC 87₅₇₂. + '4o3f5g4x4a,' + // #4152: 5 fonts: HK 91₁₁₈, JP 66₂₀₂, KR 93₃₅₃, SC 82₄₆₆, TC 87₅₇₂. + '4o3f5u4i4b,' + // #4153: 5 fonts: HK 91₁₁₈, JP 67₂₀₃, KR 77₃₃₇, SC 83₄₆₇, TC 87₅₇₂. + '4o3g5d4z4a,' + // #4154: 5 fonts: HK 91₁₁₈, JP 67₂₀₃, KR 85₃₄₅, SC 51₄₃₅, TC 87₅₇₂. + '4o3g5l3l5g,' + // #4155: 5 fonts: HK 91₁₁₈, JP 68₂₀₄, KR 88₃₄₈, SC 84₄₆₈, TC 87₅₇₂. + '4o3h5n4p3z,' + // #4156: 5 fonts: HK 91₁₁₈, JP 69₂₀₅, KR 66₃₂₆, SC 7₃₉₁, TC 87₅₇₂. + '4o3i4q2m6y,' + // #4157: 5 fonts: HK 91₁₁₈, JP 69₂₀₅, KR 73₃₃₃, SC 25₄₀₉, TC 87₅₇₂. + '4o3i4x2x6g,' + // #4158: 5 fonts: HK 91₁₁₈, JP 69₂₀₅, KR 74₃₃₄, SC 27₄₁₁, TC 87₅₇₂. + '4o3i4y2y6e,' + // #4159: 5 fonts: HK 91₁₁₈, JP 69₂₀₅, KR 81₃₄₁, SC 43₄₂₇, TC 87₅₇₂. + '4o3i5f3h5o,' + // #4160: 5 fonts: HK 91₁₁₈, JP 70₂₀₆, KR 73₃₃₃, SC 83₄₆₇, TC 87₅₇₂. + '4o3j4w5d4a,' + // #4161: 5 fonts: HK 91₁₁₈, JP 70₂₀₆, KR 76₃₃₆, SC 87₄₇₁, TC 87₅₇₂. + '4o3j4z5e3w,' + // #4162: 5 fonts: HK 91₁₁₈, JP 71₂₀₇, KR 68₃₂₈, SC 13₃₉₇, TC 87₅₇₂. + '4o3k4q2q6s,' + // #4163: 5 fonts: HK 91₁₁₈, JP 71₂₀₇, KR 80₃₄₀, SC 83₄₆₇, TC 87₅₇₂. + '4o3k5c4w4a,' + // #4164: 5 fonts: HK 91₁₁₈, JP 71₂₀₇, KR 83₃₄₃, SC 85₄₆₉, TC 87₅₇₂. + '4o3k5f4v3y,' + // #4165: 5 fonts: HK 91₁₁₈, JP 72₂₀₈, KR 89₃₄₉, SC 82₄₆₆, TC 87₅₇₂. + '4o3l5k4m4b,' + // #4166: 5 fonts: HK 91₁₁₈, JP 72₂₀₈, KR 93₃₅₃, SC 93₄₇₇, TC 87₅₇₂. + '4o3l5o4t3q,' + // #4167: 5 fonts: HK 91₁₁₈, JP 73₂₀₉, KR 70₃₃₀, SC 18₄₀₂, TC 87₅₇₂. + '4o3m4q2t6n,' + // #4168: 5 fonts: HK 91₁₁₈, JP 73₂₀₉, KR 74₃₃₄, SC 82₄₆₆, TC 87₅₇₂. + '4o3m4u5b4b,' + // #4169: 5 fonts: HK 91₁₁₈, JP 73₂₀₉, KR 78₃₃₈, SC 78₄₆₂, TC 87₅₇₂. + '4o3m4y4t4f,' + // #4170: 5 fonts: HK 91₁₁₈, JP 73₂₀₉, KR 80₃₄₀, SC 41₄₂₅, TC 87₅₇₂. + '4o3m5a3g5q,' + // #4171: 5 fonts: HK 91₁₁₈, JP 73₂₀₉, KR 84₃₄₄, SC 49₄₃₃, TC 87₅₇₂. + '4o3m5e3k5i,' + // #4172: 5 fonts: HK 91₁₁₈, JP 73₂₀₉, KR 89₃₄₉, SC 78₄₆₂, TC 87₅₇₂. + '4o3m5j4i4f,' + // #4173: 5 fonts: HK 91₁₁₈, JP 74₂₁₀, KR 72₃₃₂, SC 85₄₆₉, TC 87₅₇₂. + '4o3n4r5g3y,' + // #4174: 5 fonts: HK 91₁₁₈, JP 74₂₁₀, KR 85₃₄₅, SC 93₄₇₇, TC 87₅₇₂. + '4o3n5e5b3q,' + // #4175: 5 fonts: HK 91₁₁₈, JP 74₂₁₀, KR 89₃₄₉, SC 84₄₆₈, TC 87₅₇₂. + '4o3n5i4o3z,' + // #4176: 5 fonts: HK 91₁₁₈, JP 75₂₁₁, KR 74₃₃₄, SC 26₄₁₀, TC 87₅₇₂. + '4o3o4s2x6f,' + // #4177: 5 fonts: HK 91₁₁₈, JP 75₂₁₁, KR 74₃₃₄, SC 27₄₁₁, TC 87₅₇₂. + '4o3o4s2y6e,' + // #4178: 5 fonts: HK 91₁₁₈, JP 75₂₁₁, KR 88₃₄₈, SC 55₄₃₉, TC 87₅₇₂. + '4o3o5g3m5c,' + // #4179: 5 fonts: HK 91₁₁₈, JP 76₂₁₂, KR 68₃₂₈, SC 81₄₆₅, TC 87₅₇₂. + '4o3p4l5g4c,' + // #4180: 5 fonts: HK 91₁₁₈, JP 76₂₁₂, KR 73₃₃₃, SC 77₄₆₁, TC 87₅₇₂. + '4o3p4q4x4g,' + // #4181: 4 fonts: HK 91₁₁₈, JP 76₂₁₂, SC 88₄₇₂, TC 87₅₇₂. + '4o3p9z3v,' + // #4182: 5 fonts: HK 91₁₁₈, JP 77₂₁₃, KR 74₃₃₄, SC 28₄₁₂, TC 87₅₇₂. + '4o3q4q2z6d,' + // #4183: 5 fonts: HK 91₁₁₈, JP 77₂₁₃, KR 80₃₄₀, SC 41₄₂₅, TC 87₅₇₂. + '4o3q4w3g5q,' + // #4184: 5 fonts: HK 91₁₁₈, JP 78₂₁₄, KR 67₃₂₇, SC 85₄₆₉, TC 87₅₇₂. + '4o3r4i5l3y,' + // #4185: 5 fonts: HK 91₁₁₈, JP 78₂₁₄, KR 76₃₃₆, SC 32₄₁₆, TC 87₅₇₂. + '4o3r4r3b5z,' + // #4186: 5 fonts: HK 91₁₁₈, JP 78₂₁₄, KR 82₃₄₂, SC 82₄₆₆, TC 87₅₇₂. + '4o3r4x4t4b,' + // #4187: 5 fonts: HK 91₁₁₈, JP 79₂₁₅, KR 73₃₃₃, SC 81₄₆₅, TC 87₅₇₂. + '4o3s4n5b4c,' + // #4188: 5 fonts: HK 91₁₁₈, JP 79₂₁₅, KR 73₃₃₃, SC 82₄₆₆, TC 87₅₇₂. + '4o3s4n5c4b,' + // #4189: 5 fonts: HK 91₁₁₈, JP 79₂₁₅, KR 76₃₃₆, SC 33₄₁₇, TC 87₅₇₂. + '4o3s4q3c5y,' + // #4190: 5 fonts: HK 91₁₁₈, JP 81₂₁₇, KR 72₃₃₂, SC 81₄₆₅, TC 87₅₇₂. + '4o3u4k5c4c,' + // #4191: 5 fonts: HK 91₁₁₈, JP 81₂₁₇, KR 84₃₄₄, SC 48₄₃₂, TC 87₅₇₂. + '4o3u4w3j5j,' + // #4192: 5 fonts: HK 91₁₁₈, JP 81₂₁₇, KR 87₃₄₇, SC 85₄₆₉, TC 87₅₇₂. + '4o3u4z4r3y,' + // #4193: 5 fonts: HK 91₁₁₈, JP 81₂₁₇, KR 91₃₅₁, SC 83₄₆₇, TC 87₅₇₂. + '4o3u5d4l4a,' + // #4194: 5 fonts: HK 91₁₁₈, JP 81₂₁₇, KR 91₃₅₁, SC 84₄₆₈, TC 87₅₇₂. + '4o3u5d4m3z,' + // #4195: 5 fonts: HK 91₁₁₈, JP 82₂₁₈, KR 88₃₄₈, SC 84₄₆₈, TC 87₅₇₂. + '4o3v4z4p3z,' + // #4196: 5 fonts: HK 91₁₁₈, JP 82₂₁₈, KR 91₃₅₁, SC 83₄₆₇, TC 87₅₇₂. + '4o3v5c4l4a,' + // #4197: 5 fonts: HK 91₁₁₈, JP 83₂₁₉, KR 72₃₃₂, SC 22₄₀₆, TC 87₅₇₂. + '4o3w4i2v6j,' + // #4198: 5 fonts: HK 91₁₁₈, JP 83₂₁₉, KR 90₃₅₀, SC 83₄₆₇, TC 87₅₇₂. + '4o3w5a4m4a,' + // #4199: 5 fonts: HK 91₁₁₈, JP 84₂₂₀, KR 88₃₄₈, SC 57₄₄₁, TC 87₅₇₂. + '4o3x4x3o5a,' + // #4200: 5 fonts: HK 91₁₁₈, JP 84₂₂₀, KR 91₃₅₁, SC 86₄₇₀, TC 87₅₇₂. + '4o3x5a4o3x,' + // #4201: 5 fonts: HK 91₁₁₈, JP 85₂₂₁, KR 73₃₃₃, SC 82₄₆₆, TC 87₅₇₂. + '4o3y4h5c4b,' + // #4202: 5 fonts: HK 91₁₁₈, JP 85₂₂₁, KR 80₃₄₀, SC 83₄₆₇, TC 87₅₇₂. + '4o3y4o4w4a,' + // #4203: 5 fonts: HK 91₁₁₈, JP 85₂₂₁, KR 91₃₅₁, SC 83₄₆₇, TC 87₅₇₂. + '4o3y4z4l4a,' + // #4204: 5 fonts: HK 91₁₁₈, JP 86₂₂₂, KR 80₃₄₀, SC 83₄₆₇, TC 87₅₇₂. + '4o3z4n4w4a,' + // #4205: 5 fonts: HK 91₁₁₈, JP 86₂₂₂, KR 84₃₄₄, SC 84₄₆₈, TC 87₅₇₂. + '4o3z4r4t3z,' + // #4206: 5 fonts: HK 91₁₁₈, JP 87₂₂₃, KR 72₃₃₂, SC 81₄₆₅, TC 87₅₇₂. + '4o4a4e5c4c,' + // #4207: 5 fonts: HK 91₁₁₈, JP 89₂₂₅, KR 77₃₃₇, SC 84₄₆₈, TC 87₅₇₂. + '4o4c4h5a3z,' + // #4208: 5 fonts: HK 91₁₁₈, JP 90₂₂₆, KR 82₃₄₂, SC 83₄₆₇, TC 87₅₇₂. + '4o4d4l4u4a,' + // #4209: 5 fonts: HK 91₁₁₈, JP 90₂₂₆, KR 93₃₅₃, SC 84₄₆₈, TC 87₅₇₂. + '4o4d4w4k3z,' + // #4210: 5 fonts: HK 91₁₁₈, JP 91₂₂₇, KR 76₃₃₆, SC 79₄₆₃, TC 87₅₇₂. + '4o4e4e4w4e,' + // #4211: 7 fonts: HK 91₁₁₈, JP 94₂₃₀, KR 103₃₆₃, SC 82₄₆₆, TC 87₅₇₂, Math₆₅₅, Symbols₇₀₂. + '4o4h5c3y4b3e1u,' + // #4212: 5 fonts: HK 91₁₁₈, JP 95₂₃₁, KR 84₃₄₄, SC 81₄₆₅, TC 87₅₇₂. + '4o4i4i4q4c,' + // #4213: 5 fonts: HK 91₁₁₈, JP 101₂₃₇, KR 83₃₄₃, SC 47₄₃₁, TC 87₅₇₂. + '4o4o4b3j5k,' + // #4214: 5 fonts: HK 91₁₁₈, JP 102₂₃₈, KR 0₂₆₀, SC 81₄₆₅, TC 87₅₇₂. + '4o4pv7w4c,' + // #4215: 5 fonts: HK 91₁₁₈, JP 102₂₃₈, KR 91₃₅₁, SC 82₄₆₆, TC 87₅₇₂. + '4o4p4i4k4b,' + // #4216: 4 fonts: HK 91₁₁₈, JP 102₂₃₈, SC 95₄₇₉, TC 87₅₇₂. + '4o4p9g3o,' + // #4217: 5 fonts: HK 91₁₁₈, JP 105₂₄₁, KR 85₃₄₅, SC 83₄₆₇, TC 87₅₇₂. + '4o4s3z4r4a,' + // #4218: 5 fonts: HK 91₁₁₈, JP 109₂₄₅, KR 0₂₆₀, SC 77₄₆₁, TC 87₅₇₂. + '4o4wo7s4g,' + // #4219: 5 fonts: HK 91₁₁₈, JP 110₂₄₆, KR 77₃₃₇, SC 35₄₁₉, TC 87₅₇₂. + '4o4x3m3d5w,' + // #4220: 4 fonts: HK 91₁₁₈, JP 110₂₄₆, SC 94₄₇₈, TC 87₅₇₂. + '4o4x8x3p,' + // #4221: 3 fonts: HK 91₁₁₈, SC 29₄₁₃, TC 87₅₇₂. + '4o11i6c,' + // #4222: 3 fonts: HK 91₁₁₈, SC 33₄₁₇, TC 87₅₇₂. + '4o11m5y,' + // #4223: 3 fonts: HK 91₁₁₈, SC 44₄₂₈, TC 87₅₇₂. + '4o11x5n,' + // #4224: 3 fonts: HK 91₁₁₈, SC 60₄₄₄, TC 87₅₇₂. + '4o12n4x,' + // #4225: 3 fonts: HK 91₁₁₈, SC 61₄₄₅, TC 87₅₇₂. + '4o12o4w,' + // #4226: 3 fonts: HK 91₁₁₈, SC 62₄₄₆, TC 87₅₇₂. + '4o12p4v,' + // #4227: 3 fonts: HK 91₁₁₈, SC 82₄₆₆, TC 87₅₇₂. + '4o13j4b,' + // #4228: 4 fonts: HK 92₁₁₉, JP 3₁₃₉, SC 2₃₈₆, TC 88₅₇₃. + '4pt9m7e,' + // #4229: 5 fonts: HK 92₁₁₉, JP 6₁₄₂, KR 65₃₂₅, SC 4₃₈₈, TC 88₅₇₃. + '4pw7a2k7c,' + // #4230: 5 fonts: HK 92₁₁₉, JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 88₅₇₃. + '4py6z2m6z,' + // #4231: 4 fonts: HK 92₁₁₉, JP 8₁₄₄, SC 7₃₉₁, TC 88₅₇₃. + '4py9m6z,' + // #4232: 5 fonts: HK 92₁₁₉, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 88₅₇₃. + '4pz6y2n6y,' + // #4233: 5 fonts: HK 92₁₁₉, JP 10₁₄₆, KR 67₃₂₇, SC 9₃₉₃, TC 88₅₇₃. + '4p1a6y2n6x,' + // #4234: 5 fonts: HK 92₁₁₉, JP 10₁₄₆, KR 67₃₂₇, SC 85₄₆₉, TC 88₅₇₃. + '4p1a6y5l3z,' + // #4235: 4 fonts: HK 92₁₁₉, JP 10₁₄₆, SC 86₄₇₀, TC 88₅₇₃. + '4p1a12l3y,' + // #4236: 5 fonts: HK 92₁₁₉, JP 11₁₄₇, KR 67₃₂₇, SC 12₃₉₆, TC 88₅₇₃. + '4p1b6x2q6u,' + // #4237: 4 fonts: HK 92₁₁₉, JP 11₁₄₇, SC 12₃₉₆, TC 88₅₇₃. + '4p1b9o6u,' + // #4238: 5 fonts: HK 92₁₁₉, JP 12₁₄₈, KR 67₃₂₇, SC 13₃₉₇, TC 88₅₇₃. + '4p1c6w2r6t,' + // #4239: 5 fonts: HK 92₁₁₉, JP 12₁₄₈, KR 68₃₂₈, SC 13₃₉₇, TC 88₅₇₃. + '4p1c6x2q6t,' + // #4240: 4 fonts: HK 92₁₁₉, JP 12₁₄₈, SC 13₃₉₇, TC 88₅₇₃. + '4p1c9o6t,' + // #4241: 4 fonts: HK 92₁₁₉, JP 14₁₅₀, SC 84₄₆₈, TC 88₅₇₃. + '4p1e12f4a,' + // #4242: 5 fonts: HK 92₁₁₉, JP 15₁₅₁, KR 69₃₂₉, SC 16₄₀₀, TC 88₅₇₃. + '4p1f6v2s6q,' + // #4243: 5 fonts: HK 92₁₁₉, JP 15₁₅₁, KR 69₃₂₉, SC 85₄₆₉, TC 88₅₇₃. + '4p1f6v5j3z,' + // #4244: 4 fonts: HK 92₁₁₉, JP 15₁₅₁, SC 17₄₀₁, TC 88₅₇₃. + '4p1f9p6p,' + // #4245: 4 fonts: HK 92₁₁₉, JP 15₁₅₁, SC 79₄₆₃, TC 88₅₇₃. + '4p1f11z4f,' + // #4246: 5 fonts: HK 92₁₁₉, JP 16₁₅₂, KR 70₃₃₀, SC 18₄₀₂, TC 88₅₇₃. + '4p1g6v2t6o,' + // #4247: 5 fonts: HK 92₁₁₉, JP 18₁₅₄, KR 71₃₃₁, SC 21₄₀₅, TC 88₅₇₃. + '4p1i6u2v6l,' + // #4248: 5 fonts: HK 92₁₁₉, JP 19₁₅₅, KR 71₃₃₁, SC 81₄₆₅, TC 88₅₇₃. + '4p1j6t5d4d,' + // #4249: 5 fonts: HK 92₁₁₉, JP 20₁₅₆, KR 72₃₃₂, SC 22₄₀₆, TC 88₅₇₃. + '4p1k6t2v6k,' + // #4250: 5 fonts: HK 92₁₁₉, JP 20₁₅₆, KR 72₃₃₂, SC 77₄₆₁, TC 88₅₇₃. + '4p1k6t4y4h,' + // #4251: 4 fonts: HK 92₁₁₉, JP 20₁₅₆, SC 23₄₀₇, TC 88₅₇₃. + '4p1k9q6j,' + // #4252: 4 fonts: HK 92₁₁₉, JP 20₁₅₆, SC 84₄₆₈, TC 88₅₇₃. + '4p1k11z4a,' + // #4253: 5 fonts: HK 92₁₁₉, JP 21₁₅₇, KR 73₃₃₃, SC 83₄₆₇, TC 88₅₇₃. + '4p1l6t5d4b,' + // #4254: 5 fonts: HK 92₁₁₉, JP 21₁₅₇, KR 73₃₃₃, SC 84₄₆₈, TC 88₅₇₃. + '4p1l6t5e4a,' + // #4255: 5 fonts: HK 92₁₁₉, JP 21₁₅₇, KR 73₃₃₃, SC 86₄₇₀, TC 88₅₇₃. + '4p1l6t5g3y,' + // #4256: 5 fonts: HK 92₁₁₉, JP 22₁₅₈, KR 74₃₃₄, SC 83₄₆₇, TC 88₅₇₃. + '4p1m6t5c4b,' + // #4257: 5 fonts: HK 92₁₁₉, JP 23₁₅₉, KR 74₃₃₄, SC 84₄₆₈, TC 88₅₇₃. + '4p1n6s5d4a,' + // #4258: 5 fonts: HK 92₁₁₉, JP 24₁₆₀, KR 75₃₃₅, SC 85₄₆₉, TC 88₅₇₃. + '4p1o6s5d3z,' + // #4259: 4 fonts: HK 92₁₁₉, JP 24₁₆₀, SC 29₄₁₃, TC 88₅₇₃. + '4p1o9s6d,' + // #4260: 4 fonts: HK 92₁₁₉, JP 25₁₆₁, SC 80₄₆₄, TC 88₅₇₃. + '4p1p11q4e,' + // #4261: 5 fonts: HK 92₁₁₉, JP 27₁₆₃, KR 77₃₃₇, SC 34₄₁₈, TC 88₅₇₃. + '4p1r6r3c5y,' + // #4262: 5 fonts: HK 92₁₁₉, JP 27₁₆₃, KR 77₃₃₇, SC 82₄₆₆, TC 88₅₇₃. + '4p1r6r4y4c,' + // #4263: 4 fonts: HK 92₁₁₉, JP 28₁₆₄, SC 34₄₁₈, TC 88₅₇₃. + '4p1s9t5y,' + // #4264: 4 fonts: HK 92₁₁₉, JP 28₁₆₄, SC 82₄₆₆, TC 88₅₇₃. + '4p1s11p4c,' + // #4265: 5 fonts: HK 92₁₁₉, JP 29₁₆₅, KR 77₃₃₇, SC 86₄₇₀, TC 88₅₇₃. + '4p1t6p5c3y,' + // #4266: 5 fonts: HK 92₁₁₉, JP 29₁₆₅, KR 78₃₃₈, SC 36₄₂₀, TC 88₅₇₃. + '4p1t6q3d5w,' + // #4267: 5 fonts: HK 92₁₁₉, JP 29₁₆₅, KR 78₃₃₈, SC 80₄₆₄, TC 88₅₇₃. + '4p1t6q4v4e,' + // #4268: 5 fonts: HK 92₁₁₉, JP 30₁₆₆, KR 78₃₃₈, SC 79₄₆₃, TC 88₅₇₃. + '4p1u6p4u4f,' + // #4269: 5 fonts: HK 92₁₁₉, JP 30₁₆₆, KR 79₃₃₉, SC 38₄₂₂, TC 88₅₇₃. + '4p1u6q3e5u,' + // #4270: 4 fonts: HK 92₁₁₉, JP 30₁₆₆, SC 38₄₂₂, TC 88₅₇₃. + '4p1u9v5u,' + // #4271: 5 fonts: HK 92₁₁₉, JP 31₁₆₇, KR 79₃₃₉, SC 39₄₂₃, TC 88₅₇₃. + '4p1v6p3f5t,' + // #4272: 5 fonts: HK 92₁₁₉, JP 31₁₆₇, KR 79₃₃₉, SC 80₄₆₄, TC 88₅₇₃. + '4p1v6p4u4e,' + // #4273: 5 fonts: HK 92₁₁₉, JP 31₁₆₇, KR 79₃₃₉, SC 86₄₇₀, TC 88₅₇₃. + '4p1v6p5a3y,' + // #4274: 5 fonts: HK 92₁₁₉, JP 31₁₆₇, KR 79₃₃₉, SC 88₄₇₂, TC 88₅₇₃. + '4p1v6p5c3w,' + // #4275: 5 fonts: HK 92₁₁₉, JP 32₁₆₈, KR 80₃₄₀, SC 82₄₆₆, TC 88₅₇₃. + '4p1w6p4v4c,' + // #4276: 5 fonts: HK 92₁₁₉, JP 33₁₆₉, KR 81₃₄₁, SC 81₄₆₅, TC 88₅₇₃. + '4p1x6p4t4d,' + // #4277: 5 fonts: HK 92₁₁₉, JP 33₁₆₉, KR 81₃₄₁, SC 85₄₆₉, TC 88₅₇₃. + '4p1x6p4x3z,' + // #4278: 5 fonts: HK 92₁₁₉, JP 33₁₆₉, KR 81₃₄₁, SC 90₄₇₄, TC 88₅₇₃. + '4p1x6p5c3u,' + // #4279: 4 fonts: HK 92₁₁₉, JP 34₁₇₀, SC 81₄₆₅, TC 88₅₇₃. + '4p1y11i4d,' + // #4280: 5 fonts: HK 92₁₁₉, JP 36₁₇₂, KR 83₃₄₃, SC 47₄₃₁, TC 88₅₇₃. + '4p2a6o3j5l,' + // #4281: 5 fonts: HK 92₁₁₉, JP 36₁₇₂, KR 83₃₄₃, SC 89₄₇₃, TC 88₅₇₃. + '4p2a6o4z3v,' + // #4282: 4 fonts: HK 92₁₁₉, JP 37₁₇₃, SC 48₄₃₂, TC 88₅₇₃. + '4p2b9y5k,' + // #4283: 4 fonts: HK 92₁₁₉, JP 37₁₇₃, SC 85₄₆₉, TC 88₅₇₃. + '4p2b11j3z,' + // #4284: 5 fonts: HK 92₁₁₉, JP 38₁₇₄, KR 84₃₄₄, SC 84₄₆₈, TC 88₅₇₃. + '4p2c6n4t4a,' + // #4285: 5 fonts: HK 92₁₁₉, JP 38₁₇₄, KR 84₃₄₄, SC 87₄₇₁, TC 88₅₇₃. + '4p2c6n4w3x,' + // #4286: 4 fonts: HK 92₁₁₉, JP 38₁₇₄, SC 49₄₃₃, TC 88₅₇₃. + '4p2c9y5j,' + // #4287: 4 fonts: HK 92₁₁₉, JP 38₁₇₄, SC 50₄₃₄, TC 88₅₇₃. + '4p2c9z5i,' + // #4288: 4 fonts: HK 92₁₁₉, JP 38₁₇₄, SC 83₄₆₇, TC 88₅₇₃. + '4p2c11g4b,' + // #4289: 4 fonts: HK 92₁₁₉, JP 38₁₇₄, SC 84₄₆₈, TC 88₅₇₃. + '4p2c11h4a,' + // #4290: 5 fonts: HK 92₁₁₉, JP 39₁₇₅, KR 85₃₄₅, SC 83₄₆₇, TC 88₅₇₃. + '4p2d6n4r4b,' + // #4291: 4 fonts: HK 92₁₁₉, JP 39₁₇₅, SC 83₄₆₇, TC 88₅₇₃. + '4p2d11f4b,' + // #4292: 4 fonts: HK 92₁₁₉, JP 39₁₇₅, SC 88₄₇₂, TC 88₅₇₃. + '4p2d11k3w,' + // #4293: 5 fonts: HK 92₁₁₉, JP 40₁₇₆, KR 86₃₄₆, SC 77₄₆₁, TC 88₅₇₃. + '4p2e6n4k4h,' + // #4294: 5 fonts: HK 92₁₁₉, JP 41₁₇₇, KR 86₃₄₆, SC 53₄₃₇, TC 88₅₇₃. + '4p2f6m3m5f,' + // #4295: 5 fonts: HK 92₁₁₉, JP 41₁₇₇, KR 87₃₄₇, SC 80₄₆₄, TC 88₅₇₃. + '4p2f6n4m4e,' + // #4296: 5 fonts: HK 92₁₁₉, JP 41₁₇₇, KR 87₃₄₇, SC 83₄₆₇, TC 88₅₇₃. + '4p2f6n4p4b,' + // #4297: 4 fonts: HK 92₁₁₉, JP 41₁₇₇, SC 85₄₆₉, TC 88₅₇₃. + '4p2f11f3z,' + // #4298: 5 fonts: HK 92₁₁₉, JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉, TC 88₅₇₃. + '4p2g6m3n5d,' + // #4299: 4 fonts: HK 92₁₁₉, JP 42₁₇₈, SC 84₄₆₈, TC 88₅₇₃. + '4p2g11d4a,' + // #4300: 5 fonts: HK 92₁₁₉, JP 43₁₇₉, KR 88₃₄₈, SC 83₄₆₇, TC 88₅₇₃. + '4p2h6m4o4b,' + // #4301: 5 fonts: HK 92₁₁₉, JP 44₁₈₀, KR 89₃₄₉, SC 83₄₆₇, TC 88₅₇₃. + '4p2i6m4n4b,' + // #4302: 5 fonts: HK 92₁₁₉, JP 44₁₈₀, KR 89₃₄₉, SC 85₄₆₉, TC 88₅₇₃. + '4p2i6m4p3z,' + // #4303: 4 fonts: HK 92₁₁₉, JP 44₁₈₀, SC 58₄₄₂, TC 88₅₇₃. + '4p2i10b5a,' + // #4304: 4 fonts: HK 92₁₁₉, JP 45₁₈₁, SC 89₄₇₃, TC 88₅₇₃. + '4p2j11f3v,' + // #4305: 5 fonts: HK 92₁₁₉, JP 46₁₈₂, KR 90₃₅₀, SC 84₄₆₈, TC 88₅₇₃. + '4p2k6l4n4a,' + // #4306: 4 fonts: HK 92₁₁₉, JP 46₁₈₂, SC 60₄₄₄, TC 88₅₇₃. + '4p2k10b4y,' + // #4307: 4 fonts: HK 92₁₁₉, JP 46₁₈₂, SC 62₄₄₆, TC 88₅₇₃. + '4p2k10d4w,' + // #4308: 4 fonts: HK 92₁₁₉, JP 46₁₈₂, SC 82₄₆₆, TC 88₅₇₃. + '4p2k10x4c,' + // #4309: 5 fonts: HK 92₁₁₉, JP 47₁₈₃, KR 90₃₅₀, SC 79₄₆₃, TC 88₅₇₃. + '4p2l6k4i4f,' + // #4310: 5 fonts: HK 92₁₁₉, JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇, TC 88₅₇₃. + '4p2l6l3r4v,' + // #4311: 4 fonts: HK 92₁₁₉, JP 47₁₈₃, SC 82₄₆₆, TC 88₅₇₃. + '4p2l10w4c,' + // #4312: 4 fonts: HK 92₁₁₉, JP 47₁₈₃, SC 83₄₆₇, TC 88₅₇₃. + '4p2l10x4b,' + // #4313: 5 fonts: HK 92₁₁₉, JP 48₁₈₄, KR 92₃₅₂, SC 64₄₄₈, TC 88₅₇₃. + '4p2m6l3r4u,' + // #4314: 5 fonts: HK 92₁₁₉, JP 49₁₈₅, KR 92₃₅₂, SC 83₄₆₇, TC 88₅₇₃. + '4p2n6k4k4b,' + // #4315: 5 fonts: HK 92₁₁₉, JP 50₁₈₆, KR 93₃₅₃, SC 81₄₆₅, TC 88₅₇₃. + '4p2o6k4h4d,' + // #4316: 5 fonts: HK 92₁₁₉, JP 50₁₈₆, KR 93₃₅₃, SC 82₄₆₆, TC 88₅₇₃. + '4p2o6k4i4c,' + // #4317: 5 fonts: HK 92₁₁₉, JP 50₁₈₆, KR 93₃₅₃, SC 84₄₆₈, TC 88₅₇₃. + '4p2o6k4k4a,' + // #4318: 4 fonts: HK 92₁₁₉, JP 50₁₈₆, SC 95₄₇₉, TC 88₅₇₃. + '4p2o11g3p,' + // #4319: 5 fonts: HK 92₁₁₉, JP 61₁₉₇, KR 91₃₅₁, SC 84₄₆₈, TC 88₅₇₃. + '4p2z5x4m4a,' + // #4320: 5 fonts: HK 92₁₁₉, JP 62₁₉₈, KR 66₃₂₆, SC 7₃₉₁, TC 88₅₇₃. + '4p3a4x2m6z,' + // #4321: 5 fonts: HK 92₁₁₉, JP 62₁₉₈, KR 72₃₃₂, SC 83₄₆₇, TC 88₅₇₃. + '4p3a5d5e4b,' + // #4322: 5 fonts: HK 92₁₁₉, JP 64₂₀₀, KR 69₃₂₉, SC 17₄₀₁, TC 88₅₇₃. + '4p3c4y2t6p,' + // #4323: 5 fonts: HK 92₁₁₉, JP 65₂₀₁, KR 66₃₂₆, SC 8₃₉₂, TC 88₅₇₃. + '4p3d4u2n6y,' + // #4324: 5 fonts: HK 92₁₁₉, JP 66₂₀₂, KR 76₃₃₆, SC 85₄₆₉, TC 88₅₇₃. + '4p3e5d5c3z,' + // #4325: 5 fonts: HK 92₁₁₉, JP 66₂₀₂, KR 82₃₄₂, SC 83₄₆₇, TC 88₅₇₃. + '4p3e5j4u4b,' + // #4326: 5 fonts: HK 92₁₁₉, JP 67₂₀₃, KR 81₃₄₁, SC 84₄₆₈, TC 88₅₇₃. + '4p3f5h4w4a,' + // #4327: 5 fonts: HK 92₁₁₉, JP 68₂₀₄, KR 70₃₃₀, SC 18₄₀₂, TC 88₅₇₃. + '4p3g4v2t6o,' + // #4328: 5 fonts: HK 92₁₁₉, JP 68₂₀₄, KR 89₃₄₉, SC 58₄₄₂, TC 88₅₇₃. + '4p3g5o3o5a,' + // #4329: 5 fonts: HK 92₁₁₉, JP 69₂₀₅, KR 79₃₃₉, SC 82₄₆₆, TC 88₅₇₃. + '4p3h5d4w4c,' + // #4330: 5 fonts: HK 92₁₁₉, JP 69₂₀₅, KR 79₃₃₉, SC 83₄₆₇, TC 88₅₇₃. + '4p3h5d4x4b,' + // #4331: 5 fonts: HK 92₁₁₉, JP 70₂₀₆, KR 85₃₄₅, SC 84₄₆₈, TC 88₅₇₃. + '4p3i5i4s4a,' + // #4332: 5 fonts: HK 92₁₁₉, JP 70₂₀₆, KR 87₃₄₇, SC 54₄₃₈, TC 88₅₇₃. + '4p3i5k3m5e,' + // #4333: 5 fonts: HK 92₁₁₉, JP 71₂₀₇, KR 69₃₂₉, SC 16₄₀₀, TC 88₅₇₃. + '4p3j4r2s6q,' + // #4334: 5 fonts: HK 92₁₁₉, JP 71₂₀₇, KR 73₃₃₃, SC 84₄₆₈, TC 88₅₇₃. + '4p3j4v5e4a,' + // #4335: 5 fonts: HK 92₁₁₉, JP 71₂₀₇, KR 78₃₃₈, SC 90₄₇₄, TC 88₅₇₃. + '4p3j5a5f3u,' + // #4336: 5 fonts: HK 92₁₁₉, JP 72₂₀₈, KR 77₃₃₇, SC 84₄₆₈, TC 88₅₇₃. + '4p3k4y5a4a,' + // #4337: 5 fonts: HK 92₁₁₉, JP 72₂₀₈, KR 82₃₄₂, SC 45₄₂₉, TC 88₅₇₃. + '4p3k5d3i5n,' + // #4338: 5 fonts: HK 92₁₁₉, JP 73₂₀₉, KR 68₃₂₈, SC 84₄₆₈, TC 88₅₇₃. + '4p3l4o5j4a,' + // #4339: 5 fonts: HK 92₁₁₉, JP 73₂₀₉, KR 68₃₂₈, SC 85₄₆₉, TC 88₅₇₃. + '4p3l4o5k3z,' + // #4340: 5 fonts: HK 92₁₁₉, JP 73₂₀₉, KR 89₃₄₉, SC 83₄₆₇, TC 88₅₇₃. + '4p3l5j4n4b,' + // #4341: 5 fonts: HK 92₁₁₉, JP 74₂₁₀, KR 66₃₂₆, SC 8₃₉₂, TC 88₅₇₃. + '4p3m4l2n6y,' + // #4342: 5 fonts: HK 92₁₁₉, JP 74₂₁₀, KR 77₃₃₇, SC 85₄₆₉, TC 88₅₇₃. + '4p3m4w5b3z,' + // #4343: 5 fonts: HK 92₁₁₉, JP 74₂₁₀, KR 91₃₅₁, SC 84₄₆₈, TC 88₅₇₃. + '4p3m5k4m4a,' + // #4344: 5 fonts: HK 92₁₁₉, JP 75₂₁₁, KR 85₃₄₅, SC 84₄₆₈, TC 88₅₇₃. + '4p3n5d4s4a,' + // #4345: 5 fonts: HK 92₁₁₉, JP 75₂₁₁, KR 86₃₄₆, SC 52₄₃₆, TC 88₅₇₃. + '4p3n5e3l5g,' + // #4346: 5 fonts: HK 92₁₁₉, JP 76₂₁₂, KR 90₃₅₀, SC 82₄₆₆, TC 88₅₇₃. + '4p3o5h4l4c,' + // #4347: 5 fonts: HK 92₁₁₉, JP 77₂₁₃, KR 65₃₂₅, SC 6₃₉₀, TC 88₅₇₃. + '4p3p4h2m7a,' + // #4348: 5 fonts: HK 92₁₁₉, JP 77₂₁₃, KR 80₃₄₀, SC 81₄₆₅, TC 88₅₇₃. + '4p3p4w4u4d,' + // #4349: 5 fonts: HK 92₁₁₉, JP 77₂₁₃, KR 81₃₄₁, SC 85₄₆₉, TC 88₅₇₃. + '4p3p4x4x3z,' + // #4350: 5 fonts: HK 92₁₁₉, JP 78₂₁₄, KR 84₃₄₄, SC 87₄₇₁, TC 88₅₇₃. + '4p3q4z4w3x,' + // #4351: 5 fonts: HK 92₁₁₉, JP 78₂₁₄, KR 85₃₄₅, SC 83₄₆₇, TC 88₅₇₃. + '4p3q5a4r4b,' + // #4352: 5 fonts: HK 92₁₁₉, JP 79₂₁₅, KR 66₃₂₆, SC 84₄₆₈, TC 88₅₇₃. + '4p3r4g5l4a,' + // #4353: 5 fonts: HK 92₁₁₉, JP 79₂₁₅, KR 82₃₄₂, SC 86₄₇₀, TC 88₅₇₃. + '4p3r4w4x3y,' + // #4354: 5 fonts: HK 92₁₁₉, JP 79₂₁₅, KR 83₃₄₃, SC 95₄₇₉, TC 88₅₇₃. + '4p3r4x5f3p,' + // #4355: 5 fonts: HK 92₁₁₉, JP 79₂₁₅, KR 84₃₄₄, SC 84₄₆₈, TC 88₅₇₃. + '4p3r4y4t4a,' + // #4356: 5 fonts: HK 92₁₁₉, JP 79₂₁₅, KR 85₃₄₅, SC 84₄₆₈, TC 88₅₇₃. + '4p3r4z4s4a,' + // #4357: 5 fonts: HK 92₁₁₉, JP 79₂₁₅, KR 87₃₄₇, SC 84₄₆₈, TC 88₅₇₃. + '4p3r5b4q4a,' + // #4358: 5 fonts: HK 92₁₁₉, JP 79₂₁₅, KR 92₃₅₂, SC 81₄₆₅, TC 88₅₇₃. + '4p3r5g4i4d,' + // #4359: 5 fonts: HK 92₁₁₉, JP 80₂₁₆, KR 89₃₄₉, SC 85₄₆₉, TC 88₅₇₃. + '4p3s5c4p3z,' + // #4360: 5 fonts: HK 92₁₁₉, JP 80₂₁₆, KR 92₃₅₂, SC 65₄₄₉, TC 88₅₇₃. + '4p3s5f3s4t,' + // #4361: 5 fonts: HK 92₁₁₉, JP 81₂₁₇, KR 71₃₃₁, SC 85₄₆₉, TC 88₅₇₃. + '4p3t4j5h3z,' + // #4362: 5 fonts: HK 92₁₁₉, JP 81₂₁₇, KR 73₃₃₃, SC 83₄₆₇, TC 88₅₇₃. + '4p3t4l5d4b,' + // #4363: 5 fonts: HK 92₁₁₉, JP 81₂₁₇, KR 75₃₃₅, SC 29₄₁₃, TC 88₅₇₃. + '4p3t4n2z6d,' + // #4364: 5 fonts: HK 92₁₁₉, JP 81₂₁₇, KR 80₃₄₀, SC 40₄₂₄, TC 88₅₇₃. + '4p3t4s3f5s,' + // #4365: 5 fonts: HK 92₁₁₉, JP 81₂₁₇, KR 90₃₅₀, SC 83₄₆₇, TC 88₅₇₃. + '4p3t5c4m4b,' + // #4366: 5 fonts: HK 92₁₁₉, JP 81₂₁₇, KR 93₃₅₃, SC 79₄₆₃, TC 88₅₇₃. + '4p3t5f4f4f,' + // #4367: 5 fonts: HK 92₁₁₉, JP 82₂₁₈, KR 80₃₄₀, SC 82₄₆₆, TC 88₅₇₃. + '4p3u4r4v4c,' + // #4368: 5 fonts: HK 92₁₁₉, JP 82₂₁₈, KR 80₃₄₀, SC 83₄₆₇, TC 88₅₇₃. + '4p3u4r4w4b,' + // #4369: 5 fonts: HK 92₁₁₉, JP 83₂₁₉, KR 69₃₂₉, SC 17₄₀₁, TC 88₅₇₃. + '4p3v4f2t6p,' + // #4370: 5 fonts: HK 92₁₁₉, JP 83₂₁₉, KR 76₃₃₆, SC 81₄₆₅, TC 88₅₇₃. + '4p3v4m4y4d,' + // #4371: 5 fonts: HK 92₁₁₉, JP 83₂₁₉, KR 84₃₄₄, SC 49₄₃₃, TC 88₅₇₃. + '4p3v4u3k5j,' + // #4372: 5 fonts: HK 92₁₁₉, JP 83₂₁₉, KR 85₃₄₅, SC 82₄₆₆, TC 88₅₇₃. + '4p3v4v4q4c,' + // #4373: 5 fonts: HK 92₁₁₉, JP 83₂₁₉, KR 93₃₅₃, SC 84₄₆₈, TC 88₅₇₃. + '4p3v5d4k4a,' + // #4374: 5 fonts: HK 92₁₁₉, JP 84₂₂₀, KR 72₃₃₂, SC 84₄₆₈, TC 88₅₇₃. + '4p3w4h5f4a,' + // #4375: 5 fonts: HK 92₁₁₉, JP 84₂₂₀, KR 83₃₄₃, SC 84₄₆₈, TC 88₅₇₃. + '4p3w4s4u4a,' + // #4376: 5 fonts: HK 92₁₁₉, JP 84₂₂₀, KR 85₃₄₅, SC 86₄₇₀, TC 88₅₇₃. + '4p3w4u4u3y,' + // #4377: 5 fonts: HK 92₁₁₉, JP 85₂₂₁, KR 72₃₃₂, SC 84₄₆₈, TC 88₅₇₃. + '4p3x4g5f4a,' + // #4378: 5 fonts: HK 92₁₁₉, JP 85₂₂₁, KR 80₃₄₀, SC 87₄₇₁, TC 88₅₇₃. + '4p3x4o5a3x,' + // #4379: 5 fonts: HK 92₁₁₉, JP 85₂₂₁, KR 84₃₄₄, SC 84₄₆₈, TC 88₅₇₃. + '4p3x4s4t4a,' + // #4380: 5 fonts: HK 92₁₁₉, JP 85₂₂₁, KR 87₃₄₇, SC 84₄₆₈, TC 88₅₇₃. + '4p3x4v4q4a,' + // #4381: 5 fonts: HK 92₁₁₉, JP 85₂₂₁, KR 89₃₄₉, SC 85₄₆₉, TC 88₅₇₃. + '4p3x4x4p3z,' + // #4382: 4 fonts: HK 92₁₁₉, JP 85₂₂₁, SC 65₄₄₉, TC 88₅₇₃. + '4p3x8t4t,' + // #4383: 5 fonts: HK 92₁₁₉, JP 86₂₂₂, KR 69₃₂₉, SC 17₄₀₁, TC 88₅₇₃. + '4p3y4c2t6p,' + // #4384: 5 fonts: HK 92₁₁₉, JP 86₂₂₂, KR 79₃₃₉, SC 84₄₆₈, TC 88₅₇₃. + '4p3y4m4y4a,' + // #4385: 5 fonts: HK 92₁₁₉, JP 86₂₂₂, KR 86₃₄₆, SC 85₄₆₉, TC 88₅₇₃. + '4p3y4t4s3z,' + // #4386: 5 fonts: HK 92₁₁₉, JP 87₂₂₃, KR 72₃₃₂, SC 84₄₆₈, TC 88₅₇₃. + '4p3z4e5f4a,' + // #4387: 5 fonts: HK 92₁₁₉, JP 87₂₂₃, KR 83₃₄₃, SC 83₄₆₇, TC 88₅₇₃. + '4p3z4p4t4b,' + // #4388: 5 fonts: HK 92₁₁₉, JP 87₂₂₃, KR 84₃₄₄, SC 85₄₆₉, TC 88₅₇₃. + '4p3z4q4u3z,' + // #4389: 5 fonts: HK 92₁₁₉, JP 88₂₂₄, KR 79₃₃₉, SC 82₄₆₆, TC 88₅₇₃. + '4p4a4k4w4c,' + // #4390: 5 fonts: HK 92₁₁₉, JP 89₂₂₅, KR 69₃₂₉, SC 17₄₀₁, TC 88₅₇₃. + '4p4b3z2t6p,' + // #4391: 5 fonts: HK 92₁₁₉, JP 89₂₂₅, KR 70₃₃₀, SC 18₄₀₂, TC 88₅₇₃. + '4p4b4a2t6o,' + // #4392: 5 fonts: HK 92₁₁₉, JP 89₂₂₅, KR 81₃₄₁, SC 83₄₆₇, TC 88₅₇₃. + '4p4b4l4v4b,' + // #4393: 5 fonts: HK 92₁₁₉, JP 90₂₂₆, KR 85₃₄₅, SC 84₄₆₈, TC 88₅₇₃. + '4p4c4o4s4a,' + // #4394: 5 fonts: HK 92₁₁₉, JP 92₂₂₈, KR 88₃₄₈, SC 83₄₆₇, TC 88₅₇₃. + '4p4e4p4o4b,' + // #4395: 5 fonts: HK 92₁₁₉, JP 92₂₂₈, KR 89₃₄₉, SC 83₄₆₇, TC 88₅₇₃. + '4p4e4q4n4b,' + // #4396: 5 fonts: HK 92₁₁₉, JP 94₂₃₀, KR 77₃₃₇, SC 84₄₆₈, TC 88₅₇₃. + '4p4g4c5a4a,' + // #4397: 5 fonts: HK 92₁₁₉, JP 94₂₃₀, KR 81₃₄₁, SC 84₄₆₈, TC 88₅₇₃. + '4p4g4g4w4a,' + // #4398: 5 fonts: HK 92₁₁₉, JP 96₂₃₂, KR 66₃₂₆, SC 83₄₆₇, TC 88₅₇₃. + '4p4i3p5k4b,' + // #4399: 5 fonts: HK 92₁₁₉, JP 96₂₃₂, KR 75₃₃₅, SC 84₄₆₈, TC 88₅₇₃. + '4p4i3y5c4a,' + // #4400: 5 fonts: HK 92₁₁₉, JP 97₂₃₃, KR 83₃₄₃, SC 82₄₆₆, TC 88₅₇₃. + '4p4j4f4s4c,' + // #4401: 5 fonts: HK 92₁₁₉, JP 100₂₃₆, KR 93₃₅₃, SC 85₄₆₉, TC 88₅₇₃. + '4p4m4m4l3z,' + // #4402: 4 fonts: HK 92₁₁₉, JP 100₂₃₆, SC 93₄₇₇, TC 88₅₇₃. + '4p4m9g3r,' + // #4403: 5 fonts: HK 92₁₁₉, JP 103₂₃₉, KR 0₂₆₀, SC 81₄₆₅, TC 88₅₇₃. + '4p4pu7w4d,' + // #4404: 5 fonts: HK 92₁₁₉, JP 106₂₄₂, KR 87₃₄₇, SC 84₄₆₈, TC 88₅₇₃. + '4p4s4a4q4a,' + // #4405: 5 fonts: HK 92₁₁₉, JP 106₂₄₂, KR 92₃₅₂, SC 85₄₆₉, TC 88₅₇₃. + '4p4s4f4m3z,' + // #4406: 5 fonts: HK 92₁₁₉, JP 108₂₄₄, KR 87₃₄₇, SC 81₄₆₅, TC 88₅₇₃. + '4p4u3y4n4d,' + // #4407: 5 fonts: HK 92₁₁₉, JP 109₂₄₅, KR 0₂₆₀, SC 77₄₆₁, TC 88₅₇₃. + '4p4vo7s4h,' + // #4408: 5 fonts: HK 92₁₁₉, JP 109₂₄₅, KR 0₂₆₀, SC 79₄₆₃, TC 88₅₇₃. + '4p4vo7u4f,' + // #4409: 4 fonts: HK 92₁₁₉, JP 115₂₅₁, SC 95₄₇₉, TC 88₅₇₃. + '4p5b8t3p,' + // #4410: 3 fonts: HK 92₁₁₉, SC 60₄₄₄, TC 88₅₇₃. + '4p12m4y,' + // #4411: 3 fonts: HK 92₁₁₉, SC 64₄₄₈, TC 88₅₇₃. + '4p12q4u,' + // #4412: 3 fonts: HK 92₁₁₉, SC 66₄₅₀, TC 88₅₇₃. + '4p12s4s,' + // #4413: 3 fonts: HK 92₁₁₉, SC 82₄₆₆, TC 88₅₇₃. + '4p13i4c,' + // #4414: 3 fonts: HK 92₁₁₉, SC 83₄₆₇, TC 88₅₇₃. + '4p13j4b,' + // #4415: 3 fonts: HK 92₁₁₉, SC 92₄₇₆, TC 88₅₇₃. + '4p13s3s,' + // #4416: 5 fonts: HK 93₁₂₀, JP 5₁₄₁, KR 65₃₂₅, SC 4₃₈₈, TC 89₅₇₄. + '4qu7b2k7d,' + // #4417: 5 fonts: HK 93₁₂₀, JP 5₁₄₁, KR 65₃₂₅, SC 84₄₆₈, TC 89₅₇₄. + '4qu7b5m4b,' + // #4418: 4 fonts: HK 93₁₂₀, JP 5₁₄₁, SC 80₄₆₄, TC 89₅₇₄. + '4qu12k4f,' + // #4419: 4 fonts: HK 93₁₂₀, JP 9₁₄₅, SC 8₃₉₂, TC 89₅₇₄. + '4qy9m6z,' + // #4420: 4 fonts: HK 93₁₂₀, JP 9₁₄₅, SC 9₃₉₃, TC 89₅₇₄. + '4qy9n6y,' + // #4421: 5 fonts: HK 93₁₂₀, JP 10₁₄₆, KR 67₃₂₇, SC 9₃₉₃, TC 89₅₇₄. + '4qz6y2n6y,' + // #4422: 4 fonts: HK 93₁₂₀, JP 10₁₄₆, SC 95₄₇₉, TC 89₅₇₄. + '4qz12u3q,' + // #4423: 4 fonts: HK 93₁₂₀, JP 11₁₄₇, SC 13₃₉₇, TC 89₅₇₄. + '4q1a9p6u,' + // #4424: 4 fonts: HK 93₁₂₀, JP 12₁₄₈, SC 81₄₆₅, TC 89₅₇₄. + '4q1b12e4e,' + // #4425: 5 fonts: HK 93₁₂₀, JP 13₁₄₉, KR 68₃₂₈, SC 81₄₆₅, TC 89₅₇₄. + '4q1c6w5g4e,' + // #4426: 5 fonts: HK 93₁₂₀, JP 13₁₄₉, KR 68₃₂₈, SC 87₄₇₁, TC 89₅₇₄. + '4q1c6w5m3y,' + // #4427: 5 fonts: HK 93₁₂₀, JP 14₁₅₀, KR 69₃₂₉, SC 15₃₉₉, TC 89₅₇₄. + '4q1d6w2r6s,' + // #4428: 5 fonts: HK 93₁₂₀, JP 15₁₅₁, KR 69₃₂₉, SC 85₄₆₉, TC 89₅₇₄. + '4q1e6v5j4a,' + // #4429: 4 fonts: HK 93₁₂₀, JP 15₁₅₁, SC 82₄₆₆, TC 89₅₇₄. + '4q1e12c4d,' + // #4430: 5 fonts: HK 93₁₂₀, JP 16₁₅₂, KR 70₃₃₀, SC 18₄₀₂, TC 89₅₇₄. + '4q1f6v2t6p,' + // #4431: 5 fonts: HK 93₁₂₀, JP 17₁₅₃, KR 70₃₃₀, SC 19₄₀₃, TC 89₅₇₄. + '4q1g6u2u6o,' + // #4432: 5 fonts: HK 93₁₂₀, JP 17₁₅₃, KR 71₃₃₁, SC 85₄₆₉, TC 89₅₇₄. + '4q1g6v5h4a,' + // #4433: 4 fonts: HK 93₁₂₀, JP 17₁₅₃, SC 19₄₀₃, TC 89₅₇₄. + '4q1g9p6o,' + // #4434: 5 fonts: HK 93₁₂₀, JP 19₁₅₅, KR 71₃₃₁, SC 21₄₀₅, TC 89₅₇₄. + '4q1i6t2v6m,' + // #4435: 5 fonts: HK 93₁₂₀, JP 19₁₅₅, KR 72₃₃₂, SC 21₄₀₅, TC 89₅₇₄. + '4q1i6u2u6m,' + // #4436: 5 fonts: HK 93₁₂₀, JP 19₁₅₅, KR 72₃₃₂, SC 22₄₀₆, TC 89₅₇₄. + '4q1i6u2v6l,' + // #4437: 5 fonts: HK 93₁₂₀, JP 20₁₅₆, KR 72₃₃₂, SC 85₄₆₉, TC 89₅₇₄. + '4q1j6t5g4a,' + // #4438: 4 fonts: HK 93₁₂₀, JP 20₁₅₆, SC 83₄₆₇, TC 89₅₇₄. + '4q1j11y4c,' + // #4439: 5 fonts: HK 93₁₂₀, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 89₅₇₄. + '4q1k6t2w6j,' + // #4440: 4 fonts: HK 93₁₂₀, JP 21₁₅₇, SC 24₄₀₈, TC 89₅₇₄. + '4q1k9q6j,' + // #4441: 5 fonts: HK 93₁₂₀, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 89₅₇₄. + '4q1m6s2y6g,' + // #4442: 5 fonts: HK 93₁₂₀, JP 24₁₆₀, KR 75₃₃₅, SC 28₄₁₂, TC 89₅₇₄. + '4q1n6s2y6f,' + // #4443: 5 fonts: HK 93₁₂₀, JP 25₁₆₁, KR 75₃₃₅, SC 85₄₆₉, TC 89₅₇₄. + '4q1o6r5d4a,' + // #4444: 5 fonts: HK 93₁₂₀, JP 25₁₆₁, KR 76₃₃₆, SC 82₄₆₆, TC 89₅₇₄. + '4q1o6s4z4d,' + // #4445: 4 fonts: HK 93₁₂₀, JP 26₁₆₂, SC 32₄₁₆, TC 89₅₇₄. + '4q1p9t6b,' + // #4446: 5 fonts: HK 93₁₂₀, JP 28₁₆₄, KR 77₃₃₇, SC 85₄₆₉, TC 89₅₇₄. + '4q1r6q5b4a,' + // #4447: 5 fonts: HK 93₁₂₀, JP 29₁₆₅, KR 78₃₃₈, SC 83₄₆₇, TC 89₅₇₄. + '4q1s6q4y4c,' + // #4448: 5 fonts: HK 93₁₂₀, JP 29₁₆₅, KR 78₃₃₈, SC 84₄₆₈, TC 89₅₇₄. + '4q1s6q4z4b,' + // #4449: 5 fonts: HK 93₁₂₀, JP 31₁₆₇, KR 79₃₃₉, SC 40₄₂₄, TC 89₅₇₄. + '4q1u6p3g5t,' + // #4450: 5 fonts: HK 93₁₂₀, JP 31₁₆₇, KR 80₃₄₀, SC 79₄₆₃, TC 89₅₇₄. + '4q1u6q4s4g,' + // #4451: 5 fonts: HK 93₁₂₀, JP 31₁₆₇, KR 80₃₄₀, SC 82₄₆₆, TC 89₅₇₄. + '4q1u6q4v4d,' + // #4452: 5 fonts: HK 93₁₂₀, JP 31₁₆₇, KR 80₃₄₀, SC 85₄₆₉, TC 89₅₇₄. + '4q1u6q4y4a,' + // #4453: 4 fonts: HK 93₁₂₀, JP 31₁₆₇, SC 83₄₆₇, TC 89₅₇₄. + '4q1u11n4c,' + // #4454: 4 fonts: HK 93₁₂₀, JP 31₁₆₇, SC 87₄₇₁, TC 89₅₇₄. + '4q1u11r3y,' + // #4455: 5 fonts: HK 93₁₂₀, JP 33₁₆₉, KR 81₃₄₁, SC 87₄₇₁, TC 89₅₇₄. + '4q1w6p4z3y,' + // #4456: 4 fonts: HK 93₁₂₀, JP 33₁₆₉, SC 86₄₇₀, TC 89₅₇₄. + '4q1w11o3z,' + // #4457: 5 fonts: HK 93₁₂₀, JP 35₁₇₁, KR 82₃₄₂, SC 46₄₃₀, TC 89₅₇₄. + '4q1y6o3j5n,' + // #4458: 5 fonts: HK 93₁₂₀, JP 37₁₇₃, KR 83₃₄₃, SC 47₄₃₁, TC 89₅₇₄. + '4q2a6n3j5m,' + // #4459: 4 fonts: HK 93₁₂₀, JP 37₁₇₃, SC 49₄₃₃, TC 89₅₇₄. + '4q2a9z5k,' + // #4460: 4 fonts: HK 93₁₂₀, JP 38₁₇₄, SC 83₄₆₇, TC 89₅₇₄. + '4q2b11g4c,' + // #4461: 5 fonts: HK 93₁₂₀, JP 39₁₇₅, KR 85₃₄₅, SC 51₄₃₅, TC 89₅₇₄. + '4q2c6n3l5i,' + // #4462: 4 fonts: HK 93₁₂₀, JP 39₁₇₅, SC 84₄₆₈, TC 89₅₇₄. + '4q2c11g4b,' + // #4463: 5 fonts: HK 93₁₂₀, JP 40₁₇₆, KR 86₃₄₆, SC 51₄₃₅, TC 89₅₇₄. + '4q2d6n3k5i,' + // #4464: 5 fonts: HK 93₁₂₀, JP 40₁₇₆, KR 86₃₄₆, SC 84₄₆₈, TC 89₅₇₄. + '4q2d6n4r4b,' + // #4465: 5 fonts: HK 93₁₂₀, JP 40₁₇₆, KR 86₃₄₆, SC 85₄₆₉, TC 89₅₇₄. + '4q2d6n4s4a,' + // #4466: 5 fonts: HK 93₁₂₀, JP 40₁₇₆, KR 86₃₄₆, SC 88₄₇₂, TC 89₅₇₄. + '4q2d6n4v3x,' + // #4467: 5 fonts: HK 93₁₂₀, JP 41₁₇₇, KR 86₃₄₆, SC 53₄₃₇, TC 89₅₇₄. + '4q2e6m3m5g,' + // #4468: 5 fonts: HK 93₁₂₀, JP 41₁₇₇, KR 87₃₄₇, SC 53₄₃₇, TC 89₅₇₄. + '4q2e6n3l5g,' + // #4469: 5 fonts: HK 93₁₂₀, JP 42₁₇₈, KR 88₃₄₈, SC 56₄₄₀, TC 89₅₇₄. + '4q2f6n3n5d,' + // #4470: 5 fonts: HK 93₁₂₀, JP 42₁₇₈, KR 88₃₄₈, SC 85₄₆₉, TC 89₅₇₄. + '4q2f6n4q4a,' + // #4471: 5 fonts: HK 93₁₂₀, JP 43₁₇₉, KR 88₃₄₈, SC 84₄₆₈, TC 89₅₇₄. + '4q2g6m4p4b,' + // #4472: 4 fonts: HK 93₁₂₀, JP 43₁₇₉, SC 84₄₆₈, TC 89₅₇₄. + '4q2g11c4b,' + // #4473: 5 fonts: HK 93₁₂₀, JP 45₁₈₁, KR 90₃₅₀, SC 85₄₆₉, TC 89₅₇₄. + '4q2i6m4o4a,' + // #4474: 4 fonts: HK 93₁₂₀, JP 46₁₈₂, SC 62₄₄₆, TC 89₅₇₄. + '4q2j10d4x,' + // #4475: 4 fonts: HK 93₁₂₀, JP 46₁₈₂, SC 82₄₆₆, TC 89₅₇₄. + '4q2j10x4d,' + // #4476: 5 fonts: HK 93₁₂₀, JP 47₁₈₃, KR 90₃₅₀, SC 63₄₄₇, TC 89₅₇₄. + '4q2k6k3s4w,' + // #4477: 5 fonts: HK 93₁₂₀, JP 47₁₈₃, KR 91₃₅₁, SC 83₄₆₇, TC 89₅₇₄. + '4q2k6l4l4c,' + // #4478: 5 fonts: HK 93₁₂₀, JP 48₁₈₄, KR 91₃₅₁, SC 83₄₆₇, TC 89₅₇₄. + '4q2l6k4l4c,' + // #4479: 5 fonts: HK 93₁₂₀, JP 48₁₈₄, KR 92₃₅₂, SC 64₄₄₈, TC 89₅₇₄. + '4q2l6l3r4v,' + // #4480: 5 fonts: HK 93₁₂₀, JP 48₁₈₄, KR 92₃₅₂, SC 87₄₇₁, TC 89₅₇₄. + '4q2l6l4o3y,' + // #4481: 5 fonts: HK 93₁₂₀, JP 49₁₈₅, KR 92₃₅₂, SC 86₄₇₀, TC 89₅₇₄. + '4q2m6k4n3z,' + // #4482: 5 fonts: HK 93₁₂₀, JP 50₁₈₆, KR 93₃₅₃, SC 86₄₇₀, TC 89₅₇₄. + '4q2n6k4m3z,' + // #4483: 4 fonts: HK 93₁₂₀, JP 50₁₈₆, SC 91₄₇₅, TC 89₅₇₄. + '4q2n11c3u,' + // #4484: 6 fonts: HK 93₁₂₀, JP 58₁₉₄, KR 98₃₅₈, SC 72₄₅₆, TC 89₅₇₄, Math₆₅₅. + '4q2v6h3t4n3c,' + // #4485: 5 fonts: HK 93₁₂₀, JP 60₁₉₆, KR 70₃₃₀, SC 18₄₀₂, TC 89₅₇₄. + '4q2x5d2t6p,' + // #4486: 5 fonts: HK 93₁₂₀, JP 60₁₉₆, KR 84₃₄₄, SC 84₄₆₈, TC 89₅₇₄. + '4q2x5r4t4b,' + // #4487: 5 fonts: HK 93₁₂₀, JP 60₁₉₆, KR 86₃₄₆, SC 84₄₆₈, TC 89₅₇₄. + '4q2x5t4r4b,' + // #4488: 5 fonts: HK 93₁₂₀, JP 60₁₉₆, KR 88₃₄₈, SC 85₄₆₉, TC 89₅₇₄. + '4q2x5v4q4a,' + // #4489: 4 fonts: HK 93₁₂₀, JP 60₁₉₆, SC 84₄₆₈, TC 89₅₇₄. + '4q2x10l4b,' + // #4490: 5 fonts: HK 93₁₂₀, JP 61₁₉₇, KR 71₃₃₁, SC 85₄₆₉, TC 89₅₇₄. + '4q2y5d5h4a,' + // #4491: 5 fonts: HK 93₁₂₀, JP 61₁₉₇, KR 77₃₃₇, SC 86₄₇₀, TC 89₅₇₄. + '4q2y5j5c3z,' + // #4492: 5 fonts: HK 93₁₂₀, JP 62₁₉₈, KR 74₃₃₄, SC 82₄₆₆, TC 89₅₇₄. + '4q2z5f5b4d,' + // #4493: 5 fonts: HK 93₁₂₀, JP 63₁₉₉, KR 81₃₄₁, SC 42₄₂₆, TC 89₅₇₄. + '4q3a5l3g5r,' + // #4494: 5 fonts: HK 93₁₂₀, JP 63₁₉₉, KR 88₃₄₈, SC 56₄₄₀, TC 89₅₇₄. + '4q3a5s3n5d,' + // #4495: 5 fonts: HK 93₁₂₀, JP 63₁₉₉, KR 90₃₅₀, SC 85₄₆₉, TC 89₅₇₄. + '4q3a5u4o4a,' + // #4496: 5 fonts: HK 93₁₂₀, JP 64₂₀₀, KR 71₃₃₁, SC 80₄₆₄, TC 89₅₇₄. + '4q3b5a5c4f,' + // #4497: 5 fonts: HK 93₁₂₀, JP 64₂₀₀, KR 90₃₅₀, SC 85₄₆₉, TC 89₅₇₄. + '4q3b5t4o4a,' + // #4498: 5 fonts: HK 93₁₂₀, JP 64₂₀₀, KR 92₃₅₂, SC 78₄₆₂, TC 89₅₇₄. + '4q3b5v4f4h,' + // #4499: 5 fonts: HK 93₁₂₀, JP 65₂₀₁, KR 69₃₂₉, SC 15₃₉₉, TC 89₅₇₄. + '4q3c4x2r6s,' + // #4500: 5 fonts: HK 93₁₂₀, JP 65₂₀₁, KR 78₃₃₈, SC 83₄₆₇, TC 89₅₇₄. + '4q3c5g4y4c,' + // #4501: 5 fonts: HK 93₁₂₀, JP 66₂₀₂, KR 68₃₂₈, SC 13₃₉₇, TC 89₅₇₄. + '4q3d4v2q6u,' + // #4502: 5 fonts: HK 93₁₂₀, JP 66₂₀₂, KR 83₃₄₃, SC 83₄₆₇, TC 89₅₇₄. + '4q3d5k4t4c,' + // #4503: 5 fonts: HK 93₁₂₀, JP 66₂₀₂, KR 84₃₄₄, SC 86₄₇₀, TC 89₅₇₄. + '4q3d5l4v3z,' + // #4504: 5 fonts: HK 93₁₂₀, JP 67₂₀₃, KR 89₃₄₉, SC 58₄₄₂, TC 89₅₇₄. + '4q3e5p3o5b,' + // #4505: 5 fonts: HK 93₁₂₀, JP 67₂₀₃, KR 89₃₄₉, SC 85₄₆₉, TC 89₅₇₄. + '4q3e5p4p4a,' + // #4506: 5 fonts: HK 93₁₂₀, JP 67₂₀₃, KR 90₃₅₀, SC 85₄₆₉, TC 89₅₇₄. + '4q3e5q4o4a,' + // #4507: 5 fonts: HK 93₁₂₀, JP 69₂₀₅, KR 79₃₃₉, SC 39₄₂₃, TC 89₅₇₄. + '4q3g5d3f5u,' + // #4508: 5 fonts: HK 93₁₂₀, JP 70₂₀₆, KR 68₃₂₈, SC 86₄₇₀, TC 89₅₇₄. + '4q3h4r5l3z,' + // #4509: 5 fonts: HK 93₁₂₀, JP 70₂₀₆, KR 71₃₃₁, SC 82₄₆₆, TC 89₅₇₄. + '4q3h4u5e4d,' + // #4510: 5 fonts: HK 93₁₂₀, JP 70₂₀₆, KR 74₃₃₄, SC 84₄₆₈, TC 89₅₇₄. + '4q3h4x5d4b,' + // #4511: 5 fonts: HK 93₁₂₀, JP 70₂₀₆, KR 86₃₄₆, SC 53₄₃₇, TC 89₅₇₄. + '4q3h5j3m5g,' + // #4512: 5 fonts: HK 93₁₂₀, JP 70₂₀₆, KR 86₃₄₆, SC 84₄₆₈, TC 89₅₇₄. + '4q3h5j4r4b,' + // #4513: 5 fonts: HK 93₁₂₀, JP 71₂₀₇, KR 90₃₅₀, SC 60₄₄₄, TC 89₅₇₄. + '4q3i5m3p4z,' + // #4514: 5 fonts: HK 93₁₂₀, JP 72₂₀₈, KR 72₃₃₂, SC 85₄₆₉, TC 89₅₇₄. + '4q3j4t5g4a,' + // #4515: 5 fonts: HK 93₁₂₀, JP 72₂₀₈, KR 73₃₃₃, SC 86₄₇₀, TC 89₅₇₄. + '4q3j4u5g3z,' + // #4516: 5 fonts: HK 93₁₂₀, JP 72₂₀₈, KR 80₃₄₀, SC 84₄₆₈, TC 89₅₇₄. + '4q3j5b4x4b,' + // #4517: 4 fonts: HK 93₁₂₀, JP 72₂₀₈, SC 86₄₇₀, TC 89₅₇₄. + '4q3j10b3z,' + // #4518: 5 fonts: HK 93₁₂₀, JP 73₂₀₉, KR 85₃₄₅, SC 87₄₇₁, TC 89₅₇₄. + '4q3k5f4v3y,' + // #4519: 5 fonts: HK 93₁₂₀, JP 73₂₀₉, KR 87₃₄₇, SC 85₄₆₉, TC 89₅₇₄. + '4q3k5h4r4a,' + // #4520: 5 fonts: HK 93₁₂₀, JP 74₂₁₀, KR 68₃₂₈, SC 84₄₆₈, TC 89₅₇₄. + '4q3l4n5j4b,' + // #4521: 5 fonts: HK 93₁₂₀, JP 74₂₁₀, KR 71₃₃₁, SC 85₄₆₉, TC 89₅₇₄. + '4q3l4q5h4a,' + // #4522: 5 fonts: HK 93₁₂₀, JP 74₂₁₀, KR 86₃₄₆, SC 84₄₆₈, TC 89₅₇₄. + '4q3l5f4r4b,' + // #4523: 5 fonts: HK 93₁₂₀, JP 74₂₁₀, KR 87₃₄₇, SC 85₄₆₉, TC 89₅₇₄. + '4q3l5g4r4a,' + // #4524: 5 fonts: HK 93₁₂₀, JP 74₂₁₀, KR 90₃₅₀, SC 85₄₆₉, TC 89₅₇₄. + '4q3l5j4o4a,' + // #4525: 5 fonts: HK 93₁₂₀, JP 75₂₁₁, KR 69₃₂₉, SC 16₄₀₀, TC 89₅₇₄. + '4q3m4n2s6r,' + // #4526: 5 fonts: HK 93₁₂₀, JP 75₂₁₁, KR 79₃₃₉, SC 40₄₂₄, TC 89₅₇₄. + '4q3m4x3g5t,' + // #4527: 5 fonts: HK 93₁₂₀, JP 75₂₁₁, KR 82₃₄₂, SC 82₄₆₆, TC 89₅₇₄. + '4q3m5a4t4d,' + // #4528: 5 fonts: HK 93₁₂₀, JP 75₂₁₁, KR 82₃₄₂, SC 86₄₇₀, TC 89₅₇₄. + '4q3m5a4x3z,' + // #4529: 5 fonts: HK 93₁₂₀, JP 75₂₁₁, KR 90₃₅₀, SC 61₄₄₅, TC 89₅₇₄. + '4q3m5i3q4y,' + // #4530: 5 fonts: HK 93₁₂₀, JP 75₂₁₁, KR 92₃₅₂, SC 65₄₄₉, TC 89₅₇₄. + '4q3m5k3s4u,' + // #4531: 5 fonts: HK 93₁₂₀, JP 76₂₁₂, KR 68₃₂₈, SC 14₃₉₈, TC 89₅₇₄. + '4q3n4l2r6t,' + // #4532: 5 fonts: HK 93₁₂₀, JP 76₂₁₂, KR 69₃₂₉, SC 16₄₀₀, TC 89₅₇₄. + '4q3n4m2s6r,' + // #4533: 5 fonts: HK 93₁₂₀, JP 76₂₁₂, KR 76₃₃₆, SC 85₄₆₉, TC 89₅₇₄. + '4q3n4t5c4a,' + // #4534: 5 fonts: HK 93₁₂₀, JP 76₂₁₂, KR 80₃₄₀, SC 86₄₇₀, TC 89₅₇₄. + '4q3n4x4z3z,' + // #4535: 5 fonts: HK 93₁₂₀, JP 76₂₁₂, KR 82₃₄₂, SC 88₄₇₂, TC 89₅₇₄. + '4q3n4z4z3x,' + // #4536: 5 fonts: HK 93₁₂₀, JP 76₂₁₂, KR 86₃₄₆, SC 84₄₆₈, TC 89₅₇₄. + '4q3n5d4r4b,' + // #4537: 5 fonts: HK 93₁₂₀, JP 77₂₁₃, KR 71₃₃₁, SC 86₄₇₀, TC 89₅₇₄. + '4q3o4n5i3z,' + // #4538: 5 fonts: HK 93₁₂₀, JP 77₂₁₃, KR 73₃₃₃, SC 24₄₀₈, TC 89₅₇₄. + '4q3o4p2w6j,' + // #4539: 5 fonts: HK 93₁₂₀, JP 77₂₁₃, KR 91₃₅₁, SC 86₄₇₀, TC 89₅₇₄. + '4q3o5h4o3z,' + // #4540: 5 fonts: HK 93₁₂₀, JP 77₂₁₃, KR 92₃₅₂, SC 84₄₆₈, TC 89₅₇₄. + '4q3o5i4l4b,' + // #4541: 5 fonts: HK 93₁₂₀, JP 77₂₁₃, KR 93₃₅₃, SC 85₄₆₉, TC 89₅₇₄. + '4q3o5j4l4a,' + // #4542: 5 fonts: HK 93₁₂₀, JP 78₂₁₄, KR 73₃₃₃, SC 86₄₇₀, TC 89₅₇₄. + '4q3p4o5g3z,' + // #4543: 5 fonts: HK 93₁₂₀, JP 79₂₁₅, KR 82₃₄₂, SC 45₄₂₉, TC 89₅₇₄. + '4q3q4w3i5o,' + // #4544: 5 fonts: HK 93₁₂₀, JP 79₂₁₅, KR 82₃₄₂, SC 85₄₆₉, TC 89₅₇₄. + '4q3q4w4w4a,' + // #4545: 5 fonts: HK 93₁₂₀, JP 79₂₁₅, KR 85₃₄₅, SC 85₄₆₉, TC 89₅₇₄. + '4q3q4z4t4a,' + // #4546: 5 fonts: HK 93₁₂₀, JP 80₂₁₆, KR 77₃₃₇, SC 85₄₆₉, TC 89₅₇₄. + '4q3r4q5b4a,' + // #4547: 5 fonts: HK 93₁₂₀, JP 80₂₁₆, KR 80₃₄₀, SC 40₄₂₄, TC 89₅₇₄. + '4q3r4t3f5t,' + // #4548: 5 fonts: HK 93₁₂₀, JP 80₂₁₆, KR 85₃₄₅, SC 85₄₆₉, TC 89₅₇₄. + '4q3r4y4t4a,' + // #4549: 5 fonts: HK 93₁₂₀, JP 80₂₁₆, KR 89₃₄₉, SC 85₄₆₉, TC 89₅₇₄. + '4q3r5c4p4a,' + // #4550: 5 fonts: HK 93₁₂₀, JP 80₂₁₆, KR 91₃₅₁, SC 85₄₆₉, TC 89₅₇₄. + '4q3r5e4n4a,' + // #4551: 5 fonts: HK 93₁₂₀, JP 81₂₁₇, KR 74₃₃₄, SC 27₄₁₁, TC 89₅₇₄. + '4q3s4m2y6g,' + // #4552: 5 fonts: HK 93₁₂₀, JP 81₂₁₇, KR 85₃₄₅, SC 84₄₆₈, TC 89₅₇₄. + '4q3s4x4s4b,' + // #4553: 5 fonts: HK 93₁₂₀, JP 82₂₁₈, KR 69₃₂₉, SC 15₃₉₉, TC 89₅₇₄. + '4q3t4g2r6s,' + // #4554: 5 fonts: HK 93₁₂₀, JP 82₂₁₈, KR 72₃₃₂, SC 82₄₆₆, TC 89₅₇₄. + '4q3t4j5d4d,' + // #4555: 5 fonts: HK 93₁₂₀, JP 82₂₁₈, KR 73₃₃₃, SC 25₄₀₉, TC 89₅₇₄. + '4q3t4k2x6i,' + // #4556: 5 fonts: HK 93₁₂₀, JP 82₂₁₈, KR 73₃₃₃, SC 84₄₆₈, TC 89₅₇₄. + '4q3t4k5e4b,' + // #4557: 5 fonts: HK 93₁₂₀, JP 82₂₁₈, KR 81₃₄₁, SC 87₄₇₁, TC 89₅₇₄. + '4q3t4s4z3y,' + // #4558: 5 fonts: HK 93₁₂₀, JP 82₂₁₈, KR 82₃₄₂, SC 84₄₆₈, TC 89₅₇₄. + '4q3t4t4v4b,' + // #4559: 5 fonts: HK 93₁₂₀, JP 83₂₁₉, KR 82₃₄₂, SC 86₄₇₀, TC 89₅₇₄. + '4q3u4s4x3z,' + // #4560: 5 fonts: HK 93₁₂₀, JP 83₂₁₉, KR 85₃₄₅, SC 51₄₃₅, TC 89₅₇₄. + '4q3u4v3l5i,' + // #4561: 5 fonts: HK 93₁₂₀, JP 83₂₁₉, KR 93₃₅₃, SC 86₄₇₀, TC 89₅₇₄. + '4q3u5d4m3z,' + // #4562: 5 fonts: HK 93₁₂₀, JP 84₂₂₀, KR 74₃₃₄, SC 28₄₁₂, TC 89₅₇₄. + '4q3v4j2z6f,' + // #4563: 5 fonts: HK 93₁₂₀, JP 84₂₂₀, KR 92₃₅₂, SC 65₄₄₉, TC 89₅₇₄. + '4q3v5b3s4u,' + // #4564: 4 fonts: HK 93₁₂₀, JP 84₂₂₀, SC 86₄₇₀, TC 89₅₇₄. + '4q3v9p3z,' + // #4565: 5 fonts: HK 93₁₂₀, JP 85₂₂₁, KR 74₃₃₄, SC 86₄₇₀, TC 89₅₇₄. + '4q3w4i5f3z,' + // #4566: 5 fonts: HK 93₁₂₀, JP 85₂₂₁, KR 89₃₄₉, SC 86₄₇₀, TC 89₅₇₄. + '4q3w4x4q3z,' + // #4567: 5 fonts: HK 93₁₂₀, JP 85₂₂₁, KR 90₃₅₀, SC 61₄₄₅, TC 89₅₇₄. + '4q3w4y3q4y,' + // #4568: 5 fonts: HK 93₁₂₀, JP 86₂₂₂, KR 83₃₄₃, SC 84₄₆₈, TC 89₅₇₄. + '4q3x4q4u4b,' + // #4569: 5 fonts: HK 93₁₂₀, JP 86₂₂₂, KR 85₃₄₅, SC 51₄₃₅, TC 89₅₇₄. + '4q3x4s3l5i,' + // #4570: 5 fonts: HK 93₁₂₀, JP 86₂₂₂, KR 89₃₄₉, SC 87₄₇₁, TC 89₅₇₄. + '4q3x4w4r3y,' + // #4571: 4 fonts: HK 93₁₂₀, JP 86₂₂₂, SC 84₄₆₈, TC 89₅₇₄. + '4q3x9l4b,' + // #4572: 5 fonts: HK 93₁₂₀, JP 87₂₂₃, KR 70₃₃₀, SC 18₄₀₂, TC 89₅₇₄. + '4q3y4c2t6p,' + // #4573: 5 fonts: HK 93₁₂₀, JP 88₂₂₄, KR 78₃₃₈, SC 85₄₆₉, TC 89₅₇₄. + '4q3z4j5a4a,' + // #4574: 5 fonts: HK 93₁₂₀, JP 88₂₂₄, KR 82₃₄₂, SC 86₄₇₀, TC 89₅₇₄. + '4q3z4n4x3z,' + // #4575: 5 fonts: HK 93₁₂₀, JP 88₂₂₄, KR 86₃₄₆, SC 84₄₆₈, TC 89₅₇₄. + '4q3z4r4r4b,' + // #4576: 5 fonts: HK 93₁₂₀, JP 88₂₂₄, KR 93₃₅₃, SC 84₄₆₈, TC 89₅₇₄. + '4q3z4y4k4b,' + // #4577: 5 fonts: HK 93₁₂₀, JP 89₂₂₅, KR 92₃₅₂, SC 64₄₄₈, TC 89₅₇₄. + '4q4a4w3r4v,' + // #4578: 5 fonts: HK 93₁₂₀, JP 90₂₂₆, KR 85₃₄₅, SC 85₄₆₉, TC 89₅₇₄. + '4q4b4o4t4a,' + // #4579: 5 fonts: HK 93₁₂₀, JP 90₂₂₆, KR 86₃₄₆, SC 83₄₆₇, TC 89₅₇₄. + '4q4b4p4q4c,' + // #4580: 5 fonts: HK 93₁₂₀, JP 91₂₂₇, KR 68₃₂₈, SC 86₄₇₀, TC 89₅₇₄. + '4q4c3w5l3z,' + // #4581: 5 fonts: HK 93₁₂₀, JP 92₂₂₈, KR 85₃₄₅, SC 50₄₃₄, TC 89₅₇₄. + '4q4d4m3k5j,' + // #4582: 5 fonts: HK 93₁₂₀, JP 93₂₂₉, KR 0₂₆₀, SC 1₃₈₅, TC 89₅₇₄. + '4q4e1e4u7g,' + // #4583: 5 fonts: HK 93₁₂₀, JP 93₂₂₉, KR 83₃₄₃, SC 83₄₆₇, TC 89₅₇₄. + '4q4e4j4t4c,' + // #4584: 5 fonts: HK 93₁₂₀, JP 93₂₂₉, KR 86₃₄₆, SC 53₄₃₇, TC 89₅₇₄. + '4q4e4m3m5g,' + // #4585: 5 fonts: HK 93₁₂₀, JP 94₂₃₀, KR 72₃₃₂, SC 86₄₇₀, TC 89₅₇₄. + '4q4f3x5h3z,' + // #4586: 5 fonts: HK 93₁₂₀, JP 95₂₃₁, KR 71₃₃₁, SC 83₄₆₇, TC 89₅₇₄. + '4q4g3v5f4c,' + // #4587: 5 fonts: HK 93₁₂₀, JP 95₂₃₁, KR 89₃₄₉, SC 85₄₆₉, TC 89₅₇₄. + '4q4g4n4p4a,' + // #4588: 5 fonts: HK 93₁₂₀, JP 95₂₃₁, KR 90₃₅₀, SC 62₄₄₆, TC 89₅₇₄. + '4q4g4o3r4x,' + // #4589: 5 fonts: HK 93₁₂₀, JP 96₂₃₂, KR 80₃₄₀, SC 86₄₇₀, TC 89₅₇₄. + '4q4h4d4z3z,' + // #4590: 5 fonts: HK 93₁₂₀, JP 97₂₃₃, KR 69₃₂₉, SC 17₄₀₁, TC 89₅₇₄. + '4q4i3r2t6q,' + // #4591: 5 fonts: HK 93₁₂₀, JP 98₂₃₄, KR 92₃₅₂, SC 91₄₇₅, TC 89₅₇₄. + '4q4j4n4s3u,' + // #4592: 5 fonts: HK 93₁₂₀, JP 99₂₃₅, KR 75₃₃₅, SC 84₄₆₈, TC 89₅₇₄. + '4q4k3v5c4b,' + // #4593: 5 fonts: HK 93₁₂₀, JP 99₂₃₅, KR 81₃₄₁, SC 83₄₆₇, TC 89₅₇₄. + '4q4k4b4v4c,' + // #4594: 5 fonts: HK 93₁₂₀, JP 100₂₃₆, KR 73₃₃₃, SC 83₄₆₇, TC 89₅₇₄. + '4q4l3s5d4c,' + // #4595: 5 fonts: HK 93₁₂₀, JP 100₂₃₆, KR 88₃₄₈, SC 84₄₆₈, TC 89₅₇₄. + '4q4l4h4p4b,' + // #4596: 5 fonts: HK 93₁₂₀, JP 103₂₃₉, KR 87₃₄₇, SC 87₄₇₁, TC 89₅₇₄. + '4q4o4d4t3y,' + // #4597: 5 fonts: HK 93₁₂₀, JP 109₂₄₅, KR 86₃₄₆, SC 84₄₆₈, TC 89₅₇₄. + '4q4u3w4r4b,' + // #4598: 5 fonts: HK 93₁₂₀, JP 111₂₄₇, KR 0₂₆₀, SC 79₄₆₃, TC 89₅₇₄. + '4q4wm7u4g,' + // #4599: 5 fonts: HK 93₁₂₀, JP 112₂₄₈, KR 102₃₆₂, SC 77₄₆₁, TC 89₅₇₄. + '4q4x4j3u4i,' + // #4600: 5 fonts: HK 93₁₂₀, JP 113₂₄₉, KR 0₂₆₀, SC 80₄₆₄, TC 89₅₇₄. + '4q4yk7v4f,' + // #4601: 5 fonts: HK 93₁₂₀, JP 114₂₅₀, KR 76₃₃₆, SC 93₄₇₇, TC 89₅₇₄. + '4q4z3h5k3s,' + // #4602: 3 fonts: HK 93₁₂₀, SC 50₄₃₄, TC 89₅₇₄. + '4q12b5j,' + // #4603: 3 fonts: HK 93₁₂₀, SC 84₄₆₈, TC 89₅₇₄. + '4q13j4b,' + // #4604: 3 fonts: HK 93₁₂₀, SC 85₄₆₉, TC 89₅₇₄. + '4q13k4a,' + // #4605: 3 fonts: HK 93₁₂₀, SC 95₄₇₉, TC 89₅₇₄. + '4q13u3q,' + // #4606: 5 fonts: HK 94₁₂₁, JP 5₁₄₁, KR 65₃₂₅, SC 3₃₈₇, TC 90₅₇₅. + '4rt7b2j7f,' + // #4607: 5 fonts: HK 94₁₂₁, JP 6₁₄₂, KR 65₃₂₅, SC 4₃₈₈, TC 90₅₇₅. + '4ru7a2k7e,' + // #4608: 4 fonts: HK 94₁₂₁, JP 8₁₄₄, SC 6₃₉₀, TC 90₅₇₅. + '4rw9l7c,' + // #4609: 4 fonts: HK 94₁₂₁, JP 8₁₄₄, SC 7₃₉₁, TC 90₅₇₅. + '4rw9m7b,' + // #4610: 4 fonts: HK 94₁₂₁, JP 8₁₄₄, SC 93₄₇₇, TC 90₅₇₅. + '4rw12u3t,' + // #4611: 5 fonts: HK 94₁₂₁, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 90₅₇₅. + '4rx6y2n7a,' + // #4612: 4 fonts: HK 94₁₂₁, JP 9₁₄₅, SC 95₄₇₉, TC 90₅₇₅. + '4rx12v3r,' + // #4613: 5 fonts: HK 94₁₂₁, JP 10₁₄₆, KR 66₃₂₆, SC 86₄₇₀, TC 90₅₇₅. + '4ry6x5n4a,' + // #4614: 5 fonts: HK 94₁₂₁, JP 11₁₄₇, KR 67₃₂₇, SC 12₃₉₆, TC 90₅₇₅. + '4rz6x2q6w,' + // #4615: 5 fonts: HK 94₁₂₁, JP 11₁₄₇, KR 67₃₂₇, SC 13₃₉₇, TC 90₅₇₅. + '4rz6x2r6v,' + // #4616: 5 fonts: HK 94₁₂₁, JP 12₁₄₈, KR 68₃₂₈, SC 87₄₇₁, TC 90₅₇₅. + '4r1a6x5m3z,' + // #4617: 5 fonts: HK 94₁₂₁, JP 13₁₄₉, KR 68₃₂₈, SC 83₄₆₇, TC 90₅₇₅. + '4r1b6w5i4d,' + // #4618: 5 fonts: HK 94₁₂₁, JP 14₁₅₀, KR 69₃₂₉, SC 15₃₉₉, TC 90₅₇₅. + '4r1c6w2r6t,' + // #4619: 5 fonts: HK 94₁₂₁, JP 15₁₅₁, KR 69₃₂₉, SC 16₄₀₀, TC 90₅₇₅. + '4r1d6v2s6s,' + // #4620: 5 fonts: HK 94₁₂₁, JP 15₁₅₁, KR 70₃₃₀, SC 18₄₀₂, TC 90₅₇₅. + '4r1d6w2t6q,' + // #4621: 5 fonts: HK 94₁₂₁, JP 19₁₅₅, KR 71₃₃₁, SC 21₄₀₅, TC 90₅₇₅. + '4r1h6t2v6n,' + // #4622: 5 fonts: HK 94₁₂₁, JP 19₁₅₅, KR 72₃₃₂, SC 85₄₆₉, TC 90₅₇₅. + '4r1h6u5g4b,' + // #4623: 4 fonts: HK 94₁₂₁, JP 19₁₅₅, SC 86₄₇₀, TC 90₅₇₅. + '4r1h12c4a,' + // #4624: 5 fonts: HK 94₁₂₁, JP 20₁₅₆, KR 72₃₃₂, SC 85₄₆₉, TC 90₅₇₅. + '4r1i6t5g4b,' + // #4625: 5 fonts: HK 94₁₂₁, JP 20₁₅₆, KR 72₃₃₂, SC 86₄₇₀, TC 90₅₇₅. + '4r1i6t5h4a,' + // #4626: 5 fonts: HK 94₁₂₁, JP 21₁₅₇, KR 73₃₃₃, SC 87₄₇₁, TC 90₅₇₅. + '4r1j6t5h3z,' + // #4627: 4 fonts: HK 94₁₂₁, JP 21₁₅₇, SC 24₄₀₈, TC 90₅₇₅. + '4r1j9q6k,' + // #4628: 5 fonts: HK 94₁₂₁, JP 22₁₅₈, KR 73₃₃₃, SC 25₄₀₉, TC 90₅₇₅. + '4r1k6s2x6j,' + // #4629: 5 fonts: HK 94₁₂₁, JP 22₁₅₈, KR 73₃₃₃, SC 92₄₇₆, TC 90₅₇₅. + '4r1k6s5m3u,' + // #4630: 5 fonts: HK 94₁₂₁, JP 23₁₅₉, KR 74₃₃₄, SC 84₄₆₈, TC 90₅₇₅. + '4r1l6s5d4c,' + // #4631: 4 fonts: HK 94₁₂₁, JP 23₁₅₉, SC 27₄₁₁, TC 90₅₇₅. + '4r1l9r6h,' + // #4632: 4 fonts: HK 94₁₂₁, JP 23₁₅₉, SC 28₄₁₂, TC 90₅₇₅. + '4r1l9s6g,' + // #4633: 5 fonts: HK 94₁₂₁, JP 26₁₆₂, KR 76₃₃₆, SC 31₄₁₅, TC 90₅₇₅. + '4r1o6r3a6d,' + // #4634: 5 fonts: HK 94₁₂₁, JP 26₁₆₂, KR 76₃₃₆, SC 32₄₁₆, TC 90₅₇₅. + '4r1o6r3b6c,' + // #4635: 4 fonts: HK 94₁₂₁, JP 26₁₆₂, SC 84₄₆₈, TC 90₅₇₅. + '4r1o11t4c,' + // #4636: 5 fonts: HK 94₁₂₁, JP 27₁₆₃, KR 77₃₃₇, SC 34₄₁₈, TC 90₅₇₅. + '4r1p6r3c6a,' + // #4637: 5 fonts: HK 94₁₂₁, JP 27₁₆₃, KR 77₃₃₇, SC 86₄₇₀, TC 90₅₇₅. + '4r1p6r5c4a,' + // #4638: 4 fonts: HK 94₁₂₁, JP 27₁₆₃, SC 33₄₁₇, TC 90₅₇₅. + '4r1p9t6b,' + // #4639: 5 fonts: HK 94₁₂₁, JP 28₁₆₄, KR 77₃₃₇, SC 35₄₁₉, TC 90₅₇₅. + '4r1q6q3d5z,' + // #4640: 5 fonts: HK 94₁₂₁, JP 29₁₆₅, KR 78₃₃₈, SC 83₄₆₇, TC 90₅₇₅. + '4r1r6q4y4d,' + // #4641: 5 fonts: HK 94₁₂₁, JP 29₁₆₅, KR 78₃₃₈, SC 85₄₆₉, TC 90₅₇₅. + '4r1r6q5a4b,' + // #4642: 5 fonts: HK 94₁₂₁, JP 30₁₆₆, KR 78₃₃₈, SC 37₄₂₁, TC 90₅₇₅. + '4r1s6p3e5x,' + // #4643: 5 fonts: HK 94₁₂₁, JP 30₁₆₆, KR 78₃₃₈, SC 89₄₇₃, TC 90₅₇₅. + '4r1s6p5e3x,' + // #4644: 5 fonts: HK 94₁₂₁, JP 31₁₆₇, KR 79₃₃₉, SC 83₄₆₇, TC 90₅₇₅. + '4r1t6p4x4d,' + // #4645: 5 fonts: HK 94₁₂₁, JP 32₁₆₈, KR 80₃₄₀, SC 87₄₇₁, TC 90₅₇₅. + '4r1u6p5a3z,' + // #4646: 5 fonts: HK 94₁₂₁, JP 33₁₆₉, KR 81₃₄₁, SC 82₄₆₆, TC 90₅₇₅. + '4r1v6p4u4e,' + // #4647: 5 fonts: HK 94₁₂₁, JP 37₁₇₃, KR 84₃₄₄, SC 86₄₇₀, TC 90₅₇₅. + '4r1z6o4v4a,' + // #4648: 4 fonts: HK 94₁₂₁, JP 37₁₇₃, SC 48₄₃₂, TC 90₅₇₅. + '4r1z9y5m,' + // #4649: 5 fonts: HK 94₁₂₁, JP 38₁₇₄, KR 84₃₄₄, SC 86₄₇₀, TC 90₅₇₅. + '4r2a6n4v4a,' + // #4650: 5 fonts: HK 94₁₂₁, JP 38₁₇₄, KR 85₃₄₅, SC 85₄₆₉, TC 90₅₇₅. + '4r2a6o4t4b,' + // #4651: 4 fonts: HK 94₁₂₁, JP 38₁₇₄, SC 49₄₃₃, TC 90₅₇₅. + '4r2a9y5l,' + // #4652: 4 fonts: HK 94₁₂₁, JP 38₁₇₄, SC 87₄₇₁, TC 90₅₇₅. + '4r2a11k3z,' + // #4653: 5 fonts: HK 94₁₂₁, JP 40₁₇₆, KR 86₃₄₆, SC 85₄₆₉, TC 90₅₇₅. + '4r2c6n4s4b,' + // #4654: 5 fonts: HK 94₁₂₁, JP 40₁₇₆, KR 86₃₄₆, SC 86₄₇₀, TC 90₅₇₅. + '4r2c6n4t4a,' + // #4655: 4 fonts: HK 94₁₂₁, JP 40₁₇₆, SC 52₄₃₆, TC 90₅₇₅. + '4r2c9z5i,' + // #4656: 5 fonts: HK 94₁₂₁, JP 41₁₇₇, KR 87₃₄₇, SC 53₄₃₇, TC 90₅₇₅. + '4r2d6n3l5h,' + // #4657: 5 fonts: HK 94₁₂₁, JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉, TC 90₅₇₅. + '4r2e6m3n5f,' + // #4658: 5 fonts: HK 94₁₂₁, JP 42₁₇₈, KR 87₃₄₇, SC 80₄₆₄, TC 90₅₇₅. + '4r2e6m4m4g,' + // #4659: 5 fonts: HK 94₁₂₁, JP 43₁₇₉, KR 88₃₄₈, SC 88₄₇₂, TC 90₅₇₅. + '4r2f6m4t3y,' + // #4660: 5 fonts: HK 94₁₂₁, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 90₅₇₅. + '4r2g6m3o5c,' + // #4661: 4 fonts: HK 94₁₂₁, JP 44₁₈₀, SC 58₄₄₂, TC 90₅₇₅. + '4r2g10b5c,' + // #4662: 5 fonts: HK 94₁₂₁, JP 45₁₈₁, KR 89₃₄₉, SC 86₄₇₀, TC 90₅₇₅. + '4r2h6l4q4a,' + // #4663: 4 fonts: HK 94₁₂₁, JP 45₁₈₁, SC 85₄₆₉, TC 90₅₇₅. + '4r2h11b4b,' + // #4664: 5 fonts: HK 94₁₂₁, JP 46₁₈₂, KR 90₃₅₀, SC 80₄₆₄, TC 90₅₇₅. + '4r2i6l4j4g,' + // #4665: 5 fonts: HK 94₁₂₁, JP 46₁₈₂, KR 90₃₅₀, SC 84₄₆₈, TC 90₅₇₅. + '4r2i6l4n4c,' + // #4666: 5 fonts: HK 94₁₂₁, JP 47₁₈₃, KR 91₃₅₁, SC 86₄₇₀, TC 90₅₇₅. + '4r2j6l4o4a,' + // #4667: 4 fonts: HK 94₁₂₁, JP 47₁₈₃, SC 63₄₄₇, TC 90₅₇₅. + '4r2j10d4x,' + // #4668: 4 fonts: HK 94₁₂₁, JP 48₁₈₄, SC 64₄₄₈, TC 90₅₇₅. + '4r2k10d4w,' + // #4669: 4 fonts: HK 94₁₂₁, JP 48₁₈₄, SC 92₄₇₆, TC 90₅₇₅. + '4r2k11f3u,' + // #4670: 5 fonts: HK 94₁₂₁, JP 49₁₈₅, KR 92₃₅₂, SC 65₄₄₉, TC 90₅₇₅. + '4r2l6k3s4v,' + // #4671: 5 fonts: HK 94₁₂₁, JP 49₁₈₅, KR 92₃₅₂, SC 84₄₆₈, TC 90₅₇₅. + '4r2l6k4l4c,' + // #4672: 4 fonts: HK 94₁₂₁, JP 49₁₈₅, SC 65₄₄₉, TC 90₅₇₅. + '4r2l10d4v,' + // #4673: 4 fonts: HK 94₁₂₁, JP 50₁₈₆, SC 67₄₅₁, TC 90₅₇₅. + '4r2m10e4t,' + // #4674: 4 fonts: HK 94₁₂₁, JP 50₁₈₆, SC 88₄₇₂, TC 90₅₇₅. + '4r2m10z3y,' + // #4675: 5 fonts: HK 94₁₂₁, JP 60₁₉₆, KR 65₃₂₅, SC 5₃₈₉, TC 90₅₇₅. + '4r2w4y2l7d,' + // #4676: 5 fonts: HK 94₁₂₁, JP 60₁₉₆, KR 84₃₄₄, SC 49₄₃₃, TC 90₅₇₅. + '4r2w5r3k5l,' + // #4677: 5 fonts: HK 94₁₂₁, JP 61₁₉₇, KR 74₃₃₄, SC 28₄₁₂, TC 90₅₇₅. + '4r2x5g2z6g,' + // #4678: 5 fonts: HK 94₁₂₁, JP 62₁₉₈, KR 80₃₄₀, SC 85₄₆₉, TC 90₅₇₅. + '4r2y5l4y4b,' + // #4679: 5 fonts: HK 94₁₂₁, JP 62₁₉₈, KR 89₃₄₉, SC 87₄₇₁, TC 90₅₇₅. + '4r2y5u4r3z,' + // #4680: 5 fonts: HK 94₁₂₁, JP 62₁₉₈, KR 91₃₅₁, SC 85₄₆₉, TC 90₅₇₅. + '4r2y5w4n4b,' + // #4681: 5 fonts: HK 94₁₂₁, JP 63₁₉₉, KR 85₃₄₅, SC 85₄₆₉, TC 90₅₇₅. + '4r2z5p4t4b,' + // #4682: 5 fonts: HK 94₁₂₁, JP 63₁₉₉, KR 92₃₅₂, SC 66₄₅₀, TC 90₅₇₅. + '4r2z5w3t4u,' + // #4683: 5 fonts: HK 94₁₂₁, JP 64₂₀₀, KR 66₃₂₆, SC 8₃₉₂, TC 90₅₇₅. + '4r3a4v2n7a,' + // #4684: 5 fonts: HK 94₁₂₁, JP 64₂₀₀, KR 76₃₃₆, SC 33₄₁₇, TC 90₅₇₅. + '4r3a5f3c6b,' + // #4685: 5 fonts: HK 94₁₂₁, JP 65₂₀₁, KR 80₃₄₀, SC 82₄₆₆, TC 90₅₇₅. + '4r3b5i4v4e,' + // #4686: 5 fonts: HK 94₁₂₁, JP 65₂₀₁, KR 87₃₄₇, SC 84₄₆₈, TC 90₅₇₅. + '4r3b5p4q4c,' + // #4687: 5 fonts: HK 94₁₂₁, JP 65₂₀₁, KR 88₃₄₈, SC 86₄₇₀, TC 90₅₇₅. + '4r3b5q4r4a,' + // #4688: 5 fonts: HK 94₁₂₁, JP 66₂₀₂, KR 66₃₂₆, SC 85₄₆₉, TC 90₅₇₅. + '4r3c4t5m4b,' + // #4689: 5 fonts: HK 94₁₂₁, JP 66₂₀₂, KR 79₃₃₉, SC 86₄₇₀, TC 90₅₇₅. + '4r3c5g5a4a,' + // #4690: 5 fonts: HK 94₁₂₁, JP 67₂₀₃, KR 73₃₃₃, SC 84₄₆₈, TC 90₅₇₅. + '4r3d4z5e4c,' + // #4691: 5 fonts: HK 94₁₂₁, JP 67₂₀₃, KR 78₃₃₈, SC 38₄₂₂, TC 90₅₇₅. + '4r3d5e3f5w,' + // #4692: 5 fonts: HK 94₁₂₁, JP 68₂₀₄, KR 69₃₂₉, SC 85₄₆₉, TC 90₅₇₅. + '4r3e4u5j4b,' + // #4693: 5 fonts: HK 94₁₂₁, JP 68₂₀₄, KR 74₃₃₄, SC 26₄₁₀, TC 90₅₇₅. + '4r3e4z2x6i,' + // #4694: 5 fonts: HK 94₁₂₁, JP 68₂₀₄, KR 75₃₃₅, SC 31₄₁₅, TC 90₅₇₅. + '4r3e5a3b6d,' + // #4695: 5 fonts: HK 94₁₂₁, JP 68₂₀₄, KR 77₃₃₇, SC 85₄₆₉, TC 90₅₇₅. + '4r3e5c5b4b,' + // #4696: 5 fonts: HK 94₁₂₁, JP 68₂₀₄, KR 79₃₃₉, SC 85₄₆₉, TC 90₅₇₅. + '4r3e5e4z4b,' + // #4697: 5 fonts: HK 94₁₂₁, JP 68₂₀₄, KR 84₃₄₄, SC 86₄₇₀, TC 90₅₇₅. + '4r3e5j4v4a,' + // #4698: 5 fonts: HK 94₁₂₁, JP 70₂₀₆, KR 73₃₃₃, SC 84₄₆₈, TC 90₅₇₅. + '4r3g4w5e4c,' + // #4699: 5 fonts: HK 94₁₂₁, JP 70₂₀₆, KR 86₃₄₆, SC 85₄₆₉, TC 90₅₇₅. + '4r3g5j4s4b,' + // #4700: 5 fonts: HK 94₁₂₁, JP 71₂₀₇, KR 71₃₃₁, SC 85₄₆₉, TC 90₅₇₅. + '4r3h4t5h4b,' + // #4701: 5 fonts: HK 94₁₂₁, JP 73₂₀₉, KR 76₃₃₆, SC 86₄₇₀, TC 90₅₇₅. + '4r3j4w5d4a,' + // #4702: 5 fonts: HK 94₁₂₁, JP 74₂₁₀, KR 76₃₃₆, SC 86₄₇₀, TC 90₅₇₅. + '4r3k4v5d4a,' + // #4703: 5 fonts: HK 94₁₂₁, JP 74₂₁₀, KR 87₃₄₇, SC 86₄₇₀, TC 90₅₇₅. + '4r3k5g4s4a,' + // #4704: 5 fonts: HK 94₁₂₁, JP 75₂₁₁, KR 85₃₄₅, SC 84₄₆₈, TC 90₅₇₅. + '4r3l5d4s4c,' + // #4705: 5 fonts: HK 94₁₂₁, JP 75₂₁₁, KR 87₃₄₇, SC 85₄₆₉, TC 90₅₇₅. + '4r3l5f4r4b,' + // #4706: 5 fonts: HK 94₁₂₁, JP 76₂₁₂, KR 65₃₂₅, SC 5₃₈₉, TC 90₅₇₅. + '4r3m4i2l7d,' + // #4707: 5 fonts: HK 94₁₂₁, JP 76₂₁₂, KR 86₃₄₆, SC 86₄₇₀, TC 90₅₇₅. + '4r3m5d4t4a,' + // #4708: 5 fonts: HK 94₁₂₁, JP 76₂₁₂, KR 89₃₄₉, SC 82₄₆₆, TC 90₅₇₅. + '4r3m5g4m4e,' + // #4709: 5 fonts: HK 94₁₂₁, JP 77₂₁₃, KR 71₃₃₁, SC 85₄₆₉, TC 90₅₇₅. + '4r3n4n5h4b,' + // #4710: 5 fonts: HK 94₁₂₁, JP 77₂₁₃, KR 72₃₃₂, SC 84₄₆₈, TC 90₅₇₅. + '4r3n4o5f4c,' + // #4711: 5 fonts: HK 94₁₂₁, JP 77₂₁₃, KR 78₃₃₈, SC 87₄₇₁, TC 90₅₇₅. + '4r3n4u5c3z,' + // #4712: 5 fonts: HK 94₁₂₁, JP 77₂₁₃, KR 85₃₄₅, SC 86₄₇₀, TC 90₅₇₅. + '4r3n5b4u4a,' + // #4713: 5 fonts: HK 94₁₂₁, JP 78₂₁₄, KR 68₃₂₈, SC 84₄₆₈, TC 90₅₇₅. + '4r3o4j5j4c,' + // #4714: 5 fonts: HK 94₁₂₁, JP 78₂₁₄, KR 69₃₂₉, SC 15₃₉₉, TC 90₅₇₅. + '4r3o4k2r6t,' + // #4715: 5 fonts: HK 94₁₂₁, JP 78₂₁₄, KR 84₃₄₄, SC 86₄₇₀, TC 90₅₇₅. + '4r3o4z4v4a,' + // #4716: 5 fonts: HK 94₁₂₁, JP 79₂₁₅, KR 68₃₂₈, SC 88₄₇₂, TC 90₅₇₅. + '4r3p4i5n3y,' + // #4717: 5 fonts: HK 94₁₂₁, JP 79₂₁₅, KR 71₃₃₁, SC 85₄₆₉, TC 90₅₇₅. + '4r3p4l5h4b,' + // #4718: 5 fonts: HK 94₁₂₁, JP 79₂₁₅, KR 73₃₃₃, SC 25₄₀₉, TC 90₅₇₅. + '4r3p4n2x6j,' + // #4719: 5 fonts: HK 94₁₂₁, JP 79₂₁₅, KR 74₃₃₄, SC 26₄₁₀, TC 90₅₇₅. + '4r3p4o2x6i,' + // #4720: 5 fonts: HK 94₁₂₁, JP 80₂₁₆, KR 68₃₂₈, SC 84₄₆₈, TC 90₅₇₅. + '4r3q4h5j4c,' + // #4721: 5 fonts: HK 94₁₂₁, JP 80₂₁₆, KR 76₃₃₆, SC 86₄₇₀, TC 90₅₇₅. + '4r3q4p5d4a,' + // #4722: 5 fonts: HK 94₁₂₁, JP 80₂₁₆, KR 86₃₄₆, SC 91₄₇₅, TC 90₅₇₅. + '4r3q4z4y3v,' + // #4723: 5 fonts: HK 94₁₂₁, JP 80₂₁₆, KR 93₃₅₃, SC 85₄₆₉, TC 90₅₇₅. + '4r3q5g4l4b,' + // #4724: 5 fonts: HK 94₁₂₁, JP 81₂₁₇, KR 65₃₂₅, SC 5₃₈₉, TC 90₅₇₅. + '4r3r4d2l7d,' + // #4725: 5 fonts: HK 94₁₂₁, JP 81₂₁₇, KR 73₃₃₃, SC 85₄₆₉, TC 90₅₇₅. + '4r3r4l5f4b,' + // #4726: 5 fonts: HK 94₁₂₁, JP 81₂₁₇, KR 78₃₃₈, SC 86₄₇₀, TC 90₅₇₅. + '4r3r4q5b4a,' + // #4727: 5 fonts: HK 94₁₂₁, JP 81₂₁₇, KR 90₃₅₀, SC 88₄₇₂, TC 90₅₇₅. + '4r3r5c4r3y,' + // #4728: 5 fonts: HK 94₁₂₁, JP 82₂₁₈, KR 67₃₂₇, SC 86₄₇₀, TC 90₅₇₅. + '4r3s4e5m4a,' + // #4729: 5 fonts: HK 94₁₂₁, JP 82₂₁₈, KR 71₃₃₁, SC 87₄₇₁, TC 90₅₇₅. + '4r3s4i5j3z,' + // #4730: 5 fonts: HK 94₁₂₁, JP 82₂₁₈, KR 72₃₃₂, SC 85₄₆₉, TC 90₅₇₅. + '4r3s4j5g4b,' + // #4731: 5 fonts: HK 94₁₂₁, JP 83₂₁₉, KR 75₃₃₅, SC 86₄₇₀, TC 90₅₇₅. + '4r3t4l5e4a,' + // #4732: 5 fonts: HK 94₁₂₁, JP 83₂₁₉, KR 84₃₄₄, SC 85₄₆₉, TC 90₅₇₅. + '4r3t4u4u4b,' + // #4733: 10 fonts: HK 94₁₂₁, JP 83₂₁₉, KR 101₃₆₁, SC 77₄₆₁, TC 90₅₇₅, Mongolian₆₆₂, New Tai Lue₆₆₈, Phags Pa₆₈₇, Tai Le₇₀₆, Yi₇₂₁. + '4r3t5l3v4j3ifsso,' + // #4734: 10 fonts: HK 94₁₂₁, JP 83₂₁₉, KR 101₃₆₁, SC 78₄₆₂, TC 90₅₇₅, Mongolian₆₆₂, New Tai Lue₆₆₈, Phags Pa₆₈₇, Tai Le₇₀₆, Yi₇₂₁. + '4r3t5l3w4i3ifsso,' + // #4735: 5 fonts: HK 94₁₂₁, JP 84₂₂₀, KR 68₃₂₈, SC 85₄₆₉, TC 90₅₇₅. + '4r3u4d5k4b,' + // #4736: 5 fonts: HK 94₁₂₁, JP 84₂₂₀, KR 75₃₃₅, SC 85₄₆₉, TC 90₅₇₅. + '4r3u4k5d4b,' + // #4737: 5 fonts: HK 94₁₂₁, JP 84₂₂₀, KR 80₃₄₀, SC 86₄₇₀, TC 90₅₇₅. + '4r3u4p4z4a,' + // #4738: 5 fonts: HK 94₁₂₁, JP 85₂₂₁, KR 73₃₃₃, SC 85₄₆₉, TC 90₅₇₅. + '4r3v4h5f4b,' + // #4739: 5 fonts: HK 94₁₂₁, JP 86₂₂₂, KR 69₃₂₉, SC 17₄₀₁, TC 90₅₇₅. + '4r3w4c2t6r,' + // #4740: 5 fonts: HK 94₁₂₁, JP 87₂₂₃, KR 67₃₂₇, SC 10₃₉₄, TC 90₅₇₅. + '4r3x3z2o6y,' + // #4741: 5 fonts: HK 94₁₂₁, JP 87₂₂₃, KR 72₃₃₂, SC 86₄₇₀, TC 90₅₇₅. + '4r3x4e5h4a,' + // #4742: 5 fonts: HK 94₁₂₁, JP 87₂₂₃, KR 81₃₄₁, SC 86₄₇₀, TC 90₅₇₅. + '4r3x4n4y4a,' + // #4743: 5 fonts: HK 94₁₂₁, JP 87₂₂₃, KR 83₃₄₃, SC 85₄₆₉, TC 90₅₇₅. + '4r3x4p4v4b,' + // #4744: 5 fonts: HK 94₁₂₁, JP 87₂₂₃, KR 91₃₅₁, SC 86₄₇₀, TC 90₅₇₅. + '4r3x4x4o4a,' + // #4745: 5 fonts: HK 94₁₂₁, JP 87₂₂₃, KR 92₃₅₂, SC 65₄₄₉, TC 90₅₇₅. + '4r3x4y3s4v,' + // #4746: 5 fonts: HK 94₁₂₁, JP 88₂₂₄, KR 69₃₂₉, SC 15₃₉₉, TC 90₅₇₅. + '4r3y4a2r6t,' + // #4747: 5 fonts: HK 94₁₂₁, JP 88₂₂₄, KR 71₃₃₁, SC 86₄₇₀, TC 90₅₇₅. + '4r3y4c5i4a,' + // #4748: 5 fonts: HK 94₁₂₁, JP 89₂₂₅, KR 70₃₃₀, SC 18₄₀₂, TC 90₅₇₅. + '4r3z4a2t6q,' + // #4749: 5 fonts: HK 94₁₂₁, JP 89₂₂₅, KR 81₃₄₁, SC 86₄₇₀, TC 90₅₇₅. + '4r3z4l4y4a,' + // #4750: 5 fonts: HK 94₁₂₁, JP 89₂₂₅, KR 89₃₄₉, SC 57₄₄₁, TC 90₅₇₅. + '4r3z4t3n5d,' + // #4751: 5 fonts: HK 94₁₂₁, JP 89₂₂₅, KR 89₃₄₉, SC 84₄₆₈, TC 90₅₇₅. + '4r3z4t4o4c,' + // #4752: 5 fonts: HK 94₁₂₁, JP 90₂₂₆, KR 65₃₂₅, SC 5₃₈₉, TC 90₅₇₅. + '4r4a3u2l7d,' + // #4753: 5 fonts: HK 94₁₂₁, JP 90₂₂₆, KR 78₃₃₈, SC 37₄₂₁, TC 90₅₇₅. + '4r4a4h3e5x,' + // #4754: 5 fonts: HK 94₁₂₁, JP 90₂₂₆, KR 87₃₄₇, SC 85₄₆₉, TC 90₅₇₅. + '4r4a4q4r4b,' + // #4755: 5 fonts: HK 94₁₂₁, JP 90₂₂₆, KR 89₃₄₉, SC 87₄₇₁, TC 90₅₇₅. + '4r4a4s4r3z,' + // #4756: 5 fonts: HK 94₁₂₁, JP 91₂₂₇, KR 73₃₃₃, SC 84₄₆₈, TC 90₅₇₅. + '4r4b4b5e4c,' + // #4757: 5 fonts: HK 94₁₂₁, JP 91₂₂₇, KR 78₃₃₈, SC 86₄₇₀, TC 90₅₇₅. + '4r4b4g5b4a,' + // #4758: 5 fonts: HK 94₁₂₁, JP 91₂₂₇, KR 79₃₃₉, SC 88₄₇₂, TC 90₅₇₅. + '4r4b4h5c3y,' + // #4759: 5 fonts: HK 94₁₂₁, JP 91₂₂₇, KR 87₃₄₇, SC 85₄₆₉, TC 90₅₇₅. + '4r4b4p4r4b,' + // #4760: 5 fonts: HK 94₁₂₁, JP 92₂₂₈, KR 71₃₃₁, SC 86₄₇₀, TC 90₅₇₅. + '4r4c3y5i4a,' + // #4761: 5 fonts: HK 94₁₂₁, JP 92₂₂₈, KR 76₃₃₆, SC 85₄₆₉, TC 90₅₇₅. + '4r4c4d5c4b,' + // #4762: 5 fonts: HK 94₁₂₁, JP 92₂₂₈, KR 77₃₃₇, SC 34₄₁₈, TC 90₅₇₅. + '4r4c4e3c6a,' + // #4763: 5 fonts: HK 94₁₂₁, JP 92₂₂₈, KR 84₃₄₄, SC 87₄₇₁, TC 90₅₇₅. + '4r4c4l4w3z,' + // #4764: 5 fonts: HK 94₁₂₁, JP 92₂₂₈, KR 88₃₄₈, SC 86₄₇₀, TC 90₅₇₅. + '4r4c4p4r4a,' + // #4765: 5 fonts: HK 94₁₂₁, JP 93₂₂₉, KR 76₃₃₆, SC 88₄₇₂, TC 90₅₇₅. + '4r4d4c5f3y,' + // #4766: 5 fonts: HK 94₁₂₁, JP 93₂₂₉, KR 81₃₄₁, SC 87₄₇₁, TC 90₅₇₅. + '4r4d4h4z3z,' + // #4767: 5 fonts: HK 94₁₂₁, JP 94₂₃₀, KR 76₃₃₆, SC 85₄₆₉, TC 90₅₇₅. + '4r4e4b5c4b,' + // #4768: 5 fonts: HK 94₁₂₁, JP 94₂₃₀, KR 76₃₃₆, SC 86₄₇₀, TC 90₅₇₅. + '4r4e4b5d4a,' + // #4769: 5 fonts: HK 94₁₂₁, JP 94₂₃₀, KR 83₃₄₃, SC 48₄₃₂, TC 90₅₇₅. + '4r4e4i3k5m,' + // #4770: 5 fonts: HK 94₁₂₁, JP 94₂₃₀, KR 93₃₅₃, SC 85₄₆₉, TC 90₅₇₅. + '4r4e4s4l4b,' + // #4771: 5 fonts: HK 94₁₂₁, JP 96₂₃₂, KR 67₃₂₇, SC 13₃₉₇, TC 90₅₇₅. + '4r4g3q2r6v,' + // #4772: 5 fonts: HK 94₁₂₁, JP 96₂₃₂, KR 73₃₃₃, SC 86₄₇₀, TC 90₅₇₅. + '4r4g3w5g4a,' + // #4773: 5 fonts: HK 94₁₂₁, JP 96₂₃₂, KR 78₃₃₈, SC 84₄₆₈, TC 90₅₇₅. + '4r4g4b4z4c,' + // #4774: 5 fonts: HK 94₁₂₁, JP 96₂₃₂, KR 89₃₄₉, SC 84₄₆₈, TC 90₅₇₅. + '4r4g4m4o4c,' + // #4775: 5 fonts: HK 94₁₂₁, JP 97₂₃₃, KR 69₃₂₉, SC 15₃₉₉, TC 90₅₇₅. + '4r4h3r2r6t,' + // #4776: 5 fonts: HK 94₁₂₁, JP 99₂₃₅, KR 88₃₄₈, SC 86₄₇₀, TC 90₅₇₅. + '4r4j4i4r4a,' + // #4777: 5 fonts: HK 94₁₂₁, JP 100₂₃₆, KR 83₃₄₃, SC 84₄₆₈, TC 90₅₇₅. + '4r4k4c4u4c,' + // #4778: 5 fonts: HK 94₁₂₁, JP 101₂₃₇, KR 82₃₄₂, SC 84₄₆₈, TC 90₅₇₅. + '4r4l4a4v4c,' + // #4779: 5 fonts: HK 94₁₂₁, JP 102₂₃₈, KR 80₃₄₀, SC 85₄₆₉, TC 90₅₇₅. + '4r4m3x4y4b,' + // #4780: 5 fonts: HK 94₁₂₁, JP 103₂₃₉, KR 76₃₃₆, SC 83₄₆₇, TC 90₅₇₅. + '4r4n3s5a4d,' + // #4781: 5 fonts: HK 94₁₂₁, JP 104₂₄₀, KR 69₃₂₉, SC 16₄₀₀, TC 90₅₇₅. + '4r4o3k2s6s,' + // #4782: 5 fonts: HK 94₁₂₁, JP 104₂₄₀, KR 86₃₄₆, SC 87₄₇₁, TC 90₅₇₅. + '4r4o4b4u3z,' + // #4783: 5 fonts: HK 94₁₂₁, JP 104₂₄₀, KR 93₃₅₃, SC 84₄₆₈, TC 90₅₇₅. + '4r4o4i4k4c,' + // #4784: 5 fonts: HK 94₁₂₁, JP 105₂₄₁, KR 86₃₄₆, SC 85₄₆₉, TC 90₅₇₅. + '4r4p4a4s4b,' + // #4785: 5 fonts: HK 94₁₂₁, JP 107₂₄₃, KR 69₃₂₉, SC 85₄₆₉, TC 90₅₇₅. + '4r4r3h5j4b,' + // #4786: 5 fonts: HK 94₁₂₁, JP 107₂₄₃, KR 71₃₃₁, SC 84₄₆₈, TC 90₅₇₅. + '4r4r3j5g4c,' + // #4787: 5 fonts: HK 94₁₂₁, JP 107₂₄₃, KR 91₃₅₁, SC 85₄₆₉, TC 90₅₇₅. + '4r4r4d4n4b,' + // #4788: 5 fonts: HK 94₁₂₁, JP 107₂₄₃, KR 101₃₆₁, SC 78₄₆₂, TC 90₅₇₅. + '4r4r4n3w4i,' + // #4789: 5 fonts: HK 94₁₂₁, JP 108₂₄₄, KR 67₃₂₇, SC 77₄₆₁, TC 90₅₇₅. + '4r4s3e5d4j,' + // #4790: 5 fonts: HK 94₁₂₁, JP 110₂₄₆, KR 87₃₄₇, SC 55₄₃₉, TC 90₅₇₅. + '4r4u3w3n5f,' + // #4791: 5 fonts: HK 94₁₂₁, JP 113₂₄₉, KR 0₂₆₀, SC 80₄₆₄, TC 90₅₇₅. + '4r4xk7v4g,' + // #4792: 5 fonts: HK 94₁₂₁, JP 113₂₄₉, KR 93₃₅₃, SC 83₄₆₇, TC 90₅₇₅. + '4r4x3z4j4d,' + // #4793: 3 fonts: HK 94₁₂₁, SC 85₄₆₉, TC 90₅₇₅. + '4r13j4b,' + // #4794: 3 fonts: HK 94₁₂₁, SC 87₄₇₁, TC 90₅₇₅. + '4r13l3z,' + // #4795: 5 fonts: HK 95₁₂₂, JP 5₁₄₁, KR 65₃₂₅, SC 4₃₈₈, TC 91₅₇₆. + '4ss7b2k7f,' + // #4796: 5 fonts: HK 95₁₂₂, JP 8₁₄₄, KR 65₃₂₅, SC 7₃₉₁, TC 91₅₇₆. + '4sv6y2n7c,' + // #4797: 5 fonts: HK 95₁₂₂, JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 91₅₇₆. + '4sv6z2m7c,' + // #4798: 5 fonts: HK 95₁₂₂, JP 8₁₄₄, KR 66₃₂₆, SC 87₄₇₁, TC 91₅₇₆. + '4sv6z5o4a,' + // #4799: 5 fonts: HK 95₁₂₂, JP 10₁₄₆, KR 67₃₂₇, SC 87₄₇₁, TC 91₅₇₆. + '4sx6y5n4a,' + // #4800: 4 fonts: HK 95₁₂₂, JP 10₁₄₆, SC 9₃₉₃, TC 91₅₇₆. + '4sx9m7a,' + // #4801: 5 fonts: HK 95₁₂₂, JP 11₁₄₇, KR 67₃₂₇, SC 12₃₉₆, TC 91₅₇₆. + '4sy6x2q6x,' + // #4802: 5 fonts: HK 95₁₂₂, JP 11₁₄₇, KR 67₃₂₇, SC 13₃₉₇, TC 91₅₇₆. + '4sy6x2r6w,' + // #4803: 4 fonts: HK 95₁₂₂, JP 11₁₄₇, SC 12₃₉₆, TC 91₅₇₆. + '4sy9o6x,' + // #4804: 5 fonts: HK 95₁₂₂, JP 12₁₄₈, KR 68₃₂₈, SC 13₃₉₇, TC 91₅₇₆. + '4sz6x2q6w,' + // #4805: 5 fonts: HK 95₁₂₂, JP 13₁₄₉, KR 68₃₂₈, SC 15₃₉₉, TC 91₅₇₆. + '4s1a6w2s6u,' + // #4806: 5 fonts: HK 95₁₂₂, JP 13₁₄₉, KR 68₃₂₈, SC 88₄₇₂, TC 91₅₇₆. + '4s1a6w5n3z,' + // #4807: 4 fonts: HK 95₁₂₂, JP 13₁₄₉, SC 14₃₉₈, TC 91₅₇₆. + '4s1a9o6v,' + // #4808: 5 fonts: HK 95₁₂₂, JP 14₁₅₀, KR 69₃₂₉, SC 86₄₇₀, TC 91₅₇₆. + '4s1b6w5k4b,' + // #4809: 4 fonts: HK 95₁₂₂, JP 14₁₅₀, SC 82₄₆₆, TC 91₅₇₆. + '4s1b12d4f,' + // #4810: 5 fonts: HK 95₁₂₂, JP 15₁₅₁, KR 69₃₂₉, SC 16₄₀₀, TC 91₅₇₆. + '4s1c6v2s6t,' + // #4811: 4 fonts: HK 95₁₂₂, JP 15₁₅₁, SC 16₄₀₀, TC 91₅₇₆. + '4s1c9o6t,' + // #4812: 5 fonts: HK 95₁₂₂, JP 16₁₅₂, KR 70₃₃₀, SC 18₄₀₂, TC 91₅₇₆. + '4s1d6v2t6r,' + // #4813: 5 fonts: HK 95₁₂₂, JP 17₁₅₃, KR 71₃₃₁, SC 20₄₀₄, TC 91₅₇₆. + '4s1e6v2u6p,' + // #4814: 5 fonts: HK 95₁₂₂, JP 17₁₅₃, KR 71₃₃₁, SC 87₄₇₁, TC 91₅₇₆. + '4s1e6v5j4a,' + // #4815: 5 fonts: HK 95₁₂₂, JP 20₁₅₆, KR 72₃₃₂, SC 92₄₇₆, TC 91₅₇₆. + '4s1h6t5n3v,' + // #4816: 4 fonts: HK 95₁₂₂, JP 20₁₅₆, SC 86₄₇₀, TC 91₅₇₆. + '4s1h12b4b,' + // #4817: 5 fonts: HK 95₁₂₂, JP 21₁₅₇, KR 72₃₃₂, SC 85₄₆₉, TC 91₅₇₆. + '4s1i6s5g4c,' + // #4818: 5 fonts: HK 95₁₂₂, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 91₅₇₆. + '4s1i6t2w6l,' + // #4819: 4 fonts: HK 95₁₂₂, JP 22₁₅₈, SC 86₄₇₀, TC 91₅₇₆. + '4s1j11z4b,' + // #4820: 5 fonts: HK 95₁₂₂, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 91₅₇₆. + '4s1k6s2y6i,' + // #4821: 4 fonts: HK 95₁₂₂, JP 23₁₅₉, SC 85₄₆₉, TC 91₅₇₆. + '4s1k11x4c,' + // #4822: 5 fonts: HK 95₁₂₂, JP 24₁₆₀, KR 75₃₃₅, SC 29₄₁₃, TC 91₅₇₆. + '4s1l6s2z6g,' + // #4823: 5 fonts: HK 95₁₂₂, JP 26₁₆₂, KR 76₃₃₆, SC 87₄₇₁, TC 91₅₇₆. + '4s1n6r5e4a,' + // #4824: 4 fonts: HK 95₁₂₂, JP 26₁₆₂, SC 82₄₆₆, TC 91₅₇₆. + '4s1n11r4f,' + // #4825: 4 fonts: HK 95₁₂₂, JP 26₁₆₂, SC 87₄₇₁, TC 91₅₇₆. + '4s1n11w4a,' + // #4826: 5 fonts: HK 95₁₂₂, JP 27₁₆₃, KR 77₃₃₇, SC 34₄₁₈, TC 91₅₇₆. + '4s1o6r3c6b,' + // #4827: 4 fonts: HK 95₁₂₂, JP 28₁₆₄, SC 35₄₁₉, TC 91₅₇₆. + '4s1p9u6a,' + // #4828: 5 fonts: HK 95₁₂₂, JP 32₁₆₈, KR 80₃₄₀, SC 40₄₂₄, TC 91₅₇₆. + '4s1t6p3f5v,' + // #4829: 5 fonts: HK 95₁₂₂, JP 32₁₆₈, KR 80₃₄₀, SC 85₄₆₉, TC 91₅₇₆. + '4s1t6p4y4c,' + // #4830: 5 fonts: HK 95₁₂₂, JP 32₁₆₈, KR 80₃₄₀, SC 90₄₇₄, TC 91₅₇₆. + '4s1t6p5d3x,' + // #4831: 5 fonts: HK 95₁₂₂, JP 33₁₆₉, KR 81₃₄₁, SC 43₄₂₇, TC 91₅₇₆. + '4s1u6p3h5s,' + // #4832: 5 fonts: HK 95₁₂₂, JP 33₁₆₉, KR 81₃₄₁, SC 83₄₆₇, TC 91₅₇₆. + '4s1u6p4v4e,' + // #4833: 5 fonts: HK 95₁₂₂, JP 33₁₆₉, KR 81₃₄₁, SC 87₄₇₁, TC 91₅₇₆. + '4s1u6p4z4a,' + // #4834: 4 fonts: HK 95₁₂₂, JP 33₁₆₉, SC 43₄₂₇, TC 91₅₇₆. + '4s1u9x5s,' + // #4835: 4 fonts: HK 95₁₂₂, JP 33₁₆₉, SC 85₄₆₉, TC 91₅₇₆. + '4s1u11n4c,' + // #4836: 5 fonts: HK 95₁₂₂, JP 36₁₇₂, KR 83₃₄₃, SC 84₄₆₈, TC 91₅₇₆. + '4s1x6o4u4d,' + // #4837: 4 fonts: HK 95₁₂₂, JP 36₁₇₂, SC 86₄₇₀, TC 91₅₇₆. + '4s1x11l4b,' + // #4838: 4 fonts: HK 95₁₂₂, JP 36₁₇₂, SC 87₄₇₁, TC 91₅₇₆. + '4s1x11m4a,' + // #4839: 5 fonts: HK 95₁₂₂, JP 38₁₇₄, KR 85₃₄₅, SC 86₄₇₀, TC 91₅₇₆. + '4s1z6o4u4b,' + // #4840: 5 fonts: HK 95₁₂₂, JP 38₁₇₄, KR 85₃₄₅, SC 87₄₇₁, TC 91₅₇₆. + '4s1z6o4v4a,' + // #4841: 4 fonts: HK 95₁₂₂, JP 38₁₇₄, SC 49₄₃₃, TC 91₅₇₆. + '4s1z9y5m,' + // #4842: 5 fonts: HK 95₁₂₂, JP 39₁₇₅, KR 85₃₄₅, SC 51₄₃₅, TC 91₅₇₆. + '4s2a6n3l5k,' + // #4843: 5 fonts: HK 95₁₂₂, JP 40₁₇₆, KR 86₃₄₆, SC 52₄₃₆, TC 91₅₇₆. + '4s2b6n3l5j,' + // #4844: 5 fonts: HK 95₁₂₂, JP 40₁₇₆, KR 86₃₄₆, SC 53₄₃₇, TC 91₅₇₆. + '4s2b6n3m5i,' + // #4845: 5 fonts: HK 95₁₂₂, JP 40₁₇₆, KR 86₃₄₆, SC 88₄₇₂, TC 91₅₇₆. + '4s2b6n4v3z,' + // #4846: 4 fonts: HK 95₁₂₂, JP 40₁₇₆, SC 82₄₆₆, TC 91₅₇₆. + '4s2b11d4f,' + // #4847: 5 fonts: HK 95₁₂₂, JP 43₁₇₉, KR 88₃₄₈, SC 87₄₇₁, TC 91₅₇₆. + '4s2e6m4s4a,' + // #4848: 4 fonts: HK 95₁₂₂, JP 43₁₇₉, SC 57₄₄₁, TC 91₅₇₆. + '4s2e10b5e,' + // #4849: 4 fonts: HK 95₁₂₂, JP 43₁₇₉, SC 85₄₆₉, TC 91₅₇₆. + '4s2e11d4c,' + // #4850: 5 fonts: HK 95₁₂₂, JP 44₁₈₀, KR 89₃₄₉, SC 82₄₆₆, TC 91₅₇₆. + '4s2f6m4m4f,' + // #4851: 5 fonts: HK 95₁₂₂, JP 44₁₈₀, KR 89₃₄₉, SC 86₄₇₀, TC 91₅₇₆. + '4s2f6m4q4b,' + // #4852: 4 fonts: HK 95₁₂₂, JP 44₁₈₀, SC 81₄₆₅, TC 91₅₇₆. + '4s2f10y4g,' + // #4853: 5 fonts: HK 95₁₂₂, JP 45₁₈₁, KR 90₃₅₀, SC 60₄₄₄, TC 91₅₇₆. + '4s2g6m3p5b,' + // #4854: 5 fonts: HK 95₁₂₂, JP 46₁₈₂, KR 90₃₅₀, SC 61₄₄₅, TC 91₅₇₆. + '4s2h6l3q5a,' + // #4855: 5 fonts: HK 95₁₂₂, JP 46₁₈₂, KR 90₃₅₀, SC 85₄₆₉, TC 91₅₇₆. + '4s2h6l4o4c,' + // #4856: 4 fonts: HK 95₁₂₂, JP 46₁₈₂, SC 61₄₄₅, TC 91₅₇₆. + '4s2h10c5a,' + // #4857: 5 fonts: HK 95₁₂₂, JP 47₁₈₃, KR 91₃₅₁, SC 87₄₇₁, TC 91₅₇₆. + '4s2i6l4p4a,' + // #4858: 4 fonts: HK 95₁₂₂, JP 47₁₈₃, SC 62₄₄₆, TC 91₅₇₆. + '4s2i10c4z,' + // #4859: 4 fonts: HK 95₁₂₂, JP 48₁₈₄, SC 86₄₇₀, TC 91₅₇₆. + '4s2j10z4b,' + // #4860: 5 fonts: HK 95₁₂₂, JP 49₁₈₅, KR 92₃₅₂, SC 86₄₇₀, TC 91₅₇₆. + '4s2k6k4n4b,' + // #4861: 5 fonts: HK 95₁₂₂, JP 49₁₈₅, KR 92₃₅₂, SC 88₄₇₂, TC 91₅₇₆. + '4s2k6k4p3z,' + // #4862: 4 fonts: HK 95₁₂₂, JP 49₁₈₅, SC 85₄₆₉, TC 91₅₇₆. + '4s2k10x4c,' + // #4863: 4 fonts: HK 95₁₂₂, JP 50₁₈₆, SC 66₄₅₀, TC 91₅₇₆. + '4s2l10d4v,' + // #4864: 5 fonts: HK 95₁₂₂, JP 60₁₉₆, KR 85₃₄₅, SC 87₄₇₁, TC 91₅₇₆. + '4s2v5s4v4a,' + // #4865: 5 fonts: HK 95₁₂₂, JP 61₁₉₇, KR 68₃₂₈, SC 14₃₉₈, TC 91₅₇₆. + '4s2w5a2r6v,' + // #4866: 5 fonts: HK 95₁₂₂, JP 61₁₉₇, KR 71₃₃₁, SC 88₄₇₂, TC 91₅₇₆. + '4s2w5d5k3z,' + // #4867: 5 fonts: HK 95₁₂₂, JP 61₁₉₇, KR 89₃₄₉, SC 86₄₇₀, TC 91₅₇₆. + '4s2w5v4q4b,' + // #4868: 5 fonts: HK 95₁₂₂, JP 62₁₉₈, KR 89₃₄₉, SC 87₄₇₁, TC 91₅₇₆. + '4s2x5u4r4a,' + // #4869: 5 fonts: HK 95₁₂₂, JP 63₁₉₉, KR 68₃₂₈, SC 88₄₇₂, TC 91₅₇₆. + '4s2y4y5n3z,' + // #4870: 5 fonts: HK 95₁₂₂, JP 65₂₀₁, KR 65₃₂₅, SC 5₃₈₉, TC 91₅₇₆. + '4s3a4t2l7e,' + // #4871: 5 fonts: HK 95₁₂₂, JP 65₂₀₁, KR 72₃₃₂, SC 89₄₇₃, TC 91₅₇₆. + '4s3a5a5k3y,' + // #4872: 5 fonts: HK 95₁₂₂, JP 66₂₀₂, KR 76₃₃₆, SC 84₄₆₈, TC 91₅₇₆. + '4s3b5d5b4d,' + // #4873: 5 fonts: HK 95₁₂₂, JP 66₂₀₂, KR 82₃₄₂, SC 84₄₆₈, TC 91₅₇₆. + '4s3b5j4v4d,' + // #4874: 5 fonts: HK 95₁₂₂, JP 67₂₀₃, KR 68₃₂₈, SC 88₄₇₂, TC 91₅₇₆. + '4s3c4u5n3z,' + // #4875: 5 fonts: HK 95₁₂₂, JP 67₂₀₃, KR 71₃₃₁, SC 86₄₇₀, TC 91₅₇₆. + '4s3c4x5i4b,' + // #4876: 5 fonts: HK 95₁₂₂, JP 68₂₀₄, KR 84₃₄₄, SC 49₄₃₃, TC 91₅₇₆. + '4s3d5j3k5m,' + // #4877: 5 fonts: HK 95₁₂₂, JP 68₂₀₄, KR 85₃₄₅, SC 51₄₃₅, TC 91₅₇₆. + '4s3d5k3l5k,' + // #4878: 5 fonts: HK 95₁₂₂, JP 68₂₀₄, KR 87₃₄₇, SC 87₄₇₁, TC 91₅₇₆. + '4s3d5m4t4a,' + // #4879: 5 fonts: HK 95₁₂₂, JP 69₂₀₅, KR 68₃₂₈, SC 87₄₇₁, TC 91₅₇₆. + '4s3e4s5m4a,' + // #4880: 5 fonts: HK 95₁₂₂, JP 69₂₀₅, KR 71₃₃₁, SC 21₄₀₅, TC 91₅₇₆. + '4s3e4v2v6o,' + // #4881: 5 fonts: HK 95₁₂₂, JP 69₂₀₅, KR 77₃₃₇, SC 85₄₆₉, TC 91₅₇₆. + '4s3e5b5b4c,' + // #4882: 5 fonts: HK 95₁₂₂, JP 69₂₀₅, KR 78₃₃₈, SC 37₄₂₁, TC 91₅₇₆. + '4s3e5c3e5y,' + // #4883: 5 fonts: HK 95₁₂₂, JP 69₂₀₅, KR 82₃₄₂, SC 88₄₇₂, TC 91₅₇₆. + '4s3e5g4z3z,' + // #4884: 5 fonts: HK 95₁₂₂, JP 69₂₀₅, KR 101₃₆₁, SC 86₄₇₀, TC 91₅₇₆. + '4s3e5z4e4b,' + // #4885: 5 fonts: HK 95₁₂₂, JP 70₂₀₆, KR 74₃₃₄, SC 84₄₆₈, TC 91₅₇₆. + '4s3f4x5d4d,' + // #4886: 5 fonts: HK 95₁₂₂, JP 70₂₀₆, KR 89₃₄₉, SC 58₄₄₂, TC 91₅₇₆. + '4s3f5m3o5d,' + // #4887: 4 fonts: HK 95₁₂₂, JP 71₂₀₇, SC 96₄₈₀, TC 91₅₇₆. + '4s3g10m3r,' + // #4888: 5 fonts: HK 95₁₂₂, JP 72₂₀₈, KR 77₃₃₇, SC 86₄₇₀, TC 91₅₇₆. + '4s3h4y5c4b,' + // #4889: 5 fonts: HK 95₁₂₂, JP 72₂₀₈, KR 78₃₃₈, SC 38₄₂₂, TC 91₅₇₆. + '4s3h4z3f5x,' + // #4890: 5 fonts: HK 95₁₂₂, JP 72₂₀₈, KR 83₃₄₃, SC 87₄₇₁, TC 91₅₇₆. + '4s3h5e4x4a,' + // #4891: 5 fonts: HK 95₁₂₂, JP 72₂₀₈, KR 86₃₄₆, SC 85₄₆₉, TC 91₅₇₆. + '4s3h5h4s4c,' + // #4892: 5 fonts: HK 95₁₂₂, JP 73₂₀₉, KR 87₃₄₇, SC 89₄₇₃, TC 91₅₇₆. + '4s3i5h4v3y,' + // #4893: 5 fonts: HK 95₁₂₂, JP 74₂₁₀, KR 79₃₃₉, SC 86₄₇₀, TC 91₅₇₆. + '4s3j4y5a4b,' + // #4894: 5 fonts: HK 95₁₂₂, JP 74₂₁₀, KR 80₃₄₀, SC 86₄₇₀, TC 91₅₇₆. + '4s3j4z4z4b,' + // #4895: 5 fonts: HK 95₁₂₂, JP 74₂₁₀, KR 86₃₄₆, SC 85₄₆₉, TC 91₅₇₆. + '4s3j5f4s4c,' + // #4896: 5 fonts: HK 95₁₂₂, JP 75₂₁₁, KR 74₃₃₄, SC 87₄₇₁, TC 91₅₇₆. + '4s3k4s5g4a,' + // #4897: 5 fonts: HK 95₁₂₂, JP 75₂₁₁, KR 84₃₄₄, SC 85₄₆₉, TC 91₅₇₆. + '4s3k5c4u4c,' + // #4898: 5 fonts: HK 95₁₂₂, JP 75₂₁₁, KR 91₃₅₁, SC 87₄₇₁, TC 91₅₇₆. + '4s3k5j4p4a,' + // #4899: 5 fonts: HK 95₁₂₂, JP 76₂₁₂, KR 69₃₂₉, SC 87₄₇₁, TC 91₅₇₆. + '4s3l4m5l4a,' + // #4900: 5 fonts: HK 95₁₂₂, JP 77₂₁₃, KR 67₃₂₇, SC 13₃₉₇, TC 91₅₇₆. + '4s3m4j2r6w,' + // #4901: 5 fonts: HK 95₁₂₂, JP 77₂₁₃, KR 84₃₄₄, SC 86₄₇₀, TC 91₅₇₆. + '4s3m5a4v4b,' + // #4902: 5 fonts: HK 95₁₂₂, JP 77₂₁₃, KR 90₃₅₀, SC 87₄₇₁, TC 91₅₇₆. + '4s3m5g4q4a,' + // #4903: 5 fonts: HK 95₁₂₂, JP 78₂₁₄, KR 83₃₄₃, SC 87₄₇₁, TC 91₅₇₆. + '4s3n4y4x4a,' + // #4904: 5 fonts: HK 95₁₂₂, JP 79₂₁₅, KR 80₃₄₀, SC 41₄₂₅, TC 91₅₇₆. + '4s3o4u3g5u,' + // #4905: 5 fonts: HK 95₁₂₂, JP 79₂₁₅, KR 84₃₄₄, SC 87₄₇₁, TC 91₅₇₆. + '4s3o4y4w4a,' + // #4906: 5 fonts: HK 95₁₂₂, JP 79₂₁₅, KR 86₃₄₆, SC 88₄₇₂, TC 91₅₇₆. + '4s3o5a4v3z,' + // #4907: 5 fonts: HK 95₁₂₂, JP 79₂₁₅, KR 88₃₄₈, SC 87₄₇₁, TC 91₅₇₆. + '4s3o5c4s4a,' + // #4908: 5 fonts: HK 95₁₂₂, JP 80₂₁₆, KR 65₃₂₅, SC 7₃₉₁, TC 91₅₇₆. + '4s3p4e2n7c,' + // #4909: 5 fonts: HK 95₁₂₂, JP 80₂₁₆, KR 67₃₂₇, SC 13₃₉₇, TC 91₅₇₆. + '4s3p4g2r6w,' + // #4910: 5 fonts: HK 95₁₂₂, JP 81₂₁₇, KR 66₃₂₆, SC 9₃₉₃, TC 91₅₇₆. + '4s3q4e2o7a,' + // #4911: 5 fonts: HK 95₁₂₂, JP 81₂₁₇, KR 77₃₃₇, SC 88₄₇₂, TC 91₅₇₆. + '4s3q4p5e3z,' + // #4912: 5 fonts: HK 95₁₂₂, JP 82₂₁₈, KR 67₃₂₇, SC 12₃₉₆, TC 91₅₇₆. + '4s3r4e2q6x,' + // #4913: 5 fonts: HK 95₁₂₂, JP 82₂₁₈, KR 74₃₃₄, SC 87₄₇₁, TC 91₅₇₆. + '4s3r4l5g4a,' + // #4914: 5 fonts: HK 95₁₂₂, JP 82₂₁₈, KR 78₃₃₈, SC 86₄₇₀, TC 91₅₇₆. + '4s3r4p5b4b,' + // #4915: 5 fonts: HK 95₁₂₂, JP 82₂₁₈, KR 78₃₃₈, SC 87₄₇₁, TC 91₅₇₆. + '4s3r4p5c4a,' + // #4916: 5 fonts: HK 95₁₂₂, JP 82₂₁₈, KR 81₃₄₁, SC 86₄₇₀, TC 91₅₇₆. + '4s3r4s4y4b,' + // #4917: 5 fonts: HK 95₁₂₂, JP 83₂₁₉, KR 92₃₅₂, SC 87₄₇₁, TC 91₅₇₆. + '4s3s5c4o4a,' + // #4918: 5 fonts: HK 95₁₂₂, JP 83₂₁₉, KR 97₃₅₇, SC 71₄₅₅, TC 91₅₇₆. + '4s3s5h3t4q,' + // #4919: 5 fonts: HK 95₁₂₂, JP 84₂₂₀, KR 79₃₃₉, SC 84₄₆₈, TC 91₅₇₆. + '4s3t4o4y4d,' + // #4920: 5 fonts: HK 95₁₂₂, JP 84₂₂₀, KR 81₃₄₁, SC 85₄₆₉, TC 91₅₇₆. + '4s3t4q4x4c,' + // #4921: 5 fonts: HK 95₁₂₂, JP 84₂₂₀, KR 82₃₄₂, SC 85₄₆₉, TC 91₅₇₆. + '4s3t4r4w4c,' + // #4922: 5 fonts: HK 95₁₂₂, JP 84₂₂₀, KR 83₃₄₃, SC 88₄₇₂, TC 91₅₇₆. + '4s3t4s4y3z,' + // #4923: 5 fonts: HK 95₁₂₂, JP 84₂₂₀, KR 84₃₄₄, SC 90₄₇₄, TC 91₅₇₆. + '4s3t4t4z3x,' + // #4924: 5 fonts: HK 95₁₂₂, JP 84₂₂₀, KR 86₃₄₆, SC 86₄₇₀, TC 91₅₇₆. + '4s3t4v4t4b,' + // #4925: 5 fonts: HK 95₁₂₂, JP 85₂₂₁, KR 88₃₄₈, SC 86₄₇₀, TC 91₅₇₆. + '4s3u4w4r4b,' + // #4926: 5 fonts: HK 95₁₂₂, JP 85₂₂₁, KR 90₃₅₀, SC 88₄₇₂, TC 91₅₇₆. + '4s3u4y4r3z,' + // #4927: 5 fonts: HK 95₁₂₂, JP 85₂₂₁, KR 90₃₅₀, SC 89₄₇₃, TC 91₅₇₆. + '4s3u4y4s3y,' + // #4928: 5 fonts: HK 95₁₂₂, JP 86₂₂₂, KR 75₃₃₅, SC 28₄₁₂, TC 91₅₇₆. + '4s3v4i2y6h,' + // #4929: 5 fonts: HK 95₁₂₂, JP 86₂₂₂, KR 76₃₃₆, SC 87₄₇₁, TC 91₅₇₆. + '4s3v4j5e4a,' + // #4930: 5 fonts: HK 95₁₂₂, JP 87₂₂₃, KR 72₃₃₂, SC 89₄₇₃, TC 91₅₇₆. + '4s3w4e5k3y,' + // #4931: 5 fonts: HK 95₁₂₂, JP 87₂₂₃, KR 86₃₄₆, SC 88₄₇₂, TC 91₅₇₆. + '4s3w4s4v3z,' + // #4932: 5 fonts: HK 95₁₂₂, JP 87₂₂₃, KR 88₃₄₈, SC 86₄₇₀, TC 91₅₇₆. + '4s3w4u4r4b,' + // #4933: 5 fonts: HK 95₁₂₂, JP 87₂₂₃, KR 89₃₄₉, SC 57₄₄₁, TC 91₅₇₆. + '4s3w4v3n5e,' + // #4934: 5 fonts: HK 95₁₂₂, JP 87₂₂₃, KR 90₃₅₀, SC 85₄₆₉, TC 91₅₇₆. + '4s3w4w4o4c,' + // #4935: 5 fonts: HK 95₁₂₂, JP 88₂₂₄, KR 69₃₂₉, SC 16₄₀₀, TC 91₅₇₆. + '4s3x4a2s6t,' + // #4936: 5 fonts: HK 95₁₂₂, JP 88₂₂₄, KR 73₃₃₃, SC 24₄₀₈, TC 91₅₇₆. + '4s3x4e2w6l,' + // #4937: 5 fonts: HK 95₁₂₂, JP 88₂₂₄, KR 73₃₃₃, SC 89₄₇₃, TC 91₅₇₆. + '4s3x4e5j3y,' + // #4938: 5 fonts: HK 95₁₂₂, JP 88₂₂₄, KR 80₃₄₀, SC 87₄₇₁, TC 91₅₇₆. + '4s3x4l5a4a,' + // #4939: 5 fonts: HK 95₁₂₂, JP 89₂₂₅, KR 65₃₂₅, SC 88₄₇₂, TC 91₅₇₆. + '4s3y3v5q3z,' + // #4940: 5 fonts: HK 95₁₂₂, JP 89₂₂₅, KR 73₃₃₃, SC 87₄₇₁, TC 91₅₇₆. + '4s3y4d5h4a,' + // #4941: 5 fonts: HK 95₁₂₂, JP 89₂₂₅, KR 74₃₃₄, SC 86₄₇₀, TC 91₅₇₆. + '4s3y4e5f4b,' + // #4942: 5 fonts: HK 95₁₂₂, JP 89₂₂₅, KR 81₃₄₁, SC 85₄₆₉, TC 91₅₇₆. + '4s3y4l4x4c,' + // #4943: 5 fonts: HK 95₁₂₂, JP 90₂₂₆, KR 69₃₂₉, SC 86₄₇₀, TC 91₅₇₆. + '4s3z3y5k4b,' + // #4944: 5 fonts: HK 95₁₂₂, JP 90₂₂₆, KR 71₃₃₁, SC 88₄₇₂, TC 91₅₇₆. + '4s3z4a5k3z,' + // #4945: 5 fonts: HK 95₁₂₂, JP 90₂₂₆, KR 73₃₃₃, SC 86₄₇₀, TC 91₅₇₆. + '4s3z4c5g4b,' + // #4946: 5 fonts: HK 95₁₂₂, JP 90₂₂₆, KR 76₃₃₆, SC 32₄₁₆, TC 91₅₇₆. + '4s3z4f3b6d,' + // #4947: 5 fonts: HK 95₁₂₂, JP 90₂₂₆, KR 82₃₄₂, SC 89₄₇₃, TC 91₅₇₆. + '4s3z4l5a3y,' + // #4948: 5 fonts: HK 95₁₂₂, JP 90₂₂₆, KR 85₃₄₅, SC 87₄₇₁, TC 91₅₇₆. + '4s3z4o4v4a,' + // #4949: 5 fonts: HK 95₁₂₂, JP 90₂₂₆, KR 91₃₅₁, SC 89₄₇₃, TC 91₅₇₆. + '4s3z4u4r3y,' + // #4950: 5 fonts: HK 95₁₂₂, JP 91₂₂₇, KR 65₃₂₅, SC 86₄₇₀, TC 91₅₇₆. + '4s4a3t5o4b,' + // #4951: 5 fonts: HK 95₁₂₂, JP 91₂₂₇, KR 73₃₃₃, SC 87₄₇₁, TC 91₅₇₆. + '4s4a4b5h4a,' + // #4952: 5 fonts: HK 95₁₂₂, JP 91₂₂₇, KR 80₃₄₀, SC 41₄₂₅, TC 91₅₇₆. + '4s4a4i3g5u,' + // #4953: 5 fonts: HK 95₁₂₂, JP 93₂₂₉, KR 69₃₂₉, SC 15₃₉₉, TC 91₅₇₆. + '4s4c3v2r6u,' + // #4954: 5 fonts: HK 95₁₂₂, JP 93₂₂₉, KR 79₃₃₉, SC 87₄₇₁, TC 91₅₇₆. + '4s4c4f5b4a,' + // #4955: 5 fonts: HK 95₁₂₂, JP 93₂₂₉, KR 87₃₄₇, SC 92₄₇₆, TC 91₅₇₆. + '4s4c4n4y3v,' + // #4956: 5 fonts: HK 95₁₂₂, JP 94₂₃₀, KR 68₃₂₈, SC 13₃₉₇, TC 91₅₇₆. + '4s4d3t2q6w,' + // #4957: 5 fonts: HK 95₁₂₂, JP 94₂₃₀, KR 86₃₄₆, SC 86₄₇₀, TC 91₅₇₆. + '4s4d4l4t4b,' + // #4958: 5 fonts: HK 95₁₂₂, JP 94₂₃₀, KR 86₃₄₆, SC 87₄₇₁, TC 91₅₇₆. + '4s4d4l4u4a,' + // #4959: 5 fonts: HK 95₁₂₂, JP 95₂₃₁, KR 86₃₄₆, SC 87₄₇₁, TC 91₅₇₆. + '4s4e4k4u4a,' + // #4960: 5 fonts: HK 95₁₂₂, JP 95₂₃₁, KR 89₃₄₉, SC 58₄₄₂, TC 91₅₇₆. + '4s4e4n3o5d,' + // #4961: 5 fonts: HK 95₁₂₂, JP 95₂₃₁, KR 91₃₅₁, SC 89₄₇₃, TC 91₅₇₆. + '4s4e4p4r3y,' + // #4962: 5 fonts: HK 95₁₂₂, JP 96₂₃₂, KR 69₃₂₉, SC 15₃₉₉, TC 91₅₇₆. + '4s4f3s2r6u,' + // #4963: 5 fonts: HK 95₁₂₂, JP 96₂₃₂, KR 80₃₄₀, SC 86₄₇₀, TC 91₅₇₆. + '4s4f4d4z4b,' + // #4964: 5 fonts: HK 95₁₂₂, JP 98₂₃₄, KR 80₃₄₀, SC 88₄₇₂, TC 91₅₇₆. + '4s4h4b5b3z,' + // #4965: 5 fonts: HK 95₁₂₂, JP 98₂₃₄, KR 86₃₄₆, SC 88₄₇₂, TC 91₅₇₆. + '4s4h4h4v3z,' + // #4966: 5 fonts: HK 95₁₂₂, JP 99₂₃₅, KR 66₃₂₆, SC 7₃₉₁, TC 91₅₇₆. + '4s4i3m2m7c,' + // #4967: 5 fonts: HK 95₁₂₂, JP 99₂₃₅, KR 68₃₂₈, SC 87₄₇₁, TC 91₅₇₆. + '4s4i3o5m4a,' + // #4968: 5 fonts: HK 95₁₂₂, JP 99₂₃₅, KR 91₃₅₁, SC 87₄₇₁, TC 91₅₇₆. + '4s4i4l4p4a,' + // #4969: 5 fonts: HK 95₁₂₂, JP 100₂₃₆, KR 65₃₂₅, SC 87₄₇₁, TC 91₅₇₆. + '4s4j3k5p4a,' + // #4970: 5 fonts: HK 95₁₂₂, JP 100₂₃₆, KR 68₃₂₈, SC 88₄₇₂, TC 91₅₇₆. + '4s4j3n5n3z,' + // #4971: 5 fonts: HK 95₁₂₂, JP 101₂₃₇, KR 79₃₃₉, SC 39₄₂₃, TC 91₅₇₆. + '4s4k3x3f5w,' + // #4972: 5 fonts: HK 95₁₂₂, JP 101₂₃₇, KR 81₃₄₁, SC 89₄₇₃, TC 91₅₇₆. + '4s4k3z5b3y,' + // #4973: 5 fonts: HK 95₁₂₂, JP 101₂₃₇, KR 84₃₄₄, SC 48₄₃₂, TC 91₅₇₆. + '4s4k4c3j5n,' + // #4974: 5 fonts: HK 95₁₂₂, JP 101₂₃₇, KR 87₃₄₇, SC 88₄₇₂, TC 91₅₇₆. + '4s4k4f4u3z,' + // #4975: 5 fonts: HK 95₁₂₂, JP 101₂₃₇, KR 88₃₄₈, SC 87₄₇₁, TC 91₅₇₆. + '4s4k4g4s4a,' + // #4976: 5 fonts: HK 95₁₂₂, JP 102₂₃₈, KR 65₃₂₅, SC 86₄₇₀, TC 91₅₇₆. + '4s4l3i5o4b,' + // #4977: 5 fonts: HK 95₁₂₂, JP 102₂₃₈, KR 92₃₅₂, SC 65₄₄₉, TC 91₅₇₆. + '4s4l4j3s4w,' + // #4978: 5 fonts: HK 95₁₂₂, JP 103₂₃₉, KR 71₃₃₁, SC 19₄₀₃, TC 91₅₇₆. + '4s4m3n2t6q,' + // #4979: 5 fonts: HK 95₁₂₂, JP 103₂₃₉, KR 86₃₄₆, SC 86₄₇₀, TC 91₅₇₆. + '4s4m4c4t4b,' + // #4980: 4 fonts: HK 95₁₂₂, JP 103₂₃₉, SC 87₄₇₁, TC 91₅₇₆. + '4s4m8x4a,' + // #4981: 5 fonts: HK 95₁₂₂, JP 104₂₄₀, KR 81₃₄₁, SC 86₄₇₀, TC 91₅₇₆. + '4s4n3w4y4b,' + // #4982: 5 fonts: HK 95₁₂₂, JP 104₂₄₀, KR 85₃₄₅, SC 87₄₇₁, TC 91₅₇₆. + '4s4n4a4v4a,' + // #4983: 4 fonts: HK 95₁₂₂, JP 105₂₄₁, SC 27₄₁₁, TC 91₅₇₆. + '4s4o6n6i,' + // #4984: 5 fonts: HK 95₁₂₂, JP 107₂₄₃, KR 92₃₅₂, SC 83₄₆₇, TC 91₅₇₆. + '4s4q4e4k4e,' + // #4985: 5 fonts: HK 95₁₂₂, JP 110₂₄₆, KR 93₃₅₃, SC 87₄₇₁, TC 91₅₇₆. + '4s4t4c4n4a,' + // #4986: 4 fonts: HK 95₁₂₂, JP 115₂₅₁, SC 95₄₇₉, TC 91₅₇₆. + '4s4y8t3s,' + // #4987: 3 fonts: HK 95₁₂₂, SC 61₄₄₅, TC 91₅₇₆. + '4s12k5a,' + // #4988: 5 fonts: HK 96₁₂₃, JP 5₁₄₁, KR 65₃₂₅, SC 87₄₇₁, TC 92₅₇₇. + '4tr7b5p4b,' + // #4989: 5 fonts: HK 96₁₂₃, JP 5₁₄₁, KR 65₃₂₅, SC 89₄₇₃, TC 92₅₇₇. + '4tr7b5r3z,' + // #4990: 5 fonts: HK 96₁₂₃, JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 92₅₇₇. + '4tu6z2m7d,' + // #4991: 5 fonts: HK 96₁₂₃, JP 10₁₄₆, KR 67₃₂₇, SC 11₃₉₅, TC 92₅₇₇. + '4tw6y2p6z,' + // #4992: 5 fonts: HK 96₁₂₃, JP 10₁₄₆, KR 67₃₂₇, SC 87₄₇₁, TC 92₅₇₇. + '4tw6y5n4b,' + // #4993: 4 fonts: HK 96₁₂₃, JP 11₁₄₇, SC 12₃₉₆, TC 92₅₇₇. + '4tx9o6y,' + // #4994: 5 fonts: HK 96₁₂₃, JP 13₁₄₉, KR 68₃₂₈, SC 89₄₇₃, TC 92₅₇₇. + '4tz6w5o3z,' + // #4995: 4 fonts: HK 96₁₂₃, JP 13₁₄₉, SC 15₃₉₉, TC 92₅₇₇. + '4tz9p6v,' + // #4996: 4 fonts: HK 96₁₂₃, JP 13₁₄₉, SC 85₄₆₉, TC 92₅₇₇. + '4tz12h4d,' + // #4997: 5 fonts: HK 96₁₂₃, JP 15₁₅₁, KR 70₃₃₀, SC 18₄₀₂, TC 92₅₇₇. + '4t1b6w2t6s,' + // #4998: 4 fonts: HK 96₁₂₃, JP 15₁₅₁, SC 17₄₀₁, TC 92₅₇₇. + '4t1b9p6t,' + // #4999: 5 fonts: HK 96₁₂₃, JP 16₁₅₂, KR 70₃₃₀, SC 18₄₀₂, TC 92₅₇₇. + '4t1c6v2t6s,' + // #5000: 5 fonts: HK 96₁₂₃, JP 18₁₅₄, KR 71₃₃₁, SC 21₄₀₅, TC 92₅₇₇. + '4t1e6u2v6p,' + // #5001: 5 fonts: HK 96₁₂₃, JP 19₁₅₅, KR 71₃₃₁, SC 78₄₆₂, TC 92₅₇₇. + '4t1f6t5a4k,' + // #5002: 5 fonts: HK 96₁₂₃, JP 20₁₅₆, KR 72₃₃₂, SC 88₄₇₂, TC 92₅₇₇. + '4t1g6t5j4a,' + // #5003: 5 fonts: HK 96₁₂₃, JP 21₁₅₇, KR 73₃₃₃, SC 87₄₇₁, TC 92₅₇₇. + '4t1h6t5h4b,' + // #5004: 4 fonts: HK 96₁₂₃, JP 21₁₅₇, SC 24₄₀₈, TC 92₅₇₇. + '4t1h9q6m,' + // #5005: 4 fonts: HK 96₁₂₃, JP 22₁₅₈, SC 85₄₆₉, TC 92₅₇₇. + '4t1i11y4d,' + // #5006: 5 fonts: HK 96₁₂₃, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 92₅₇₇. + '4t1j6s2y6j,' + // #5007: 4 fonts: HK 96₁₂₃, JP 24₁₆₀, SC 86₄₇₀, TC 92₅₇₇. + '4t1k11x4c,' + // #5008: 5 fonts: HK 96₁₂₃, JP 26₁₆₂, KR 76₃₃₆, SC 89₄₇₃, TC 92₅₇₇. + '4t1m6r5g3z,' + // #5009: 4 fonts: HK 96₁₂₃, JP 26₁₆₂, SC 32₄₁₆, TC 92₅₇₇. + '4t1m9t6e,' + // #5010: 5 fonts: HK 96₁₂₃, JP 27₁₆₃, KR 76₃₃₆, SC 87₄₇₁, TC 92₅₇₇. + '4t1n6q5e4b,' + // #5011: 5 fonts: HK 96₁₂₃, JP 29₁₆₅, KR 78₃₃₈, SC 36₄₂₀, TC 92₅₇₇. + '4t1p6q3d6a,' + // #5012: 5 fonts: HK 96₁₂₃, JP 29₁₆₅, KR 78₃₃₈, SC 37₄₂₁, TC 92₅₇₇. + '4t1p6q3e5z,' + // #5013: 4 fonts: HK 96₁₂₃, JP 29₁₆₅, SC 87₄₇₁, TC 92₅₇₇. + '4t1p11t4b,' + // #5014: 5 fonts: HK 96₁₂₃, JP 30₁₆₆, KR 79₃₃₉, SC 38₄₂₂, TC 92₅₇₇. + '4t1q6q3e5y,' + // #5015: 4 fonts: HK 96₁₂₃, JP 30₁₆₆, SC 87₄₇₁, TC 92₅₇₇. + '4t1q11s4b,' + // #5016: 5 fonts: HK 96₁₂₃, JP 31₁₆₇, KR 79₃₃₉, SC 80₄₆₄, TC 92₅₇₇. + '4t1r6p4u4i,' + // #5017: 5 fonts: HK 96₁₂₃, JP 31₁₆₇, KR 80₃₄₀, SC 40₄₂₄, TC 92₅₇₇. + '4t1r6q3f5w,' + // #5018: 4 fonts: HK 96₁₂₃, JP 31₁₆₇, SC 85₄₆₉, TC 92₅₇₇. + '4t1r11p4d,' + // #5019: 5 fonts: HK 96₁₂₃, JP 33₁₆₉, KR 81₃₄₁, SC 43₄₂₇, TC 92₅₇₇. + '4t1t6p3h5t,' + // #5020: 4 fonts: HK 96₁₂₃, JP 33₁₆₉, SC 89₄₇₃, TC 92₅₇₇. + '4t1t11r3z,' + // #5021: 5 fonts: HK 96₁₂₃, JP 34₁₇₀, KR 82₃₄₂, SC 44₄₂₈, TC 92₅₇₇. + '4t1u6p3h5s,' + // #5022: 4 fonts: HK 96₁₂₃, JP 34₁₇₀, SC 85₄₆₉, TC 92₅₇₇. + '4t1u11m4d,' + // #5023: 5 fonts: HK 96₁₂₃, JP 36₁₇₂, KR 83₃₄₃, SC 88₄₇₂, TC 92₅₇₇. + '4t1w6o4y4a,' + // #5024: 5 fonts: HK 96₁₂₃, JP 37₁₇₃, KR 83₃₄₃, SC 88₄₇₂, TC 92₅₇₇. + '4t1x6n4y4a,' + // #5025: 5 fonts: HK 96₁₂₃, JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂, TC 92₅₇₇. + '4t1x6o3j5o,' + // #5026: 4 fonts: HK 96₁₂₃, JP 37₁₇₃, SC 48₄₃₂, TC 92₅₇₇. + '4t1x9y5o,' + // #5027: 5 fonts: HK 96₁₂₃, JP 38₁₇₄, KR 84₃₄₄, SC 49₄₃₃, TC 92₅₇₇. + '4t1y6n3k5n,' + // #5028: 5 fonts: HK 96₁₂₃, JP 38₁₇₄, KR 84₃₄₄, SC 87₄₇₁, TC 92₅₇₇. + '4t1y6n4w4b,' + // #5029: 5 fonts: HK 96₁₂₃, JP 38₁₇₄, KR 84₃₄₄, SC 89₄₇₃, TC 92₅₇₇. + '4t1y6n4y3z,' + // #5030: 5 fonts: HK 96₁₂₃, JP 38₁₇₄, KR 85₃₄₅, SC 50₄₃₄, TC 92₅₇₇. + '4t1y6o3k5m,' + // #5031: 5 fonts: HK 96₁₂₃, JP 39₁₇₅, KR 85₃₄₅, SC 51₄₃₅, TC 92₅₇₇. + '4t1z6n3l5l,' + // #5032: 5 fonts: HK 96₁₂₃, JP 39₁₇₅, KR 85₃₄₅, SC 88₄₇₂, TC 92₅₇₇. + '4t1z6n4w4a,' + // #5033: 5 fonts: HK 96₁₂₃, JP 39₁₇₅, KR 85₃₄₅, SC 90₄₇₄, TC 92₅₇₇. + '4t1z6n4y3y,' + // #5034: 4 fonts: HK 96₁₂₃, JP 39₁₇₅, SC 86₄₇₀, TC 92₅₇₇. + '4t1z11i4c,' + // #5035: 4 fonts: HK 96₁₂₃, JP 39₁₇₅, SC 89₄₇₃, TC 92₅₇₇. + '4t1z11l3z,' + // #5036: 5 fonts: HK 96₁₂₃, JP 41₁₇₇, KR 87₃₄₇, SC 53₄₃₇, TC 92₅₇₇. + '4t2b6n3l5j,' + // #5037: 5 fonts: HK 96₁₂₃, JP 41₁₇₇, KR 87₃₄₇, SC 54₄₃₈, TC 92₅₇₇. + '4t2b6n3m5i,' + // #5038: 5 fonts: HK 96₁₂₃, JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉, TC 92₅₇₇. + '4t2c6m3n5h,' + // #5039: 5 fonts: HK 96₁₂₃, JP 43₁₇₉, KR 88₃₄₈, SC 88₄₇₂, TC 92₅₇₇. + '4t2d6m4t4a,' + // #5040: 4 fonts: HK 96₁₂₃, JP 43₁₇₉, SC 57₄₄₁, TC 92₅₇₇. + '4t2d10b5f,' + // #5041: 4 fonts: HK 96₁₂₃, JP 45₁₈₁, SC 60₄₄₄, TC 92₅₇₇. + '4t2f10c5c,' + // #5042: 5 fonts: HK 96₁₂₃, JP 46₁₈₂, KR 90₃₅₀, SC 87₄₇₁, TC 92₅₇₇. + '4t2g6l4q4b,' + // #5043: 4 fonts: HK 96₁₂₃, JP 46₁₈₂, SC 84₄₆₈, TC 92₅₇₇. + '4t2g10z4e,' + // #5044: 4 fonts: HK 96₁₂₃, JP 46₁₈₂, SC 86₄₇₀, TC 92₅₇₇. + '4t2g11b4c,' + // #5045: 4 fonts: HK 96₁₂₃, JP 46₁₈₂, SC 89₄₇₃, TC 92₅₇₇. + '4t2g11e3z,' + // #5046: 5 fonts: HK 96₁₂₃, JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇, TC 92₅₇₇. + '4t2h6l3r4z,' + // #5047: 5 fonts: HK 96₁₂₃, JP 47₁₈₃, KR 91₃₅₁, SC 89₄₇₃, TC 92₅₇₇. + '4t2h6l4r3z,' + // #5048: 5 fonts: HK 96₁₂₃, JP 48₁₈₄, KR 91₃₅₁, SC 86₄₇₀, TC 92₅₇₇. + '4t2i6k4o4c,' + // #5049: 5 fonts: HK 96₁₂₃, JP 49₁₈₅, KR 92₃₅₂, SC 86₄₇₀, TC 92₅₇₇. + '4t2j6k4n4c,' + // #5050: 5 fonts: HK 96₁₂₃, JP 50₁₈₆, KR 93₃₅₃, SC 87₄₇₁, TC 92₅₇₇. + '4t2k6k4n4b,' + // #5051: 4 fonts: HK 96₁₂₃, JP 50₁₈₆, SC 88₄₇₂, TC 92₅₇₇. + '4t2k10z4a,' + // #5052: 5 fonts: HK 96₁₂₃, JP 60₁₉₆, KR 69₃₂₉, SC 88₄₇₂, TC 92₅₇₇. + '4t2u5c5m4a,' + // #5053: 5 fonts: HK 96₁₂₃, JP 60₁₉₆, KR 71₃₃₁, SC 20₄₀₄, TC 92₅₇₇. + '4t2u5e2u6q,' + // #5054: 5 fonts: HK 96₁₂₃, JP 60₁₉₆, KR 74₃₃₄, SC 88₄₇₂, TC 92₅₇₇. + '4t2u5h5h4a,' + // #5055: 5 fonts: HK 96₁₂₃, JP 60₁₉₆, KR 91₃₅₁, SC 88₄₇₂, TC 92₅₇₇. + '4t2u5y4q4a,' + // #5056: 5 fonts: HK 96₁₂₃, JP 61₁₉₇, KR 65₃₂₅, SC 87₄₇₁, TC 92₅₇₇. + '4t2v4x5p4b,' + // #5057: 5 fonts: HK 96₁₂₃, JP 61₁₉₇, KR 74₃₃₄, SC 27₄₁₁, TC 92₅₇₇. + '4t2v5g2y6j,' + // #5058: 5 fonts: HK 96₁₂₃, JP 61₁₉₇, KR 81₃₄₁, SC 88₄₇₂, TC 92₅₇₇. + '4t2v5n5a4a,' + // #5059: 5 fonts: HK 96₁₂₃, JP 62₁₉₈, KR 68₃₂₈, SC 93₄₇₇, TC 92₅₇₇. + '4t2w4z5s3v,' + // #5060: 5 fonts: HK 96₁₂₃, JP 65₂₀₁, KR 68₃₂₈, SC 89₄₇₃, TC 92₅₇₇. + '4t2z4w5o3z,' + // #5061: 5 fonts: HK 96₁₂₃, JP 65₂₀₁, KR 79₃₃₉, SC 88₄₇₂, TC 92₅₇₇. + '4t2z5h5c4a,' + // #5062: 5 fonts: HK 96₁₂₃, JP 66₂₀₂, KR 77₃₃₇, SC 87₄₇₁, TC 92₅₇₇. + '4t3a5e5d4b,' + // #5063: 5 fonts: HK 96₁₂₃, JP 66₂₀₂, KR 79₃₃₉, SC 39₄₂₃, TC 92₅₇₇. + '4t3a5g3f5x,' + // #5064: 5 fonts: HK 96₁₂₃, JP 66₂₀₂, KR 84₃₄₄, SC 88₄₇₂, TC 92₅₇₇. + '4t3a5l4x4a,' + // #5065: 5 fonts: HK 96₁₂₃, JP 67₂₀₃, KR 88₃₄₈, SC 88₄₇₂, TC 92₅₇₇. + '4t3b5o4t4a,' + // #5066: 5 fonts: HK 96₁₂₃, JP 68₂₀₄, KR 79₃₃₉, SC 38₄₂₂, TC 92₅₇₇. + '4t3c5e3e5y,' + // #5067: 5 fonts: HK 96₁₂₃, JP 69₂₀₅, KR 73₃₃₃, SC 87₄₇₁, TC 92₅₇₇. + '4t3d4x5h4b,' + // #5068: 5 fonts: HK 96₁₂₃, JP 69₂₀₅, KR 82₃₄₂, SC 45₄₂₉, TC 92₅₇₇. + '4t3d5g3i5r,' + // #5069: 5 fonts: HK 96₁₂₃, JP 70₂₀₆, KR 88₃₄₈, SC 88₄₇₂, TC 92₅₇₇. + '4t3e5l4t4a,' + // #5070: 5 fonts: HK 96₁₂₃, JP 70₂₀₆, KR 91₃₅₁, SC 93₄₇₇, TC 92₅₇₇. + '4t3e5o4v3v,' + // #5071: 5 fonts: HK 96₁₂₃, JP 71₂₀₇, KR 66₃₂₆, SC 8₃₉₂, TC 92₅₇₇. + '4t3f4o2n7c,' + // #5072: 5 fonts: HK 96₁₂₃, JP 72₂₀₈, KR 67₃₂₇, SC 87₄₇₁, TC 92₅₇₇. + '4t3g4o5n4b,' + // #5073: 5 fonts: HK 96₁₂₃, JP 72₂₀₈, KR 88₃₄₈, SC 88₄₇₂, TC 92₅₇₇. + '4t3g5j4t4a,' + // #5074: 5 fonts: HK 96₁₂₃, JP 74₂₁₀, KR 77₃₃₇, SC 87₄₇₁, TC 92₅₇₇. + '4t3i4w5d4b,' + // #5075: 5 fonts: HK 96₁₂₃, JP 74₂₁₀, KR 84₃₄₄, SC 49₄₃₃, TC 92₅₇₇. + '4t3i5d3k5n,' + // #5076: 5 fonts: HK 96₁₂₃, JP 75₂₁₁, KR 73₃₃₃, SC 83₄₆₇, TC 92₅₇₇. + '4t3j4r5d4f,' + // #5077: 5 fonts: HK 96₁₂₃, JP 75₂₁₁, KR 89₃₄₉, SC 88₄₇₂, TC 92₅₇₇. + '4t3j5h4s4a,' + // #5078: 5 fonts: HK 96₁₂₃, JP 75₂₁₁, KR 92₃₅₂, SC 65₄₄₉, TC 92₅₇₇. + '4t3j5k3s4x,' + // #5079: 5 fonts: HK 96₁₂₃, JP 76₂₁₂, KR 73₃₃₃, SC 89₄₇₃, TC 92₅₇₇. + '4t3k4q5j3z,' + // #5080: 5 fonts: HK 96₁₂₃, JP 76₂₁₂, KR 79₃₃₉, SC 39₄₂₃, TC 92₅₇₇. + '4t3k4w3f5x,' + // #5081: 5 fonts: HK 96₁₂₃, JP 76₂₁₂, KR 93₃₅₃, SC 87₄₇₁, TC 92₅₇₇. + '4t3k5k4n4b,' + // #5082: 5 fonts: HK 96₁₂₃, JP 77₂₁₃, KR 65₃₂₅, SC 88₄₇₂, TC 92₅₇₇. + '4t3l4h5q4a,' + // #5083: 5 fonts: HK 96₁₂₃, JP 77₂₁₃, KR 75₃₃₅, SC 87₄₇₁, TC 92₅₇₇. + '4t3l4r5f4b,' + // #5084: 5 fonts: HK 96₁₂₃, JP 77₂₁₃, KR 86₃₄₆, SC 86₄₇₀, TC 92₅₇₇. + '4t3l5c4t4c,' + // #5085: 5 fonts: HK 96₁₂₃, JP 77₂₁₃, KR 87₃₄₇, SC 87₄₇₁, TC 92₅₇₇. + '4t3l5d4t4b,' + // #5086: 5 fonts: HK 96₁₂₃, JP 78₂₁₄, KR 73₃₃₃, SC 87₄₇₁, TC 92₅₇₇. + '4t3m4o5h4b,' + // #5087: 5 fonts: HK 96₁₂₃, JP 78₂₁₄, KR 75₃₃₅, SC 28₄₁₂, TC 92₅₇₇. + '4t3m4q2y6i,' + // #5088: 5 fonts: HK 96₁₂₃, JP 78₂₁₄, KR 81₃₄₁, SC 44₄₂₈, TC 92₅₇₇. + '4t3m4w3i5s,' + // #5089: 5 fonts: HK 96₁₂₃, JP 78₂₁₄, KR 84₃₄₄, SC 87₄₇₁, TC 92₅₇₇. + '4t3m4z4w4b,' + // #5090: 5 fonts: HK 96₁₂₃, JP 78₂₁₄, KR 90₃₅₀, SC 60₄₄₄, TC 92₅₇₇. + '4t3m5f3p5c,' + // #5091: 5 fonts: HK 96₁₂₃, JP 78₂₁₄, KR 93₃₅₃, SC 86₄₇₀, TC 92₅₇₇. + '4t3m5i4m4c,' + // #5092: 5 fonts: HK 96₁₂₃, JP 79₂₁₅, KR 65₃₂₅, SC 7₃₉₁, TC 92₅₇₇. + '4t3n4f2n7d,' + // #5093: 5 fonts: HK 96₁₂₃, JP 79₂₁₅, KR 81₃₄₁, SC 88₄₇₂, TC 92₅₇₇. + '4t3n4v5a4a,' + // #5094: 5 fonts: HK 96₁₂₃, JP 79₂₁₅, KR 85₃₄₅, SC 51₄₃₅, TC 92₅₇₇. + '4t3n4z3l5l,' + // #5095: 5 fonts: HK 96₁₂₃, JP 80₂₁₆, KR 69₃₂₉, SC 15₃₉₉, TC 92₅₇₇. + '4t3o4i2r6v,' + // #5096: 5 fonts: HK 96₁₂₃, JP 80₂₁₆, KR 79₃₃₉, SC 88₄₇₂, TC 92₅₇₇. + '4t3o4s5c4a,' + // #5097: 5 fonts: HK 96₁₂₃, JP 80₂₁₆, KR 89₃₄₉, SC 88₄₇₂, TC 92₅₇₇. + '4t3o5c4s4a,' + // #5098: 5 fonts: HK 96₁₂₃, JP 80₂₁₆, KR 90₃₅₀, SC 87₄₇₁, TC 92₅₇₇. + '4t3o5d4q4b,' + // #5099: 5 fonts: HK 96₁₂₃, JP 81₂₁₇, KR 75₃₃₅, SC 28₄₁₂, TC 92₅₇₇. + '4t3p4n2y6i,' + // #5100: 5 fonts: HK 96₁₂₃, JP 81₂₁₇, KR 88₃₄₈, SC 88₄₇₂, TC 92₅₇₇. + '4t3p5a4t4a,' + // #5101: 5 fonts: HK 96₁₂₃, JP 81₂₁₇, KR 103₃₆₃, SC 71₄₅₅, TC 92₅₇₇. + '4t3p5p3n4r,' + // #5102: 5 fonts: HK 96₁₂₃, JP 82₂₁₈, KR 72₃₃₂, SC 86₄₇₀, TC 92₅₇₇. + '4t3q4j5h4c,' + // #5103: 5 fonts: HK 96₁₂₃, JP 82₂₁₈, KR 88₃₄₈, SC 84₄₆₈, TC 92₅₇₇. + '4t3q4z4p4e,' + // #5104: 5 fonts: HK 96₁₂₃, JP 82₂₁₈, KR 92₃₅₂, SC 65₄₄₉, TC 92₅₇₇. + '4t3q5d3s4x,' + // #5105: 4 fonts: HK 96₁₂₃, JP 82₂₁₈, SC 87₄₇₁, TC 92₅₇₇. + '4t3q9s4b,' + // #5106: 5 fonts: HK 96₁₂₃, JP 83₂₁₉, KR 66₃₂₆, SC 7₃₉₁, TC 92₅₇₇. + '4t3r4c2m7d,' + // #5107: 5 fonts: HK 96₁₂₃, JP 83₂₁₉, KR 86₃₄₆, SC 86₄₇₀, TC 92₅₇₇. + '4t3r4w4t4c,' + // #5108: 5 fonts: HK 96₁₂₃, JP 83₂₁₉, KR 87₃₄₇, SC 90₄₇₄, TC 92₅₇₇. + '4t3r4x4w3y,' + // #5109: 5 fonts: HK 96₁₂₃, JP 84₂₂₀, KR 74₃₃₄, SC 27₄₁₁, TC 92₅₇₇. + '4t3s4j2y6j,' + // #5110: 5 fonts: HK 96₁₂₃, JP 84₂₂₀, KR 80₃₄₀, SC 40₄₂₄, TC 92₅₇₇. + '4t3s4p3f5w,' + // #5111: 5 fonts: HK 96₁₂₃, JP 84₂₂₀, KR 83₃₄₃, SC 87₄₇₁, TC 92₅₇₇. + '4t3s4s4x4b,' + // #5112: 5 fonts: HK 96₁₂₃, JP 84₂₂₀, KR 85₃₄₅, SC 88₄₇₂, TC 92₅₇₇. + '4t3s4u4w4a,' + // #5113: 5 fonts: HK 96₁₂₃, JP 84₂₂₀, KR 88₃₄₈, SC 56₄₄₀, TC 92₅₇₇. + '4t3s4x3n5g,' + // #5114: 5 fonts: HK 96₁₂₃, JP 85₂₂₁, KR 74₃₃₄, SC 88₄₇₂, TC 92₅₇₇. + '4t3t4i5h4a,' + // #5115: 5 fonts: HK 96₁₂₃, JP 85₂₂₁, KR 76₃₃₆, SC 87₄₇₁, TC 92₅₇₇. + '4t3t4k5e4b,' + // #5116: 5 fonts: HK 96₁₂₃, JP 85₂₂₁, KR 82₃₄₂, SC 81₄₆₅, TC 92₅₇₇. + '4t3t4q4s4h,' + // #5117: 5 fonts: HK 96₁₂₃, JP 85₂₂₁, KR 88₃₄₈, SC 89₄₇₃, TC 92₅₇₇. + '4t3t4w4u3z,' + // #5118: 5 fonts: HK 96₁₂₃, JP 86₂₂₂, KR 73₃₃₃, SC 24₄₀₈, TC 92₅₇₇. + '4t3u4g2w6m,' + // #5119: 5 fonts: HK 96₁₂₃, JP 86₂₂₂, KR 84₃₄₄, SC 49₄₃₃, TC 92₅₇₇. + '4t3u4r3k5n,' + // #5120: 5 fonts: HK 96₁₂₃, JP 86₂₂₂, KR 91₃₅₁, SC 64₄₄₈, TC 92₅₇₇. + '4t3u4y3s4y,' + // #5121: 5 fonts: HK 96₁₂₃, JP 87₂₂₃, KR 70₃₃₀, SC 18₄₀₂, TC 92₅₇₇. + '4t3v4c2t6s,' + // #5122: 5 fonts: HK 96₁₂₃, JP 87₂₂₃, KR 70₃₃₀, SC 19₄₀₃, TC 92₅₇₇. + '4t3v4c2u6r,' + // #5123: 5 fonts: HK 96₁₂₃, JP 87₂₂₃, KR 92₃₅₂, SC 88₄₇₂, TC 92₅₇₇. + '4t3v4y4p4a,' + // #5124: 5 fonts: HK 96₁₂₃, JP 88₂₂₄, KR 68₃₂₈, SC 89₄₇₃, TC 92₅₇₇. + '4t3w3z5o3z,' + // #5125: 5 fonts: HK 96₁₂₃, JP 88₂₂₄, KR 84₃₄₄, SC 89₄₇₃, TC 92₅₇₇. + '4t3w4p4y3z,' + // #5126: 5 fonts: HK 96₁₂₃, JP 88₂₂₄, KR 87₃₄₇, SC 86₄₇₀, TC 92₅₇₇. + '4t3w4s4s4c,' + // #5127: 5 fonts: HK 96₁₂₃, JP 89₂₂₅, KR 91₃₅₁, SC 89₄₇₃, TC 92₅₇₇. + '4t3x4v4r3z,' + // #5128: 4 fonts: HK 96₁₂₃, JP 89₂₂₅, SC 66₄₅₀, TC 92₅₇₇. + '4t3x8q4w,' + // #5129: 5 fonts: HK 96₁₂₃, JP 90₂₂₆, KR 67₃₂₇, SC 9₃₉₃, TC 92₅₇₇. + '4t3y3w2n7b,' + // #5130: 5 fonts: HK 96₁₂₃, JP 90₂₂₆, KR 79₃₃₉, SC 39₄₂₃, TC 92₅₇₇. + '4t3y4i3f5x,' + // #5131: 5 fonts: HK 96₁₂₃, JP 90₂₂₆, KR 84₃₄₄, SC 49₄₃₃, TC 92₅₇₇. + '4t3y4n3k5n,' + // #5132: 5 fonts: HK 96₁₂₃, JP 90₂₂₆, KR 86₃₄₆, SC 87₄₇₁, TC 92₅₇₇. + '4t3y4p4u4b,' + // #5133: 5 fonts: HK 96₁₂₃, JP 90₂₂₆, KR 91₃₅₁, SC 88₄₇₂, TC 92₅₇₇. + '4t3y4u4q4a,' + // #5134: 5 fonts: HK 96₁₂₃, JP 91₂₂₇, KR 70₃₃₀, SC 18₄₀₂, TC 92₅₇₇. + '4t3z3y2t6s,' + // #5135: 5 fonts: HK 96₁₂₃, JP 91₂₂₇, KR 73₃₃₃, SC 87₄₇₁, TC 92₅₇₇. + '4t3z4b5h4b,' + // #5136: 5 fonts: HK 96₁₂₃, JP 91₂₂₇, KR 76₃₃₆, SC 88₄₇₂, TC 92₅₇₇. + '4t3z4e5f4a,' + // #5137: 5 fonts: HK 96₁₂₃, JP 91₂₂₇, KR 79₃₃₉, SC 40₄₂₄, TC 92₅₇₇. + '4t3z4h3g5w,' + // #5138: 5 fonts: HK 96₁₂₃, JP 91₂₂₇, KR 91₃₅₁, SC 87₄₇₁, TC 92₅₇₇. + '4t3z4t4p4b,' + // #5139: 5 fonts: HK 96₁₂₃, JP 92₂₂₈, KR 74₃₃₄, SC 87₄₇₁, TC 92₅₇₇. + '4t4a4b5g4b,' + // #5140: 5 fonts: HK 96₁₂₃, JP 92₂₂₈, KR 83₃₄₃, SC 88₄₇₂, TC 92₅₇₇. + '4t4a4k4y4a,' + // #5141: 5 fonts: HK 96₁₂₃, JP 92₂₂₈, KR 85₃₄₅, SC 51₄₃₅, TC 92₅₇₇. + '4t4a4m3l5l,' + // #5142: 5 fonts: HK 96₁₂₃, JP 92₂₂₈, KR 88₃₄₈, SC 56₄₄₀, TC 92₅₇₇. + '4t4a4p3n5g,' + // #5143: 5 fonts: HK 96₁₂₃, JP 92₂₂₈, KR 92₃₅₂, SC 89₄₇₃, TC 92₅₇₇. + '4t4a4t4q3z,' + // #5144: 5 fonts: HK 96₁₂₃, JP 93₂₂₉, KR 69₃₂₉, SC 87₄₇₁, TC 92₅₇₇. + '4t4b3v5l4b,' + // #5145: 5 fonts: HK 96₁₂₃, JP 93₂₂₉, KR 80₃₄₀, SC 90₄₇₄, TC 92₅₇₇. + '4t4b4g5d3y,' + // #5146: 5 fonts: HK 96₁₂₃, JP 93₂₂₉, KR 85₃₄₅, SC 51₄₃₅, TC 92₅₇₇. + '4t4b4l3l5l,' + // #5147: 5 fonts: HK 96₁₂₃, JP 93₂₂₉, KR 87₃₄₇, SC 54₄₃₈, TC 92₅₇₇. + '4t4b4n3m5i,' + // #5148: 5 fonts: HK 96₁₂₃, JP 93₂₂₉, KR 89₃₄₉, SC 88₄₇₂, TC 92₅₇₇. + '4t4b4p4s4a,' + // #5149: 5 fonts: HK 96₁₂₃, JP 93₂₂₉, KR 93₃₅₃, SC 88₄₇₂, TC 92₅₇₇. + '4t4b4t4o4a,' + // #5150: 5 fonts: HK 96₁₂₃, JP 94₂₃₀, KR 67₃₂₇, SC 11₃₉₅, TC 92₅₇₇. + '4t4c3s2p6z,' + // #5151: 5 fonts: HK 96₁₂₃, JP 94₂₃₀, KR 73₃₃₃, SC 88₄₇₂, TC 92₅₇₇. + '4t4c3y5i4a,' + // #5152: 5 fonts: HK 96₁₂₃, JP 94₂₃₀, KR 83₃₄₃, SC 88₄₇₂, TC 92₅₇₇. + '4t4c4i4y4a,' + // #5153: 5 fonts: HK 96₁₂₃, JP 94₂₃₀, KR 88₃₄₈, SC 88₄₇₂, TC 92₅₇₇. + '4t4c4n4t4a,' + // #5154: 5 fonts: HK 96₁₂₃, JP 95₂₃₁, KR 68₃₂₈, SC 86₄₇₀, TC 92₅₇₇. + '4t4d3s5l4c,' + // #5155: 4 fonts: HK 96₁₂₃, JP 95₂₃₁, SC 88₄₇₂, TC 92₅₇₇. + '4t4d9g4a,' + // #5156: 5 fonts: HK 96₁₂₃, JP 96₂₃₂, KR 66₃₂₆, SC 89₄₇₃, TC 92₅₇₇. + '4t4e3p5q3z,' + // #5157: 5 fonts: HK 96₁₂₃, JP 96₂₃₂, KR 73₃₃₃, SC 87₄₇₁, TC 92₅₇₇. + '4t4e3w5h4b,' + // #5158: 5 fonts: HK 96₁₂₃, JP 96₂₃₂, KR 74₃₃₄, SC 27₄₁₁, TC 92₅₇₇. + '4t4e3x2y6j,' + // #5159: 5 fonts: HK 96₁₂₃, JP 97₂₃₃, KR 69₃₂₉, SC 16₄₀₀, TC 92₅₇₇. + '4t4f3r2s6u,' + // #5160: 5 fonts: HK 96₁₂₃, JP 97₂₃₃, KR 77₃₃₇, SC 88₄₇₂, TC 92₅₇₇. + '4t4f3z5e4a,' + // #5161: 5 fonts: HK 96₁₂₃, JP 97₂₃₃, KR 86₃₄₆, SC 88₄₇₂, TC 92₅₇₇. + '4t4f4i4v4a,' + // #5162: 5 fonts: HK 96₁₂₃, JP 97₂₃₃, KR 89₃₄₉, SC 89₄₇₃, TC 92₅₇₇. + '4t4f4l4t3z,' + // #5163: 5 fonts: HK 96₁₂₃, JP 97₂₃₃, KR 92₃₅₂, SC 87₄₇₁, TC 92₅₇₇. + '4t4f4o4o4b,' + // #5164: 5 fonts: HK 96₁₂₃, JP 98₂₃₄, KR 82₃₄₂, SC 89₄₇₃, TC 92₅₇₇. + '4t4g4d5a3z,' + // #5165: 5 fonts: HK 96₁₂₃, JP 98₂₃₄, KR 90₃₅₀, SC 87₄₇₁, TC 92₅₇₇. + '4t4g4l4q4b,' + // #5166: 5 fonts: HK 96₁₂₃, JP 98₂₃₄, KR 90₃₅₀, SC 89₄₇₃, TC 92₅₇₇. + '4t4g4l4s3z,' + // #5167: 5 fonts: HK 96₁₂₃, JP 99₂₃₅, KR 74₃₃₄, SC 87₄₇₁, TC 92₅₇₇. + '4t4h3u5g4b,' + // #5168: 5 fonts: HK 96₁₂₃, JP 99₂₃₅, KR 77₃₃₇, SC 88₄₇₂, TC 92₅₇₇. + '4t4h3x5e4a,' + // #5169: 5 fonts: HK 96₁₂₃, JP 100₂₃₆, KR 70₃₃₀, SC 18₄₀₂, TC 92₅₇₇. + '4t4i3p2t6s,' + // #5170: 5 fonts: HK 96₁₂₃, JP 101₂₃₇, KR 75₃₃₅, SC 30₄₁₄, TC 92₅₇₇. + '4t4j3t3a6g,' + // #5171: 5 fonts: HK 96₁₂₃, JP 101₂₃₇, KR 83₃₄₃, SC 88₄₇₂, TC 92₅₇₇. + '4t4j4b4y4a,' + // #5172: 5 fonts: HK 96₁₂₃, JP 101₂₃₇, KR 93₃₅₃, SC 83₄₆₇, TC 92₅₇₇. + '4t4j4l4j4f,' + // #5173: 5 fonts: HK 96₁₂₃, JP 102₂₃₈, KR 81₃₄₁, SC 88₄₇₂, TC 92₅₇₇. + '4t4k3y5a4a,' + // #5174: 5 fonts: HK 96₁₂₃, JP 103₂₃₉, KR 72₃₃₂, SC 87₄₇₁, TC 92₅₇₇. + '4t4l3o5i4b,' + // #5175: 5 fonts: HK 96₁₂₃, JP 103₂₃₉, KR 92₃₅₂, SC 65₄₄₉, TC 92₅₇₇. + '4t4l4i3s4x,' + // #5176: 5 fonts: HK 96₁₂₃, JP 104₂₄₀, KR 76₃₃₆, SC 88₄₇₂, TC 92₅₇₇. + '4t4m3r5f4a,' + // #5177: 5 fonts: HK 96₁₂₃, JP 104₂₄₀, KR 81₃₄₁, SC 43₄₂₇, TC 92₅₇₇. + '4t4m3w3h5t,' + // #5178: 5 fonts: HK 96₁₂₃, JP 105₂₄₁, KR 81₃₄₁, SC 89₄₇₃, TC 92₅₇₇. + '4t4n3v5b3z,' + // #5179: 5 fonts: HK 96₁₂₃, JP 108₂₄₄, KR 89₃₄₉, SC 86₄₇₀, TC 92₅₇₇. + '4t4q4a4q4c,' + // #5180: 5 fonts: HK 96₁₂₃, JP 108₂₄₄, KR 91₃₅₁, SC 88₄₇₂, TC 92₅₇₇. + '4t4q4c4q4a,' + // #5181: 5 fonts: HK 96₁₂₃, JP 111₂₄₇, KR 69₃₂₉, SC 17₄₀₁, TC 92₅₇₇. + '4t4t3d2t6t,' + // #5182: 5 fonts: HK 96₁₂₃, JP 115₂₅₁, KR 77₃₃₇, SC 88₄₇₂, TC 92₅₇₇. + '4t4x3h5e4a,' + // #5183: 4 fonts: HK 97₁₂₄, JP 7₁₄₃, SC 6₃₉₀, TC 93₅₇₈. + '4us9m7f,' + // #5184: 5 fonts: HK 97₁₂₄, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 93₅₇₈. + '4uu6y2n7d,' + // #5185: 4 fonts: HK 97₁₂₄, JP 9₁₄₅, SC 89₄₇₃, TC 93₅₇₈. + '4uu12p4a,' + // #5186: 5 fonts: HK 97₁₂₄, JP 10₁₄₆, KR 66₃₂₆, SC 9₃₉₃, TC 93₅₇₈. + '4uv6x2o7c,' + // #5187: 5 fonts: HK 97₁₂₄, JP 13₁₄₉, KR 68₃₂₈, SC 89₄₇₃, TC 93₅₇₈. + '4uy6w5o4a,' + // #5188: 5 fonts: HK 97₁₂₄, JP 16₁₅₂, KR 70₃₃₀, SC 19₄₀₃, TC 93₅₇₈. + '4u1b6v2u6s,' + // #5189: 4 fonts: HK 97₁₂₄, JP 17₁₅₃, SC 19₄₀₃, TC 93₅₇₈. + '4u1c9p6s,' + // #5190: 5 fonts: HK 97₁₂₄, JP 19₁₅₅, KR 72₃₃₂, SC 86₄₇₀, TC 93₅₇₈. + '4u1e6u5h4d,' + // #5191: 4 fonts: HK 97₁₂₄, JP 19₁₅₅, SC 21₄₀₅, TC 93₅₇₈. + '4u1e9p6q,' + // #5192: 5 fonts: HK 97₁₂₄, JP 20₁₅₆, KR 72₃₃₂, SC 22₄₀₆, TC 93₅₇₈. + '4u1f6t2v6p,' + // #5193: 5 fonts: HK 97₁₂₄, JP 20₁₅₆, KR 72₃₃₂, SC 23₄₀₇, TC 93₅₇₈. + '4u1f6t2w6o,' + // #5194: 5 fonts: HK 97₁₂₄, JP 20₁₅₆, KR 72₃₃₂, SC 88₄₇₂, TC 93₅₇₈. + '4u1f6t5j4b,' + // #5195: 5 fonts: HK 97₁₂₄, JP 22₁₅₈, KR 73₃₃₃, SC 25₄₀₉, TC 93₅₇₈. + '4u1h6s2x6m,' + // #5196: 5 fonts: HK 97₁₂₄, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 93₅₇₈. + '4u1i6s2y6k,' + // #5197: 5 fonts: HK 97₁₂₄, JP 24₁₆₀, KR 75₃₃₅, SC 29₄₁₃, TC 93₅₇₈. + '4u1j6s2z6i,' + // #5198: 5 fonts: HK 97₁₂₄, JP 25₁₆₁, KR 75₃₃₅, SC 31₄₁₅, TC 93₅₇₈. + '4u1k6r3b6g,' + // #5199: 5 fonts: HK 97₁₂₄, JP 26₁₆₂, KR 76₃₃₆, SC 31₄₁₅, TC 93₅₇₈. + '4u1l6r3a6g,' + // #5200: 5 fonts: HK 97₁₂₄, JP 26₁₆₂, KR 76₃₃₆, SC 88₄₇₂, TC 93₅₇₈. + '4u1l6r5f4b,' + // #5201: 5 fonts: HK 97₁₂₄, JP 28₁₆₄, KR 77₃₃₇, SC 87₄₇₁, TC 93₅₇₈. + '4u1n6q5d4c,' + // #5202: 4 fonts: HK 97₁₂₄, JP 28₁₆₄, SC 35₄₁₉, TC 93₅₇₈. + '4u1n9u6c,' + // #5203: 5 fonts: HK 97₁₂₄, JP 30₁₆₆, KR 78₃₃₈, SC 38₄₂₂, TC 93₅₇₈. + '4u1p6p3f5z,' + // #5204: 5 fonts: HK 97₁₂₄, JP 31₁₆₇, KR 79₃₃₉, SC 40₄₂₄, TC 93₅₇₈. + '4u1q6p3g5x,' + // #5205: 5 fonts: HK 97₁₂₄, JP 32₁₆₈, KR 80₃₄₀, SC 89₄₇₃, TC 93₅₇₈. + '4u1r6p5c4a,' + // #5206: 5 fonts: HK 97₁₂₄, JP 32₁₆₈, KR 80₃₄₀, SC 94₄₇₈, TC 93₅₇₈. + '4u1r6p5h3v,' + // #5207: 5 fonts: HK 97₁₂₄, JP 34₁₇₀, KR 81₃₄₁, SC 44₄₂₈, TC 93₅₇₈. + '4u1t6o3i5t,' + // #5208: 5 fonts: HK 97₁₂₄, JP 34₁₇₀, KR 81₃₄₁, SC 89₄₇₃, TC 93₅₇₈. + '4u1t6o5b4a,' + // #5209: 5 fonts: HK 97₁₂₄, JP 34₁₇₀, KR 82₃₄₂, SC 44₄₂₈, TC 93₅₇₈. + '4u1t6p3h5t,' + // #5210: 5 fonts: HK 97₁₂₄, JP 36₁₇₂, KR 83₃₄₃, SC 47₄₃₁, TC 93₅₇₈. + '4u1v6o3j5q,' + // #5211: 5 fonts: HK 97₁₂₄, JP 36₁₇₂, KR 83₃₄₃, SC 89₄₇₃, TC 93₅₇₈. + '4u1v6o4z4a,' + // #5212: 5 fonts: HK 97₁₂₄, JP 37₁₇₃, KR 84₃₄₄, SC 87₄₇₁, TC 93₅₇₈. + '4u1w6o4w4c,' + // #5213: 4 fonts: HK 97₁₂₄, JP 38₁₇₄, SC 89₄₇₃, TC 93₅₇₈. + '4u1x11m4a,' + // #5214: 4 fonts: HK 97₁₂₄, JP 38₁₇₄, SC 90₄₇₄, TC 93₅₇₈. + '4u1x11n3z,' + // #5215: 5 fonts: HK 97₁₂₄, JP 42₁₇₈, KR 88₃₄₈, SC 55₄₃₉, TC 93₅₇₈. + '4u2b6n3m5i,' + // #5216: 5 fonts: HK 97₁₂₄, JP 42₁₇₈, KR 88₃₄₈, SC 88₄₇₂, TC 93₅₇₈. + '4u2b6n4t4b,' + // #5217: 4 fonts: HK 97₁₂₄, JP 43₁₇₉, SC 56₄₄₀, TC 93₅₇₈. + '4u2c10a5h,' + // #5218: 5 fonts: HK 97₁₂₄, JP 44₁₈₀, KR 89₃₄₉, SC 89₄₇₃, TC 93₅₇₈. + '4u2d6m4t4a,' + // #5219: 5 fonts: HK 97₁₂₄, JP 45₁₈₁, KR 89₃₄₉, SC 87₄₇₁, TC 93₅₇₈. + '4u2e6l4r4c,' + // #5220: 5 fonts: HK 97₁₂₄, JP 45₁₈₁, KR 90₃₅₀, SC 88₄₇₂, TC 93₅₇₈. + '4u2e6m4r4b,' + // #5221: 4 fonts: HK 97₁₂₄, JP 45₁₈₁, SC 60₄₄₄, TC 93₅₇₈. + '4u2e10c5d,' + // #5222: 5 fonts: HK 97₁₂₄, JP 48₁₈₄, KR 91₃₅₁, SC 63₄₄₇, TC 93₅₇₈. + '4u2h6k3r5a,' + // #5223: 5 fonts: HK 97₁₂₄, JP 49₁₈₅, KR 101₃₆₁, SC 89₄₇₃, TC 93₅₇₈. + '4u2i6t4h4a,' + // #5224: 4 fonts: HK 97₁₂₄, JP 49₁₈₅, SC 88₄₇₂, TC 93₅₇₈. + '4u2i11a4b,' + // #5225: 5 fonts: HK 97₁₂₄, JP 50₁₈₆, KR 93₃₅₃, SC 95₄₇₉, TC 93₅₇₈. + '4u2j6k4v3u,' + // #5226: 5 fonts: HK 97₁₂₄, JP 62₁₉₈, KR 69₃₂₉, SC 16₄₀₀, TC 93₅₇₈. + '4u2v5a2s6v,' + // #5227: 5 fonts: HK 97₁₂₄, JP 62₁₉₈, KR 82₃₄₂, SC 45₄₂₉, TC 93₅₇₈. + '4u2v5n3i5s,' + // #5228: 5 fonts: HK 97₁₂₄, JP 63₁₉₉, KR 90₃₅₀, SC 95₄₇₉, TC 93₅₇₈. + '4u2w5u4y3u,' + // #5229: 5 fonts: HK 97₁₂₄, JP 64₂₀₀, KR 73₃₃₃, SC 89₄₇₃, TC 93₅₇₈. + '4u2x5c5j4a,' + // #5230: 5 fonts: HK 97₁₂₄, JP 64₂₀₀, KR 87₃₄₇, SC 82₄₆₆, TC 93₅₇₈. + '4u2x5q4o4h,' + // #5231: 4 fonts: HK 97₁₂₄, JP 64₂₀₀, SC 88₄₇₂, TC 93₅₇₈. + '4u2x10l4b,' + // #5232: 5 fonts: HK 97₁₂₄, JP 65₂₀₁, KR 72₃₃₂, SC 87₄₇₁, TC 93₅₇₈. + '4u2y5a5i4c,' + // #5233: 5 fonts: HK 97₁₂₄, JP 65₂₀₁, KR 73₃₃₃, SC 88₄₇₂, TC 93₅₇₈. + '4u2y5b5i4b,' + // #5234: 5 fonts: HK 97₁₂₄, JP 65₂₀₁, KR 91₃₅₁, SC 64₄₄₈, TC 93₅₇₈. + '4u2y5t3s4z,' + // #5235: 5 fonts: HK 97₁₂₄, JP 68₂₀₄, KR 88₃₄₈, SC 88₄₇₂, TC 93₅₇₈. + '4u3b5n4t4b,' + // #5236: 5 fonts: HK 97₁₂₄, JP 71₂₀₇, KR 67₃₂₇, SC 10₃₉₄, TC 93₅₇₈. + '4u3e4p2o7b,' + // #5237: 5 fonts: HK 97₁₂₄, JP 71₂₀₇, KR 83₃₄₃, SC 47₄₃₁, TC 93₅₇₈. + '4u3e5f3j5q,' + // #5238: 5 fonts: HK 97₁₂₄, JP 72₂₀₈, KR 85₃₄₅, SC 88₄₇₂, TC 93₅₇₈. + '4u3f5g4w4b,' + // #5239: 5 fonts: HK 97₁₂₄, JP 72₂₀₈, KR 88₃₄₈, SC 88₄₇₂, TC 93₅₇₈. + '4u3f5j4t4b,' + // #5240: 5 fonts: HK 97₁₂₄, JP 73₂₀₉, KR 69₃₂₉, SC 16₄₀₀, TC 93₅₇₈. + '4u3g4p2s6v,' + // #5241: 5 fonts: HK 97₁₂₄, JP 73₂₀₉, KR 84₃₄₄, SC 88₄₇₂, TC 93₅₇₈. + '4u3g5e4x4b,' + // #5242: 5 fonts: HK 97₁₂₄, JP 74₂₁₀, KR 91₃₅₁, SC 89₄₇₃, TC 93₅₇₈. + '4u3h5k4r4a,' + // #5243: 5 fonts: HK 97₁₂₄, JP 75₂₁₁, KR 65₃₂₅, SC 5₃₈₉, TC 93₅₇₈. + '4u3i4j2l7g,' + // #5244: 5 fonts: HK 97₁₂₄, JP 75₂₁₁, KR 92₃₅₂, SC 64₄₄₈, TC 93₅₇₈. + '4u3i5k3r4z,' + // #5245: 4 fonts: HK 97₁₂₄, JP 75₂₁₁, SC 12₃₉₆, TC 93₅₇₈. + '4u3i7c6z,' + // #5246: 5 fonts: HK 97₁₂₄, JP 76₂₁₂, KR 82₃₄₂, SC 88₄₇₂, TC 93₅₇₈. + '4u3j4z4z4b,' + // #5247: 4 fonts: HK 97₁₂₄, JP 76₂₁₂, SC 7₃₉₁, TC 93₅₇₈. + '4u3j6w7e,' + // #5248: 5 fonts: HK 97₁₂₄, JP 77₂₁₃, KR 71₃₃₁, SC 88₄₇₂, TC 93₅₇₈. + '4u3k4n5k4b,' + // #5249: 5 fonts: HK 97₁₂₄, JP 79₂₁₅, KR 75₃₃₅, SC 78₄₆₂, TC 93₅₇₈. + '4u3m4p4w4l,' + // #5250: 5 fonts: HK 97₁₂₄, JP 79₂₁₅, KR 84₃₄₄, SC 90₄₇₄, TC 93₅₇₈. + '4u3m4y4z3z,' + // #5251: 5 fonts: HK 97₁₂₄, JP 79₂₁₅, KR 86₃₄₆, SC 83₄₆₇, TC 93₅₇₈. + '4u3m5a4q4g,' + // #5252: 5 fonts: HK 97₁₂₄, JP 79₂₁₅, KR 89₃₄₉, SC 59₄₄₃, TC 93₅₇₈. + '4u3m5d3p5e,' + // #5253: 5 fonts: HK 97₁₂₄, JP 79₂₁₅, KR 89₃₄₉, SC 91₄₇₅, TC 93₅₇₈. + '4u3m5d4v3y,' + // #5254: 5 fonts: HK 97₁₂₄, JP 80₂₁₆, KR 70₃₃₀, SC 18₄₀₂, TC 93₅₇₈. + '4u3n4j2t6t,' + // #5255: 5 fonts: HK 97₁₂₄, JP 80₂₁₆, KR 72₃₃₂, SC 85₄₆₉, TC 93₅₇₈. + '4u3n4l5g4e,' + // #5256: 5 fonts: HK 97₁₂₄, JP 80₂₁₆, KR 75₃₃₅, SC 28₄₁₂, TC 93₅₇₈. + '4u3n4o2y6j,' + // #5257: 5 fonts: HK 97₁₂₄, JP 80₂₁₆, KR 92₃₅₂, SC 88₄₇₂, TC 93₅₇₈. + '4u3n5f4p4b,' + // #5258: 5 fonts: HK 97₁₂₄, JP 81₂₁₇, KR 73₃₃₃, SC 89₄₇₃, TC 93₅₇₈. + '4u3o4l5j4a,' + // #5259: 5 fonts: HK 97₁₂₄, JP 81₂₁₇, KR 80₃₄₀, SC 89₄₇₃, TC 93₅₇₈. + '4u3o4s5c4a,' + // #5260: 5 fonts: HK 97₁₂₄, JP 81₂₁₇, KR 89₃₄₉, SC 89₄₇₃, TC 93₅₇₈. + '4u3o5b4t4a,' + // #5261: 5 fonts: HK 97₁₂₄, JP 82₂₁₈, KR 83₃₄₃, SC 89₄₇₃, TC 93₅₇₈. + '4u3p4u4z4a,' + // #5262: 5 fonts: HK 97₁₂₄, JP 83₂₁₉, KR 71₃₃₁, SC 89₄₇₃, TC 93₅₇₈. + '4u3q4h5l4a,' + // #5263: 5 fonts: HK 97₁₂₄, JP 83₂₁₉, KR 73₃₃₃, SC 24₄₀₈, TC 93₅₇₈. + '4u3q4j2w6n,' + // #5264: 5 fonts: HK 97₁₂₄, JP 83₂₁₉, KR 78₃₃₈, SC 88₄₇₂, TC 93₅₇₈. + '4u3q4o5d4b,' + // #5265: 5 fonts: HK 97₁₂₄, JP 83₂₁₉, KR 79₃₃₉, SC 89₄₇₃, TC 93₅₇₈. + '4u3q4p5d4a,' + // #5266: 5 fonts: HK 97₁₂₄, JP 83₂₁₉, KR 80₃₄₀, SC 88₄₇₂, TC 93₅₇₈. + '4u3q4q5b4b,' + // #5267: 5 fonts: HK 97₁₂₄, JP 83₂₁₉, KR 82₃₄₂, SC 46₄₃₀, TC 93₅₇₈. + '4u3q4s3j5r,' + // #5268: 5 fonts: HK 97₁₂₄, JP 84₂₂₀, KR 72₃₃₂, SC 87₄₇₁, TC 93₅₇₈. + '4u3r4h5i4c,' + // #5269: 5 fonts: HK 97₁₂₄, JP 84₂₂₀, KR 85₃₄₅, SC 88₄₇₂, TC 93₅₇₈. + '4u3r4u4w4b,' + // #5270: 5 fonts: HK 97₁₂₄, JP 84₂₂₀, KR 90₃₅₀, SC 89₄₇₃, TC 93₅₇₈. + '4u3r4z4s4a,' + // #5271: 5 fonts: HK 97₁₂₄, JP 84₂₂₀, KR 101₃₆₁, SC 18₄₀₂, TC 93₅₇₈. + '4u3r5k1o6t,' + // #5272: 5 fonts: HK 97₁₂₄, JP 85₂₂₁, KR 91₃₅₁, SC 89₄₇₃, TC 93₅₇₈. + '4u3s4z4r4a,' + // #5273: 5 fonts: HK 97₁₂₄, JP 85₂₂₁, KR 92₃₅₂, SC 88₄₇₂, TC 93₅₇₈. + '4u3s5a4p4b,' + // #5274: 5 fonts: HK 97₁₂₄, JP 86₂₂₂, KR 77₃₃₇, SC 90₄₇₄, TC 93₅₇₈. + '4u3t4k5g3z,' + // #5275: 5 fonts: HK 97₁₂₄, JP 86₂₂₂, KR 79₃₃₉, SC 89₄₇₃, TC 93₅₇₈. + '4u3t4m5d4a,' + // #5276: 5 fonts: HK 97₁₂₄, JP 86₂₂₂, KR 83₃₄₃, SC 88₄₇₂, TC 93₅₇₈. + '4u3t4q4y4b,' + // #5277: 5 fonts: HK 97₁₂₄, JP 86₂₂₂, KR 87₃₄₇, SC 89₄₇₃, TC 93₅₇₈. + '4u3t4u4v4a,' + // #5278: 5 fonts: HK 97₁₂₄, JP 86₂₂₂, KR 89₃₄₉, SC 86₄₇₀, TC 93₅₇₈. + '4u3t4w4q4d,' + // #5279: 5 fonts: HK 97₁₂₄, JP 86₂₂₂, KR 92₃₅₂, SC 65₄₄₉, TC 93₅₇₈. + '4u3t4z3s4y,' + // #5280: 5 fonts: HK 97₁₂₄, JP 87₂₂₃, KR 67₃₂₇, SC 9₃₉₃, TC 93₅₇₈. + '4u3u3z2n7c,' + // #5281: 5 fonts: HK 97₁₂₄, JP 87₂₂₃, KR 75₃₃₅, SC 89₄₇₃, TC 93₅₇₈. + '4u3u4h5h4a,' + // #5282: 5 fonts: HK 97₁₂₄, JP 87₂₂₃, KR 91₃₅₁, SC 89₄₇₃, TC 93₅₇₈. + '4u3u4x4r4a,' + // #5283: 5 fonts: HK 97₁₂₄, JP 87₂₂₃, KR 92₃₅₂, SC 89₄₇₃, TC 93₅₇₈. + '4u3u4y4q4a,' + // #5284: 5 fonts: HK 97₁₂₄, JP 87₂₂₃, KR 93₃₅₃, SC 89₄₇₃, TC 93₅₇₈. + '4u3u4z4p4a,' + // #5285: 5 fonts: HK 97₁₂₄, JP 88₂₂₄, KR 81₃₄₁, SC 88₄₇₂, TC 93₅₇₈. + '4u3v4m5a4b,' + // #5286: 5 fonts: HK 97₁₂₄, JP 88₂₂₄, KR 84₃₄₄, SC 90₄₇₄, TC 93₅₇₈. + '4u3v4p4z3z,' + // #5287: 5 fonts: HK 97₁₂₄, JP 88₂₂₄, KR 87₃₄₇, SC 90₄₇₄, TC 93₅₇₈. + '4u3v4s4w3z,' + // #5288: 5 fonts: HK 97₁₂₄, JP 88₂₂₄, KR 90₃₅₀, SC 90₄₇₄, TC 93₅₇₈. + '4u3v4v4t3z,' + // #5289: 5 fonts: HK 97₁₂₄, JP 89₂₂₅, KR 65₃₂₅, SC 7₃₉₁, TC 93₅₇₈. + '4u3w3v2n7e,' + // #5290: 5 fonts: HK 97₁₂₄, JP 89₂₂₅, KR 67₃₂₇, SC 12₃₉₆, TC 93₅₇₈. + '4u3w3x2q6z,' + // #5291: 5 fonts: HK 97₁₂₄, JP 89₂₂₅, KR 86₃₄₆, SC 88₄₇₂, TC 93₅₇₈. + '4u3w4q4v4b,' + // #5292: 5 fonts: HK 97₁₂₄, JP 89₂₂₅, KR 91₃₅₁, SC 88₄₇₂, TC 93₅₇₈. + '4u3w4v4q4b,' + // #5293: 4 fonts: HK 97₁₂₄, JP 89₂₂₅, SC 42₄₂₆, TC 93₅₇₈. + '4u3w7s5v,' + // #5294: 5 fonts: HK 97₁₂₄, JP 90₂₂₆, KR 68₃₂₈, SC 88₄₇₂, TC 93₅₇₈. + '4u3x3x5n4b,' + // #5295: 5 fonts: HK 97₁₂₄, JP 90₂₂₆, KR 78₃₃₈, SC 88₄₇₂, TC 93₅₇₈. + '4u3x4h5d4b,' + // #5296: 5 fonts: HK 97₁₂₄, JP 90₂₂₆, KR 90₃₅₀, SC 90₄₇₄, TC 93₅₇₈. + '4u3x4t4t3z,' + // #5297: 5 fonts: HK 97₁₂₄, JP 90₂₂₆, KR 92₃₅₂, SC 64₄₄₈, TC 93₅₇₈. + '4u3x4v3r4z,' + // #5298: 5 fonts: HK 97₁₂₄, JP 91₂₂₇, KR 70₃₃₀, SC 18₄₀₂, TC 93₅₇₈. + '4u3y3y2t6t,' + // #5299: 5 fonts: HK 97₁₂₄, JP 91₂₂₇, KR 75₃₃₅, SC 89₄₇₃, TC 93₅₇₈. + '4u3y4d5h4a,' + // #5300: 5 fonts: HK 97₁₂₄, JP 91₂₂₇, KR 91₃₅₁, SC 90₄₇₄, TC 93₅₇₈. + '4u3y4t4s3z,' + // #5301: 5 fonts: HK 97₁₂₄, JP 92₂₂₈, KR 68₃₂₈, SC 14₃₉₈, TC 93₅₇₈. + '4u3z3v2r6x,' + // #5302: 5 fonts: HK 97₁₂₄, JP 92₂₂₈, KR 69₃₂₉, SC 16₄₀₀, TC 93₅₇₈. + '4u3z3w2s6v,' + // #5303: 5 fonts: HK 97₁₂₄, JP 92₂₂₈, KR 71₃₃₁, SC 91₄₇₅, TC 93₅₇₈. + '4u3z3y5n3y,' + // #5304: 5 fonts: HK 97₁₂₄, JP 92₂₂₈, KR 81₃₄₁, SC 44₄₂₈, TC 93₅₇₈. + '4u3z4i3i5t,' + // #5305: 5 fonts: HK 97₁₂₄, JP 92₂₂₈, KR 86₃₄₆, SC 88₄₇₂, TC 93₅₇₈. + '4u3z4n4v4b,' + // #5306: 5 fonts: HK 97₁₂₄, JP 92₂₂₈, KR 87₃₄₇, SC 87₄₇₁, TC 93₅₇₈. + '4u3z4o4t4c,' + // #5307: 5 fonts: HK 97₁₂₄, JP 92₂₂₈, KR 88₃₄₈, SC 56₄₄₀, TC 93₅₇₈. + '4u3z4p3n5h,' + // #5308: 5 fonts: HK 97₁₂₄, JP 92₂₂₈, KR 93₃₅₃, SC 89₄₇₃, TC 93₅₇₈. + '4u3z4u4p4a,' + // #5309: 5 fonts: HK 97₁₂₄, JP 93₂₂₉, KR 65₃₂₅, SC 89₄₇₃, TC 93₅₇₈. + '4u4a3r5r4a,' + // #5310: 5 fonts: HK 97₁₂₄, JP 93₂₂₉, KR 66₃₂₆, SC 89₄₇₃, TC 93₅₇₈. + '4u4a3s5q4a,' + // #5311: 5 fonts: HK 97₁₂₄, JP 93₂₂₉, KR 74₃₃₄, SC 27₄₁₁, TC 93₅₇₈. + '4u4a4a2y6k,' + // #5312: 5 fonts: HK 97₁₂₄, JP 93₂₂₉, KR 74₃₃₄, SC 88₄₇₂, TC 93₅₇₈. + '4u4a4a5h4b,' + // #5313: 5 fonts: HK 97₁₂₄, JP 94₂₃₀, KR 71₃₃₁, SC 20₄₀₄, TC 93₅₇₈. + '4u4b3w2u6r,' + // #5314: 5 fonts: HK 97₁₂₄, JP 94₂₃₀, KR 89₃₄₉, SC 59₄₄₃, TC 93₅₇₈. + '4u4b4o3p5e,' + // #5315: 5 fonts: HK 97₁₂₄, JP 95₂₃₁, KR 69₃₂₉, SC 89₄₇₃, TC 93₅₇₈. + '4u4c3t5n4a,' + // #5316: 5 fonts: HK 97₁₂₄, JP 95₂₃₁, KR 85₃₄₅, SC 51₄₃₅, TC 93₅₇₈. + '4u4c4j3l5m,' + // #5317: 5 fonts: HK 97₁₂₄, JP 96₂₃₂, KR 65₃₂₅, SC 89₄₇₃, TC 93₅₇₈. + '4u4d3o5r4a,' + // #5318: 5 fonts: HK 97₁₂₄, JP 96₂₃₂, KR 70₃₃₀, SC 91₄₇₅, TC 93₅₇₈. + '4u4d3t5o3y,' + // #5319: 5 fonts: HK 97₁₂₄, JP 96₂₃₂, KR 72₃₃₂, SC 88₄₇₂, TC 93₅₇₈. + '4u4d3v5j4b,' + // #5320: 5 fonts: HK 97₁₂₄, JP 96₂₃₂, KR 73₃₃₃, SC 87₄₇₁, TC 93₅₇₈. + '4u4d3w5h4c,' + // #5321: 5 fonts: HK 97₁₂₄, JP 96₂₃₂, KR 79₃₃₉, SC 40₄₂₄, TC 93₅₇₈. + '4u4d4c3g5x,' + // #5322: 5 fonts: HK 97₁₂₄, JP 96₂₃₂, KR 84₃₄₄, SC 91₄₇₅, TC 93₅₇₈. + '4u4d4h5a3y,' + // #5323: 5 fonts: HK 97₁₂₄, JP 96₂₃₂, KR 91₃₅₁, SC 89₄₇₃, TC 93₅₇₈. + '4u4d4o4r4a,' + // #5324: 5 fonts: HK 97₁₂₄, JP 96₂₃₂, KR 93₃₅₃, SC 87₄₇₁, TC 93₅₇₈. + '4u4d4q4n4c,' + // #5325: 5 fonts: HK 97₁₂₄, JP 97₂₃₃, KR 75₃₃₅, SC 30₄₁₄, TC 93₅₇₈. + '4u4e3x3a6h,' + // #5326: 5 fonts: HK 97₁₂₄, JP 97₂₃₃, KR 78₃₃₈, SC 90₄₇₄, TC 93₅₇₈. + '4u4e4a5f3z,' + // #5327: 5 fonts: HK 97₁₂₄, JP 97₂₃₃, KR 80₃₄₀, SC 41₄₂₅, TC 93₅₇₈. + '4u4e4c3g5w,' + // #5328: 5 fonts: HK 97₁₂₄, JP 97₂₃₃, KR 82₃₄₂, SC 89₄₇₃, TC 93₅₇₈. + '4u4e4e5a4a,' + // #5329: 5 fonts: HK 97₁₂₄, JP 97₂₃₃, KR 83₃₄₃, SC 89₄₇₃, TC 93₅₇₈. + '4u4e4f4z4a,' + // #5330: 5 fonts: HK 97₁₂₄, JP 97₂₃₃, KR 84₃₄₄, SC 49₄₃₃, TC 93₅₇₈. + '4u4e4g3k5o,' + // #5331: 5 fonts: HK 97₁₂₄, JP 97₂₃₃, KR 91₃₅₁, SC 90₄₇₄, TC 93₅₇₈. + '4u4e4n4s3z,' + // #5332: 5 fonts: HK 97₁₂₄, JP 98₂₃₄, KR 84₃₄₄, SC 91₄₇₅, TC 93₅₇₈. + '4u4f4f5a3y,' + // #5333: 5 fonts: HK 97₁₂₄, JP 98₂₃₄, KR 91₃₅₁, SC 89₄₇₃, TC 93₅₇₈. + '4u4f4m4r4a,' + // #5334: 5 fonts: HK 97₁₂₄, JP 99₂₃₅, KR 83₃₄₃, SC 89₄₇₃, TC 93₅₇₈. + '4u4g4d4z4a,' + // #5335: 5 fonts: HK 97₁₂₄, JP 99₂₃₅, KR 85₃₄₅, SC 50₄₃₄, TC 93₅₇₈. + '4u4g4f3k5n,' + // #5336: 5 fonts: HK 97₁₂₄, JP 99₂₃₅, KR 87₃₄₇, SC 89₄₇₃, TC 93₅₇₈. + '4u4g4h4v4a,' + // #5337: 5 fonts: HK 97₁₂₄, JP 99₂₃₅, KR 88₃₄₈, SC 89₄₇₃, TC 93₅₇₈. + '4u4g4i4u4a,' + // #5338: 5 fonts: HK 97₁₂₄, JP 99₂₃₅, KR 89₃₄₉, SC 89₄₇₃, TC 93₅₇₈. + '4u4g4j4t4a,' + // #5339: 5 fonts: HK 97₁₂₄, JP 100₂₃₆, KR 79₃₃₉, SC 88₄₇₂, TC 93₅₇₈. + '4u4h3y5c4b,' + // #5340: 5 fonts: HK 97₁₂₄, JP 100₂₃₆, KR 80₃₄₀, SC 87₄₇₁, TC 93₅₇₈. + '4u4h3z5a4c,' + // #5341: 5 fonts: HK 97₁₂₄, JP 101₂₃₇, KR 100₃₆₀, SC 89₄₇₃, TC 93₅₇₈. + '4u4i4s4i4a,' + // #5342: 5 fonts: HK 97₁₂₄, JP 102₂₃₈, KR 81₃₄₁, SC 88₄₇₂, TC 93₅₇₈. + '4u4j3y5a4b,' + // #5343: 5 fonts: HK 97₁₂₄, JP 102₂₃₈, KR 90₃₅₀, SC 89₄₇₃, TC 93₅₇₈. + '4u4j4h4s4a,' + // #5344: 5 fonts: HK 97₁₂₄, JP 102₂₃₈, KR 91₃₅₁, SC 88₄₇₂, TC 93₅₇₈. + '4u4j4i4q4b,' + // #5345: 5 fonts: HK 97₁₂₄, JP 102₂₃₈, KR 91₃₅₁, SC 90₄₇₄, TC 93₅₇₈. + '4u4j4i4s3z,' + // #5346: 5 fonts: HK 97₁₂₄, JP 102₂₃₈, KR 93₃₅₃, SC 89₄₇₃, TC 93₅₇₈. + '4u4j4k4p4a,' + // #5347: 6 fonts: HK 97₁₂₄, JP 103₂₃₉, KR 0₂₆₀, SC 87₄₇₁, TC 93₅₇₈, Mongolian₆₆₂. + '4u4ku8c4c3f,' + // #5348: 5 fonts: HK 97₁₂₄, JP 103₂₃₉, KR 75₃₃₅, SC 88₄₇₂, TC 93₅₇₈. + '4u4k3r5g4b,' + // #5349: 5 fonts: HK 97₁₂₄, JP 103₂₃₉, KR 88₃₄₈, SC 89₄₇₃, TC 93₅₇₈. + '4u4k4e4u4a,' + // #5350: 5 fonts: HK 97₁₂₄, JP 103₂₃₉, KR 91₃₅₁, SC 89₄₇₃, TC 93₅₇₈. + '4u4k4h4r4a,' + // #5351: 5 fonts: HK 97₁₂₄, JP 103₂₃₉, KR 92₃₅₂, SC 88₄₇₂, TC 93₅₇₈. + '4u4k4i4p4b,' + // #5352: 5 fonts: HK 97₁₂₄, JP 104₂₄₀, KR 68₃₂₈, SC 90₄₇₄, TC 93₅₇₈. + '4u4l3j5p3z,' + // #5353: 5 fonts: HK 97₁₂₄, JP 104₂₄₀, KR 89₃₄₉, SC 89₄₇₃, TC 93₅₇₈. + '4u4l4e4t4a,' + // #5354: 5 fonts: HK 97₁₂₄, JP 104₂₄₀, KR 93₃₅₃, SC 89₄₇₃, TC 93₅₇₈. + '4u4l4i4p4a,' + // #5355: 5 fonts: HK 97₁₂₄, JP 105₂₄₁, KR 65₃₂₅, SC 5₃₈₉, TC 93₅₇₈. + '4u4m3f2l7g,' + // #5356: 5 fonts: HK 97₁₂₄, JP 106₂₄₂, KR 83₃₄₃, SC 90₄₇₄, TC 93₅₇₈. + '4u4n3w5a3z,' + // #5357: 5 fonts: HK 97₁₂₄, JP 106₂₄₂, KR 91₃₅₁, SC 89₄₇₃, TC 93₅₇₈. + '4u4n4e4r4a,' + // #5358: 5 fonts: HK 97₁₂₄, JP 107₂₄₃, KR 83₃₄₃, SC 88₄₇₂, TC 93₅₇₈. + '4u4o3v4y4b,' + // #5359: 5 fonts: HK 97₁₂₄, JP 107₂₄₃, KR 83₃₄₃, SC 91₄₇₅, TC 93₅₇₈. + '4u4o3v5b3y,' + // #5360: 7 fonts: HK 97₁₂₄, JP 107₂₄₃, KR 110₃₇₀, SC 86₄₇₀, TC 93₅₇₈, Math₆₅₅, Symbols₇₀₂. + '4u4o4w3v4d2y1u,' + // #5361: 5 fonts: HK 97₁₂₄, JP 108₂₄₄, KR 70₃₃₀, SC 89₄₇₃, TC 93₅₇₈. + '4u4p3h5m4a,' + // #5362: 5 fonts: HK 97₁₂₄, JP 108₂₄₄, KR 83₃₄₃, SC 90₄₇₄, TC 93₅₇₈. + '4u4p3u5a3z,' + // #5363: 5 fonts: HK 97₁₂₄, JP 110₂₄₆, KR 83₃₄₃, SC 89₄₇₃, TC 93₅₇₈. + '4u4r3s4z4a,' + // #5364: 8 fonts: HK 97₁₂₄, JP 111₂₄₇, KR 107₃₆₇, SC 83₄₆₇, TC 93₅₇₈, Mongolian₆₆₂, Phags Pa₆₈₇, Yi₇₂₁. + '4u4s4p3v4g3fy1h,' + // #5365: 5 fonts: HK 97₁₂₄, JP 112₂₄₈, KR 68₃₂₈, SC 14₃₉₈, TC 93₅₇₈. + '4u4t3b2r6x,' + // #5366: 5 fonts: HK 97₁₂₄, JP 112₂₄₈, KR 76₃₃₆, SC 90₄₇₄, TC 93₅₇₈. + '4u4t3j5h3z,' + // #5367: 6 fonts: HK 97₁₂₄, JP 113₂₄₉, KR 111₃₇₁, SC 79₄₆₃, TC 93₅₇₈, Noto Sans₅₉₁. + '4u4u4r3n4km,' + // #5368: 5 fonts: HK 97₁₂₄, JP 114₂₅₀, KR 93₃₅₃, SC 88₄₇₂, TC 93₅₇₈. + '4u4v3y4o4b,' + // #5369: 5 fonts: HK 98₁₂₅, JP 7₁₄₃, KR 65₃₂₅, SC 6₃₉₀, TC 94₅₇₉. + '4vr6z2m7g,' + // #5370: 5 fonts: HK 98₁₂₅, JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 94₅₇₉. + '4vs6z2m7f,' + // #5371: 5 fonts: HK 98₁₂₅, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 94₅₇₉. + '4vt6y2n7e,' + // #5372: 5 fonts: HK 98₁₂₅, JP 9₁₄₅, KR 66₃₂₆, SC 88₄₇₂, TC 94₅₇₉. + '4vt6y5p4c,' + // #5373: 4 fonts: HK 98₁₂₅, JP 9₁₄₅, SC 90₄₇₄, TC 94₅₇₉. + '4vt12q4a,' + // #5374: 4 fonts: HK 98₁₂₅, JP 10₁₄₆, SC 9₃₉₃, TC 94₅₇₉. + '4vu9m7d,' + // #5375: 5 fonts: HK 98₁₂₅, JP 12₁₄₈, KR 68₃₂₈, SC 13₃₉₇, TC 94₅₇₉. + '4vw6x2q6z,' + // #5376: 4 fonts: HK 98₁₂₅, JP 15₁₅₁, SC 17₄₀₁, TC 94₅₇₉. + '4vz9p6v,' + // #5377: 5 fonts: HK 98₁₂₅, JP 19₁₅₅, KR 71₃₃₁, SC 21₄₀₅, TC 94₅₇₉. + '4v1d6t2v6r,' + // #5378: 5 fonts: HK 98₁₂₅, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 94₅₇₉. + '4v1f6t2w6o,' + // #5379: 5 fonts: HK 98₁₂₅, JP 21₁₅₇, KR 73₃₃₃, SC 88₄₇₂, TC 94₅₇₉. + '4v1f6t5i4c,' + // #5380: 5 fonts: HK 98₁₂₅, JP 22₁₅₈, KR 73₃₃₃, SC 89₄₇₃, TC 94₅₇₉. + '4v1g6s5j4b,' + // #5381: 5 fonts: HK 98₁₂₅, JP 22₁₅₈, KR 73₃₃₃, SC 91₄₇₅, TC 94₅₇₉. + '4v1g6s5l3z,' + // #5382: 5 fonts: HK 98₁₂₅, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 94₅₇₉. + '4v1h6s2y6l,' + // #5383: 5 fonts: HK 98₁₂₅, JP 23₁₅₉, KR 74₃₃₄, SC 90₄₇₄, TC 94₅₇₉. + '4v1h6s5j4a,' + // #5384: 5 fonts: HK 98₁₂₅, JP 24₁₆₀, KR 75₃₃₅, SC 29₄₁₃, TC 94₅₇₉. + '4v1i6s2z6j,' + // #5385: 4 fonts: HK 98₁₂₅, JP 25₁₆₁, SC 90₄₇₄, TC 94₅₇₉. + '4v1j12a4a,' + // #5386: 5 fonts: HK 98₁₂₅, JP 27₁₆₃, KR 77₃₃₇, SC 88₄₇₂, TC 94₅₇₉. + '4v1l6r5e4c,' + // #5387: 5 fonts: HK 98₁₂₅, JP 28₁₆₄, KR 77₃₃₇, SC 35₄₁₉, TC 94₅₇₉. + '4v1m6q3d6d,' + // #5388: 5 fonts: HK 98₁₂₅, JP 31₁₆₇, KR 79₃₃₉, SC 40₄₂₄, TC 94₅₇₉. + '4v1p6p3g5y,' + // #5389: 5 fonts: HK 98₁₂₅, JP 32₁₆₈, KR 80₃₄₀, SC 41₄₂₅, TC 94₅₇₉. + '4v1q6p3g5x,' + // #5390: 5 fonts: HK 98₁₂₅, JP 35₁₇₁, KR 82₃₄₂, SC 91₄₇₅, TC 94₅₇₉. + '4v1t6o5c3z,' + // #5391: 4 fonts: HK 98₁₂₅, JP 36₁₇₂, SC 89₄₇₃, TC 94₅₇₉. + '4v1u11o4b,' + // #5392: 4 fonts: HK 98₁₂₅, JP 38₁₇₄, SC 49₄₃₃, TC 94₅₇₉. + '4v1w9y5p,' + // #5393: 4 fonts: HK 98₁₂₅, JP 38₁₇₄, SC 90₄₇₄, TC 94₅₇₉. + '4v1w11n4a,' + // #5394: 5 fonts: HK 98₁₂₅, JP 39₁₇₅, KR 85₃₄₅, SC 51₄₃₅, TC 94₅₇₉. + '4v1x6n3l5n,' + // #5395: 4 fonts: HK 98₁₂₅, JP 39₁₇₅, SC 89₄₇₃, TC 94₅₇₉. + '4v1x11l4b,' + // #5396: 4 fonts: HK 98₁₂₅, JP 39₁₇₅, SC 90₄₇₄, TC 94₅₇₉. + '4v1x11m4a,' + // #5397: 5 fonts: HK 98₁₂₅, JP 40₁₇₆, KR 86₃₄₆, SC 52₄₃₆, TC 94₅₇₉. + '4v1y6n3l5m,' + // #5398: 4 fonts: HK 98₁₂₅, JP 40₁₇₆, SC 89₄₇₃, TC 94₅₇₉. + '4v1y11k4b,' + // #5399: 5 fonts: HK 98₁₂₅, JP 41₁₇₇, KR 86₃₄₆, SC 53₄₃₇, TC 94₅₇₉. + '4v1z6m3m5l,' + // #5400: 4 fonts: HK 98₁₂₅, JP 42₁₇₈, SC 90₄₇₄, TC 94₅₇₉. + '4v2a11j4a,' + // #5401: 4 fonts: HK 98₁₂₅, JP 44₁₈₀, SC 89₄₇₃, TC 94₅₇₉. + '4v2c11g4b,' + // #5402: 5 fonts: HK 98₁₂₅, JP 45₁₈₁, KR 89₃₄₉, SC 59₄₄₃, TC 94₅₇₉. + '4v2d6l3p5f,' + // #5403: 5 fonts: HK 98₁₂₅, JP 45₁₈₁, KR 90₃₅₀, SC 60₄₄₄, TC 94₅₇₉. + '4v2d6m3p5e,' + // #5404: 5 fonts: HK 98₁₂₅, JP 46₁₈₂, KR 90₃₅₀, SC 90₄₇₄, TC 94₅₇₉. + '4v2e6l4t4a,' + // #5405: 4 fonts: HK 98₁₂₅, JP 46₁₈₂, SC 78₄₆₂, TC 94₅₇₉. + '4v2e10t4m,' + // #5406: 4 fonts: HK 98₁₂₅, JP 46₁₈₂, SC 86₄₇₀, TC 94₅₇₉. + '4v2e11b4e,' + // #5407: 4 fonts: HK 98₁₂₅, JP 46₁₈₂, SC 87₄₇₁, TC 94₅₇₉. + '4v2e11c4d,' + // #5408: 4 fonts: HK 98₁₂₅, JP 46₁₈₂, SC 91₄₇₅, TC 94₅₇₉. + '4v2e11g3z,' + // #5409: 5 fonts: HK 98₁₂₅, JP 47₁₈₃, KR 90₃₅₀, SC 62₄₄₆, TC 94₅₇₉. + '4v2f6k3r5c,' + // #5410: 5 fonts: HK 98₁₂₅, JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇, TC 94₅₇₉. + '4v2f6l3r5b,' + // #5411: 5 fonts: HK 98₁₂₅, JP 48₁₈₄, KR 91₃₅₁, SC 64₄₄₈, TC 94₅₇₉. + '4v2g6k3s5a,' + // #5412: 5 fonts: HK 98₁₂₅, JP 50₁₈₆, KR 92₃₅₂, SC 66₄₅₀, TC 94₅₇₉. + '4v2i6j3t4y,' + // #5413: 5 fonts: HK 98₁₂₅, JP 50₁₈₆, KR 93₃₅₃, SC 67₄₅₁, TC 94₅₇₉. + '4v2i6k3t4x,' + // #5414: 5 fonts: HK 98₁₂₅, JP 50₁₈₆, KR 93₃₅₃, SC 89₄₇₃, TC 94₅₇₉. + '4v2i6k4p4b,' + // #5415: 5 fonts: HK 98₁₂₅, JP 50₁₈₆, KR 93₃₅₃, SC 90₄₇₄, TC 94₅₇₉. + '4v2i6k4q4a,' + // #5416: 5 fonts: HK 98₁₂₅, JP 61₁₉₇, KR 82₃₄₂, SC 45₄₂₉, TC 94₅₇₉. + '4v2t5o3i5t,' + // #5417: 5 fonts: HK 98₁₂₅, JP 61₁₉₇, KR 88₃₄₈, SC 90₄₇₄, TC 94₅₇₉. + '4v2t5u4v4a,' + // #5418: 5 fonts: HK 98₁₂₅, JP 62₁₉₈, KR 69₃₂₉, SC 88₄₇₂, TC 94₅₇₉. + '4v2u5a5m4c,' + // #5419: 5 fonts: HK 98₁₂₅, JP 62₁₉₈, KR 79₃₃₉, SC 38₄₂₂, TC 94₅₇₉. + '4v2u5k3e6a,' + // #5420: 5 fonts: HK 98₁₂₅, JP 62₁₉₈, KR 89₃₄₉, SC 90₄₇₄, TC 94₅₇₉. + '4v2u5u4u4a,' + // #5421: 5 fonts: HK 98₁₂₅, JP 66₂₀₂, KR 78₃₃₈, SC 90₄₇₄, TC 94₅₇₉. + '4v2y5f5f4a,' + // #5422: 5 fonts: HK 98₁₂₅, JP 67₂₀₃, KR 90₃₅₀, SC 88₄₇₂, TC 94₅₇₉. + '4v2z5q4r4c,' + // #5423: 5 fonts: HK 98₁₂₅, JP 69₂₀₅, KR 77₃₃₇, SC 89₄₇₃, TC 94₅₇₉. + '4v3b5b5f4b,' + // #5424: 5 fonts: HK 98₁₂₅, JP 69₂₀₅, KR 85₃₄₅, SC 89₄₇₃, TC 94₅₇₉. + '4v3b5j4x4b,' + // #5425: 5 fonts: HK 98₁₂₅, JP 70₂₀₆, KR 87₃₄₇, SC 54₄₃₈, TC 94₅₇₉. + '4v3c5k3m5k,' + // #5426: 5 fonts: HK 98₁₂₅, JP 72₂₀₈, KR 84₃₄₄, SC 49₄₃₃, TC 94₅₇₉. + '4v3e5f3k5p,' + // #5427: 5 fonts: HK 98₁₂₅, JP 72₂₀₈, KR 89₃₄₉, SC 58₄₄₂, TC 94₅₇₉. + '4v3e5k3o5g,' + // #5428: 5 fonts: HK 98₁₂₅, JP 73₂₀₉, KR 78₃₃₈, SC 89₄₇₃, TC 94₅₇₉. + '4v3f4y5e4b,' + // #5429: 5 fonts: HK 98₁₂₅, JP 76₂₁₂, KR 88₃₄₈, SC 89₄₇₃, TC 94₅₇₉. + '4v3i5f4u4b,' + // #5430: 5 fonts: HK 98₁₂₅, JP 77₂₁₃, KR 71₃₃₁, SC 21₄₀₅, TC 94₅₇₉. + '4v3j4n2v6r,' + // #5431: 5 fonts: HK 98₁₂₅, JP 77₂₁₃, KR 72₃₃₂, SC 22₄₀₆, TC 94₅₇₉. + '4v3j4o2v6q,' + // #5432: 5 fonts: HK 98₁₂₅, JP 78₂₁₄, KR 68₃₂₈, SC 90₄₇₄, TC 94₅₇₉. + '4v3k4j5p4a,' + // #5433: 5 fonts: HK 98₁₂₅, JP 78₂₁₄, KR 74₃₃₄, SC 88₄₇₂, TC 94₅₇₉. + '4v3k4p5h4c,' + // #5434: 5 fonts: HK 98₁₂₅, JP 78₂₁₄, KR 75₃₃₅, SC 90₄₇₄, TC 94₅₇₉. + '4v3k4q5i4a,' + // #5435: 5 fonts: HK 98₁₂₅, JP 79₂₁₅, KR 69₃₂₉, SC 17₄₀₁, TC 94₅₇₉. + '4v3l4j2t6v,' + // #5436: 5 fonts: HK 98₁₂₅, JP 79₂₁₅, KR 91₃₅₁, SC 88₄₇₂, TC 94₅₇₉. + '4v3l5f4q4c,' + // #5437: 5 fonts: HK 98₁₂₅, JP 80₂₁₆, KR 78₃₃₈, SC 89₄₇₃, TC 94₅₇₉. + '4v3m4r5e4b,' + // #5438: 5 fonts: HK 98₁₂₅, JP 81₂₁₇, KR 68₃₂₈, SC 89₄₇₃, TC 94₅₇₉. + '4v3n4g5o4b,' + // #5439: 5 fonts: HK 98₁₂₅, JP 81₂₁₇, KR 82₃₄₂, SC 92₄₇₆, TC 94₅₇₉. + '4v3n4u5d3y,' + // #5440: 5 fonts: HK 98₁₂₅, JP 82₂₁₈, KR 66₃₂₆, SC 90₄₇₄, TC 94₅₇₉. + '4v3o4d5r4a,' + // #5441: 5 fonts: HK 98₁₂₅, JP 82₂₁₈, KR 67₃₂₇, SC 89₄₇₃, TC 94₅₇₉. + '4v3o4e5p4b,' + // #5442: 5 fonts: HK 98₁₂₅, JP 82₂₁₈, KR 80₃₄₀, SC 40₄₂₄, TC 94₅₇₉. + '4v3o4r3f5y,' + // #5443: 5 fonts: HK 98₁₂₅, JP 82₂₁₈, KR 89₃₄₉, SC 58₄₄₂, TC 94₅₇₉. + '4v3o5a3o5g,' + // #5444: 5 fonts: HK 98₁₂₅, JP 83₂₁₉, KR 78₃₃₈, SC 90₄₇₄, TC 94₅₇₉. + '4v3p4o5f4a,' + // #5445: 5 fonts: HK 98₁₂₅, JP 83₂₁₉, KR 90₃₅₀, SC 90₄₇₄, TC 94₅₇₉. + '4v3p5a4t4a,' + // #5446: 5 fonts: HK 98₁₂₅, JP 84₂₂₀, KR 72₃₃₂, SC 22₄₀₆, TC 94₅₇₉. + '4v3q4h2v6q,' + // #5447: 5 fonts: HK 98₁₂₅, JP 84₂₂₀, KR 76₃₃₆, SC 89₄₇₃, TC 94₅₇₉. + '4v3q4l5g4b,' + // #5448: 5 fonts: HK 98₁₂₅, JP 84₂₂₀, KR 86₃₄₆, SC 89₄₇₃, TC 94₅₇₉. + '4v3q4v4w4b,' + // #5449: 5 fonts: HK 98₁₂₅, JP 85₂₂₁, KR 70₃₃₀, SC 19₄₀₃, TC 94₅₇₉. + '4v3r4e2u6t,' + // #5450: 5 fonts: HK 98₁₂₅, JP 85₂₂₁, KR 85₃₄₅, SC 51₄₃₅, TC 94₅₇₉. + '4v3r4t3l5n,' + // #5451: 5 fonts: HK 98₁₂₅, JP 85₂₂₁, KR 85₃₄₅, SC 89₄₇₃, TC 94₅₇₉. + '4v3r4t4x4b,' + // #5452: 5 fonts: HK 98₁₂₅, JP 85₂₂₁, KR 85₃₄₅, SC 91₄₇₅, TC 94₅₇₉. + '4v3r4t4z3z,' + // #5453: 5 fonts: HK 98₁₂₅, JP 87₂₂₃, KR 67₃₂₇, SC 88₄₇₂, TC 94₅₇₉. + '4v3t3z5o4c,' + // #5454: 5 fonts: HK 98₁₂₅, JP 87₂₂₃, KR 68₃₂₈, SC 86₄₇₀, TC 94₅₇₉. + '4v3t4a5l4e,' + // #5455: 5 fonts: HK 98₁₂₅, JP 88₂₂₄, KR 76₃₃₆, SC 90₄₇₄, TC 94₅₇₉. + '4v3u4h5h4a,' + // #5456: 5 fonts: HK 98₁₂₅, JP 88₂₂₄, KR 80₃₄₀, SC 89₄₇₃, TC 94₅₇₉. + '4v3u4l5c4b,' + // #5457: 5 fonts: HK 98₁₂₅, JP 88₂₂₄, KR 81₃₄₁, SC 88₄₇₂, TC 94₅₇₉. + '4v3u4m5a4c,' + // #5458: 5 fonts: HK 98₁₂₅, JP 88₂₂₄, KR 81₃₄₁, SC 90₄₇₄, TC 94₅₇₉. + '4v3u4m5c4a,' + // #5459: 5 fonts: HK 98₁₂₅, JP 88₂₂₄, KR 93₃₅₃, SC 87₄₇₁, TC 94₅₇₉. + '4v3u4y4n4d,' + // #5460: 5 fonts: HK 98₁₂₅, JP 90₂₂₆, KR 70₃₃₀, SC 18₄₀₂, TC 94₅₇₉. + '4v3w3z2t6u,' + // #5461: 5 fonts: HK 98₁₂₅, JP 90₂₂₆, KR 77₃₃₇, SC 91₄₇₅, TC 94₅₇₉. + '4v3w4g5h3z,' + // #5462: 5 fonts: HK 98₁₂₅, JP 90₂₂₆, KR 82₃₄₂, SC 88₄₇₂, TC 94₅₇₉. + '4v3w4l4z4c,' + // #5463: 5 fonts: HK 98₁₂₅, JP 90₂₂₆, KR 92₃₅₂, SC 91₄₇₅, TC 94₅₇₉. + '4v3w4v4s3z,' + // #5464: 5 fonts: HK 98₁₂₅, JP 91₂₂₇, KR 80₃₄₀, SC 40₄₂₄, TC 94₅₇₉. + '4v3x4i3f5y,' + // #5465: 5 fonts: HK 98₁₂₅, JP 91₂₂₇, KR 83₃₄₃, SC 89₄₇₃, TC 94₅₇₉. + '4v3x4l4z4b,' + // #5466: 5 fonts: HK 98₁₂₅, JP 91₂₂₇, KR 88₃₄₈, SC 90₄₇₄, TC 94₅₇₉. + '4v3x4q4v4a,' + // #5467: 5 fonts: HK 98₁₂₅, JP 92₂₂₈, KR 70₃₃₀, SC 18₄₀₂, TC 94₅₇₉. + '4v3y3x2t6u,' + // #5468: 5 fonts: HK 98₁₂₅, JP 92₂₂₈, KR 73₃₃₃, SC 24₄₀₈, TC 94₅₇₉. + '4v3y4a2w6o,' + // #5469: 5 fonts: HK 98₁₂₅, JP 92₂₂₈, KR 83₃₄₃, SC 89₄₇₃, TC 94₅₇₉. + '4v3y4k4z4b,' + // #5470: 5 fonts: HK 98₁₂₅, JP 92₂₂₈, KR 85₃₄₅, SC 89₄₇₃, TC 94₅₇₉. + '4v3y4m4x4b,' + // #5471: 5 fonts: HK 98₁₂₅, JP 92₂₂₈, KR 85₃₄₅, SC 90₄₇₄, TC 94₅₇₉. + '4v3y4m4y4a,' + // #5472: 5 fonts: HK 98₁₂₅, JP 92₂₂₈, KR 88₃₄₈, SC 91₄₇₅, TC 94₅₇₉. + '4v3y4p4w3z,' + // #5473: 5 fonts: HK 98₁₂₅, JP 92₂₂₈, KR 92₃₅₂, SC 65₄₄₉, TC 94₅₇₉. + '4v3y4t3s4z,' + // #5474: 5 fonts: HK 98₁₂₅, JP 93₂₂₉, KR 68₃₂₈, SC 15₃₉₉, TC 94₅₇₉. + '4v3z3u2s6x,' + // #5475: 5 fonts: HK 98₁₂₅, JP 93₂₂₉, KR 68₃₂₈, SC 89₄₇₃, TC 94₅₇₉. + '4v3z3u5o4b,' + // #5476: 5 fonts: HK 98₁₂₅, JP 93₂₂₉, KR 69₃₂₉, SC 90₄₇₄, TC 94₅₇₉. + '4v3z3v5o4a,' + // #5477: 5 fonts: HK 98₁₂₅, JP 93₂₂₉, KR 73₃₃₃, SC 89₄₇₃, TC 94₅₇₉. + '4v3z3z5j4b,' + // #5478: 5 fonts: HK 98₁₂₅, JP 93₂₂₉, KR 86₃₄₆, SC 89₄₇₃, TC 94₅₇₉. + '4v3z4m4w4b,' + // #5479: 5 fonts: HK 98₁₂₅, JP 94₂₃₀, KR 70₃₃₀, SC 18₄₀₂, TC 94₅₇₉. + '4v4a3v2t6u,' + // #5480: 5 fonts: HK 98₁₂₅, JP 94₂₃₀, KR 74₃₃₄, SC 28₄₁₂, TC 94₅₇₉. + '4v4a3z2z6k,' + // #5481: 5 fonts: HK 98₁₂₅, JP 94₂₃₀, KR 76₃₃₆, SC 90₄₇₄, TC 94₅₇₉. + '4v4a4b5h4a,' + // #5482: 5 fonts: HK 98₁₂₅, JP 94₂₃₀, KR 79₃₃₉, SC 90₄₇₄, TC 94₅₇₉. + '4v4a4e5e4a,' + // #5483: 5 fonts: HK 98₁₂₅, JP 94₂₃₀, KR 83₃₄₃, SC 89₄₇₃, TC 94₅₇₉. + '4v4a4i4z4b,' + // #5484: 5 fonts: HK 98₁₂₅, JP 94₂₃₀, KR 83₃₄₃, SC 91₄₇₅, TC 94₅₇₉. + '4v4a4i5b3z,' + // #5485: 5 fonts: HK 98₁₂₅, JP 94₂₃₀, KR 84₃₄₄, SC 90₄₇₄, TC 94₅₇₉. + '4v4a4j4z4a,' + // #5486: 5 fonts: HK 98₁₂₅, JP 94₂₃₀, KR 85₃₄₅, SC 89₄₇₃, TC 94₅₇₉. + '4v4a4k4x4b,' + // #5487: 5 fonts: HK 98₁₂₅, JP 94₂₃₀, KR 87₃₄₇, SC 89₄₇₃, TC 94₅₇₉. + '4v4a4m4v4b,' + // #5488: 5 fonts: HK 98₁₂₅, JP 94₂₃₀, KR 87₃₄₇, SC 90₄₇₄, TC 94₅₇₉. + '4v4a4m4w4a,' + // #5489: 5 fonts: HK 98₁₂₅, JP 95₂₃₁, KR 83₃₄₃, SC 92₄₇₆, TC 94₅₇₉. + '4v4b4h5c3y,' + // #5490: 5 fonts: HK 98₁₂₅, JP 95₂₃₁, KR 85₃₄₅, SC 89₄₇₃, TC 94₅₇₉. + '4v4b4j4x4b,' + // #5491: 5 fonts: HK 98₁₂₅, JP 95₂₃₁, KR 85₃₄₅, SC 90₄₇₄, TC 94₅₇₉. + '4v4b4j4y4a,' + // #5492: 5 fonts: HK 98₁₂₅, JP 96₂₃₂, KR 70₃₃₀, SC 91₄₇₅, TC 94₅₇₉. + '4v4c3t5o3z,' + // #5493: 5 fonts: HK 98₁₂₅, JP 96₂₃₂, KR 85₃₄₅, SC 90₄₇₄, TC 94₅₇₉. + '4v4c4i4y4a,' + // #5494: 5 fonts: HK 98₁₂₅, JP 96₂₃₂, KR 88₃₄₈, SC 90₄₇₄, TC 94₅₇₉. + '4v4c4l4v4a,' + // #5495: 5 fonts: HK 98₁₂₅, JP 97₂₃₃, KR 66₃₂₆, SC 91₄₇₅, TC 94₅₇₉. + '4v4d3o5s3z,' + // #5496: 5 fonts: HK 98₁₂₅, JP 97₂₃₃, KR 84₃₄₄, SC 48₄₃₂, TC 94₅₇₉. + '4v4d4g3j5q,' + // #5497: 5 fonts: HK 98₁₂₅, JP 97₂₃₃, KR 88₃₄₈, SC 90₄₇₄, TC 94₅₇₉. + '4v4d4k4v4a,' + // #5498: 5 fonts: HK 98₁₂₅, JP 97₂₃₃, KR 93₃₅₃, SC 93₄₇₇, TC 94₅₇₉. + '4v4d4p4t3x,' + // #5499: 5 fonts: HK 98₁₂₅, JP 98₂₃₄, KR 66₃₂₆, SC 90₄₇₄, TC 94₅₇₉. + '4v4e3n5r4a,' + // #5500: 5 fonts: HK 98₁₂₅, JP 98₂₃₄, KR 67₃₂₇, SC 91₄₇₅, TC 94₅₇₉. + '4v4e3o5r3z,' + // #5501: 5 fonts: HK 98₁₂₅, JP 98₂₃₄, KR 74₃₃₄, SC 27₄₁₁, TC 94₅₇₉. + '4v4e3v2y6l,' + // #5502: 5 fonts: HK 98₁₂₅, JP 98₂₃₄, KR 77₃₃₇, SC 34₄₁₈, TC 94₅₇₉. + '4v4e3y3c6e,' + // #5503: 5 fonts: HK 98₁₂₅, JP 98₂₃₄, KR 77₃₃₇, SC 92₄₇₆, TC 94₅₇₉. + '4v4e3y5i3y,' + // #5504: 5 fonts: HK 98₁₂₅, JP 98₂₃₄, KR 86₃₄₆, SC 89₄₇₃, TC 94₅₇₉. + '4v4e4h4w4b,' + // #5505: 5 fonts: HK 98₁₂₅, JP 98₂₃₄, KR 87₃₄₇, SC 88₄₇₂, TC 94₅₇₉. + '4v4e4i4u4c,' + // #5506: 5 fonts: HK 98₁₂₅, JP 98₂₃₄, KR 87₃₄₇, SC 90₄₇₄, TC 94₅₇₉. + '4v4e4i4w4a,' + // #5507: 5 fonts: HK 98₁₂₅, JP 98₂₃₄, KR 88₃₄₈, SC 90₄₇₄, TC 94₅₇₉. + '4v4e4j4v4a,' + // #5508: 5 fonts: HK 98₁₂₅, JP 98₂₃₄, KR 103₃₆₃, SC 89₄₇₃, TC 94₅₇₉. + '4v4e4y4f4b,' + // #5509: 5 fonts: HK 98₁₂₅, JP 99₂₃₅, KR 77₃₃₇, SC 89₄₇₃, TC 94₅₇₉. + '4v4f3x5f4b,' + // #5510: 5 fonts: HK 98₁₂₅, JP 99₂₃₅, KR 77₃₃₇, SC 91₄₇₅, TC 94₅₇₉. + '4v4f3x5h3z,' + // #5511: 5 fonts: HK 98₁₂₅, JP 99₂₃₅, KR 82₃₄₂, SC 44₄₂₈, TC 94₅₇₉. + '4v4f4c3h5u,' + // #5512: 5 fonts: HK 98₁₂₅, JP 99₂₃₅, KR 102₃₆₂, SC 89₄₇₃, TC 94₅₇₉. + '4v4f4w4g4b,' + // #5513: 5 fonts: HK 98₁₂₅, JP 100₂₃₆, KR 70₃₃₀, SC 18₄₀₂, TC 94₅₇₉. + '4v4g3p2t6u,' + // #5514: 5 fonts: HK 98₁₂₅, JP 100₂₃₆, KR 74₃₃₄, SC 27₄₁₁, TC 94₅₇₉. + '4v4g3t2y6l,' + // #5515: 5 fonts: HK 98₁₂₅, JP 100₂₃₆, KR 75₃₃₅, SC 28₄₁₂, TC 94₅₇₉. + '4v4g3u2y6k,' + // #5516: 5 fonts: HK 98₁₂₅, JP 100₂₃₆, KR 84₃₄₄, SC 89₄₇₃, TC 94₅₇₉. + '4v4g4d4y4b,' + // #5517: 5 fonts: HK 98₁₂₅, JP 100₂₃₆, KR 88₃₄₈, SC 90₄₇₄, TC 94₅₇₉. + '4v4g4h4v4a,' + // #5518: 5 fonts: HK 98₁₂₅, JP 100₂₃₆, KR 92₃₅₂, SC 90₄₇₄, TC 94₅₇₉. + '4v4g4l4r4a,' + // #5519: 5 fonts: HK 98₁₂₅, JP 101₂₃₇, KR 72₃₃₂, SC 89₄₇₃, TC 94₅₇₉. + '4v4h3q5k4b,' + // #5520: 5 fonts: HK 98₁₂₅, JP 102₂₃₈, KR 65₃₂₅, SC 90₄₇₄, TC 94₅₇₉. + '4v4i3i5s4a,' + // #5521: 5 fonts: HK 98₁₂₅, JP 102₂₃₈, KR 67₃₂₇, SC 9₃₉₃, TC 94₅₇₉. + '4v4i3k2n7d,' + // #5522: 5 fonts: HK 98₁₂₅, JP 102₂₃₈, KR 69₃₂₉, SC 15₃₉₉, TC 94₅₇₉. + '4v4i3m2r6x,' + // #5523: 5 fonts: HK 98₁₂₅, JP 102₂₃₈, KR 73₃₃₃, SC 90₄₇₄, TC 94₅₇₉. + '4v4i3q5k4a,' + // #5524: 5 fonts: HK 98₁₂₅, JP 102₂₃₈, KR 77₃₃₇, SC 90₄₇₄, TC 94₅₇₉. + '4v4i3u5g4a,' + // #5525: 5 fonts: HK 98₁₂₅, JP 102₂₃₈, KR 88₃₄₈, SC 57₄₄₁, TC 94₅₇₉. + '4v4i4f3o5h,' + // #5526: 5 fonts: HK 98₁₂₅, JP 102₂₃₈, KR 90₃₅₀, SC 90₄₇₄, TC 94₅₇₉. + '4v4i4h4t4a,' + // #5527: 5 fonts: HK 98₁₂₅, JP 103₂₃₉, KR 68₃₂₈, SC 13₃₉₇, TC 94₅₇₉. + '4v4j3k2q6z,' + // #5528: 5 fonts: HK 98₁₂₅, JP 103₂₃₉, KR 75₃₃₅, SC 93₄₇₇, TC 94₅₇₉. + '4v4j3r5l3x,' + // #5529: 5 fonts: HK 98₁₂₅, JP 103₂₃₉, KR 81₃₄₁, SC 91₄₇₅, TC 94₅₇₉. + '4v4j3x5d3z,' + // #5530: 5 fonts: HK 98₁₂₅, JP 103₂₃₉, KR 83₃₄₃, SC 90₄₇₄, TC 94₅₇₉. + '4v4j3z5a4a,' + // #5531: 5 fonts: HK 98₁₂₅, JP 103₂₃₉, KR 88₃₄₈, SC 90₄₇₄, TC 94₅₇₉. + '4v4j4e4v4a,' + // #5532: 5 fonts: HK 98₁₂₅, JP 103₂₃₉, KR 90₃₅₀, SC 89₄₇₃, TC 94₅₇₉. + '4v4j4g4s4b,' + // #5533: 5 fonts: HK 98₁₂₅, JP 103₂₃₉, KR 100₃₆₀, SC 90₄₇₄, TC 94₅₇₉. + '4v4j4q4j4a,' + // #5534: 5 fonts: HK 98₁₂₅, JP 104₂₄₀, KR 67₃₂₇, SC 90₄₇₄, TC 94₅₇₉. + '4v4k3i5q4a,' + // #5535: 5 fonts: HK 98₁₂₅, JP 104₂₄₀, KR 72₃₃₂, SC 89₄₇₃, TC 94₅₇₉. + '4v4k3n5k4b,' + // #5536: 5 fonts: HK 98₁₂₅, JP 104₂₄₀, KR 77₃₃₇, SC 91₄₇₅, TC 94₅₇₉. + '4v4k3s5h3z,' + // #5537: 5 fonts: HK 98₁₂₅, JP 104₂₄₀, KR 100₃₆₀, SC 93₄₇₇, TC 94₅₇₉. + '4v4k4p4m3x,' + // #5538: 5 fonts: HK 98₁₂₅, JP 105₂₄₁, KR 70₃₃₀, SC 18₄₀₂, TC 94₅₇₉. + '4v4l3k2t6u,' + // #5539: 5 fonts: HK 98₁₂₅, JP 105₂₄₁, KR 71₃₃₁, SC 88₄₇₂, TC 94₅₇₉. + '4v4l3l5k4c,' + // #5540: 5 fonts: HK 98₁₂₅, JP 105₂₄₁, KR 75₃₃₅, SC 91₄₇₅, TC 94₅₇₉. + '4v4l3p5j3z,' + // #5541: 5 fonts: HK 98₁₂₅, JP 105₂₄₁, KR 77₃₃₇, SC 85₄₆₉, TC 94₅₇₉. + '4v4l3r5b4f,' + // #5542: 5 fonts: HK 98₁₂₅, JP 105₂₄₁, KR 79₃₃₉, SC 91₄₇₅, TC 94₅₇₉. + '4v4l3t5f3z,' + // #5543: 5 fonts: HK 98₁₂₅, JP 105₂₄₁, KR 82₃₄₂, SC 44₄₂₈, TC 94₅₇₉. + '4v4l3w3h5u,' + // #5544: 5 fonts: HK 98₁₂₅, JP 105₂₄₁, KR 88₃₄₈, SC 88₄₇₂, TC 94₅₇₉. + '4v4l4c4t4c,' + // #5545: 5 fonts: HK 98₁₂₅, JP 106₂₄₂, KR 76₃₃₆, SC 91₄₇₅, TC 94₅₇₉. + '4v4m3p5i3z,' + // #5546: 5 fonts: HK 98₁₂₅, JP 106₂₄₂, KR 84₃₄₄, SC 86₄₇₀, TC 94₅₇₉. + '4v4m3x4v4e,' + // #5547: 5 fonts: HK 98₁₂₅, JP 106₂₄₂, KR 91₃₅₁, SC 63₄₄₇, TC 94₅₇₉. + '4v4m4e3r5b,' + // #5548: 5 fonts: HK 98₁₂₅, JP 107₂₄₃, KR 75₃₃₅, SC 28₄₁₂, TC 94₅₇₉. + '4v4n3n2y6k,' + // #5549: 5 fonts: HK 98₁₂₅, JP 107₂₄₃, KR 76₃₃₆, SC 90₄₇₄, TC 94₅₇₉. + '4v4n3o5h4a,' + // #5550: 5 fonts: HK 98₁₂₅, JP 107₂₄₃, KR 76₃₃₆, SC 92₄₇₆, TC 94₅₇₉. + '4v4n3o5j3y,' + // #5551: 5 fonts: HK 98₁₂₅, JP 107₂₄₃, KR 78₃₃₈, SC 90₄₇₄, TC 94₅₇₉. + '4v4n3q5f4a,' + // #5552: 5 fonts: HK 98₁₂₅, JP 107₂₄₃, KR 82₃₄₂, SC 45₄₂₉, TC 94₅₇₉. + '4v4n3u3i5t,' + // #5553: 5 fonts: HK 98₁₂₅, JP 108₂₄₄, KR 86₃₄₆, SC 90₄₇₄, TC 94₅₇₉. + '4v4o3x4x4a,' + // #5554: 5 fonts: HK 98₁₂₅, JP 108₂₄₄, KR 88₃₄₈, SC 88₄₇₂, TC 94₅₇₉. + '4v4o3z4t4c,' + // #5555: 5 fonts: HK 98₁₂₅, JP 108₂₄₄, KR 89₃₄₉, SC 91₄₇₅, TC 94₅₇₉. + '4v4o4a4v3z,' + // #5556: 4 fonts: HK 98₁₂₅, JP 108₂₄₄, SC 90₄₇₄, TC 94₅₇₉. + '4v4o8v4a,' + // #5557: 5 fonts: HK 98₁₂₅, JP 109₂₄₅, KR 0₂₆₀, SC 82₄₆₆, TC 94₅₇₉. + '4v4po7x4i,' + // #5558: 5 fonts: HK 98₁₂₅, JP 109₂₄₅, KR 83₃₄₃, SC 90₄₇₄, TC 94₅₇₉. + '4v4p3t5a4a,' + // #5559: 5 fonts: HK 98₁₂₅, JP 109₂₄₅, KR 88₃₄₈, SC 56₄₄₀, TC 94₅₇₉. + '4v4p3y3n5i,' + // #5560: 5 fonts: HK 98₁₂₅, JP 110₂₄₆, KR 70₃₃₀, SC 18₄₀₂, TC 94₅₇₉. + '4v4q3f2t6u,' + // #5561: 5 fonts: HK 98₁₂₅, JP 110₂₄₆, KR 72₃₃₂, SC 22₄₀₆, TC 94₅₇₉. + '4v4q3h2v6q,' + // #5562: 5 fonts: HK 98₁₂₅, JP 110₂₄₆, KR 77₃₃₇, SC 92₄₇₆, TC 94₅₇₉. + '4v4q3m5i3y,' + // #5563: 5 fonts: HK 98₁₂₅, JP 111₂₄₇, KR 66₃₂₆, SC 8₃₉₂, TC 94₅₇₉. + '4v4r3a2n7e,' + // #5564: 5 fonts: HK 98₁₂₅, JP 111₂₄₇, KR 105₃₆₅, SC 84₄₆₈, TC 94₅₇₉. + '4v4r4n3y4g,' + // #5565: 8 fonts: HK 98₁₂₅, JP 111₂₄₇, KR 107₃₆₇, SC 83₄₆₇, TC 94₅₇₉, Mongolian₆₆₂, Phags Pa₆₈₇, Yi₇₂₁. + '4v4r4p3v4h3ey1h,' + // #5566: 5 fonts: HK 98₁₂₅, JP 112₂₄₈, KR 84₃₄₄, SC 90₄₇₄, TC 94₅₇₉. + '4v4s3r4z4a,' + // #5567: 5 fonts: HK 98₁₂₅, JP 113₂₄₉, KR 101₃₆₁, SC 90₄₇₄, TC 94₅₇₉. + '4v4t4h4i4a,' + // #5568: 4 fonts: HK 98₁₂₅, JP 116₂₅₂, SC 95₄₇₉, TC 94₅₇₉. + '4v4w8s3v,' + // #5569: 6 fonts: HK 98₁₂₅, JP 119₂₅₅, KR 0₂₆₀, SC 96₄₈₀, TC 94₅₇₉, New Tai Lue₆₆₈. + '4v4ze8l3u3k,' + // #5570: 3 fonts: HK 98₁₂₅, SC 14₃₉₈, TC 94₅₇₉. + '4v10m6y,' + // #5571: 4 fonts: HK 99₁₂₆, JP 9₁₄₅, SC 8₃₉₂, TC 95₅₈₀. + '4ws9m7f,' + // #5572: 4 fonts: HK 99₁₂₆, JP 14₁₅₀, SC 90₄₇₄, TC 95₅₈₀. + '4wx12l4b,' + // #5573: 4 fonts: HK 99₁₂₆, JP 21₁₅₇, SC 24₄₀₈, TC 95₅₈₀. + '4w1e9q6p,' + // #5574: 5 fonts: HK 99₁₂₆, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 95₅₈₀. + '4w1g6s2y6m,' + // #5575: 5 fonts: HK 99₁₂₆, JP 25₁₆₁, KR 75₃₃₅, SC 90₄₇₄, TC 95₅₈₀. + '4w1i6r5i4b,' + // #5576: 5 fonts: HK 99₁₂₆, JP 27₁₆₃, KR 77₃₃₇, SC 34₄₁₈, TC 95₅₈₀. + '4w1k6r3c6f,' + // #5577: 4 fonts: HK 99₁₂₆, JP 28₁₆₄, SC 89₄₇₃, TC 95₅₈₀. + '4w1l11w4c,' + // #5578: 5 fonts: HK 99₁₂₆, JP 37₁₇₃, KR 84₃₄₄, SC 49₄₃₃, TC 95₅₈₀. + '4w1u6o3k5q,' + // #5579: 5 fonts: HK 99₁₂₆, JP 39₁₇₅, KR 85₃₄₅, SC 51₄₃₅, TC 95₅₈₀. + '4w1w6n3l5o,' + // #5580: 5 fonts: HK 99₁₂₆, JP 40₁₇₆, KR 86₃₄₆, SC 53₄₃₇, TC 95₅₈₀. + '4w1x6n3m5m,' + // #5581: 4 fonts: HK 99₁₂₆, JP 43₁₇₉, SC 57₄₄₁, TC 95₅₈₀. + '4w2a10b5i,' + // #5582: 5 fonts: HK 99₁₂₆, JP 45₁₈₁, KR 89₃₄₉, SC 91₄₇₅, TC 95₅₈₀. + '4w2c6l4v4a,' + // #5583: 5 fonts: HK 99₁₂₆, JP 46₁₈₂, KR 90₃₅₀, SC 90₄₇₄, TC 95₅₈₀. + '4w2d6l4t4b,' + // #5584: 5 fonts: HK 99₁₂₆, JP 48₁₈₄, KR 91₃₅₁, SC 64₄₄₈, TC 95₅₈₀. + '4w2f6k3s5b,' + // #5585: 4 fonts: HK 99₁₂₆, JP 48₁₈₄, SC 90₄₇₄, TC 95₅₈₀. + '4w2f11d4b,' + // #5586: 5 fonts: HK 99₁₂₆, JP 50₁₈₆, KR 93₃₅₃, SC 88₄₇₂, TC 95₅₈₀. + '4w2h6k4o4d,' + // #5587: 5 fonts: HK 99₁₂₆, JP 64₂₀₀, KR 85₃₄₅, SC 88₄₇₂, TC 95₅₈₀. + '4w2v5o4w4d,' + // #5588: 5 fonts: HK 99₁₂₆, JP 74₂₁₀, KR 87₃₄₇, SC 91₄₇₅, TC 95₅₈₀. + '4w3f5g4x4a,' + // #5589: 5 fonts: HK 99₁₂₆, JP 84₂₂₀, KR 88₃₄₈, SC 91₄₇₅, TC 95₅₈₀. + '4w3p4x4w4a,' + // #5590: 5 fonts: HK 99₁₂₆, JP 85₂₂₁, KR 80₃₄₀, SC 92₄₇₆, TC 95₅₈₀. + '4w3q4o5f3z,' + // #5591: 5 fonts: HK 99₁₂₆, JP 87₂₂₃, KR 68₃₂₈, SC 91₄₇₅, TC 95₅₈₀. + '4w3s4a5q4a,' + // #5592: 5 fonts: HK 99₁₂₆, JP 87₂₂₃, KR 70₃₃₀, SC 18₄₀₂, TC 95₅₈₀. + '4w3s4c2t6v,' + // #5593: 5 fonts: HK 99₁₂₆, JP 88₂₂₄, KR 80₃₄₀, SC 91₄₇₅, TC 95₅₈₀. + '4w3t4l5e4a,' + // #5594: 5 fonts: HK 99₁₂₆, JP 89₂₂₅, KR 78₃₃₈, SC 91₄₇₅, TC 95₅₈₀. + '4w3u4i5g4a,' + // #5595: 5 fonts: HK 99₁₂₆, JP 89₂₂₅, KR 93₃₅₃, SC 91₄₇₅, TC 95₅₈₀. + '4w3u4x4r4a,' + // #5596: 5 fonts: HK 99₁₂₆, JP 91₂₂₇, KR 80₃₄₀, SC 90₄₇₄, TC 95₅₈₀. + '4w3w4i5d4b,' + // #5597: 5 fonts: HK 99₁₂₆, JP 92₂₂₈, KR 93₃₅₃, SC 83₄₆₇, TC 95₅₈₀. + '4w3x4u4j4i,' + // #5598: 5 fonts: HK 99₁₂₆, JP 93₂₂₉, KR 76₃₃₆, SC 91₄₇₅, TC 95₅₈₀. + '4w3y4c5i4a,' + // #5599: 5 fonts: HK 99₁₂₆, JP 94₂₃₀, KR 73₃₃₃, SC 25₄₀₉, TC 95₅₈₀. + '4w3z3y2x6o,' + // #5600: 5 fonts: HK 99₁₂₆, JP 94₂₃₀, KR 75₃₃₅, SC 87₄₇₁, TC 95₅₈₀. + '4w3z4a5f4e,' + // #5601: 5 fonts: HK 99₁₂₆, JP 95₂₃₁, KR 67₃₂₇, SC 12₃₉₆, TC 95₅₈₀. + '4w4a3r2q7b,' + // #5602: 5 fonts: HK 99₁₂₆, JP 96₂₃₂, KR 89₃₄₉, SC 90₄₇₄, TC 95₅₈₀. + '4w4b4m4u4b,' + // #5603: 5 fonts: HK 99₁₂₆, JP 96₂₃₂, KR 91₃₅₁, SC 89₄₇₃, TC 95₅₈₀. + '4w4b4o4r4c,' + // #5604: 5 fonts: HK 99₁₂₆, JP 96₂₃₂, KR 92₃₅₂, SC 65₄₄₉, TC 95₅₈₀. + '4w4b4p3s5a,' + // #5605: 5 fonts: HK 99₁₂₆, JP 97₂₃₃, KR 73₃₃₃, SC 93₄₇₇, TC 95₅₈₀. + '4w4c3v5n3y,' + // #5606: 5 fonts: HK 99₁₂₆, JP 97₂₃₃, KR 81₃₄₁, SC 43₄₂₇, TC 95₅₈₀. + '4w4c4d3h5w,' + // #5607: 5 fonts: HK 99₁₂₆, JP 98₂₃₄, KR 71₃₃₁, SC 91₄₇₅, TC 95₅₈₀. + '4w4d3s5n4a,' + // #5608: 5 fonts: HK 99₁₂₆, JP 98₂₃₄, KR 91₃₅₁, SC 90₄₇₄, TC 95₅₈₀. + '4w4d4m4s4b,' + // #5609: 5 fonts: HK 99₁₂₆, JP 99₂₃₅, KR 71₃₃₁, SC 20₄₀₄, TC 95₅₈₀. + '4w4e3r2u6t,' + // #5610: 5 fonts: HK 99₁₂₆, JP 100₂₃₆, KR 66₃₂₆, SC 7₃₉₁, TC 95₅₈₀. + '4w4f3l2m7g,' + // #5611: 5 fonts: HK 99₁₂₆, JP 100₂₃₆, KR 80₃₄₀, SC 41₄₂₅, TC 95₅₈₀. + '4w4f3z3g5y,' + // #5612: 5 fonts: HK 99₁₂₆, JP 100₂₃₆, KR 108₃₆₈, SC 90₄₇₄, TC 95₅₈₀. + '4w4f5b4b4b,' + // #5613: 5 fonts: HK 99₁₂₆, JP 101₂₃₇, KR 72₃₃₂, SC 90₄₇₄, TC 95₅₈₀. + '4w4g3q5l4b,' + // #5614: 5 fonts: HK 99₁₂₆, JP 102₂₃₈, KR 65₃₂₅, SC 90₄₇₄, TC 95₅₈₀. + '4w4h3i5s4b,' + // #5615: 5 fonts: HK 99₁₂₆, JP 102₂₃₈, KR 69₃₂₉, SC 17₄₀₁, TC 95₅₈₀. + '4w4h3m2t6w,' + // #5616: 5 fonts: HK 99₁₂₆, JP 102₂₃₈, KR 77₃₃₇, SC 34₄₁₈, TC 95₅₈₀. + '4w4h3u3c6f,' + // #5617: 5 fonts: HK 99₁₂₆, JP 102₂₃₈, KR 81₃₄₁, SC 93₄₇₇, TC 95₅₈₀. + '4w4h3y5f3y,' + // #5618: 5 fonts: HK 99₁₂₆, JP 103₂₃₉, KR 71₃₃₁, SC 19₄₀₃, TC 95₅₈₀. + '4w4i3n2t6u,' + // #5619: 5 fonts: HK 99₁₂₆, JP 103₂₃₉, KR 72₃₃₂, SC 90₄₇₄, TC 95₅₈₀. + '4w4i3o5l4b,' + // #5620: 5 fonts: HK 99₁₂₆, JP 103₂₃₉, KR 91₃₅₁, SC 63₄₄₇, TC 95₅₈₀. + '4w4i4h3r5c,' + // #5621: 5 fonts: HK 99₁₂₆, JP 104₂₄₀, KR 87₃₄₇, SC 90₄₇₄, TC 95₅₈₀. + '4w4j4c4w4b,' + // #5622: 5 fonts: HK 99₁₂₆, JP 105₂₄₁, KR 85₃₄₅, SC 92₄₇₆, TC 95₅₈₀. + '4w4k3z5a3z,' + // #5623: 5 fonts: HK 99₁₂₆, JP 105₂₄₁, KR 87₃₄₇, SC 90₄₇₄, TC 95₅₈₀. + '4w4k4b4w4b,' + // #5624: 5 fonts: HK 99₁₂₆, JP 106₂₄₂, KR 81₃₄₁, SC 90₄₇₄, TC 95₅₈₀. + '4w4l3u5c4b,' + // #5625: 5 fonts: HK 99₁₂₆, JP 106₂₄₂, KR 88₃₄₈, SC 91₄₇₅, TC 95₅₈₀. + '4w4l4b4w4a,' + // #5626: 5 fonts: HK 99₁₂₆, JP 106₂₄₂, KR 89₃₄₉, SC 91₄₇₅, TC 95₅₈₀. + '4w4l4c4v4a,' + // #5627: 5 fonts: HK 99₁₂₆, JP 107₂₄₃, KR 100₃₆₀, SC 91₄₇₅, TC 95₅₈₀. + '4w4m4m4k4a,' + // #5628: 5 fonts: HK 99₁₂₆, JP 108₂₄₄, KR 82₃₄₂, SC 89₄₇₃, TC 95₅₈₀. + '4w4n3t5a4c,' + // #5629: 5 fonts: HK 99₁₂₆, JP 108₂₄₄, KR 84₃₄₄, SC 50₄₃₄, TC 95₅₈₀. + '4w4n3v3l5p,' + // #5630: 5 fonts: HK 99₁₂₆, JP 108₂₄₄, KR 84₃₄₄, SC 91₄₇₅, TC 95₅₈₀. + '4w4n3v5a4a,' + // #5631: 5 fonts: HK 99₁₂₆, JP 108₂₄₄, KR 100₃₆₀, SC 91₄₇₅, TC 95₅₈₀. + '4w4n4l4k4a,' + // #5632: 5 fonts: HK 99₁₂₆, JP 110₂₄₆, KR 68₃₂₈, SC 92₄₇₆, TC 95₅₈₀. + '4w4p3d5r3z,' + // #5633: 5 fonts: HK 99₁₂₆, JP 113₂₄₉, KR 103₃₆₃, SC 94₄₇₈, TC 95₅₈₀. + '4w4s4j4k3x,' + // #5634: 5 fonts: HK 99₁₂₆, JP 114₂₅₀, KR 102₃₆₂, SC 90₄₇₄, TC 95₅₈₀. + '4w4t4h4h4b,' + // #5635: 6 fonts: HK 99₁₂₆, JP 119₂₅₅, KR 107₃₆₇, SC 96₄₈₀, TC 95₅₈₀, Yi₇₂₁. + '4w4y4h4i3v5k,' + // #5636: 5 fonts: HK 99₁₂₆, JP 119₂₅₅, SC 96₄₈₀, TC 95₅₈₀, Noto Sans₅₉₁. + '4w4y8q3vk,' + // #5637: 4 fonts: HK 100₁₂₇, JP 7₁₄₃, SC 6₃₉₀, TC 96₅₈₁. + '4xp9m7i,' + // #5638: 5 fonts: HK 100₁₂₇, JP 9₁₄₅, KR 66₃₂₆, SC 9₃₉₃, TC 96₅₈₁. + '4xr6y2o7f,' + // #5639: 5 fonts: HK 100₁₂₇, JP 10₁₄₆, KR 67₃₂₇, SC 10₃₉₄, TC 96₅₈₁. + '4xs6y2o7e,' + // #5640: 4 fonts: HK 100₁₂₇, JP 14₁₅₀, SC 15₃₉₉, TC 96₅₈₁. + '4xw9o6z,' + // #5641: 5 fonts: HK 100₁₂₇, JP 15₁₅₁, KR 69₃₂₉, SC 16₄₀₀, TC 96₅₈₁. + '4xx6v2s6y,' + // #5642: 5 fonts: HK 100₁₂₇, JP 15₁₅₁, KR 70₃₃₀, SC 17₄₀₁, TC 96₅₈₁. + '4xx6w2s6x,' + // #5643: 4 fonts: HK 100₁₂₇, JP 15₁₅₁, SC 17₄₀₁, TC 96₅₈₁. + '4xx9p6x,' + // #5644: 5 fonts: HK 100₁₂₇, JP 18₁₅₄, KR 71₃₃₁, SC 90₄₇₄, TC 96₅₈₁. + '4x1a6u5m4c,' + // #5645: 4 fonts: HK 100₁₂₇, JP 21₁₅₇, SC 24₄₀₈, TC 96₅₈₁. + '4x1d9q6q,' + // #5646: 5 fonts: HK 100₁₂₇, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 96₅₈₁. + '4x1f6s2y6n,' + // #5647: 4 fonts: HK 100₁₂₇, JP 24₁₆₀, SC 28₄₁₂, TC 96₅₈₁. + '4x1g9r6m,' + // #5648: 4 fonts: HK 100₁₂₇, JP 27₁₆₃, SC 33₄₁₇, TC 96₅₈₁. + '4x1j9t6h,' + // #5649: 5 fonts: HK 100₁₂₇, JP 30₁₆₆, KR 78₃₃₈, SC 38₄₂₂, TC 96₅₈₁. + '4x1m6p3f6c,' + // #5650: 4 fonts: HK 100₁₂₇, JP 30₁₆₆, SC 37₄₂₁, TC 96₅₈₁. + '4x1m9u6d,' + // #5651: 5 fonts: HK 100₁₂₇, JP 31₁₆₇, KR 79₃₃₉, SC 90₄₇₄, TC 96₅₈₁. + '4x1n6p5e4c,' + // #5652: 5 fonts: HK 100₁₂₇, JP 34₁₇₀, KR 81₃₄₁, SC 44₄₂₈, TC 96₅₈₁. + '4x1q6o3i5w,' + // #5653: 5 fonts: HK 100₁₂₇, JP 34₁₇₀, KR 81₃₄₁, SC 80₄₆₄, TC 96₅₈₁. + '4x1q6o4s4m,' + // #5654: 4 fonts: HK 100₁₂₇, JP 34₁₇₀, SC 44₄₂₈, TC 96₅₈₁. + '4x1q9x5w,' + // #5655: 4 fonts: HK 100₁₂₇, JP 37₁₇₃, SC 91₄₇₅, TC 96₅₈₁. + '4x1t11p4b,' + // #5656: 5 fonts: HK 100₁₂₇, JP 38₁₇₄, KR 85₃₄₅, SC 91₄₇₅, TC 96₅₈₁. + '4x1u6o4z4b,' + // #5657: 4 fonts: HK 100₁₂₇, JP 40₁₇₆, SC 93₄₇₇, TC 96₅₈₁. + '4x1w11o3z,' + // #5658: 5 fonts: HK 100₁₂₇, JP 41₁₇₇, KR 87₃₄₇, SC 53₄₃₇, TC 96₅₈₁. + '4x1x6n3l5n,' + // #5659: 5 fonts: HK 100₁₂₇, JP 42₁₇₈, KR 88₃₄₈, SC 55₄₃₉, TC 96₅₈₁. + '4x1y6n3m5l,' + // #5660: 5 fonts: HK 100₁₂₇, JP 43₁₇₉, KR 88₃₄₈, SC 91₄₇₅, TC 96₅₈₁. + '4x1z6m4w4b,' + // #5661: 4 fonts: HK 100₁₂₇, JP 43₁₇₉, SC 56₄₄₀, TC 96₅₈₁. + '4x1z10a5k,' + // #5662: 4 fonts: HK 100₁₂₇, JP 43₁₇₉, SC 92₄₇₆, TC 96₅₈₁. + '4x1z11k4a,' + // #5663: 5 fonts: HK 100₁₂₇, JP 44₁₈₀, KR 89₃₄₉, SC 58₄₄₂, TC 96₅₈₁. + '4x2a6m3o5i,' + // #5664: 5 fonts: HK 100₁₂₇, JP 44₁₈₀, KR 89₃₄₉, SC 92₄₇₆, TC 96₅₈₁. + '4x2a6m4w4a,' + // #5665: 4 fonts: HK 100₁₂₇, JP 44₁₈₀, SC 91₄₇₅, TC 96₅₈₁. + '4x2a11i4b,' + // #5666: 5 fonts: HK 100₁₂₇, JP 45₁₈₁, KR 89₃₄₉, SC 59₄₄₃, TC 96₅₈₁. + '4x2b6l3p5h,' + // #5667: 4 fonts: HK 100₁₂₇, JP 46₁₈₂, SC 92₄₇₆, TC 96₅₈₁. + '4x2c11h4a,' + // #5668: 4 fonts: HK 100₁₂₇, JP 47₁₈₃, SC 63₄₄₇, TC 96₅₈₁. + '4x2d10d5d,' + // #5669: 5 fonts: HK 100₁₂₇, JP 60₁₉₆, KR 83₃₄₃, SC 91₄₇₅, TC 96₅₈₁. + '4x2q5q5b4b,' + // #5670: 5 fonts: HK 100₁₂₇, JP 64₂₀₀, KR 67₃₂₇, SC 11₃₉₅, TC 96₅₈₁. + '4x2u4w2p7d,' + // #5671: 5 fonts: HK 100₁₂₇, JP 64₂₀₀, KR 93₃₅₃, SC 88₄₇₂, TC 96₅₈₁. + '4x2u5w4o4e,' + // #5672: 5 fonts: HK 100₁₂₇, JP 66₂₀₂, KR 81₃₄₁, SC 92₄₇₆, TC 96₅₈₁. + '4x2w5i5e4a,' + // #5673: 5 fonts: HK 100₁₂₇, JP 67₂₀₃, KR 75₃₃₅, SC 91₄₇₅, TC 96₅₈₁. + '4x2x5b5j4b,' + // #5674: 5 fonts: HK 100₁₂₇, JP 69₂₀₅, KR 84₃₄₄, SC 93₄₇₇, TC 96₅₈₁. + '4x2z5i5c3z,' + // #5675: 5 fonts: HK 100₁₂₇, JP 71₂₀₇, KR 67₃₂₇, SC 12₃₉₆, TC 96₅₈₁. + '4x3b4p2q7c,' + // #5676: 5 fonts: HK 100₁₂₇, JP 71₂₀₇, KR 78₃₃₈, SC 91₄₇₅, TC 96₅₈₁. + '4x3b5a5g4b,' + // #5677: 5 fonts: HK 100₁₂₇, JP 71₂₀₇, KR 92₃₅₂, SC 65₄₄₉, TC 96₅₈₁. + '4x3b5o3s5b,' + // #5678: 5 fonts: HK 100₁₂₇, JP 73₂₀₉, KR 66₃₂₆, SC 90₄₇₄, TC 96₅₈₁. + '4x3d4m5r4c,' + // #5679: 5 fonts: HK 100₁₂₇, JP 74₂₁₀, KR 88₃₄₈, SC 91₄₇₅, TC 96₅₈₁. + '4x3e5h4w4b,' + // #5680: 5 fonts: HK 100₁₂₇, JP 76₂₁₂, KR 69₃₂₉, SC 90₄₇₄, TC 96₅₈₁. + '4x3g4m5o4c,' + // #5681: 5 fonts: HK 100₁₂₇, JP 77₂₁₃, KR 83₃₄₃, SC 92₄₇₆, TC 96₅₈₁. + '4x3h4z5c4a,' + // #5682: 5 fonts: HK 100₁₂₇, JP 78₂₁₄, KR 71₃₃₁, SC 21₄₀₅, TC 96₅₈₁. + '4x3i4m2v6t,' + // #5683: 5 fonts: HK 100₁₂₇, JP 78₂₁₄, KR 88₃₄₈, SC 92₄₇₆, TC 96₅₈₁. + '4x3i5d4x4a,' + // #5684: 5 fonts: HK 100₁₂₇, JP 78₂₁₄, KR 90₃₅₀, SC 89₄₇₃, TC 96₅₈₁. + '4x3i5f4s4d,' + // #5685: 5 fonts: HK 100₁₂₇, JP 79₂₁₅, KR 70₃₃₀, SC 87₄₇₁, TC 96₅₈₁. + '4x3j4k5k4f,' + // #5686: 5 fonts: HK 100₁₂₇, JP 79₂₁₅, KR 93₃₅₃, SC 91₄₇₅, TC 96₅₈₁. + '4x3j5h4r4b,' + // #5687: 5 fonts: HK 100₁₂₇, JP 81₂₁₇, KR 81₃₄₁, SC 90₄₇₄, TC 96₅₈₁. + '4x3l4t5c4c,' + // #5688: 5 fonts: HK 100₁₂₇, JP 84₂₂₀, KR 81₃₄₁, SC 91₄₇₅, TC 96₅₈₁. + '4x3o4q5d4b,' + // #5689: 5 fonts: HK 100₁₂₇, JP 85₂₂₁, KR 66₃₂₆, SC 77₄₆₁, TC 96₅₈₁. + '4x3p4a5e4p,' + // #5690: 5 fonts: HK 100₁₂₇, JP 85₂₂₁, KR 83₃₄₃, SC 91₄₇₅, TC 96₅₈₁. + '4x3p4r5b4b,' + // #5691: 5 fonts: HK 100₁₂₇, JP 86₂₂₂, KR 66₃₂₆, SC 8₃₉₂, TC 96₅₈₁. + '4x3q3z2n7g,' + // #5692: 5 fonts: HK 100₁₂₇, JP 87₂₂₃, KR 69₃₂₉, SC 15₃₉₉, TC 96₅₈₁. + '4x3r4b2r6z,' + // #5693: 4 fonts: HK 100₁₂₇, JP 87₂₂₃, SC 91₄₇₅, TC 96₅₈₁. + '4x3r9r4b,' + // #5694: 8 fonts: HK 100₁₂₇, JP 88₂₂₄, KR 0₂₆₀, SC 92₄₇₆, TC 96₅₈₁, Mongolian₆₆₂, New Tai Lue₆₆₈, Yi₇₂₁. + '4x3s1j8h4a3cf2a,' + // #5695: 5 fonts: HK 100₁₂₇, JP 88₂₂₄, KR 68₃₂₈, SC 91₄₇₅, TC 96₅₈₁. + '4x3s3z5q4b,' + // #5696: 5 fonts: HK 100₁₂₇, JP 88₂₂₄, KR 74₃₃₄, SC 26₄₁₀, TC 96₅₈₁. + '4x3s4f2x6o,' + // #5697: 5 fonts: HK 100₁₂₇, JP 88₂₂₄, KR 88₃₄₈, SC 90₄₇₄, TC 96₅₈₁. + '4x3s4t4v4c,' + // #5698: 5 fonts: HK 100₁₂₇, JP 88₂₂₄, KR 91₃₅₁, SC 64₄₄₈, TC 96₅₈₁. + '4x3s4w3s5c,' + // #5699: 5 fonts: HK 100₁₂₇, JP 89₂₂₅, KR 84₃₄₄, SC 89₄₇₃, TC 96₅₈₁. + '4x3t4o4y4d,' + // #5700: 5 fonts: HK 100₁₂₇, JP 89₂₂₅, KR 84₃₄₄, SC 92₄₇₆, TC 96₅₈₁. + '4x3t4o5b4a,' + // #5701: 5 fonts: HK 100₁₂₇, JP 89₂₂₅, KR 85₃₄₅, SC 93₄₇₇, TC 96₅₈₁. + '4x3t4p5b3z,' + // #5702: 5 fonts: HK 100₁₂₇, JP 90₂₂₆, KR 66₃₂₆, SC 90₄₇₄, TC 96₅₈₁. + '4x3u3v5r4c,' + // #5703: 5 fonts: HK 100₁₂₇, JP 91₂₂₇, KR 81₃₄₁, SC 92₄₇₆, TC 96₅₈₁. + '4x3v4j5e4a,' + // #5704: 5 fonts: HK 100₁₂₇, JP 91₂₂₇, KR 82₃₄₂, SC 88₄₇₂, TC 96₅₈₁. + '4x3v4k4z4e,' + // #5705: 5 fonts: HK 100₁₂₇, JP 92₂₂₈, KR 77₃₃₇, SC 92₄₇₆, TC 96₅₈₁. + '4x3w4e5i4a,' + // #5706: 5 fonts: HK 100₁₂₇, JP 92₂₂₈, KR 84₃₄₄, SC 90₄₇₄, TC 96₅₈₁. + '4x3w4l4z4c,' + // #5707: 5 fonts: HK 100₁₂₇, JP 92₂₂₈, KR 87₃₄₇, SC 55₄₃₉, TC 96₅₈₁. + '4x3w4o3n5l,' + // #5708: 5 fonts: HK 100₁₂₇, JP 93₂₂₉, KR 66₃₂₆, SC 8₃₉₂, TC 96₅₈₁. + '4x3x3s2n7g,' + // #5709: 5 fonts: HK 100₁₂₇, JP 93₂₂₉, KR 75₃₃₅, SC 30₄₁₄, TC 96₅₈₁. + '4x3x4b3a6k,' + // #5710: 5 fonts: HK 100₁₂₇, JP 93₂₂₉, KR 78₃₃₈, SC 91₄₇₅, TC 96₅₈₁. + '4x3x4e5g4b,' + // #5711: 5 fonts: HK 100₁₂₇, JP 93₂₂₉, KR 87₃₄₇, SC 91₄₇₅, TC 96₅₈₁. + '4x3x4n4x4b,' + // #5712: 5 fonts: HK 100₁₂₇, JP 93₂₂₉, KR 90₃₅₀, SC 91₄₇₅, TC 96₅₈₁. + '4x3x4q4u4b,' + // #5713: 5 fonts: HK 100₁₂₇, JP 94₂₃₀, KR 90₃₅₀, SC 90₄₇₄, TC 96₅₈₁. + '4x3y4p4t4c,' + // #5714: 5 fonts: HK 100₁₂₇, JP 94₂₃₀, KR 92₃₅₂, SC 92₄₇₆, TC 96₅₈₁. + '4x3y4r4t4a,' + // #5715: 5 fonts: HK 100₁₂₇, JP 95₂₃₁, KR 66₃₂₆, SC 7₃₉₁, TC 96₅₈₁. + '4x3z3q2m7h,' + // #5716: 5 fonts: HK 100₁₂₇, JP 95₂₃₁, KR 67₃₂₇, SC 9₃₉₃, TC 96₅₈₁. + '4x3z3r2n7f,' + // #5717: 5 fonts: HK 100₁₂₇, JP 95₂₃₁, KR 68₃₂₈, SC 14₃₉₈, TC 96₅₈₁. + '4x3z3s2r7a,' + // #5718: 5 fonts: HK 100₁₂₇, JP 95₂₃₁, KR 69₃₂₉, SC 15₃₉₉, TC 96₅₈₁. + '4x3z3t2r6z,' + // #5719: 5 fonts: HK 100₁₂₇, JP 95₂₃₁, KR 81₃₄₁, SC 91₄₇₅, TC 96₅₈₁. + '4x3z4f5d4b,' + // #5720: 5 fonts: HK 100₁₂₇, JP 95₂₃₁, KR 104₃₆₄, SC 15₃₉₉, TC 96₅₈₁. + '4x3z5c1i6z,' + // #5721: 5 fonts: HK 100₁₂₇, JP 96₂₃₂, KR 70₃₃₀, SC 19₄₀₃, TC 96₅₈₁. + '4x4a3t2u6v,' + // #5722: 5 fonts: HK 100₁₂₇, JP 96₂₃₂, KR 76₃₃₆, SC 91₄₇₅, TC 96₅₈₁. + '4x4a3z5i4b,' + // #5723: 5 fonts: HK 100₁₂₇, JP 96₂₃₂, KR 86₃₄₆, SC 93₄₇₇, TC 96₅₈₁. + '4x4a4j5a3z,' + // #5724: 5 fonts: HK 100₁₂₇, JP 96₂₃₂, KR 93₃₅₃, SC 91₄₇₅, TC 96₅₈₁. + '4x4a4q4r4b,' + // #5725: 5 fonts: HK 100₁₂₇, JP 97₂₃₃, KR 65₃₂₅, SC 6₃₉₀, TC 96₅₈₁. + '4x4b3n2m7i,' + // #5726: 5 fonts: HK 100₁₂₇, JP 97₂₃₃, KR 91₃₅₁, SC 64₄₄₈, TC 96₅₈₁. + '4x4b4n3s5c,' + // #5727: 5 fonts: HK 100₁₂₇, JP 98₂₃₄, KR 65₃₂₅, SC 6₃₉₀, TC 96₅₈₁. + '4x4c3m2m7i,' + // #5728: 5 fonts: HK 100₁₂₇, JP 98₂₃₄, KR 77₃₃₇, SC 34₄₁₈, TC 96₅₈₁. + '4x4c3y3c6g,' + // #5729: 5 fonts: HK 100₁₂₇, JP 98₂₃₄, KR 80₃₄₀, SC 41₄₂₅, TC 96₅₈₁. + '4x4c4b3g5z,' + // #5730: 5 fonts: HK 100₁₂₇, JP 98₂₃₄, KR 81₃₄₁, SC 91₄₇₅, TC 96₅₈₁. + '4x4c4c5d4b,' + // #5731: 5 fonts: HK 100₁₂₇, JP 98₂₃₄, KR 89₃₄₉, SC 92₄₇₆, TC 96₅₈₁. + '4x4c4k4w4a,' + // #5732: 5 fonts: HK 100₁₂₇, JP 99₂₃₅, KR 65₃₂₅, SC 4₃₈₈, TC 96₅₈₁. + '4x4d3l2k7k,' + // #5733: 5 fonts: HK 100₁₂₇, JP 99₂₃₅, KR 66₃₂₆, SC 90₄₇₄, TC 96₅₈₁. + '4x4d3m5r4c,' + // #5734: 5 fonts: HK 100₁₂₇, JP 99₂₃₅, KR 68₃₂₈, SC 91₄₇₅, TC 96₅₈₁. + '4x4d3o5q4b,' + // #5735: 5 fonts: HK 100₁₂₇, JP 99₂₃₅, KR 77₃₃₇, SC 91₄₇₅, TC 96₅₈₁. + '4x4d3x5h4b,' + // #5736: 5 fonts: HK 100₁₂₇, JP 99₂₃₅, KR 79₃₃₉, SC 91₄₇₅, TC 96₅₈₁. + '4x4d3z5f4b,' + // #5737: 5 fonts: HK 100₁₂₇, JP 99₂₃₅, KR 84₃₄₄, SC 91₄₇₅, TC 96₅₈₁. + '4x4d4e5a4b,' + // #5738: 5 fonts: HK 100₁₂₇, JP 99₂₃₅, KR 85₃₄₅, SC 90₄₇₄, TC 96₅₈₁. + '4x4d4f4y4c,' + // #5739: 5 fonts: HK 100₁₂₇, JP 99₂₃₅, KR 91₃₅₁, SC 94₄₇₈, TC 96₅₈₁. + '4x4d4l4w3y,' + // #5740: 5 fonts: HK 100₁₂₇, JP 99₂₃₅, KR 92₃₅₂, SC 91₄₇₅, TC 96₅₈₁. + '4x4d4m4s4b,' + // #5741: 5 fonts: HK 100₁₂₇, JP 100₂₃₆, KR 65₃₂₅, SC 7₃₉₁, TC 96₅₈₁. + '4x4e3k2n7h,' + // #5742: 5 fonts: HK 100₁₂₇, JP 100₂₃₆, KR 66₃₂₆, SC 8₃₉₂, TC 96₅₈₁. + '4x4e3l2n7g,' + // #5743: 5 fonts: HK 100₁₂₇, JP 100₂₃₆, KR 67₃₂₇, SC 9₃₉₃, TC 96₅₈₁. + '4x4e3m2n7f,' + // #5744: 5 fonts: HK 100₁₂₇, JP 100₂₃₆, KR 68₃₂₈, SC 91₄₇₅, TC 96₅₈₁. + '4x4e3n5q4b,' + // #5745: 5 fonts: HK 100₁₂₇, JP 100₂₃₆, KR 73₃₃₃, SC 92₄₇₆, TC 96₅₈₁. + '4x4e3s5m4a,' + // #5746: 5 fonts: HK 100₁₂₇, JP 100₂₃₆, KR 76₃₃₆, SC 92₄₇₆, TC 96₅₈₁. + '4x4e3v5j4a,' + // #5747: 5 fonts: HK 100₁₂₇, JP 100₂₃₆, KR 86₃₄₆, SC 92₄₇₆, TC 96₅₈₁. + '4x4e4f4z4a,' + // #5748: 5 fonts: HK 100₁₂₇, JP 100₂₃₆, KR 90₃₅₀, SC 90₄₇₄, TC 96₅₈₁. + '4x4e4j4t4c,' + // #5749: 5 fonts: HK 100₁₂₇, JP 100₂₃₆, KR 91₃₅₁, SC 90₄₇₄, TC 96₅₈₁. + '4x4e4k4s4c,' + // #5750: 5 fonts: HK 100₁₂₇, JP 101₂₃₇, KR 67₃₂₇, SC 9₃₉₃, TC 96₅₈₁. + '4x4f3l2n7f,' + // #5751: 5 fonts: HK 100₁₂₇, JP 101₂₃₇, KR 70₃₃₀, SC 78₄₆₂, TC 96₅₈₁. + '4x4f3o5b4o,' + // #5752: 5 fonts: HK 100₁₂₇, JP 101₂₃₇, KR 75₃₃₅, SC 30₄₁₄, TC 96₅₈₁. + '4x4f3t3a6k,' + // #5753: 5 fonts: HK 100₁₂₇, JP 101₂₃₇, KR 76₃₃₆, SC 91₄₇₅, TC 96₅₈₁. + '4x4f3u5i4b,' + // #5754: 5 fonts: HK 100₁₂₇, JP 101₂₃₇, KR 81₃₄₁, SC 91₄₇₅, TC 96₅₈₁. + '4x4f3z5d4b,' + // #5755: 5 fonts: HK 100₁₂₇, JP 101₂₃₇, KR 84₃₄₄, SC 50₄₃₄, TC 96₅₈₁. + '4x4f4c3l5q,' + // #5756: 5 fonts: HK 100₁₂₇, JP 101₂₃₇, KR 87₃₄₇, SC 90₄₇₄, TC 96₅₈₁. + '4x4f4f4w4c,' + // #5757: 5 fonts: HK 100₁₂₇, JP 102₂₃₈, KR 69₃₂₉, SC 16₄₀₀, TC 96₅₈₁. + '4x4g3m2s6y,' + // #5758: 5 fonts: HK 100₁₂₇, JP 102₂₃₈, KR 70₃₃₀, SC 18₄₀₂, TC 96₅₈₁. + '4x4g3n2t6w,' + // #5759: 5 fonts: HK 100₁₂₇, JP 102₂₃₈, KR 81₃₄₁, SC 90₄₇₄, TC 96₅₈₁. + '4x4g3y5c4c,' + // #5760: 5 fonts: HK 100₁₂₇, JP 102₂₃₈, KR 86₃₄₆, SC 90₄₇₄, TC 96₅₈₁. + '4x4g4d4x4c,' + // #5761: 5 fonts: HK 100₁₂₇, JP 102₂₃₈, KR 87₃₄₇, SC 90₄₇₄, TC 96₅₈₁. + '4x4g4e4w4c,' + // #5762: 5 fonts: HK 100₁₂₇, JP 102₂₃₈, KR 89₃₄₉, SC 58₄₄₂, TC 96₅₈₁. + '4x4g4g3o5i,' + // #5763: 5 fonts: HK 100₁₂₇, JP 102₂₃₈, KR 89₃₄₉, SC 91₄₇₅, TC 96₅₈₁. + '4x4g4g4v4b,' + // #5764: 5 fonts: HK 100₁₂₇, JP 102₂₃₈, KR 92₃₅₂, SC 92₄₇₆, TC 96₅₈₁. + '4x4g4j4t4a,' + // #5765: 5 fonts: HK 100₁₂₇, JP 102₂₃₈, KR 93₃₅₃, SC 91₄₇₅, TC 96₅₈₁. + '4x4g4k4r4b,' + // #5766: 5 fonts: HK 100₁₂₇, JP 102₂₃₈, KR 93₃₅₃, SC 92₄₇₆, TC 96₅₈₁. + '4x4g4k4s4a,' + // #5767: 5 fonts: HK 100₁₂₇, JP 103₂₃₉, KR 71₃₃₁, SC 19₄₀₃, TC 96₅₈₁. + '4x4h3n2t6v,' + // #5768: 5 fonts: HK 100₁₂₇, JP 103₂₃₉, KR 86₃₄₆, SC 53₄₃₇, TC 96₅₈₁. + '4x4h4c3m5n,' + // #5769: 5 fonts: HK 100₁₂₇, JP 103₂₃₉, KR 91₃₅₁, SC 63₄₄₇, TC 96₅₈₁. + '4x4h4h3r5d,' + // #5770: 5 fonts: HK 100₁₂₇, JP 103₂₃₉, KR 104₃₆₄, SC 91₄₇₅, TC 96₅₈₁. + '4x4h4u4g4b,' + // #5771: 5 fonts: HK 100₁₂₇, JP 104₂₄₀, KR 66₃₂₆, SC 8₃₉₂, TC 96₅₈₁. + '4x4i3h2n7g,' + // #5772: 5 fonts: HK 100₁₂₇, JP 104₂₄₀, KR 69₃₂₉, SC 17₄₀₁, TC 96₅₈₁. + '4x4i3k2t6x,' + // #5773: 5 fonts: HK 100₁₂₇, JP 104₂₄₀, KR 73₃₃₃, SC 90₄₇₄, TC 96₅₈₁. + '4x4i3o5k4c,' + // #5774: 5 fonts: HK 100₁₂₇, JP 104₂₄₀, KR 73₃₃₃, SC 93₄₇₇, TC 96₅₈₁. + '4x4i3o5n3z,' + // #5775: 5 fonts: HK 100₁₂₇, JP 104₂₄₀, KR 75₃₃₅, SC 28₄₁₂, TC 96₅₈₁. + '4x4i3q2y6m,' + // #5776: 5 fonts: HK 100₁₂₇, JP 104₂₄₀, KR 75₃₃₅, SC 92₄₇₆, TC 96₅₈₁. + '4x4i3q5k4a,' + // #5777: 5 fonts: HK 100₁₂₇, JP 104₂₄₀, KR 76₃₃₆, SC 92₄₇₆, TC 96₅₈₁. + '4x4i3r5j4a,' + // #5778: 5 fonts: HK 100₁₂₇, JP 104₂₄₀, KR 78₃₃₈, SC 92₄₇₆, TC 96₅₈₁. + '4x4i3t5h4a,' + // #5779: 5 fonts: HK 100₁₂₇, JP 104₂₄₀, KR 87₃₄₇, SC 92₄₇₆, TC 96₅₈₁. + '4x4i4c4y4a,' + // #5780: 5 fonts: HK 100₁₂₇, JP 104₂₄₀, KR 88₃₄₈, SC 91₄₇₅, TC 96₅₈₁. + '4x4i4d4w4b,' + // #5781: 5 fonts: HK 100₁₂₇, JP 105₂₄₁, KR 66₃₂₆, SC 8₃₉₂, TC 96₅₈₁. + '4x4j3g2n7g,' + // #5782: 5 fonts: HK 100₁₂₇, JP 105₂₄₁, KR 68₃₂₈, SC 14₃₉₈, TC 96₅₈₁. + '4x4j3i2r7a,' + // #5783: 5 fonts: HK 100₁₂₇, JP 105₂₄₁, KR 70₃₃₀, SC 18₄₀₂, TC 96₅₈₁. + '4x4j3k2t6w,' + // #5784: 5 fonts: HK 100₁₂₇, JP 105₂₄₁, KR 75₃₃₅, SC 92₄₇₆, TC 96₅₈₁. + '4x4j3p5k4a,' + // #5785: 5 fonts: HK 100₁₂₇, JP 105₂₄₁, KR 83₃₄₃, SC 92₄₇₆, TC 96₅₈₁. + '4x4j3x5c4a,' + // #5786: 5 fonts: HK 100₁₂₇, JP 105₂₄₁, KR 84₃₄₄, SC 90₄₇₄, TC 96₅₈₁. + '4x4j3y4z4c,' + // #5787: 5 fonts: HK 100₁₂₇, JP 105₂₄₁, KR 100₃₆₀, SC 13₃₉₇, TC 96₅₈₁. + '4x4j4o1k7b,' + // #5788: 5 fonts: HK 100₁₂₇, JP 106₂₄₂, KR 68₃₂₈, SC 91₄₇₅, TC 96₅₈₁. + '4x4k3h5q4b,' + // #5789: 5 fonts: HK 100₁₂₇, JP 106₂₄₂, KR 73₃₃₃, SC 91₄₇₅, TC 96₅₈₁. + '4x4k3m5l4b,' + // #5790: 5 fonts: HK 100₁₂₇, JP 106₂₄₂, KR 88₃₄₈, SC 92₄₇₆, TC 96₅₈₁. + '4x4k4b4x4a,' + // #5791: 5 fonts: HK 100₁₂₇, JP 107₂₄₃, KR 82₃₄₂, SC 92₄₇₆, TC 96₅₈₁. + '4x4l3u5d4a,' + // #5792: 5 fonts: HK 100₁₂₇, JP 107₂₄₃, KR 88₃₄₈, SC 86₄₇₀, TC 96₅₈₁. + '4x4l4a4r4g,' + // #5793: 5 fonts: HK 100₁₂₇, JP 107₂₄₃, KR 90₃₅₀, SC 91₄₇₅, TC 96₅₈₁. + '4x4l4c4u4b,' + // #5794: 5 fonts: HK 100₁₂₇, JP 107₂₄₃, KR 92₃₅₂, SC 66₄₅₀, TC 96₅₈₁. + '4x4l4e3t5a,' + // #5795: 5 fonts: HK 100₁₂₇, JP 107₂₄₃, KR 92₃₅₂, SC 92₄₇₆, TC 96₅₈₁. + '4x4l4e4t4a,' + // #5796: 5 fonts: HK 100₁₂₇, JP 107₂₄₃, KR 93₃₅₃, SC 91₄₇₅, TC 96₅₈₁. + '4x4l4f4r4b,' + // #5797: 4 fonts: HK 100₁₂₇, JP 107₂₄₃, SC 92₄₇₆, TC 96₅₈₁. + '4x4l8y4a,' + // #5798: 5 fonts: HK 100₁₂₇, JP 108₂₄₄, KR 70₃₃₀, SC 92₄₇₆, TC 96₅₈₁. + '4x4m3h5p4a,' + // #5799: 5 fonts: HK 100₁₂₇, JP 108₂₄₄, KR 73₃₃₃, SC 25₄₀₉, TC 96₅₈₁. + '4x4m3k2x6p,' + // #5800: 5 fonts: HK 100₁₂₇, JP 108₂₄₄, KR 75₃₃₅, SC 92₄₇₆, TC 96₅₈₁. + '4x4m3m5k4a,' + // #5801: 5 fonts: HK 100₁₂₇, JP 108₂₄₄, KR 77₃₃₇, SC 35₄₁₉, TC 96₅₈₁. + '4x4m3o3d6f,' + // #5802: 5 fonts: HK 100₁₂₇, JP 108₂₄₄, KR 86₃₄₆, SC 91₄₇₅, TC 96₅₈₁. + '4x4m3x4y4b,' + // #5803: 5 fonts: HK 100₁₂₇, JP 108₂₄₄, KR 101₃₆₁, SC 91₄₇₅, TC 96₅₈₁. + '4x4m4m4j4b,' + // #5804: 4 fonts: HK 100₁₂₇, JP 108₂₄₄, SC 7₃₉₁, TC 96₅₈₁. + '4x4m5q7h,' + // #5805: 5 fonts: HK 100₁₂₇, JP 109₂₄₅, KR 69₃₂₉, SC 92₄₇₆, TC 96₅₈₁. + '4x4n3f5q4a,' + // #5806: 5 fonts: HK 100₁₂₇, JP 109₂₄₅, KR 82₃₄₂, SC 92₄₇₆, TC 96₅₈₁. + '4x4n3s5d4a,' + // #5807: 5 fonts: HK 100₁₂₇, JP 109₂₄₅, KR 87₃₄₇, SC 53₄₃₇, TC 96₅₈₁. + '4x4n3x3l5n,' + // #5808: 5 fonts: HK 100₁₂₇, JP 109₂₄₅, KR 93₃₅₃, SC 91₄₇₅, TC 96₅₈₁. + '4x4n4d4r4b,' + // #5809: 5 fonts: HK 100₁₂₇, JP 109₂₄₅, KR 100₃₆₀, SC 91₄₇₅, TC 96₅₈₁. + '4x4n4k4k4b,' + // #5810: 5 fonts: HK 100₁₂₇, JP 110₂₄₆, KR 67₃₂₇, SC 10₃₉₄, TC 96₅₈₁. + '4x4o3c2o7e,' + // #5811: 5 fonts: HK 100₁₂₇, JP 110₂₄₆, KR 81₃₄₁, SC 92₄₇₆, TC 96₅₈₁. + '4x4o3q5e4a,' + // #5812: 5 fonts: HK 100₁₂₇, JP 110₂₄₆, KR 86₃₄₆, SC 92₄₇₆, TC 96₅₈₁. + '4x4o3v4z4a,' + // #5813: 5 fonts: HK 100₁₂₇, JP 110₂₄₆, KR 93₃₅₃, SC 93₄₇₇, TC 96₅₈₁. + '4x4o4c4t3z,' + // #5814: 5 fonts: HK 100₁₂₇, JP 110₂₄₆, KR 100₃₆₀, SC 92₄₇₆, TC 96₅₈₁. + '4x4o4j4l4a,' + // #5815: 5 fonts: HK 100₁₂₇, JP 112₂₄₈, KR 74₃₃₄, SC 28₄₁₂, TC 96₅₈₁. + '4x4q3h2z6m,' + // #5816: 5 fonts: HK 100₁₂₇, JP 112₂₄₈, KR 75₃₃₅, SC 92₄₇₆, TC 96₅₈₁. + '4x4q3i5k4a,' + // #5817: 5 fonts: HK 100₁₂₇, JP 112₂₄₈, KR 81₃₄₁, SC 43₄₂₇, TC 96₅₈₁. + '4x4q3o3h5x,' + // #5818: 5 fonts: HK 100₁₂₇, JP 112₂₄₈, KR 87₃₄₇, SC 55₄₃₉, TC 96₅₈₁. + '4x4q3u3n5l,' + // #5819: 5 fonts: HK 100₁₂₇, JP 112₂₄₈, KR 101₃₆₁, SC 91₄₇₅, TC 96₅₈₁. + '4x4q4i4j4b,' + // #5820: 5 fonts: HK 100₁₂₇, JP 113₂₄₉, KR 70₃₃₀, SC 18₄₀₂, TC 96₅₈₁. + '4x4r3c2t6w,' + // #5821: 5 fonts: HK 100₁₂₇, JP 113₂₄₉, KR 87₃₄₇, SC 90₄₇₄, TC 96₅₈₁. + '4x4r3t4w4c,' + // #5822: 5 fonts: HK 100₁₂₇, JP 113₂₄₉, KR 91₃₅₁, SC 92₄₇₆, TC 96₅₈₁. + '4x4r3x4u4a,' + // #5823: 5 fonts: HK 100₁₂₇, JP 114₂₅₀, KR 73₃₃₃, SC 92₄₇₆, TC 96₅₈₁. + '4x4s3e5m4a,' + // #5824: 5 fonts: HK 100₁₂₇, JP 114₂₅₀, KR 102₃₆₂, SC 91₄₇₅, TC 96₅₈₁. + '4x4s4h4i4b,' + // #5825: 5 fonts: HK 100₁₂₇, JP 114₂₅₀, KR 103₃₆₃, SC 90₄₇₄, TC 96₅₈₁. + '4x4s4i4g4c,' + // #5826: 5 fonts: HK 100₁₂₇, JP 115₂₅₁, KR 91₃₅₁, SC 92₄₇₆, TC 96₅₈₁. + '4x4t3v4u4a,' + // #5827: 5 fonts: HK 100₁₂₇, JP 115₂₅₁, KR 102₃₆₂, SC 88₄₇₂, TC 96₅₈₁. + '4x4t4g4f4e,' + // #5828: 5 fonts: HK 100₁₂₇, JP 116₂₅₂, KR 93₃₅₃, SC 91₄₇₅, TC 96₅₈₁. + '4x4u3w4r4b,' + // #5829: 5 fonts: HK 100₁₂₇, JP 116₂₅₂, KR 100₃₆₀, SC 94₄₇₈, TC 96₅₈₁. + '4x4u4d4n3y,' + // #5830: 3 fonts: HK 100₁₂₇, SC 58₄₄₂, TC 96₅₈₁. + '4x12c5i,' + // #5831: 3 fonts: HK 100₁₂₇, SC 61₄₄₅, TC 96₅₈₁. + '4x12f5f,' + // #5832: 5 fonts: HK 101₁₂₈, JP 5₁₄₁, KR 65₃₂₅, SC 4₃₈₈, TC 97₅₈₂. + '4ym7b2k7l,' + // #5833: 5 fonts: HK 101₁₂₈, JP 5₁₄₁, KR 100₃₆₀, SC 93₄₇₇, TC 97₅₈₂. + '4ym8k4m4a,' + // #5834: 5 fonts: HK 101₁₂₈, JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂, TC 97₅₈₂. + '4yq6y2n7h,' + // #5835: 5 fonts: HK 101₁₂₈, JP 11₁₄₇, KR 67₃₂₇, SC 12₃₉₆, TC 97₅₈₂. + '4ys6x2q7d,' + // #5836: 5 fonts: HK 101₁₂₈, JP 12₁₄₈, KR 67₃₂₇, SC 13₃₉₇, TC 97₅₈₂. + '4yt6w2r7c,' + // #5837: 5 fonts: HK 101₁₂₈, JP 12₁₄₈, KR 68₃₂₈, SC 13₃₉₇, TC 97₅₈₂. + '4yt6x2q7c,' + // #5838: 5 fonts: HK 101₁₂₈, JP 13₁₄₉, KR 69₃₂₉, SC 15₃₉₉, TC 97₅₈₂. + '4yu6x2r7a,' + // #5839: 4 fonts: HK 101₁₂₈, JP 14₁₅₀, SC 91₄₇₅, TC 97₅₈₂. + '4yv12m4c,' + // #5840: 5 fonts: HK 101₁₂₈, JP 15₁₅₁, KR 70₃₃₀, SC 17₄₀₁, TC 97₅₈₂. + '4yw6w2s6y,' + // #5841: 5 fonts: HK 101₁₂₈, JP 16₁₅₂, KR 70₃₃₀, SC 18₄₀₂, TC 97₅₈₂. + '4yx6v2t6x,' + // #5842: 5 fonts: HK 101₁₂₈, JP 16₁₅₂, KR 70₃₃₀, SC 78₄₆₂, TC 97₅₈₂. + '4yx6v5b4p,' + // #5843: 5 fonts: HK 101₁₂₈, JP 17₁₅₃, KR 71₃₃₁, SC 19₄₀₃, TC 97₅₈₂. + '4yy6v2t6w,' + // #5844: 5 fonts: HK 101₁₂₈, JP 19₁₅₅, KR 71₃₃₁, SC 95₄₇₉, TC 97₅₈₂. + '4y1a6t5r3y,' + // #5845: 5 fonts: HK 101₁₂₈, JP 21₁₅₇, KR 73₃₃₃, SC 24₄₀₈, TC 97₅₈₂. + '4y1c6t2w6r,' + // #5846: 4 fonts: HK 101₁₂₈, JP 21₁₅₇, SC 24₄₀₈, TC 97₅₈₂. + '4y1c9q6r,' + // #5847: 5 fonts: HK 101₁₂₈, JP 22₁₅₈, KR 73₃₃₃, SC 25₄₀₉, TC 97₅₈₂. + '4y1d6s2x6q,' + // #5848: 5 fonts: HK 101₁₂₈, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 97₅₈₂. + '4y1e6s2y6o,' + // #5849: 5 fonts: HK 101₁₂₈, JP 23₁₅₉, KR 74₃₃₄, SC 28₄₁₂, TC 97₅₈₂. + '4y1e6s2z6n,' + // #5850: 5 fonts: HK 101₁₂₈, JP 26₁₆₂, KR 76₃₃₆, SC 31₄₁₅, TC 97₅₈₂. + '4y1h6r3a6k,' + // #5851: 5 fonts: HK 101₁₂₈, JP 26₁₆₂, KR 76₃₃₆, SC 32₄₁₆, TC 97₅₈₂. + '4y1h6r3b6j,' + // #5852: 4 fonts: HK 101₁₂₈, JP 28₁₆₄, SC 35₄₁₉, TC 97₅₈₂. + '4y1j9u6g,' + // #5853: 5 fonts: HK 101₁₂₈, JP 30₁₆₆, KR 78₃₃₈, SC 37₄₂₁, TC 97₅₈₂. + '4y1l6p3e6e,' + // #5854: 5 fonts: HK 101₁₂₈, JP 30₁₆₆, KR 78₃₃₈, SC 38₄₂₂, TC 97₅₈₂. + '4y1l6p3f6d,' + // #5855: 5 fonts: HK 101₁₂₈, JP 31₁₆₇, KR 79₃₃₉, SC 78₄₆₂, TC 97₅₈₂. + '4y1m6p4s4p,' + // #5856: 5 fonts: HK 101₁₂₈, JP 32₁₆₈, KR 80₃₄₀, SC 41₄₂₅, TC 97₅₈₂. + '4y1n6p3g6a,' + // #5857: 5 fonts: HK 101₁₂₈, JP 34₁₇₀, KR 81₃₄₁, SC 44₄₂₈, TC 97₅₈₂. + '4y1p6o3i5x,' + // #5858: 5 fonts: HK 101₁₂₈, JP 34₁₇₀, KR 81₃₄₁, SC 93₄₇₇, TC 97₅₈₂. + '4y1p6o5f4a,' + // #5859: 5 fonts: HK 101₁₂₈, JP 34₁₇₀, KR 82₃₄₂, SC 44₄₂₈, TC 97₅₈₂. + '4y1p6p3h5x,' + // #5860: 5 fonts: HK 101₁₂₈, JP 34₁₇₀, KR 104₃₆₄, SC 44₄₂₈, TC 97₅₈₂. + '4y1p7l2l5x,' + // #5861: 5 fonts: HK 101₁₂₈, JP 37₁₇₃, KR 84₃₄₄, SC 49₄₃₃, TC 97₅₈₂. + '4y1s6o3k5s,' + // #5862: 5 fonts: HK 101₁₂₈, JP 37₁₇₃, KR 84₃₄₄, SC 93₄₇₇, TC 97₅₈₂. + '4y1s6o5c4a,' + // #5863: 5 fonts: HK 101₁₂₈, JP 38₁₇₄, KR 85₃₄₅, SC 92₄₇₆, TC 97₅₈₂. + '4y1t6o5a4b,' + // #5864: 5 fonts: HK 101₁₂₈, JP 39₁₇₅, KR 85₃₄₅, SC 50₄₃₄, TC 97₅₈₂. + '4y1u6n3k5r,' + // #5865: 5 fonts: HK 101₁₂₈, JP 39₁₇₅, KR 85₃₄₅, SC 80₄₆₄, TC 97₅₈₂. + '4y1u6n4o4n,' + // #5866: 5 fonts: HK 101₁₂₈, JP 40₁₇₆, KR 101₃₆₁, SC 92₄₇₆, TC 97₅₈₂. + '4y1v7c4k4b,' + // #5867: 4 fonts: HK 101₁₂₈, JP 41₁₇₇, SC 54₄₃₈, TC 97₅₈₂. + '4y1w10a5n,' + // #5868: 5 fonts: HK 101₁₂₈, JP 42₁₇₈, KR 87₃₄₇, SC 55₄₃₉, TC 97₅₈₂. + '4y1x6m3n5m,' + // #5869: 5 fonts: HK 101₁₂₈, JP 42₁₇₈, KR 88₃₄₈, SC 55₄₃₉, TC 97₅₈₂. + '4y1x6n3m5m,' + // #5870: 5 fonts: HK 101₁₂₈, JP 42₁₇₈, KR 88₃₄₈, SC 56₄₄₀, TC 97₅₈₂. + '4y1x6n3n5l,' + // #5871: 5 fonts: HK 101₁₂₈, JP 43₁₇₉, KR 88₃₄₈, SC 57₄₄₁, TC 97₅₈₂. + '4y1y6m3o5k,' + // #5872: 5 fonts: HK 101₁₂₈, JP 44₁₈₀, KR 89₃₄₉, SC 92₄₇₆, TC 97₅₈₂. + '4y1z6m4w4b,' + // #5873: 4 fonts: HK 101₁₂₈, JP 46₁₈₂, SC 61₄₄₅, TC 97₅₈₂. + '4y2b10c5g,' + // #5874: 4 fonts: HK 101₁₂₈, JP 46₁₈₂, SC 92₄₇₆, TC 97₅₈₂. + '4y2b11h4b,' + // #5875: 5 fonts: HK 101₁₂₈, JP 47₁₈₃, KR 90₃₅₀, SC 92₄₇₆, TC 97₅₈₂. + '4y2c6k4v4b,' + // #5876: 4 fonts: HK 101₁₂₈, JP 47₁₈₃, SC 91₄₇₅, TC 97₅₈₂. + '4y2c11f4c,' + // #5877: 4 fonts: HK 101₁₂₈, JP 47₁₈₃, SC 93₄₇₇, TC 97₅₈₂. + '4y2c11h4a,' + // #5878: 5 fonts: HK 101₁₂₈, JP 49₁₈₅, KR 92₃₅₂, SC 91₄₇₅, TC 97₅₈₂. + '4y2e6k4s4c,' + // #5879: 5 fonts: HK 101₁₂₈, JP 50₁₈₆, KR 93₃₅₃, SC 67₄₅₁, TC 97₅₈₂. + '4y2f6k3t5a,' + // #5880: 5 fonts: HK 101₁₂₈, JP 60₁₉₆, KR 66₃₂₆, SC 90₄₇₄, TC 97₅₈₂. + '4y2p4z5r4d,' + // #5881: 5 fonts: HK 101₁₂₈, JP 60₁₉₆, KR 78₃₃₈, SC 93₄₇₇, TC 97₅₈₂. + '4y2p5l5i4a,' + // #5882: 5 fonts: HK 101₁₂₈, JP 60₁₉₆, KR 89₃₄₉, SC 93₄₇₇, TC 97₅₈₂. + '4y2p5w4x4a,' + // #5883: 5 fonts: HK 101₁₂₈, JP 61₁₉₇, KR 84₃₄₄, SC 92₄₇₆, TC 97₅₈₂. + '4y2q5q5b4b,' + // #5884: 5 fonts: HK 101₁₂₈, JP 61₁₉₇, KR 86₃₄₆, SC 91₄₇₅, TC 97₅₈₂. + '4y2q5s4y4c,' + // #5885: 5 fonts: HK 101₁₂₈, JP 63₁₉₉, KR 91₃₅₁, SC 92₄₇₆, TC 97₅₈₂. + '4y2s5v4u4b,' + // #5886: 5 fonts: HK 101₁₂₈, JP 63₁₉₉, KR 93₃₅₃, SC 93₄₇₇, TC 97₅₈₂. + '4y2s5x4t4a,' + // #5887: 5 fonts: HK 101₁₂₈, JP 64₂₀₀, KR 68₃₂₈, SC 14₃₉₈, TC 97₅₈₂. + '4y2t4x2r7b,' + // #5888: 5 fonts: HK 101₁₂₈, JP 65₂₀₁, KR 71₃₃₁, SC 21₄₀₅, TC 97₅₈₂. + '4y2u4z2v6u,' + // #5889: 5 fonts: HK 101₁₂₈, JP 68₂₀₄, KR 87₃₄₇, SC 92₄₇₆, TC 97₅₈₂. + '4y2x5m4y4b,' + // #5890: 5 fonts: HK 101₁₂₈, JP 68₂₀₄, KR 93₃₅₃, SC 92₄₇₆, TC 97₅₈₂. + '4y2x5s4s4b,' + // #5891: 5 fonts: HK 101₁₂₈, JP 71₂₀₇, KR 85₃₄₅, SC 92₄₇₆, TC 97₅₈₂. + '4y3a5h5a4b,' + // #5892: 5 fonts: HK 101₁₂₈, JP 72₂₀₈, KR 67₃₂₇, SC 9₃₉₃, TC 97₅₈₂. + '4y3b4o2n7g,' + // #5893: 5 fonts: HK 101₁₂₈, JP 73₂₀₉, KR 83₃₄₃, SC 92₄₇₆, TC 97₅₈₂. + '4y3c5d5c4b,' + // #5894: 5 fonts: HK 101₁₂₈, JP 75₂₁₁, KR 67₃₂₇, SC 12₃₉₆, TC 97₅₈₂. + '4y3e4l2q7d,' + // #5895: 5 fonts: HK 101₁₂₈, JP 75₂₁₁, KR 67₃₂₇, SC 90₄₇₄, TC 97₅₈₂. + '4y3e4l5q4d,' + // #5896: 5 fonts: HK 101₁₂₈, JP 75₂₁₁, KR 75₃₃₅, SC 92₄₇₆, TC 97₅₈₂. + '4y3e4t5k4b,' + // #5897: 5 fonts: HK 101₁₂₈, JP 78₂₁₄, KR 87₃₄₇, SC 54₄₃₈, TC 97₅₈₂. + '4y3h5c3m5n,' + // #5898: 5 fonts: HK 101₁₂₈, JP 81₂₁₇, KR 71₃₃₁, SC 21₄₀₅, TC 97₅₈₂. + '4y3k4j2v6u,' + // #5899: 5 fonts: HK 101₁₂₈, JP 81₂₁₇, KR 85₃₄₅, SC 93₄₇₇, TC 97₅₈₂. + '4y3k4x5b4a,' + // #5900: 5 fonts: HK 101₁₂₈, JP 81₂₁₇, KR 92₃₅₂, SC 92₄₇₆, TC 97₅₈₂. + '4y3k5e4t4b,' + // #5901: 5 fonts: HK 101₁₂₈, JP 82₂₁₈, KR 86₃₄₆, SC 92₄₇₆, TC 97₅₈₂. + '4y3l4x4z4b,' + // #5902: 5 fonts: HK 101₁₂₈, JP 83₂₁₉, KR 87₃₄₇, SC 91₄₇₅, TC 97₅₈₂. + '4y3m4x4x4c,' + // #5903: 5 fonts: HK 101₁₂₈, JP 85₂₂₁, KR 67₃₂₇, SC 12₃₉₆, TC 97₅₈₂. + '4y3o4b2q7d,' + // #5904: 5 fonts: HK 101₁₂₈, JP 86₂₂₂, KR 66₃₂₆, SC 8₃₉₂, TC 97₅₈₂. + '4y3p3z2n7h,' + // #5905: 5 fonts: HK 101₁₂₈, JP 86₂₂₂, KR 73₃₃₃, SC 91₄₇₅, TC 97₅₈₂. + '4y3p4g5l4c,' + // #5906: 5 fonts: HK 101₁₂₈, JP 89₂₂₅, KR 92₃₅₂, SC 91₄₇₅, TC 97₅₈₂. + '4y3s4w4s4c,' + // #5907: 5 fonts: HK 101₁₂₈, JP 91₂₂₇, KR 67₃₂₇, SC 92₄₇₆, TC 97₅₈₂. + '4y3u3v5s4b,' + // #5908: 5 fonts: HK 101₁₂₈, JP 91₂₂₇, KR 74₃₃₄, SC 28₄₁₂, TC 97₅₈₂. + '4y3u4c2z6n,' + // #5909: 5 fonts: HK 101₁₂₈, JP 91₂₂₇, KR 100₃₆₀, SC 92₄₇₆, TC 97₅₈₂. + '4y3u5c4l4b,' + // #5910: 5 fonts: HK 101₁₂₈, JP 92₂₂₈, KR 65₃₂₅, SC 3₃₈₇, TC 97₅₈₂. + '4y3v3s2j7m,' + // #5911: 5 fonts: HK 101₁₂₈, JP 92₂₂₈, KR 66₃₂₆, SC 9₃₉₃, TC 97₅₈₂. + '4y3v3t2o7g,' + // #5912: 5 fonts: HK 101₁₂₈, JP 93₂₂₉, KR 75₃₃₅, SC 91₄₇₅, TC 97₅₈₂. + '4y3w4b5j4c,' + // #5913: 5 fonts: HK 101₁₂₈, JP 93₂₂₉, KR 86₃₄₆, SC 95₄₇₉, TC 97₅₈₂. + '4y3w4m5c3y,' + // #5914: 5 fonts: HK 101₁₂₈, JP 93₂₂₉, KR 89₃₄₉, SC 92₄₇₆, TC 97₅₈₂. + '4y3w4p4w4b,' + // #5915: 5 fonts: HK 101₁₂₈, JP 94₂₃₀, KR 66₃₂₆, SC 90₄₇₄, TC 97₅₈₂. + '4y3x3r5r4d,' + // #5916: 5 fonts: HK 101₁₂₈, JP 94₂₃₀, KR 68₃₂₈, SC 90₄₇₄, TC 97₅₈₂. + '4y3x3t5p4d,' + // #5917: 5 fonts: HK 101₁₂₈, JP 94₂₃₀, KR 69₃₂₉, SC 17₄₀₁, TC 97₅₈₂. + '4y3x3u2t6y,' + // #5918: 5 fonts: HK 101₁₂₈, JP 95₂₃₁, KR 77₃₃₇, SC 91₄₇₅, TC 97₅₈₂. + '4y3y4b5h4c,' + // #5919: 5 fonts: HK 101₁₂₈, JP 95₂₃₁, KR 78₃₃₈, SC 37₄₂₁, TC 97₅₈₂. + '4y3y4c3e6e,' + // #5920: 5 fonts: HK 101₁₂₈, JP 95₂₃₁, KR 92₃₅₂, SC 92₄₇₆, TC 97₅₈₂. + '4y3y4q4t4b,' + // #5921: 5 fonts: HK 101₁₂₈, JP 96₂₃₂, KR 70₃₃₀, SC 18₄₀₂, TC 97₅₈₂. + '4y3z3t2t6x,' + // #5922: 5 fonts: HK 101₁₂₈, JP 96₂₃₂, KR 76₃₃₆, SC 92₄₇₆, TC 97₅₈₂. + '4y3z3z5j4b,' + // #5923: 5 fonts: HK 101₁₂₈, JP 96₂₃₂, KR 88₃₄₈, SC 94₄₇₈, TC 97₅₈₂. + '4y3z4l4z3z,' + // #5924: 5 fonts: HK 101₁₂₈, JP 97₂₃₃, KR 75₃₃₅, SC 91₄₇₅, TC 97₅₈₂. + '4y4a3x5j4c,' + // #5925: 5 fonts: HK 101₁₂₈, JP 97₂₃₃, KR 87₃₄₇, SC 92₄₇₆, TC 97₅₈₂. + '4y4a4j4y4b,' + // #5926: 5 fonts: HK 101₁₂₈, JP 98₂₃₄, KR 75₃₃₅, SC 28₄₁₂, TC 97₅₈₂. + '4y4b3w2y6n,' + // #5927: 5 fonts: HK 101₁₂₈, JP 98₂₃₄, KR 86₃₄₆, SC 93₄₇₇, TC 97₅₈₂. + '4y4b4h5a4a,' + // #5928: 5 fonts: HK 101₁₂₈, JP 98₂₃₄, KR 91₃₅₁, SC 89₄₇₃, TC 97₅₈₂. + '4y4b4m4r4e,' + // #5929: 5 fonts: HK 101₁₂₈, JP 99₂₃₅, KR 66₃₂₆, SC 7₃₉₁, TC 97₅₈₂. + '4y4c3m2m7i,' + // #5930: 5 fonts: HK 101₁₂₈, JP 99₂₃₅, KR 70₃₃₀, SC 18₄₀₂, TC 97₅₈₂. + '4y4c3q2t6x,' + // #5931: 5 fonts: HK 101₁₂₈, JP 99₂₃₅, KR 75₃₃₅, SC 93₄₇₇, TC 97₅₈₂. + '4y4c3v5l4a,' + // #5932: 5 fonts: HK 101₁₂₈, JP 99₂₃₅, KR 80₃₄₀, SC 92₄₇₆, TC 97₅₈₂. + '4y4c4a5f4b,' + // #5933: 5 fonts: HK 101₁₂₈, JP 99₂₃₅, KR 80₃₄₀, SC 94₄₇₈, TC 97₅₈₂. + '4y4c4a5h3z,' + // #5934: 5 fonts: HK 101₁₂₈, JP 99₂₃₅, KR 87₃₄₇, SC 54₄₃₈, TC 97₅₈₂. + '4y4c4h3m5n,' + // #5935: 5 fonts: HK 101₁₂₈, JP 100₂₃₆, KR 69₃₂₉, SC 17₄₀₁, TC 97₅₈₂. + '4y4d3o2t6y,' + // #5936: 5 fonts: HK 101₁₂₈, JP 100₂₃₆, KR 108₃₆₈, SC 8₃₉₂, TC 97₅₈₂. + '4y4d5bx7h,' + // #5937: 5 fonts: HK 101₁₂₈, JP 101₂₃₇, KR 66₃₂₆, SC 8₃₉₂, TC 97₅₈₂. + '4y4e3k2n7h,' + // #5938: 5 fonts: HK 101₁₂₈, JP 101₂₃₇, KR 68₃₂₈, SC 93₄₇₇, TC 97₅₈₂. + '4y4e3m5s4a,' + // #5939: 5 fonts: HK 101₁₂₈, JP 101₂₃₇, KR 69₃₂₉, SC 91₄₇₅, TC 97₅₈₂. + '4y4e3n5p4c,' + // #5940: 5 fonts: HK 101₁₂₈, JP 101₂₃₇, KR 69₃₂₉, SC 93₄₇₇, TC 97₅₈₂. + '4y4e3n5r4a,' + // #5941: 5 fonts: HK 101₁₂₈, JP 101₂₃₇, KR 88₃₄₈, SC 93₄₇₇, TC 97₅₈₂. + '4y4e4g4y4a,' + // #5942: 5 fonts: HK 101₁₂₈, JP 101₂₃₇, KR 92₃₅₂, SC 92₄₇₆, TC 97₅₈₂. + '4y4e4k4t4b,' + // #5943: 5 fonts: HK 101₁₂₈, JP 101₂₃₇, KR 102₃₆₂, SC 94₄₇₈, TC 97₅₈₂. + '4y4e4u4l3z,' + // #5944: 5 fonts: HK 101₁₂₈, JP 101₂₃₇, KR 107₃₆₇, SC 92₄₇₆, TC 97₅₈₂. + '4y4e4z4e4b,' + // #5945: 5 fonts: HK 101₁₂₈, JP 102₂₃₈, KR 69₃₂₉, SC 15₃₉₉, TC 97₅₈₂. + '4y4f3m2r7a,' + // #5946: 5 fonts: HK 101₁₂₈, JP 102₂₃₈, KR 70₃₃₀, SC 18₄₀₂, TC 97₅₈₂. + '4y4f3n2t6x,' + // #5947: 5 fonts: HK 101₁₂₈, JP 102₂₃₈, KR 70₃₃₀, SC 92₄₇₆, TC 97₅₈₂. + '4y4f3n5p4b,' + // #5948: 5 fonts: HK 101₁₂₈, JP 102₂₃₈, KR 71₃₃₁, SC 91₄₇₅, TC 97₅₈₂. + '4y4f3o5n4c,' + // #5949: 5 fonts: HK 101₁₂₈, JP 103₂₃₉, KR 71₃₃₁, SC 91₄₇₅, TC 97₅₈₂. + '4y4g3n5n4c,' + // #5950: 5 fonts: HK 101₁₂₈, JP 103₂₃₉, KR 74₃₃₄, SC 26₄₁₀, TC 97₅₈₂. + '4y4g3q2x6p,' + // #5951: 5 fonts: HK 101₁₂₈, JP 103₂₃₉, KR 75₃₃₅, SC 31₄₁₅, TC 97₅₈₂. + '4y4g3r3b6k,' + // #5952: 5 fonts: HK 101₁₂₈, JP 103₂₃₉, KR 82₃₄₂, SC 93₄₇₇, TC 97₅₈₂. + '4y4g3y5e4a,' + // #5953: 5 fonts: HK 101₁₂₈, JP 103₂₃₉, KR 83₃₄₃, SC 93₄₇₇, TC 97₅₈₂. + '4y4g3z5d4a,' + // #5954: 5 fonts: HK 101₁₂₈, JP 103₂₃₉, KR 85₃₄₅, SC 93₄₇₇, TC 97₅₈₂. + '4y4g4b5b4a,' + // #5955: 5 fonts: HK 101₁₂₈, JP 103₂₃₉, KR 101₃₆₁, SC 92₄₇₆, TC 97₅₈₂. + '4y4g4r4k4b,' + // #5956: 5 fonts: HK 101₁₂₈, JP 104₂₄₀, KR 69₃₂₉, SC 17₄₀₁, TC 97₅₈₂. + '4y4h3k2t6y,' + // #5957: 5 fonts: HK 101₁₂₈, JP 104₂₄₀, KR 73₃₃₃, SC 24₄₀₈, TC 97₅₈₂. + '4y4h3o2w6r,' + // #5958: 5 fonts: HK 101₁₂₈, JP 104₂₄₀, KR 74₃₃₄, SC 92₄₇₆, TC 97₅₈₂. + '4y4h3p5l4b,' + // #5959: 5 fonts: HK 101₁₂₈, JP 104₂₄₀, KR 89₃₄₉, SC 92₄₇₆, TC 97₅₈₂. + '4y4h4e4w4b,' + // #5960: 5 fonts: HK 101₁₂₈, JP 104₂₄₀, KR 90₃₅₀, SC 92₄₇₆, TC 97₅₈₂. + '4y4h4f4v4b,' + // #5961: 5 fonts: HK 101₁₂₈, JP 105₂₄₁, KR 72₃₃₂, SC 91₄₇₅, TC 97₅₈₂. + '4y4i3m5m4c,' + // #5962: 5 fonts: HK 101₁₂₈, JP 105₂₄₁, KR 75₃₃₅, SC 92₄₇₆, TC 97₅₈₂. + '4y4i3p5k4b,' + // #5963: 5 fonts: HK 101₁₂₈, JP 105₂₄₁, KR 80₃₄₀, SC 93₄₇₇, TC 97₅₈₂. + '4y4i3u5g4a,' + // #5964: 5 fonts: HK 101₁₂₈, JP 105₂₄₁, KR 82₃₄₂, SC 77₄₆₁, TC 97₅₈₂. + '4y4i3w4o4q,' + // #5965: 5 fonts: HK 101₁₂₈, JP 105₂₄₁, KR 87₃₄₇, SC 91₄₇₅, TC 97₅₈₂. + '4y4i4b4x4c,' + // #5966: 5 fonts: HK 101₁₂₈, JP 105₂₄₁, KR 87₃₄₇, SC 92₄₇₆, TC 97₅₈₂. + '4y4i4b4y4b,' + // #5967: 5 fonts: HK 101₁₂₈, JP 105₂₄₁, KR 92₃₅₂, SC 93₄₇₇, TC 97₅₈₂. + '4y4i4g4u4a,' + // #5968: 5 fonts: HK 101₁₂₈, JP 106₂₄₂, KR 66₃₂₆, SC 9₃₉₃, TC 97₅₈₂. + '4y4j3f2o7g,' + // #5969: 5 fonts: HK 101₁₂₈, JP 106₂₄₂, KR 67₃₂₇, SC 93₄₇₇, TC 97₅₈₂. + '4y4j3g5t4a,' + // #5970: 5 fonts: HK 101₁₂₈, JP 106₂₄₂, KR 69₃₂₉, SC 17₄₀₁, TC 97₅₈₂. + '4y4j3i2t6y,' + // #5971: 5 fonts: HK 101₁₂₈, JP 106₂₄₂, KR 74₃₃₄, SC 26₄₁₀, TC 97₅₈₂. + '4y4j3n2x6p,' + // #5972: 5 fonts: HK 101₁₂₈, JP 106₂₄₂, KR 77₃₃₇, SC 36₄₂₀, TC 97₅₈₂. + '4y4j3q3e6f,' + // #5973: 5 fonts: HK 101₁₂₈, JP 106₂₄₂, KR 81₃₄₁, SC 42₄₂₆, TC 97₅₈₂. + '4y4j3u3g5z,' + // #5974: 5 fonts: HK 101₁₂₈, JP 106₂₄₂, KR 87₃₄₇, SC 94₄₇₈, TC 97₅₈₂. + '4y4j4a5a3z,' + // #5975: 5 fonts: HK 101₁₂₈, JP 106₂₄₂, KR 89₃₄₉, SC 92₄₇₆, TC 97₅₈₂. + '4y4j4c4w4b,' + // #5976: 5 fonts: HK 101₁₂₈, JP 106₂₄₂, KR 91₃₅₁, SC 92₄₇₆, TC 97₅₈₂. + '4y4j4e4u4b,' + // #5977: 5 fonts: HK 101₁₂₈, JP 107₂₄₃, KR 66₃₂₆, SC 8₃₉₂, TC 97₅₈₂. + '4y4k3e2n7h,' + // #5978: 5 fonts: HK 101₁₂₈, JP 107₂₄₃, KR 66₃₂₆, SC 79₄₆₃, TC 97₅₈₂. + '4y4k3e5g4o,' + // #5979: 5 fonts: HK 101₁₂₈, JP 107₂₄₃, KR 67₃₂₇, SC 92₄₇₆, TC 97₅₈₂. + '4y4k3f5s4b,' + // #5980: 5 fonts: HK 101₁₂₈, JP 107₂₄₃, KR 80₃₄₀, SC 41₄₂₅, TC 97₅₈₂. + '4y4k3s3g6a,' + // #5981: 5 fonts: HK 101₁₂₈, JP 107₂₄₃, KR 80₃₄₀, SC 92₄₇₆, TC 97₅₈₂. + '4y4k3s5f4b,' + // #5982: 5 fonts: HK 101₁₂₈, JP 107₂₄₃, KR 81₃₄₁, SC 92₄₇₆, TC 97₅₈₂. + '4y4k3t5e4b,' + // #5983: 5 fonts: HK 101₁₂₈, JP 107₂₄₃, KR 83₃₄₃, SC 92₄₇₆, TC 97₅₈₂. + '4y4k3v5c4b,' + // #5984: 5 fonts: HK 101₁₂₈, JP 107₂₄₃, KR 88₃₄₈, SC 92₄₇₆, TC 97₅₈₂. + '4y4k4a4x4b,' + // #5985: 5 fonts: HK 101₁₂₈, JP 107₂₄₃, KR 91₃₅₁, SC 92₄₇₆, TC 97₅₈₂. + '4y4k4d4u4b,' + // #5986: 5 fonts: HK 101₁₂₈, JP 107₂₄₃, KR 102₃₆₂, SC 94₄₇₈, TC 97₅₈₂. + '4y4k4o4l3z,' + // #5987: 5 fonts: HK 101₁₂₈, JP 108₂₄₄, KR 66₃₂₆, SC 7₃₉₁, TC 97₅₈₂. + '4y4l3d2m7i,' + // #5988: 5 fonts: HK 101₁₂₈, JP 108₂₄₄, KR 84₃₄₄, SC 49₄₃₃, TC 97₅₈₂. + '4y4l3v3k5s,' + // #5989: 5 fonts: HK 101₁₂₈, JP 108₂₄₄, KR 87₃₄₇, SC 91₄₇₅, TC 97₅₈₂. + '4y4l3y4x4c,' + // #5990: 5 fonts: HK 101₁₂₈, JP 108₂₄₄, KR 92₃₅₂, SC 93₄₇₇, TC 97₅₈₂. + '4y4l4d4u4a,' + // #5991: 5 fonts: HK 101₁₂₈, JP 109₂₄₅, KR 68₃₂₈, SC 14₃₉₈, TC 97₅₈₂. + '4y4m3e2r7b,' + // #5992: 5 fonts: HK 101₁₂₈, JP 109₂₄₅, KR 70₃₃₀, SC 18₄₀₂, TC 97₅₈₂. + '4y4m3g2t6x,' + // #5993: 5 fonts: HK 101₁₂₈, JP 109₂₄₅, KR 72₃₃₂, SC 92₄₇₆, TC 97₅₈₂. + '4y4m3i5n4b,' + // #5994: 5 fonts: HK 101₁₂₈, JP 109₂₄₅, KR 73₃₃₃, SC 93₄₇₇, TC 97₅₈₂. + '4y4m3j5n4a,' + // #5995: 5 fonts: HK 101₁₂₈, JP 109₂₄₅, KR 86₃₄₆, SC 51₄₃₅, TC 97₅₈₂. + '4y4m3w3k5q,' + // #5996: 5 fonts: HK 101₁₂₈, JP 109₂₄₅, KR 88₃₄₈, SC 93₄₇₇, TC 97₅₈₂. + '4y4m3y4y4a,' + // #5997: 5 fonts: HK 101₁₂₈, JP 109₂₄₅, KR 90₃₅₀, SC 94₄₇₈, TC 97₅₈₂. + '4y4m4a4x3z,' + // #5998: 4 fonts: HK 101₁₂₈, JP 109₂₄₅, SC 93₄₇₇, TC 97₅₈₂. + '4y4m8x4a,' + // #5999: 5 fonts: HK 101₁₂₈, JP 110₂₄₆, KR 68₃₂₈, SC 14₃₉₈, TC 97₅₈₂. + '4y4n3d2r7b,' + // #6000: 5 fonts: HK 101₁₂₈, JP 110₂₄₆, KR 69₃₂₉, SC 93₄₇₇, TC 97₅₈₂. + '4y4n3e5r4a,' + // #6001: 5 fonts: HK 101₁₂₈, JP 110₂₄₆, KR 74₃₃₄, SC 94₄₇₈, TC 97₅₈₂. + '4y4n3j5n3z,' + // #6002: 5 fonts: HK 101₁₂₈, JP 110₂₄₆, KR 83₃₄₃, SC 93₄₇₇, TC 97₅₈₂. + '4y4n3s5d4a,' + // #6003: 5 fonts: HK 101₁₂₈, JP 110₂₄₆, KR 84₃₄₄, SC 91₄₇₅, TC 97₅₈₂. + '4y4n3t5a4c,' + // #6004: 5 fonts: HK 101₁₂₈, JP 110₂₄₆, KR 88₃₄₈, SC 92₄₇₆, TC 97₅₈₂. + '4y4n3x4x4b,' + // #6005: 5 fonts: HK 101₁₂₈, JP 110₂₄₆, KR 102₃₆₂, SC 93₄₇₇, TC 97₅₈₂. + '4y4n4l4k4a,' + // #6006: 5 fonts: HK 101₁₂₈, JP 111₂₄₇, KR 82₃₄₂, SC 94₄₇₈, TC 97₅₈₂. + '4y4o3q5f3z,' + // #6007: 5 fonts: HK 101₁₂₈, JP 111₂₄₇, KR 84₃₄₄, SC 94₄₇₈, TC 97₅₈₂. + '4y4o3s5d3z,' + // #6008: 5 fonts: HK 101₁₂₈, JP 111₂₄₇, KR 86₃₄₆, SC 93₄₇₇, TC 97₅₈₂. + '4y4o3u5a4a,' + // #6009: 5 fonts: HK 101₁₂₈, JP 111₂₄₇, KR 90₃₅₀, SC 93₄₇₇, TC 97₅₈₂. + '4y4o3y4w4a,' + // #6010: 5 fonts: HK 101₁₂₈, JP 111₂₄₇, KR 91₃₅₁, SC 95₄₇₉, TC 97₅₈₂. + '4y4o3z4x3y,' + // #6011: 5 fonts: HK 101₁₂₈, JP 112₂₄₈, KR 74₃₃₄, SC 93₄₇₇, TC 97₅₈₂. + '4y4p3h5m4a,' + // #6012: 5 fonts: HK 101₁₂₈, JP 112₂₄₈, KR 88₃₄₈, SC 93₄₇₇, TC 97₅₈₂. + '4y4p3v4y4a,' + // #6013: 5 fonts: HK 101₁₂₈, JP 112₂₄₈, KR 92₃₅₂, SC 92₄₇₆, TC 97₅₈₂. + '4y4p3z4t4b,' + // #6014: 5 fonts: HK 101₁₂₈, JP 112₂₄₈, KR 102₃₆₂, SC 92₄₇₆, TC 97₅₈₂. + '4y4p4j4j4b,' + // #6015: 5 fonts: HK 101₁₂₈, JP 113₂₄₉, KR 68₃₂₈, SC 91₄₇₅, TC 97₅₈₂. + '4y4q3a5q4c,' + // #6016: 5 fonts: HK 101₁₂₈, JP 113₂₄₉, KR 70₃₃₀, SC 18₄₀₂, TC 97₅₈₂. + '4y4q3c2t6x,' + // #6017: 5 fonts: HK 101₁₂₈, JP 113₂₄₉, KR 76₃₃₆, SC 32₄₁₆, TC 97₅₈₂. + '4y4q3i3b6j,' + // #6018: 5 fonts: HK 101₁₂₈, JP 113₂₄₉, KR 83₃₄₃, SC 92₄₇₆, TC 97₅₈₂. + '4y4q3p5c4b,' + // #6019: 5 fonts: HK 101₁₂₈, JP 113₂₄₉, KR 85₃₄₅, SC 94₄₇₈, TC 97₅₈₂. + '4y4q3r5c3z,' + // #6020: 5 fonts: HK 101₁₂₈, JP 113₂₄₉, KR 90₃₅₀, SC 91₄₇₅, TC 97₅₈₂. + '4y4q3w4u4c,' + // #6021: 5 fonts: HK 101₁₂₈, JP 113₂₄₉, KR 90₃₅₀, SC 93₄₇₇, TC 97₅₈₂. + '4y4q3w4w4a,' + // #6022: 5 fonts: HK 101₁₂₈, JP 114₂₅₀, KR 81₃₄₁, SC 95₄₇₉, TC 97₅₈₂. + '4y4r3m5h3y,' + // #6023: 5 fonts: HK 101₁₂₈, JP 114₂₅₀, KR 86₃₄₆, SC 93₄₇₇, TC 97₅₈₂. + '4y4r3r5a4a,' + // #6024: 5 fonts: HK 101₁₂₈, JP 114₂₅₀, KR 105₃₆₅, SC 92₄₇₆, TC 97₅₈₂. + '4y4r4k4g4b,' + // #6025: 3 fonts: HK 101₁₂₈, SC 93₄₇₇, TC 97₅₈₂. + '4y13k4a,' + // #6026: 4 fonts: HK 102₁₂₉, JP 5₁₄₁, SC 4₃₈₈, TC 98₅₈₃. + '4zl9m7m,' + // #6027: 5 fonts: HK 102₁₂₉, JP 8₁₄₄, KR 65₃₂₅, SC 7₃₉₁, TC 98₅₈₃. + '4zo6y2n7j,' + // #6028: 5 fonts: HK 102₁₂₉, JP 10₁₄₆, KR 66₃₂₆, SC 9₃₉₃, TC 98₅₈₃. + '4zq6x2o7h,' + // #6029: 5 fonts: HK 102₁₂₉, JP 10₁₄₆, KR 67₃₂₇, SC 10₃₉₄, TC 98₅₈₃. + '4zq6y2o7g,' + // #6030: 5 fonts: HK 102₁₂₉, JP 11₁₄₇, KR 67₃₂₇, SC 12₃₉₆, TC 98₅₈₃. + '4zr6x2q7e,' + // #6031: 5 fonts: HK 102₁₂₉, JP 15₁₅₁, KR 69₃₂₉, SC 16₄₀₀, TC 98₅₈₃. + '4zv6v2s7a,' + // #6032: 5 fonts: HK 102₁₂₉, JP 15₁₅₁, KR 70₃₃₀, SC 18₄₀₂, TC 98₅₈₃. + '4zv6w2t6y,' + // #6033: 5 fonts: HK 102₁₂₉, JP 16₁₅₂, KR 70₃₃₀, SC 19₄₀₃, TC 98₅₈₃. + '4zw6v2u6x,' + // #6034: 5 fonts: HK 102₁₂₉, JP 16₁₅₂, KR 70₃₃₀, SC 79₄₆₃, TC 98₅₈₃. + '4zw6v5c4p,' + // #6035: 5 fonts: HK 102₁₂₉, JP 16₁₅₂, KR 70₃₃₀, SC 80₄₆₄, TC 98₅₈₃. + '4zw6v5d4o,' + // #6036: 5 fonts: HK 102₁₂₉, JP 17₁₅₃, KR 71₃₃₁, SC 19₄₀₃, TC 98₅₈₃. + '4zx6v2t6x,' + // #6037: 5 fonts: HK 102₁₂₉, JP 18₁₅₄, KR 71₃₃₁, SC 21₄₀₅, TC 98₅₈₃. + '4zy6u2v6v,' + // #6038: 5 fonts: HK 102₁₂₉, JP 22₁₅₈, KR 73₃₃₃, SC 25₄₀₉, TC 98₅₈₃. + '4z1c6s2x6r,' + // #6039: 5 fonts: HK 102₁₂₉, JP 22₁₅₈, KR 74₃₃₄, SC 94₄₇₈, TC 98₅₈₃. + '4z1c6t5n4a,' + // #6040: 5 fonts: HK 102₁₂₉, JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁, TC 98₅₈₃. + '4z1d6s2y6p,' + // #6041: 5 fonts: HK 102₁₂₉, JP 25₁₆₁, KR 76₃₃₆, SC 31₄₁₅, TC 98₅₈₃. + '4z1f6s3a6l,' + // #6042: 5 fonts: HK 102₁₂₉, JP 30₁₆₆, KR 79₃₃₉, SC 38₄₂₂, TC 98₅₈₃. + '4z1k6q3e6e,' + // #6043: 5 fonts: HK 102₁₂₉, JP 32₁₆₈, KR 80₃₄₀, SC 80₄₆₄, TC 98₅₈₃. + '4z1m6p4t4o,' + // #6044: 5 fonts: HK 102₁₂₉, JP 33₁₆₉, KR 81₃₄₁, SC 93₄₇₇, TC 98₅₈₃. + '4z1n6p5f4b,' + // #6045: 5 fonts: HK 102₁₂₉, JP 34₁₇₀, KR 81₃₄₁, SC 77₄₆₁, TC 98₅₈₃. + '4z1o6o4p4r,' + // #6046: 5 fonts: HK 102₁₂₉, JP 34₁₇₀, KR 82₃₄₂, SC 44₄₂₈, TC 98₅₈₃. + '4z1o6p3h5y,' + // #6047: 4 fonts: HK 102₁₂₉, JP 39₁₇₅, SC 50₄₃₄, TC 98₅₈₃. + '4z1t9y5s,' + // #6048: 4 fonts: HK 102₁₂₉, JP 39₁₇₅, SC 93₄₇₇, TC 98₅₈₃. + '4z1t11p4b,' + // #6049: 4 fonts: HK 102₁₂₉, JP 40₁₇₆, SC 93₄₇₇, TC 98₅₈₃. + '4z1u11o4b,' + // #6050: 4 fonts: HK 102₁₂₉, JP 40₁₇₆, SC 95₄₇₉, TC 98₅₈₃. + '4z1u11q3z,' + // #6051: 5 fonts: HK 102₁₂₉, JP 41₁₇₇, KR 87₃₄₇, SC 54₄₃₈, TC 98₅₈₃. + '4z1v6n3m5o,' + // #6052: 5 fonts: HK 102₁₂₉, JP 45₁₈₁, KR 89₃₄₉, SC 59₄₄₃, TC 98₅₈₃. + '4z1z6l3p5j,' + // #6053: 5 fonts: HK 102₁₂₉, JP 47₁₈₃, KR 90₃₅₀, SC 63₄₄₇, TC 98₅₈₃. + '4z2b6k3s5f,' + // #6054: 4 fonts: HK 102₁₂₉, JP 47₁₈₃, SC 93₄₇₇, TC 98₅₈₃. + '4z2b11h4b,' + // #6055: 5 fonts: HK 102₁₂₉, JP 48₁₈₄, KR 92₃₅₂, SC 64₄₄₈, TC 98₅₈₃. + '4z2c6l3r5e,' + // #6056: 5 fonts: HK 102₁₂₉, JP 48₁₈₄, KR 92₃₅₂, SC 65₄₄₉, TC 98₅₈₃. + '4z2c6l3s5d,' + // #6057: 5 fonts: HK 102₁₂₉, JP 48₁₈₄, KR 92₃₅₂, SC 77₄₆₁, TC 98₅₈₃. + '4z2c6l4e4r,' + // #6058: 5 fonts: HK 102₁₂₉, JP 49₁₈₅, KR 92₃₅₂, SC 94₄₇₈, TC 98₅₈₃. + '4z2d6k4v4a,' + // #6059: 4 fonts: HK 102₁₂₉, JP 50₁₈₆, SC 92₄₇₆, TC 98₅₈₃. + '4z2e11d4c,' + // #6060: 5 fonts: HK 102₁₂₉, JP 63₁₉₉, KR 86₃₄₆, SC 53₄₃₇, TC 98₅₈₃. + '4z2r5q3m5p,' + // #6061: 5 fonts: HK 102₁₂₉, JP 66₂₀₂, KR 93₃₅₃, SC 93₄₇₇, TC 98₅₈₃. + '4z2u5u4t4b,' + // #6062: 5 fonts: HK 102₁₂₉, JP 68₂₀₄, KR 72₃₃₂, SC 22₄₀₆, TC 98₅₈₃. + '4z2w4x2v6u,' + // #6063: 5 fonts: HK 102₁₂₉, JP 71₂₀₇, KR 87₃₄₇, SC 53₄₃₇, TC 98₅₈₃. + '4z2z5j3l5p,' + // #6064: 5 fonts: HK 102₁₂₉, JP 78₂₁₄, KR 90₃₅₀, SC 93₄₇₇, TC 98₅₈₃. + '4z3g5f4w4b,' + // #6065: 5 fonts: HK 102₁₂₉, JP 79₂₁₅, KR 92₃₅₂, SC 93₄₇₇, TC 98₅₈₃. + '4z3h5g4u4b,' + // #6066: 5 fonts: HK 102₁₂₉, JP 81₂₁₇, KR 66₃₂₆, SC 93₄₇₇, TC 98₅₈₃. + '4z3j4e5u4b,' + // #6067: 5 fonts: HK 102₁₂₉, JP 81₂₁₇, KR 85₃₄₅, SC 92₄₇₆, TC 98₅₈₃. + '4z3j4x5a4c,' + // #6068: 5 fonts: HK 102₁₂₉, JP 84₂₂₀, KR 88₃₄₈, SC 55₄₃₉, TC 98₅₈₃. + '4z3m4x3m5n,' + // #6069: 5 fonts: HK 102₁₂₉, JP 85₂₂₁, KR 70₃₃₀, SC 19₄₀₃, TC 98₅₈₃. + '4z3n4e2u6x,' + // #6070: 5 fonts: HK 102₁₂₉, JP 85₂₂₁, KR 78₃₃₈, SC 93₄₇₇, TC 98₅₈₃. + '4z3n4m5i4b,' + // #6071: 5 fonts: HK 102₁₂₉, JP 86₂₂₂, KR 71₃₃₁, SC 21₄₀₅, TC 98₅₈₃. + '4z3o4e2v6v,' + // #6072: 4 fonts: HK 102₁₂₉, JP 86₂₂₂, SC 92₄₇₆, TC 98₅₈₃. + '4z3o9t4c,' + // #6073: 5 fonts: HK 102₁₂₉, JP 88₂₂₄, KR 91₃₅₁, SC 80₄₆₄, TC 98₅₈₃. + '4z3q4w4i4o,' + // #6074: 5 fonts: HK 102₁₂₉, JP 89₂₂₅, KR 90₃₅₀, SC 93₄₇₇, TC 98₅₈₃. + '4z3r4u4w4b,' + // #6075: 5 fonts: HK 102₁₂₉, JP 90₂₂₆, KR 84₃₄₄, SC 94₄₇₈, TC 98₅₈₃. + '4z3s4n5d4a,' + // #6076: 5 fonts: HK 102₁₂₉, JP 93₂₂₉, KR 85₃₄₅, SC 94₄₇₈, TC 98₅₈₃. + '4z3v4l5c4a,' + // #6077: 11 fonts: HK 102₁₂₉, JP 93₂₂₉, KR 105₃₆₅, SC 93₄₇₇, TC 98₅₈₃, Lisu₆₄₆, Mongolian₆₆₂, New Tai Lue₆₆₈, Phags Pa₆₈₇, Tai Le₇₀₆, Yi₇₂₁. + '4z3v5f4h4b2kpfsso,' + // #6078: 11 fonts: HK 102₁₂₉, JP 93₂₂₉, KR 106₃₆₆, SC 93₄₇₇, TC 98₅₈₃, Lisu₆₄₆, Mongolian₆₆₂, New Tai Lue₆₆₈, Phags Pa₆₈₇, Tai Le₇₀₆, Yi₇₂₁. + '4z3v5g4g4b2kpfsso,' + // #6079: 5 fonts: HK 102₁₂₉, JP 94₂₃₀, KR 73₃₃₃, SC 93₄₇₇, TC 98₅₈₃. + '4z3w3y5n4b,' + // #6080: 5 fonts: HK 102₁₂₉, JP 94₂₃₀, KR 75₃₃₅, SC 28₄₁₂, TC 98₅₈₃. + '4z3w4a2y6o,' + // #6081: 5 fonts: HK 102₁₂₉, JP 94₂₃₀, KR 91₃₅₁, SC 64₄₄₈, TC 98₅₈₃. + '4z3w4q3s5e,' + // #6082: 5 fonts: HK 102₁₂₉, JP 95₂₃₁, KR 71₃₃₁, SC 94₄₇₈, TC 98₅₈₃. + '4z3x3v5q4a,' + // #6083: 5 fonts: HK 102₁₂₉, JP 95₂₃₁, KR 83₃₄₃, SC 93₄₇₇, TC 98₅₈₃. + '4z3x4h5d4b,' + // #6084: 5 fonts: HK 102₁₂₉, JP 95₂₃₁, KR 91₃₅₁, SC 93₄₇₇, TC 98₅₈₃. + '4z3x4p4v4b,' + // #6085: 5 fonts: HK 102₁₂₉, JP 96₂₃₂, KR 68₃₂₈, SC 93₄₇₇, TC 98₅₈₃. + '4z3y3r5s4b,' + // #6086: 5 fonts: HK 102₁₂₉, JP 97₂₃₃, KR 75₃₃₅, SC 94₄₇₈, TC 98₅₈₃. + '4z3z3x5m4a,' + // #6087: 5 fonts: HK 102₁₂₉, JP 98₂₃₄, KR 91₃₅₁, SC 93₄₇₇, TC 98₅₈₃. + '4z4a4m4v4b,' + // #6088: 5 fonts: HK 102₁₂₉, JP 99₂₃₅, KR 77₃₃₇, SC 93₄₇₇, TC 98₅₈₃. + '4z4b3x5j4b,' + // #6089: 5 fonts: HK 102₁₂₉, JP 99₂₃₅, KR 87₃₄₇, SC 93₄₇₇, TC 98₅₈₃. + '4z4b4h4z4b,' + // #6090: 5 fonts: HK 102₁₂₉, JP 99₂₃₅, KR 90₃₅₀, SC 93₄₇₇, TC 98₅₈₃. + '4z4b4k4w4b,' + // #6091: 5 fonts: HK 102₁₂₉, JP 99₂₃₅, KR 91₃₅₁, SC 94₄₇₈, TC 98₅₈₃. + '4z4b4l4w4a,' + // #6092: 5 fonts: HK 102₁₂₉, JP 100₂₃₆, KR 70₃₃₀, SC 18₄₀₂, TC 98₅₈₃. + '4z4c3p2t6y,' + // #6093: 5 fonts: HK 102₁₂₉, JP 100₂₃₆, KR 86₃₄₆, SC 95₄₇₉, TC 98₅₈₃. + '4z4c4f5c3z,' + // #6094: 5 fonts: HK 102₁₂₉, JP 100₂₃₆, KR 90₃₅₀, SC 95₄₇₉, TC 98₅₈₃. + '4z4c4j4y3z,' + // #6095: 5 fonts: HK 102₁₂₉, JP 100₂₃₆, KR 91₃₅₁, SC 64₄₄₈, TC 98₅₈₃. + '4z4c4k3s5e,' + // #6096: 5 fonts: HK 102₁₂₉, JP 100₂₃₆, KR 92₃₅₂, SC 91₄₇₅, TC 98₅₈₃. + '4z4c4l4s4d,' + // #6097: 5 fonts: HK 102₁₂₉, JP 100₂₃₆, KR 92₃₅₂, SC 94₄₇₈, TC 98₅₈₃. + '4z4c4l4v4a,' + // #6098: 5 fonts: HK 102₁₂₉, JP 100₂₃₆, KR 103₃₆₃, SC 92₄₇₆, TC 98₅₈₃. + '4z4c4w4i4c,' + // #6099: 5 fonts: HK 102₁₂₉, JP 101₂₃₇, KR 85₃₄₅, SC 94₄₇₈, TC 98₅₈₃. + '4z4d4d5c4a,' + // #6100: 5 fonts: HK 102₁₂₉, JP 101₂₃₇, KR 90₃₅₀, SC 93₄₇₇, TC 98₅₈₃. + '4z4d4i4w4b,' + // #6101: 5 fonts: HK 102₁₂₉, JP 101₂₃₇, KR 102₃₆₂, SC 94₄₇₈, TC 98₅₈₃. + '4z4d4u4l4a,' + // #6102: 4 fonts: HK 102₁₂₉, JP 101₂₃₇, SC 94₄₇₈, TC 98₅₈₃. + '4z4d9g4a,' + // #6103: 5 fonts: HK 102₁₂₉, JP 102₂₃₈, KR 74₃₃₄, SC 94₄₇₈, TC 98₅₈₃. + '4z4e3r5n4a,' + // #6104: 5 fonts: HK 102₁₂₉, JP 102₂₃₈, KR 79₃₃₉, SC 93₄₇₇, TC 98₅₈₃. + '4z4e3w5h4b,' + // #6105: 5 fonts: HK 102₁₂₉, JP 102₂₃₈, KR 88₃₄₈, SC 55₄₃₉, TC 98₅₈₃. + '4z4e4f3m5n,' + // #6106: 5 fonts: HK 102₁₂₉, JP 102₂₃₈, KR 93₃₅₃, SC 93₄₇₇, TC 98₅₈₃. + '4z4e4k4t4b,' + // #6107: 5 fonts: HK 102₁₂₉, JP 102₂₃₈, KR 101₃₆₁, SC 92₄₇₆, TC 98₅₈₃. + '4z4e4s4k4c,' + // #6108: 5 fonts: HK 102₁₂₉, JP 103₂₃₉, KR 72₃₃₂, SC 78₄₆₂, TC 98₅₈₃. + '4z4f3o4z4q,' + // #6109: 5 fonts: HK 102₁₂₉, JP 103₂₃₉, KR 100₃₆₀, SC 93₄₇₇, TC 98₅₈₃. + '4z4f4q4m4b,' + // #6110: 5 fonts: HK 102₁₂₉, JP 104₂₄₀, KR 72₃₃₂, SC 90₄₇₄, TC 98₅₈₃. + '4z4g3n5l4e,' + // #6111: 5 fonts: HK 102₁₂₉, JP 104₂₄₀, KR 78₃₃₈, SC 93₄₇₇, TC 98₅₈₃. + '4z4g3t5i4b,' + // #6112: 5 fonts: HK 102₁₂₉, JP 104₂₄₀, KR 82₃₄₂, SC 44₄₂₈, TC 98₅₈₃. + '4z4g3x3h5y,' + // #6113: 5 fonts: HK 102₁₂₉, JP 104₂₄₀, KR 87₃₄₇, SC 94₄₇₈, TC 98₅₈₃. + '4z4g4c5a4a,' + // #6114: 5 fonts: HK 102₁₂₉, JP 104₂₄₀, KR 91₃₅₁, SC 94₄₇₈, TC 98₅₈₃. + '4z4g4g4w4a,' + // #6115: 5 fonts: HK 102₁₂₉, JP 104₂₄₀, KR 100₃₆₀, SC 93₄₇₇, TC 98₅₈₃. + '4z4g4p4m4b,' + // #6116: 4 fonts: HK 102₁₂₉, JP 104₂₄₀, SC 94₄₇₈, TC 98₅₈₃. + '4z4g9d4a,' + // #6117: 5 fonts: HK 102₁₂₉, JP 105₂₄₁, KR 91₃₅₁, SC 93₄₇₇, TC 98₅₈₃. + '4z4h4f4v4b,' + // #6118: 5 fonts: HK 102₁₂₉, JP 105₂₄₁, KR 101₃₆₁, SC 94₄₇₈, TC 98₅₈₃. + '4z4h4p4m4a,' + // #6119: 5 fonts: HK 102₁₂₉, JP 105₂₄₁, KR 102₃₆₂, SC 93₄₇₇, TC 98₅₈₃. + '4z4h4q4k4b,' + // #6120: 5 fonts: HK 102₁₂₉, JP 105₂₄₁, KR 105₃₆₅, SC 93₄₇₇, TC 98₅₈₃. + '4z4h4t4h4b,' + // #6121: 4 fonts: HK 102₁₂₉, JP 105₂₄₁, SC 67₄₅₁, TC 98₅₈₃. + '4z4h8b5b,' + // #6122: 5 fonts: HK 102₁₂₉, JP 106₂₄₂, KR 68₃₂₈, SC 93₄₇₇, TC 98₅₈₃. + '4z4i3h5s4b,' + // #6123: 5 fonts: HK 102₁₂₉, JP 106₂₄₂, KR 70₃₃₀, SC 19₄₀₃, TC 98₅₈₃. + '4z4i3j2u6x,' + // #6124: 5 fonts: HK 102₁₂₉, JP 106₂₄₂, KR 75₃₃₅, SC 30₄₁₄, TC 98₅₈₃. + '4z4i3o3a6m,' + // #6125: 5 fonts: HK 102₁₂₉, JP 106₂₄₂, KR 75₃₃₅, SC 78₄₆₂, TC 98₅₈₃. + '4z4i3o4w4q,' + // #6126: 5 fonts: HK 102₁₂₉, JP 106₂₄₂, KR 84₃₄₄, SC 93₄₇₇, TC 98₅₈₃. + '4z4i3x5c4b,' + // #6127: 5 fonts: HK 102₁₂₉, JP 106₂₄₂, KR 87₃₄₇, SC 78₄₆₂, TC 98₅₈₃. + '4z4i4a4k4q,' + // #6128: 5 fonts: HK 102₁₂₉, JP 106₂₄₂, KR 87₃₄₇, SC 93₄₇₇, TC 98₅₈₃. + '4z4i4a4z4b,' + // #6129: 5 fonts: HK 102₁₂₉, JP 107₂₄₃, KR 66₃₂₆, SC 77₄₆₁, TC 98₅₈₃. + '4z4j3e5e4r,' + // #6130: 5 fonts: HK 102₁₂₉, JP 107₂₄₃, KR 84₃₄₄, SC 93₄₇₇, TC 98₅₈₃. + '4z4j3w5c4b,' + // #6131: 5 fonts: HK 102₁₂₉, JP 107₂₄₃, KR 85₃₄₅, SC 95₄₇₉, TC 98₅₈₃. + '4z4j3x5d3z,' + // #6132: 5 fonts: HK 102₁₂₉, JP 107₂₄₃, KR 93₃₅₃, SC 94₄₇₈, TC 98₅₈₃. + '4z4j4f4u4a,' + // #6133: 5 fonts: HK 102₁₂₉, JP 107₂₄₃, KR 100₃₆₀, SC 95₄₇₉, TC 98₅₈₃. + '4z4j4m4o3z,' + // #6134: 5 fonts: HK 102₁₂₉, JP 108₂₄₄, KR 68₃₂₈, SC 93₄₇₇, TC 98₅₈₃. + '4z4k3f5s4b,' + // #6135: 5 fonts: HK 102₁₂₉, JP 108₂₄₄, KR 80₃₄₀, SC 94₄₇₈, TC 98₅₈₃. + '4z4k3r5h4a,' + // #6136: 5 fonts: HK 102₁₂₉, JP 108₂₄₄, KR 84₃₄₄, SC 95₄₇₉, TC 98₅₈₃. + '4z4k3v5e3z,' + // #6137: 5 fonts: HK 102₁₂₉, JP 108₂₄₄, KR 92₃₅₂, SC 93₄₇₇, TC 98₅₈₃. + '4z4k4d4u4b,' + // #6138: 5 fonts: HK 102₁₂₉, JP 108₂₄₄, KR 100₃₆₀, SC 93₄₇₇, TC 98₅₈₃. + '4z4k4l4m4b,' + // #6139: 5 fonts: HK 102₁₂₉, JP 108₂₄₄, KR 101₃₆₁, SC 93₄₇₇, TC 98₅₈₃. + '4z4k4m4l4b,' + // #6140: 5 fonts: HK 102₁₂₉, JP 108₂₄₄, KR 102₃₆₂, SC 94₄₇₈, TC 98₅₈₃. + '4z4k4n4l4a,' + // #6141: 5 fonts: HK 102₁₂₉, JP 108₂₄₄, KR 106₃₆₆, SC 94₄₇₈, TC 98₅₈₃. + '4z4k4r4h4a,' + // #6142: 5 fonts: HK 102₁₂₉, JP 109₂₄₅, KR 68₃₂₈, SC 94₄₇₈, TC 98₅₈₃. + '4z4l3e5t4a,' + // #6143: 5 fonts: HK 102₁₂₉, JP 109₂₄₅, KR 71₃₃₁, SC 20₄₀₄, TC 98₅₈₃. + '4z4l3h2u6w,' + // #6144: 5 fonts: HK 102₁₂₉, JP 109₂₄₅, KR 73₃₃₃, SC 25₄₀₉, TC 98₅₈₃. + '4z4l3j2x6r,' + // #6145: 5 fonts: HK 102₁₂₉, JP 109₂₄₅, KR 75₃₃₅, SC 29₄₁₃, TC 98₅₈₃. + '4z4l3l2z6n,' + // #6146: 5 fonts: HK 102₁₂₉, JP 109₂₄₅, KR 75₃₃₅, SC 95₄₇₉, TC 98₅₈₃. + '4z4l3l5n3z,' + // #6147: 5 fonts: HK 102₁₂₉, JP 109₂₄₅, KR 81₃₄₁, SC 94₄₇₈, TC 98₅₈₃. + '4z4l3r5g4a,' + // #6148: 5 fonts: HK 102₁₂₉, JP 109₂₄₅, KR 84₃₄₄, SC 92₄₇₆, TC 98₅₈₃. + '4z4l3u5b4c,' + // #6149: 5 fonts: HK 102₁₂₉, JP 109₂₄₅, KR 86₃₄₆, SC 53₄₃₇, TC 98₅₈₃. + '4z4l3w3m5p,' + // #6150: 5 fonts: HK 102₁₂₉, JP 109₂₄₅, KR 90₃₅₀, SC 93₄₇₇, TC 98₅₈₃. + '4z4l4a4w4b,' + // #6151: 5 fonts: HK 102₁₂₉, JP 109₂₄₅, KR 101₃₆₁, SC 93₄₇₇, TC 98₅₈₃. + '4z4l4l4l4b,' + // #6152: 5 fonts: HK 102₁₂₉, JP 109₂₄₅, KR 104₃₆₄, SC 92₄₇₆, TC 98₅₈₃. + '4z4l4o4h4c,' + // #6153: 5 fonts: HK 102₁₂₉, JP 110₂₄₆, KR 71₃₃₁, SC 19₄₀₃, TC 98₅₈₃. + '4z4m3g2t6x,' + // #6154: 5 fonts: HK 102₁₂₉, JP 110₂₄₆, KR 77₃₃₇, SC 94₄₇₈, TC 98₅₈₃. + '4z4m3m5k4a,' + // #6155: 5 fonts: HK 102₁₂₉, JP 110₂₄₆, KR 84₃₄₄, SC 94₄₇₈, TC 98₅₈₃. + '4z4m3t5d4a,' + // #6156: 5 fonts: HK 102₁₂₉, JP 110₂₄₆, KR 89₃₄₉, SC 59₄₄₃, TC 98₅₈₃. + '4z4m3y3p5j,' + // #6157: 5 fonts: HK 102₁₂₉, JP 110₂₄₆, KR 90₃₅₀, SC 93₄₇₇, TC 98₅₈₃. + '4z4m3z4w4b,' + // #6158: 5 fonts: HK 102₁₂₉, JP 110₂₄₆, KR 91₃₅₁, SC 94₄₇₈, TC 98₅₈₃. + '4z4m4a4w4a,' + // #6159: 5 fonts: HK 102₁₂₉, JP 110₂₄₆, KR 92₃₅₂, SC 65₄₄₉, TC 98₅₈₃. + '4z4m4b3s5d,' + // #6160: 5 fonts: HK 102₁₂₉, JP 110₂₄₆, KR 92₃₅₂, SC 92₄₇₆, TC 98₅₈₃. + '4z4m4b4t4c,' + // #6161: 5 fonts: HK 102₁₂₉, JP 110₂₄₆, KR 101₃₆₁, SC 94₄₇₈, TC 98₅₈₃. + '4z4m4k4m4a,' + // #6162: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 67₃₂₇, SC 93₄₇₇, TC 98₅₈₃. + '4z4n3b5t4b,' + // #6163: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 69₃₂₉, SC 16₄₀₀, TC 98₅₈₃. + '4z4n3d2s7a,' + // #6164: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 74₃₃₄, SC 27₄₁₁, TC 98₅₈₃. + '4z4n3i2y6p,' + // #6165: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 74₃₃₄, SC 28₄₁₂, TC 98₅₈₃. + '4z4n3i2z6o,' + // #6166: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 75₃₃₅, SC 94₄₇₈, TC 98₅₈₃. + '4z4n3j5m4a,' + // #6167: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 76₃₃₆, SC 92₄₇₆, TC 98₅₈₃. + '4z4n3k5j4c,' + // #6168: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 77₃₃₇, SC 93₄₇₇, TC 98₅₈₃. + '4z4n3l5j4b,' + // #6169: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 79₃₃₉, SC 94₄₇₈, TC 98₅₈₃. + '4z4n3n5i4a,' + // #6170: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 81₃₄₁, SC 94₄₇₈, TC 98₅₈₃. + '4z4n3p5g4a,' + // #6171: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 83₃₄₃, SC 93₄₇₇, TC 98₅₈₃. + '4z4n3r5d4b,' + // #6172: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 89₃₄₉, SC 93₄₇₇, TC 98₅₈₃. + '4z4n3x4x4b,' + // #6173: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 89₃₄₉, SC 94₄₇₈, TC 98₅₈₃. + '4z4n3x4y4a,' + // #6174: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 92₃₅₂, SC 77₄₆₁, TC 98₅₈₃. + '4z4n4a4e4r,' + // #6175: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 100₃₆₀, SC 94₄₇₈, TC 98₅₈₃. + '4z4n4i4n4a,' + // #6176: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 102₃₆₂, SC 78₄₆₂, TC 98₅₈₃. + '4z4n4k3v4q,' + // #6177: 5 fonts: HK 102₁₂₉, JP 111₂₄₇, KR 107₃₆₇, SC 93₄₇₇, TC 98₅₈₃. + '4z4n4p4f4b,' + // #6178: 4 fonts: HK 102₁₂₉, JP 111₂₄₇, SC 77₄₆₁, TC 98₅₈₃. + '4z4n8f4r,' + // #6179: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 67₃₂₇, SC 80₄₆₄, TC 98₅₈₃. + '4z4o3a5g4o,' + // #6180: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 68₃₂₈, SC 14₃₉₈, TC 98₅₈₃. + '4z4o3b2r7c,' + // #6181: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 68₃₂₈, SC 93₄₇₇, TC 98₅₈₃. + '4z4o3b5s4b,' + // #6182: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 69₃₂₉, SC 17₄₀₁, TC 98₅₈₃. + '4z4o3c2t6z,' + // #6183: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 76₃₃₆, SC 31₄₁₅, TC 98₅₈₃. + '4z4o3j3a6l,' + // #6184: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 92₃₅₂, SC 93₄₇₇, TC 98₅₈₃. + '4z4o3z4u4b,' + // #6185: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 92₃₅₂, SC 94₄₇₈, TC 98₅₈₃. + '4z4o3z4v4a,' + // #6186: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 93₃₅₃, SC 94₄₇₈, TC 98₅₈₃. + '4z4o4a4u4a,' + // #6187: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 100₃₆₀, SC 94₄₇₈, TC 98₅₈₃. + '4z4o4h4n4a,' + // #6188: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 102₃₆₂, SC 93₄₇₇, TC 98₅₈₃. + '4z4o4j4k4b,' + // #6189: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 104₃₆₄, SC 93₄₇₇, TC 98₅₈₃. + '4z4o4l4i4b,' + // #6190: 5 fonts: HK 102₁₂₉, JP 112₂₄₈, KR 105₃₆₅, SC 93₄₇₇, TC 98₅₈₃. + '4z4o4m4h4b,' + // #6191: 4 fonts: HK 102₁₂₉, JP 112₂₄₈, SC 94₄₇₈, TC 98₅₈₃. + '4z4o8v4a,' + // #6192: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 67₃₂₇, SC 94₄₇₈, TC 98₅₈₃. + '4z4p2z5u4a,' + // #6193: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 69₃₂₉, SC 16₄₀₀, TC 98₅₈₃. + '4z4p3b2s7a,' + // #6194: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 70₃₃₀, SC 18₄₀₂, TC 98₅₈₃. + '4z4p3c2t6y,' + // #6195: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 70₃₃₀, SC 94₄₇₈, TC 98₅₈₃. + '4z4p3c5r4a,' + // #6196: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 85₃₄₅, SC 93₄₇₇, TC 98₅₈₃. + '4z4p3r5b4b,' + // #6197: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 85₃₄₅, SC 94₄₇₈, TC 98₅₈₃. + '4z4p3r5c4a,' + // #6198: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 86₃₄₆, SC 93₄₇₇, TC 98₅₈₃. + '4z4p3s5a4b,' + // #6199: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 90₃₅₀, SC 94₄₇₈, TC 98₅₈₃. + '4z4p3w4x4a,' + // #6200: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 92₃₅₂, SC 94₄₇₈, TC 98₅₈₃. + '4z4p3y4v4a,' + // #6201: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 101₃₆₁, SC 94₄₇₈, TC 98₅₈₃. + '4z4p4h4m4a,' + // #6202: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 104₃₆₄, SC 93₄₇₇, TC 98₅₈₃. + '4z4p4k4i4b,' + // #6203: 5 fonts: HK 102₁₂₉, JP 113₂₄₉, KR 111₃₇₁, SC 86₄₇₀, TC 98₅₈₃. + '4z4p4r3u4i,' + // #6204: 5 fonts: HK 102₁₂₉, JP 114₂₅₀, KR 68₃₂₈, SC 93₄₇₇, TC 98₅₈₃. + '4z4q2z5s4b,' + // #6205: 5 fonts: HK 102₁₂₉, JP 114₂₅₀, KR 69₃₂₉, SC 15₃₉₉, TC 98₅₈₃. + '4z4q3a2r7b,' + // #6206: 5 fonts: HK 102₁₂₉, JP 114₂₅₀, KR 87₃₄₇, SC 93₄₇₇, TC 98₅₈₃. + '4z4q3s4z4b,' + // #6207: 5 fonts: HK 102₁₂₉, JP 114₂₅₀, KR 90₃₅₀, SC 93₄₇₇, TC 98₅₈₃. + '4z4q3v4w4b,' + // #6208: 5 fonts: HK 102₁₂₉, JP 114₂₅₀, KR 93₃₅₃, SC 94₄₇₈, TC 98₅₈₃. + '4z4q3y4u4a,' + // #6209: 5 fonts: HK 102₁₂₉, JP 114₂₅₀, KR 100₃₆₀, SC 93₄₇₇, TC 98₅₈₃. + '4z4q4f4m4b,' + // #6210: 5 fonts: HK 102₁₂₉, JP 114₂₅₀, KR 101₃₆₁, SC 93₄₇₇, TC 98₅₈₃. + '4z4q4g4l4b,' + // #6211: 5 fonts: HK 102₁₂₉, JP 114₂₅₀, KR 104₃₆₄, SC 94₄₇₈, TC 98₅₈₃. + '4z4q4j4j4a,' + // #6212: 5 fonts: HK 102₁₂₉, JP 114₂₅₀, KR 105₃₆₅, SC 93₄₇₇, TC 98₅₈₃. + '4z4q4k4h4b,' + // #6213: 5 fonts: HK 102₁₂₉, JP 115₂₅₁, KR 70₃₃₀, SC 19₄₀₃, TC 98₅₈₃. + '4z4r3a2u6x,' + // #6214: 5 fonts: HK 102₁₂₉, JP 115₂₅₁, KR 76₃₃₆, SC 94₄₇₈, TC 98₅₈₃. + '4z4r3g5l4a,' + // #6215: 5 fonts: HK 102₁₂₉, JP 115₂₅₁, KR 91₃₅₁, SC 78₄₆₂, TC 98₅₈₃. + '4z4r3v4g4q,' + // #6216: 5 fonts: HK 102₁₂₉, JP 115₂₅₁, KR 92₃₅₂, SC 93₄₇₇, TC 98₅₈₃. + '4z4r3w4u4b,' + // #6217: 5 fonts: HK 102₁₂₉, JP 115₂₅₁, KR 101₃₆₁, SC 77₄₆₁, TC 98₅₈₃. + '4z4r4f3v4r,' + // #6218: 5 fonts: HK 102₁₂₉, JP 115₂₅₁, KR 101₃₆₁, SC 93₄₇₇, TC 98₅₈₃. + '4z4r4f4l4b,' + // #6219: 5 fonts: HK 102₁₂₉, JP 116₂₅₂, KR 75₃₃₅, SC 28₄₁₂, TC 98₅₈₃. + '4z4s3e2y6o,' + // #6220: 5 fonts: HK 102₁₂₉, JP 116₂₅₂, KR 90₃₅₀, SC 94₄₇₈, TC 98₅₈₃. + '4z4s3t4x4a,' + // #6221: 5 fonts: HK 102₁₂₉, JP 116₂₅₂, KR 100₃₆₀, SC 94₄₇₈, TC 98₅₈₃. + '4z4s4d4n4a,' + // #6222: 5 fonts: HK 102₁₂₉, JP 116₂₅₂, KR 101₃₆₁, SC 93₄₇₇, TC 98₅₈₃. + '4z4s4e4l4b,' + // #6223: 7 fonts: HK 102₁₂₉, JP 116₂₅₂, KR 108₃₆₈, SC 94₄₇₈, TC 98₅₈₃, Phags Pa₆₈₇, Yi₇₂₁. + '4z4s4l4f4a3z1h,' + // #6224: 6 fonts: HK 102₁₂₉, JP 117₂₅₃, KR 110₃₇₀, SC 93₄₇₇, TC 98₅₈₃, New Tai Lue₆₆₈. + '4z4t4m4c4b3g,' + // #6225: 3 fonts: HK 102₁₂₉, SC 94₄₇₈, TC 98₅₈₃. + '4z13k4a,' + // #6226: 5 fonts: HK 103₁₃₀, JP 5₁₄₁, KR 65₃₂₅, SC 79₄₆₃, TC 99₅₈₄. + '5ak7b5h4q,' + // #6227: 5 fonts: HK 103₁₃₀, JP 14₁₅₀, KR 69₃₂₉, SC 15₃₉₉, TC 99₅₈₄. + '5at6w2r7c,' + // #6228: 5 fonts: HK 103₁₃₀, JP 16₁₅₂, KR 70₃₃₀, SC 78₄₆₂, TC 99₅₈₄. + '5av6v5b4r,' + // #6229: 5 fonts: HK 103₁₃₀, JP 18₁₅₄, KR 71₃₃₁, SC 21₄₀₅, TC 99₅₈₄. + '5ax6u2v6w,' + // #6230: 5 fonts: HK 103₁₃₀, JP 22₁₅₈, KR 73₃₃₃, SC 25₄₀₉, TC 99₅₈₄. + '5a1b6s2x6s,' + // #6231: 5 fonts: HK 103₁₃₀, JP 23₁₅₉, KR 74₃₃₄, SC 78₄₆₂, TC 99₅₈₄. + '5a1c6s4x4r,' + // #6232: 5 fonts: HK 103₁₃₀, JP 28₁₆₄, KR 77₃₃₇, SC 80₄₆₄, TC 99₅₈₄. + '5a1h6q4w4p,' + // #6233: 4 fonts: HK 103₁₃₀, JP 28₁₆₄, SC 35₄₁₉, TC 99₅₈₄. + '5a1h9u6i,' + // #6234: 5 fonts: HK 103₁₃₀, JP 33₁₆₉, KR 81₃₄₁, SC 77₄₆₁, TC 99₅₈₄. + '5a1m6p4p4s,' + // #6235: 5 fonts: HK 103₁₃₀, JP 34₁₇₀, KR 81₃₄₁, SC 77₄₆₁, TC 99₅₈₄. + '5a1n6o4p4s,' + // #6236: 5 fonts: HK 103₁₃₀, JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂, TC 99₅₈₄. + '5a1q6o3j5v,' + // #6237: 5 fonts: HK 103₁₃₀, JP 37₁₇₃, KR 84₃₄₄, SC 95₄₇₉, TC 99₅₈₄. + '5a1q6o5e4a,' + // #6238: 5 fonts: HK 103₁₃₀, JP 41₁₇₇, KR 87₃₄₇, SC 94₄₇₈, TC 99₅₈₄. + '5a1u6n5a4b,' + // #6239: 5 fonts: HK 103₁₃₀, JP 42₁₇₈, KR 88₃₄₈, SC 55₄₃₉, TC 99₅₈₄. + '5a1v6n3m5o,' + // #6240: 5 fonts: HK 103₁₃₀, JP 42₁₇₈, KR 88₃₄₈, SC 77₄₆₁, TC 99₅₈₄. + '5a1v6n4i4s,' + // #6241: 5 fonts: HK 103₁₃₀, JP 42₁₇₈, KR 88₃₄₈, SC 79₄₆₃, TC 99₅₈₄. + '5a1v6n4k4q,' + // #6242: 5 fonts: HK 103₁₃₀, JP 45₁₈₁, KR 89₃₄₉, SC 77₄₆₁, TC 99₅₈₄. + '5a1y6l4h4s,' + // #6243: 5 fonts: HK 103₁₃₀, JP 46₁₈₂, KR 90₃₅₀, SC 61₄₄₅, TC 99₅₈₄. + '5a1z6l3q5i,' + // #6244: 5 fonts: HK 103₁₃₀, JP 47₁₈₃, KR 91₃₅₁, SC 77₄₆₁, TC 99₅₈₄. + '5a2a6l4f4s,' + // #6245: 5 fonts: HK 103₁₃₀, JP 48₁₈₄, KR 103₃₆₃, SC 81₄₆₅, TC 99₅₈₄. + '5a2b6w3x4o,' + // #6246: 5 fonts: HK 103₁₃₀, JP 49₁₈₅, KR 92₃₅₂, SC 65₄₄₉, TC 99₅₈₄. + '5a2c6k3s5e,' + // #6247: 5 fonts: HK 103₁₃₀, JP 49₁₈₅, KR 92₃₅₂, SC 78₄₆₂, TC 99₅₈₄. + '5a2c6k4f4r,' + // #6248: 5 fonts: HK 103₁₃₀, JP 61₁₉₇, KR 100₃₆₀, SC 79₄₆₃, TC 99₅₈₄. + '5a2o6g3y4q,' + // #6249: 5 fonts: HK 103₁₃₀, JP 62₁₉₈, KR 81₃₄₁, SC 43₄₂₇, TC 99₅₈₄. + '5a2p5m3h6a,' + // #6250: 5 fonts: HK 103₁₃₀, JP 62₁₉₈, KR 82₃₄₂, SC 78₄₆₂, TC 99₅₈₄. + '5a2p5n4p4r,' + // #6251: 5 fonts: HK 103₁₃₀, JP 62₁₉₈, KR 84₃₄₄, SC 94₄₇₈, TC 99₅₈₄. + '5a2p5p5d4b,' + // #6252: 4 fonts: HK 103₁₃₀, JP 63₁₉₉, SC 95₄₇₉, TC 99₅₈₄. + '5a2q10t4a,' + // #6253: 5 fonts: HK 103₁₃₀, JP 64₂₀₀, KR 101₃₆₁, SC 80₄₆₄, TC 99₅₈₄. + '5a2r6e3y4p,' + // #6254: 5 fonts: HK 103₁₃₀, JP 66₂₀₂, KR 70₃₃₀, SC 19₄₀₃, TC 99₅₈₄. + '5a2t4x2u6y,' + // #6255: 5 fonts: HK 103₁₃₀, JP 66₂₀₂, KR 85₃₄₅, SC 51₄₃₅, TC 99₅₈₄. + '5a2t5m3l5s,' + // #6256: 5 fonts: HK 103₁₃₀, JP 67₂₀₃, KR 85₃₄₅, SC 94₄₇₈, TC 99₅₈₄. + '5a2u5l5c4b,' + // #6257: 5 fonts: HK 103₁₃₀, JP 67₂₀₃, KR 93₃₅₃, SC 94₄₇₈, TC 99₅₈₄. + '5a2u5t4u4b,' + // #6258: 5 fonts: HK 103₁₃₀, JP 68₂₀₄, KR 103₃₆₃, SC 80₄₆₄, TC 99₅₈₄. + '5a2v6c3w4p,' + // #6259: 5 fonts: HK 103₁₃₀, JP 69₂₀₅, KR 92₃₅₂, SC 80₄₆₄, TC 99₅₈₄. + '5a2w5q4h4p,' + // #6260: 5 fonts: HK 103₁₃₀, JP 70₂₀₆, KR 81₃₄₁, SC 94₄₇₈, TC 99₅₈₄. + '5a2x5e5g4b,' + // #6261: 5 fonts: HK 103₁₃₀, JP 70₂₀₆, KR 91₃₅₁, SC 95₄₇₉, TC 99₅₈₄. + '5a2x5o4x4a,' + // #6262: 5 fonts: HK 103₁₃₀, JP 71₂₀₇, KR 75₃₃₅, SC 95₄₇₉, TC 99₅₈₄. + '5a2y4x5n4a,' + // #6263: 5 fonts: HK 103₁₃₀, JP 71₂₀₇, KR 81₃₄₁, SC 93₄₇₇, TC 99₅₈₄. + '5a2y5d5f4c,' + // #6264: 5 fonts: HK 103₁₃₀, JP 75₂₁₁, KR 68₃₂₈, SC 79₄₆₃, TC 99₅₈₄. + '5a3c4m5e4q,' + // #6265: 5 fonts: HK 103₁₃₀, JP 75₂₁₁, KR 82₃₄₂, SC 77₄₆₁, TC 99₅₈₄. + '5a3c5a4o4s,' + // #6266: 5 fonts: HK 103₁₃₀, JP 86₂₂₂, KR 89₃₄₉, SC 95₄₇₉, TC 99₅₈₄. + '5a3n4w4z4a,' + // #6267: 5 fonts: HK 103₁₃₀, JP 92₂₂₈, KR 101₃₆₁, SC 94₄₇₈, TC 99₅₈₄. + '5a3t5c4m4b,' + // #6268: 5 fonts: HK 103₁₃₀, JP 94₂₃₀, KR 68₃₂₈, SC 77₄₆₁, TC 99₅₈₄. + '5a3v3t5c4s,' + // #6269: 5 fonts: HK 103₁₃₀, JP 94₂₃₀, KR 76₃₃₆, SC 95₄₇₉, TC 99₅₈₄. + '5a3v4b5m4a,' + // #6270: 5 fonts: HK 103₁₃₀, JP 94₂₃₀, KR 108₃₆₈, SC 86₄₇₀, TC 99₅₈₄. + '5a3v5h3x4j,' + // #6271: 5 fonts: HK 103₁₃₀, JP 95₂₃₁, KR 101₃₆₁, SC 95₄₇₉, TC 99₅₈₄. + '5a3w4z4n4a,' + // #6272: 5 fonts: HK 103₁₃₀, JP 98₂₃₄, KR 88₃₄₈, SC 94₄₇₈, TC 99₅₈₄. + '5a3z4j4z4b,' + // #6273: 5 fonts: HK 103₁₃₀, JP 100₂₃₆, KR 78₃₃₈, SC 95₄₇₉, TC 99₅₈₄. + '5a4b3x5k4a,' + // #6274: 5 fonts: HK 103₁₃₀, JP 100₂₃₆, KR 90₃₅₀, SC 94₄₇₈, TC 99₅₈₄. + '5a4b4j4x4b,' + // #6275: 5 fonts: HK 103₁₃₀, JP 101₂₃₇, KR 66₃₂₆, SC 95₄₇₉, TC 99₅₈₄. + '5a4c3k5w4a,' + // #6276: 5 fonts: HK 103₁₃₀, JP 101₂₃₇, KR 84₃₄₄, SC 95₄₇₉, TC 99₅₈₄. + '5a4c4c5e4a,' + // #6277: 5 fonts: HK 103₁₃₀, JP 102₂₃₈, KR 86₃₄₆, SC 94₄₇₈, TC 99₅₈₄. + '5a4d4d5b4b,' + // #6278: 5 fonts: HK 103₁₃₀, JP 103₂₃₉, KR 70₃₃₀, SC 81₄₆₅, TC 99₅₈₄. + '5a4e3m5e4o,' + // #6279: 5 fonts: HK 103₁₃₀, JP 104₂₄₀, KR 75₃₃₅, SC 94₄₇₈, TC 99₅₈₄. + '5a4f3q5m4b,' + // #6280: 5 fonts: HK 103₁₃₀, JP 104₂₄₀, KR 79₃₃₉, SC 38₄₂₂, TC 99₅₈₄. + '5a4f3u3e6f,' + // #6281: 5 fonts: HK 103₁₃₀, JP 105₂₄₁, KR 68₃₂₈, SC 14₃₉₈, TC 99₅₈₄. + '5a4g3i2r7d,' + // #6282: 5 fonts: HK 103₁₃₀, JP 105₂₄₁, KR 100₃₆₀, SC 94₄₇₈, TC 99₅₈₄. + '5a4g4o4n4b,' + // #6283: 5 fonts: HK 103₁₃₀, JP 106₂₄₂, KR 69₃₂₉, SC 17₄₀₁, TC 99₅₈₄. + '5a4h3i2t7a,' + // #6284: 5 fonts: HK 103₁₃₀, JP 106₂₄₂, KR 69₃₂₉, SC 94₄₇₈, TC 99₅₈₄. + '5a4h3i5s4b,' + // #6285: 5 fonts: HK 103₁₃₀, JP 106₂₄₂, KR 70₃₃₀, SC 79₄₆₃, TC 99₅₈₄. + '5a4h3j5c4q,' + // #6286: 5 fonts: HK 103₁₃₀, JP 106₂₄₂, KR 74₃₃₄, SC 28₄₁₂, TC 99₅₈₄. + '5a4h3n2z6p,' + // #6287: 5 fonts: HK 103₁₃₀, JP 106₂₄₂, KR 76₃₃₆, SC 94₄₇₈, TC 99₅₈₄. + '5a4h3p5l4b,' + // #6288: 5 fonts: HK 103₁₃₀, JP 106₂₄₂, KR 101₃₆₁, SC 94₄₇₈, TC 99₅₈₄. + '5a4h4o4m4b,' + // #6289: 5 fonts: HK 103₁₃₀, JP 107₂₄₃, KR 83₃₄₃, SC 95₄₇₉, TC 99₅₈₄. + '5a4i3v5f4a,' + // #6290: 5 fonts: HK 103₁₃₀, JP 107₂₄₃, KR 86₃₄₆, SC 94₄₇₈, TC 99₅₈₄. + '5a4i3y5b4b,' + // #6291: 5 fonts: HK 103₁₃₀, JP 108₂₄₄, KR 69₃₂₉, SC 16₄₀₀, TC 99₅₈₄. + '5a4j3g2s7b,' + // #6292: 5 fonts: HK 103₁₃₀, JP 108₂₄₄, KR 69₃₂₉, SC 94₄₇₈, TC 99₅₈₄. + '5a4j3g5s4b,' + // #6293: 5 fonts: HK 103₁₃₀, JP 109₂₄₅, KR 103₃₆₃, SC 94₄₇₈, TC 99₅₈₄. + '5a4k4n4k4b,' + // #6294: 5 fonts: HK 103₁₃₀, JP 110₂₄₆, KR 66₃₂₆, SC 77₄₆₁, TC 99₅₈₄. + '5a4l3b5e4s,' + // #6295: 5 fonts: HK 103₁₃₀, JP 110₂₄₆, KR 68₃₂₈, SC 81₄₆₅, TC 99₅₈₄. + '5a4l3d5g4o,' + // #6296: 5 fonts: HK 103₁₃₀, JP 110₂₄₆, KR 78₃₃₈, SC 95₄₇₉, TC 99₅₈₄. + '5a4l3n5k4a,' + // #6297: 5 fonts: HK 103₁₃₀, JP 110₂₄₆, KR 90₃₅₀, SC 95₄₇₉, TC 99₅₈₄. + '5a4l3z4y4a,' + // #6298: 5 fonts: HK 103₁₃₀, JP 110₂₄₆, KR 93₃₅₃, SC 95₄₇₉, TC 99₅₈₄. + '5a4l4c4v4a,' + // #6299: 5 fonts: HK 103₁₃₀, JP 110₂₄₆, KR 100₃₆₀, SC 79₄₆₃, TC 99₅₈₄. + '5a4l4j3y4q,' + // #6300: 5 fonts: HK 103₁₃₀, JP 110₂₄₆, KR 100₃₆₀, SC 94₄₇₈, TC 99₅₈₄. + '5a4l4j4n4b,' + // #6301: 5 fonts: HK 103₁₃₀, JP 111₂₄₇, KR 66₃₂₆, SC 8₃₉₂, TC 99₅₈₄. + '5a4m3a2n7j,' + // #6302: 5 fonts: HK 103₁₃₀, JP 111₂₄₇, KR 68₃₂₈, SC 80₄₆₄, TC 99₅₈₄. + '5a4m3c5f4p,' + // #6303: 5 fonts: HK 103₁₃₀, JP 111₂₄₇, KR 74₃₃₄, SC 27₄₁₁, TC 99₅₈₄. + '5a4m3i2y6q,' + // #6304: 5 fonts: HK 103₁₃₀, JP 111₂₄₇, KR 87₃₄₇, SC 95₄₇₉, TC 99₅₈₄. + '5a4m3v5b4a,' + // #6305: 5 fonts: HK 103₁₃₀, JP 111₂₄₇, KR 88₃₄₈, SC 95₄₇₉, TC 99₅₈₄. + '5a4m3w5a4a,' + // #6306: 5 fonts: HK 103₁₃₀, JP 111₂₄₇, KR 91₃₅₁, SC 63₄₄₇, TC 99₅₈₄. + '5a4m3z3r5g,' + // #6307: 5 fonts: HK 103₁₃₀, JP 111₂₄₇, KR 91₃₅₁, SC 93₄₇₇, TC 99₅₈₄. + '5a4m3z4v4c,' + // #6308: 5 fonts: HK 103₁₃₀, JP 111₂₄₇, KR 102₃₆₂, SC 94₄₇₈, TC 99₅₈₄. + '5a4m4k4l4b,' + // #6309: 5 fonts: HK 103₁₃₀, JP 111₂₄₇, KR 105₃₆₅, SC 95₄₇₉, TC 99₅₈₄. + '5a4m4n4j4a,' + // #6310: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 66₃₂₆, SC 81₄₆₅, TC 99₅₈₄. + '5a4n2z5i4o,' + // #6311: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 69₃₂₉, SC 94₄₇₈, TC 99₅₈₄. + '5a4n3c5s4b,' + // #6312: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 70₃₃₀, SC 19₄₀₃, TC 99₅₈₄. + '5a4n3d2u6y,' + // #6313: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 75₃₃₅, SC 95₄₇₉, TC 99₅₈₄. + '5a4n3i5n4a,' + // #6314: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 83₃₄₃, SC 95₄₇₉, TC 99₅₈₄. + '5a4n3q5f4a,' + // #6315: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 87₃₄₇, SC 94₄₇₈, TC 99₅₈₄. + '5a4n3u5a4b,' + // #6316: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 92₃₅₂, SC 95₄₇₉, TC 99₅₈₄. + '5a4n3z4w4a,' + // #6317: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 93₃₅₃, SC 95₄₇₉, TC 99₅₈₄. + '5a4n4a4v4a,' + // #6318: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 100₃₆₀, SC 94₄₇₈, TC 99₅₈₄. + '5a4n4h4n4b,' + // #6319: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 100₃₆₀, SC 95₄₇₉, TC 99₅₈₄. + '5a4n4h4o4a,' + // #6320: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 101₃₆₁, SC 94₄₇₈, TC 99₅₈₄. + '5a4n4i4m4b,' + // #6321: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 101₃₆₁, SC 95₄₇₉, TC 99₅₈₄. + '5a4n4i4n4a,' + // #6322: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 102₃₆₂, SC 94₄₇₈, TC 99₅₈₄. + '5a4n4j4l4b,' + // #6323: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 103₃₆₃, SC 94₄₇₈, TC 99₅₈₄. + '5a4n4k4k4b,' + // #6324: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 104₃₆₄, SC 95₄₇₉, TC 99₅₈₄. + '5a4n4l4k4a,' + // #6325: 5 fonts: HK 103₁₃₀, JP 112₂₄₈, KR 109₃₆₉, SC 94₄₇₈, TC 99₅₈₄. + '5a4n4q4e4b,' + // #6326: 5 fonts: HK 103₁₃₀, JP 113₂₄₉, KR 68₃₂₈, SC 80₄₆₄, TC 99₅₈₄. + '5a4o3a5f4p,' + // #6327: 5 fonts: HK 103₁₃₀, JP 113₂₄₉, KR 88₃₄₈, SC 94₄₇₈, TC 99₅₈₄. + '5a4o3u4z4b,' + // #6328: 5 fonts: HK 103₁₃₀, JP 113₂₄₉, KR 90₃₅₀, SC 94₄₇₈, TC 99₅₈₄. + '5a4o3w4x4b,' + // #6329: 5 fonts: HK 103₁₃₀, JP 113₂₄₉, KR 100₃₆₀, SC 94₄₇₈, TC 99₅₈₄. + '5a4o4g4n4b,' + // #6330: 5 fonts: HK 103₁₃₀, JP 113₂₄₉, KR 101₃₆₁, SC 94₄₇₈, TC 99₅₈₄. + '5a4o4h4m4b,' + // #6331: 5 fonts: HK 103₁₃₀, JP 113₂₄₉, KR 101₃₆₁, SC 95₄₇₉, TC 99₅₈₄. + '5a4o4h4n4a,' + // #6332: 5 fonts: HK 103₁₃₀, JP 113₂₄₉, KR 102₃₆₂, SC 94₄₇₈, TC 99₅₈₄. + '5a4o4i4l4b,' + // #6333: 5 fonts: HK 103₁₃₀, JP 113₂₄₉, KR 102₃₆₂, SC 95₄₇₉, TC 99₅₈₄. + '5a4o4i4m4a,' + // #6334: 5 fonts: HK 103₁₃₀, JP 113₂₄₉, KR 103₃₆₃, SC 15₃₉₉, TC 99₅₈₄. + '5a4o4j1j7c,' + // #6335: 5 fonts: HK 103₁₃₀, JP 113₂₄₉, KR 103₃₆₃, SC 95₄₇₉, TC 99₅₈₄. + '5a4o4j4l4a,' + // #6336: 5 fonts: HK 103₁₃₀, JP 113₂₄₉, KR 105₃₆₅, SC 95₄₇₉, TC 99₅₈₄. + '5a4o4l4j4a,' + // #6337: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 74₃₃₄, SC 78₄₆₂, TC 99₅₈₄. + '5a4p3f4x4r,' + // #6338: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 83₃₄₃, SC 94₄₇₈, TC 99₅₈₄. + '5a4p3o5e4b,' + // #6339: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 90₃₅₀, SC 77₄₆₁, TC 99₅₈₄. + '5a4p3v4g4s,' + // #6340: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 90₃₅₀, SC 95₄₇₉, TC 99₅₈₄. + '5a4p3v4y4a,' + // #6341: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 93₃₅₃, SC 94₄₇₈, TC 99₅₈₄. + '5a4p3y4u4b,' + // #6342: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 104₃₆₄, SC 81₄₆₅, TC 99₅₈₄. + '5a4p4j3w4o,' + // #6343: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 104₃₆₄, SC 94₄₇₈, TC 99₅₈₄. + '5a4p4j4j4b,' + // #6344: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 104₃₆₄, SC 95₄₇₉, TC 99₅₈₄. + '5a4p4j4k4a,' + // #6345: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 105₃₆₅, SC 95₄₇₉, TC 99₅₈₄. + '5a4p4k4j4a,' + // #6346: 5 fonts: HK 103₁₃₀, JP 114₂₅₀, KR 107₃₆₇, SC 94₄₇₈, TC 99₅₈₄. + '5a4p4m4g4b,' + // #6347: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 68₃₂₈, SC 14₃₉₈, TC 99₅₈₄. + '5a4q2y2r7d,' + // #6348: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 70₃₃₀, SC 18₄₀₂, TC 99₅₈₄. + '5a4q3a2t6z,' + // #6349: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 78₃₃₈, SC 80₄₆₄, TC 99₅₈₄. + '5a4q3i4v4p,' + // #6350: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 81₃₄₁, SC 95₄₇₉, TC 99₅₈₄. + '5a4q3l5h4a,' + // #6351: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 82₃₄₂, SC 79₄₆₃, TC 99₅₈₄. + '5a4q3m4q4q,' + // #6352: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 83₃₄₃, SC 95₄₇₉, TC 99₅₈₄. + '5a4q3n5f4a,' + // #6353: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 90₃₅₀, SC 95₄₇₉, TC 99₅₈₄. + '5a4q3u4y4a,' + // #6354: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 100₃₆₀, SC 77₄₆₁, TC 99₅₈₄. + '5a4q4e3w4s,' + // #6355: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 100₃₆₀, SC 95₄₇₉, TC 99₅₈₄. + '5a4q4e4o4a,' + // #6356: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 102₃₆₂, SC 80₄₆₄, TC 99₅₈₄. + '5a4q4g3x4p,' + // #6357: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 103₃₆₃, SC 95₄₇₉, TC 99₅₈₄. + '5a4q4h4l4a,' + // #6358: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 104₃₆₄, SC 79₄₆₃, TC 99₅₈₄. + '5a4q4i3u4q,' + // #6359: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 104₃₆₄, SC 94₄₇₈, TC 99₅₈₄. + '5a4q4i4j4b,' + // #6360: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 105₃₆₅, SC 95₄₇₉, TC 99₅₈₄. + '5a4q4j4j4a,' + // #6361: 5 fonts: HK 103₁₃₀, JP 115₂₅₁, KR 110₃₇₀, SC 95₄₇₉, TC 99₅₈₄. + '5a4q4o4e4a,' + // #6362: 4 fonts: HK 103₁₃₀, JP 115₂₅₁, SC 94₄₇₈, TC 99₅₈₄. + '5a4q8s4b,' + // #6363: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 68₃₂₈, SC 77₄₆₁, TC 99₅₈₄. + '5a4r2x5c4s,' + // #6364: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 70₃₃₀, SC 95₄₇₉, TC 99₅₈₄. + '5a4r2z5s4a,' + // #6365: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 71₃₃₁, SC 95₄₇₉, TC 99₅₈₄. + '5a4r3a5r4a,' + // #6366: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 73₃₃₃, SC 95₄₇₉, TC 99₅₈₄. + '5a4r3c5p4a,' + // #6367: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 82₃₄₂, SC 78₄₆₂, TC 99₅₈₄. + '5a4r3l4p4r,' + // #6368: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 84₃₄₄, SC 94₄₇₈, TC 99₅₈₄. + '5a4r3n5d4b,' + // #6369: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 92₃₅₂, SC 94₄₇₈, TC 99₅₈₄. + '5a4r3v4v4b,' + // #6370: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 101₃₆₁, SC 94₄₇₈, TC 99₅₈₄. + '5a4r4e4m4b,' + // #6371: 5 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 102₃₆₂, SC 81₄₆₅, TC 99₅₈₄. + '5a4r4f3y4o,' + // #6372: 8 fonts: HK 103₁₃₀, JP 116₂₅₂, KR 102₃₆₂, SC 94₄₇₈, TC 99₅₈₄, Mongolian₆₆₂, New Tai Lue₆₆₈, Yi₇₂₁. + '5a4r4f4l4b2zf2a,' + // #6373: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 89₃₄₉, SC 58₄₄₂, TC 99₅₈₄. + '5a4s3r3o5l,' + // #6374: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 90₃₅₀, SC 81₄₆₅, TC 99₅₈₄. + '5a4s3s4k4o,' + // #6375: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 100₃₆₀, SC 78₄₆₂, TC 99₅₈₄. + '5a4s4c3x4r,' + // #6376: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 100₃₆₀, SC 94₄₇₈, TC 99₅₈₄. + '5a4s4c4n4b,' + // #6377: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 101₃₆₁, SC 82₄₆₆, TC 99₅₈₄. + '5a4s4d4a4n,' + // #6378: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 101₃₆₁, SC 94₄₇₈, TC 99₅₈₄. + '5a4s4d4m4b,' + // #6379: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 102₃₆₂, SC 18₄₀₂, TC 99₅₈₄. + '5a4s4e1n6z,' + // #6380: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 102₃₆₂, SC 80₄₆₄, TC 99₅₈₄. + '5a4s4e3x4p,' + // #6381: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 102₃₆₂, SC 94₄₇₈, TC 99₅₈₄. + '5a4s4e4l4b,' + // #6382: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 105₃₆₅, SC 95₄₇₉, TC 99₅₈₄. + '5a4s4h4j4a,' + // #6383: 7 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 105₃₆₅, SC 95₄₇₉, TC 99₅₈₄, Mongolian₆₆₂, New Tai Lue₆₆₈. + '5a4s4h4j4a2zf,' + // #6384: 8 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 105₃₆₅, SC 95₄₇₉, TC 99₅₈₄, Mongolian₆₆₂, New Tai Lue₆₆₈, Yi₇₂₁. + '5a4s4h4j4a2zf2a,' + // #6385: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 106₃₆₆, SC 81₄₆₅, TC 99₅₈₄. + '5a4s4i3u4o,' + // #6386: 5 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 106₃₆₆, SC 95₄₇₉, TC 99₅₈₄. + '5a4s4i4i4a,' + // #6387: 8 fonts: HK 103₁₃₀, JP 117₂₅₃, KR 109₃₆₉, SC 87₄₇₁, TC 99₅₈₄, Mongolian₆₆₂, Phags Pa₆₈₇, Yi₇₂₁. + '5a4s4l3x4i2zy1h,' + // #6388: 4 fonts: HK 103₁₃₀, JP 117₂₅₃, SC 95₄₇₉, TC 99₅₈₄. + '5a4s8r4a,' + // #6389: 15 fonts: HK 104₁₃₁, HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 75₄₅₉, SC 98₄₈₂, SC 99₄₈₃, TC 100₅₈₅, TC 102₅₈₇, TC 103₅₈₈, Noto Sans₅₉₁. + '5bba2i2ja4sa2ywa3xbac,' + // #6390: 15 fonts: HK 104₁₃₁, HK 106₁₃₃, HK 107₁₃₄, JP 118₂₅₄, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 75₄₅₉, SC 98₄₈₂, SC 99₄₈₃, TC 100₅₈₅, TC 102₅₈₇, TC 103₅₈₈, Noto Sans₅₉₁. + '5bba4pca4sa2ywa3xbac,' + // #6391: 12 fonts: HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 76₄₆₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Sans₅₉₁, Tamil₇₁₀. + '5bd4oe3vx2yx3wdb4o,' + // #6392: 12 fonts: HK 104₁₃₁, HK 108₁₃₅, JP 118₂₅₄, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 96₄₈₀, SC 100₄₈₄, TC 100₅₈₅, TC 104₅₈₉, Noto Sans₅₉₁, Tamil₇₁₀. + '5bd4oe3vx3sd3wdb4o,' + // #6393: 5 fonts: HK 104₁₃₁, JP 10₁₄₆, KR 67₃₂₇, SC 79₄₆₃, TC 100₅₈₅. + '5bo6y5f4r,' + // #6394: 4 fonts: HK 104₁₃₁, JP 54₁₉₀, SC 67₄₅₁, TC 100₅₈₅. + '5b2g10a5d,' + // #6395: 4 fonts: HK 104₁₃₁, JP 54₁₉₀, SC 96₄₈₀, TC 100₅₈₅. + '5b2g11d4a,' + // #6396: 5 fonts: HK 104₁₃₁, JP 60₁₉₆, KR 100₃₆₀, SC 81₄₆₅, TC 100₅₈₅. + '5b2m6h4a4p,' + // #6397: 5 fonts: HK 104₁₃₁, JP 66₂₀₂, KR 83₃₄₃, SC 81₄₆₅, TC 100₅₈₅. + '5b2s5k4r4p,' + // #6398: 5 fonts: HK 104₁₃₁, JP 68₂₀₄, KR 84₃₄₄, SC 81₄₆₅, TC 100₅₈₅. + '5b2u5j4q4p,' + // #6399: 5 fonts: HK 104₁₃₁, JP 69₂₀₅, KR 66₃₂₆, SC 8₃₉₂, TC 100₅₈₅. + '5b2v4q2n7k,' + // #6400: 5 fonts: HK 104₁₃₁, JP 80₂₁₆, KR 105₃₆₅, SC 83₄₆₇, TC 100₅₈₅. + '5b3g5s3x4n,' + // #6401: 5 fonts: HK 104₁₃₁, JP 88₂₂₄, KR 74₃₃₄, SC 80₄₆₄, TC 100₅₈₅. + '5b3o4f4z4q,' + // #6402: 5 fonts: HK 104₁₃₁, JP 97₂₃₃, KR 100₃₆₀, SC 95₄₇₉, TC 100₅₈₅. + '5b3x4w4o4b,' + // #6403: 5 fonts: HK 104₁₃₁, JP 98₂₃₄, KR 91₃₅₁, SC 95₄₇₉, TC 100₅₈₅. + '5b3y4m4x4b,' + // #6404: 5 fonts: HK 104₁₃₁, JP 101₂₃₇, KR 103₃₆₃, SC 95₄₇₉, TC 100₅₈₅. + '5b4b4v4l4b,' + // #6405: 4 fonts: HK 104₁₃₁, JP 104₂₄₀, SC 80₄₆₄, TC 100₅₈₅. + '5b4e8p4q,' + // #6406: 5 fonts: HK 104₁₃₁, JP 110₂₄₆, KR 104₃₆₄, SC 94₄₇₈, TC 100₅₈₅. + '5b4k4n4j4c,' + // #6407: 5 fonts: HK 104₁₃₁, JP 114₂₅₀, KR 92₃₅₂, SC 80₄₆₄, TC 100₅₈₅. + '5b4o3x4h4q,' + // #6408: 5 fonts: HK 104₁₃₁, JP 114₂₅₀, KR 101₃₆₁, SC 95₄₇₉, TC 100₅₈₅. + '5b4o4g4n4b,' + // #6409: 5 fonts: HK 104₁₃₁, JP 114₂₅₀, KR 104₃₆₄, SC 95₄₇₉, TC 100₅₈₅. + '5b4o4j4k4b,' + // #6410: 5 fonts: HK 104₁₃₁, JP 114₂₅₀, KR 105₃₆₅, SC 95₄₇₉, TC 100₅₈₅. + '5b4o4k4j4b,' + // #6411: 5 fonts: HK 104₁₃₁, JP 115₂₅₁, KR 66₃₂₆, SC 79₄₆₃, TC 100₅₈₅. + '5b4p2w5g4r,' + // #6412: 5 fonts: HK 104₁₃₁, JP 115₂₅₁, KR 104₃₆₄, SC 95₄₇₉, TC 100₅₈₅. + '5b4p4i4k4b,' + // #6413: 5 fonts: HK 104₁₃₁, JP 116₂₅₂, KR 100₃₆₀, SC 82₄₆₆, TC 100₅₈₅. + '5b4q4d4b4o,' + // #6414: 5 fonts: HK 104₁₃₁, JP 116₂₅₂, KR 103₃₆₃, SC 95₄₇₉, TC 100₅₈₅. + '5b4q4g4l4b,' + // #6415: 7 fonts: HK 104₁₃₁, JP 116₂₅₂, KR 104₃₆₄, SC 96₄₈₀, TC 100₅₈₅, New Tai Lue₆₆₈, Yi₇₂₁. + '5b4q4h4l4a3e2a,' + // #6416: 5 fonts: HK 104₁₃₁, JP 116₂₅₂, KR 105₃₆₅, SC 95₄₇₉, TC 100₅₈₅. + '5b4q4i4j4b,' + // #6417: 5 fonts: HK 104₁₃₁, JP 116₂₅₂, KR 106₃₆₆, SC 95₄₇₉, TC 100₅₈₅. + '5b4q4j4i4b,' + // #6418: 5 fonts: HK 104₁₃₁, JP 116₂₅₂, KR 112₃₇₂, SC 95₄₇₉, TC 100₅₈₅. + '5b4q4p4c4b,' + // #6419: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 101₃₆₁, SC 95₄₇₉, TC 100₅₈₅. + '5b4r4d4n4b,' + // #6420: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 102₃₆₂, SC 80₄₆₄, TC 100₅₈₅. + '5b4r4e3x4q,' + // #6421: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 103₃₆₃, SC 82₄₆₆, TC 100₅₈₅. + '5b4r4f3y4o,' + // #6422: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 106₃₆₆, SC 95₄₇₉, TC 100₅₈₅. + '5b4r4i4i4b,' + // #6423: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 106₃₆₆, SC 96₄₈₀, TC 100₅₈₅. + '5b4r4i4j4a,' + // #6424: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 108₃₆₈, SC 95₄₇₉, TC 100₅₈₅. + '5b4r4k4g4b,' + // #6425: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 108₃₆₈, SC 96₄₈₀, TC 100₅₈₅. + '5b4r4k4h4a,' + // #6426: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 109₃₆₉, SC 95₄₇₉, TC 100₅₈₅. + '5b4r4l4f4b,' + // #6427: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 111₃₇₁, SC 95₄₇₉, TC 100₅₈₅. + '5b4r4n4d4b,' + // #6428: 5 fonts: HK 104₁₃₁, JP 117₂₅₃, KR 112₃₇₂, SC 96₄₈₀, TC 100₅₈₅. + '5b4r4o4d4a,' + // #6429: 5 fonts: HK 104₁₃₁, JP 118₂₅₄, KR 79₃₃₉, SC 96₄₈₀, TC 100₅₈₅. + '5b4s3g5k4a,' + // #6430: 5 fonts: HK 104₁₃₁, JP 118₂₅₄, KR 107₃₆₇, SC 96₄₈₀, TC 100₅₈₅. + '5b4s4i4i4a,' + // #6431: 5 fonts: HK 104₁₃₁, JP 118₂₅₄, KR 112₃₇₂, SC 96₄₈₀, TC 100₅₈₅. + '5b4s4n4d4a,' + // #6432: 8 fonts: HK 104₁₃₁, JP 119₂₅₅, KR 103₃₆₃, SC 96₄₈₀, TC 100₅₈₅, Mongolian₆₆₂, New Tai Lue₆₆₈, Yi₇₂₁. + '5b4t4d4m4a2yf2a,' + // #6433: 10 fonts: HK 104₁₃₁, JP 119₂₅₅, KR 105₃₆₅, SC 96₄₈₀, TC 100₅₈₅, Mongolian₆₆₂, New Tai Lue₆₆₈, Phags Pa₆₈₇, Tai Le₇₀₆, Yi₇₂₁. + '5b4t4f4k4a2yfsso,' + // #6434: 10 fonts: HK 104₁₃₁, JP 119₂₅₅, KR 106₃₆₆, SC 96₄₈₀, TC 100₅₈₅, Mongolian₆₆₂, New Tai Lue₆₆₈, Phags Pa₆₈₇, Tai Le₇₀₆, Yi₇₂₁. + '5b4t4g4j4a2yfsso,' + // #6435: 13 fonts: HK 106₁₃₃, HK 107₁₃₄, JP 59₁₉₅, JP 121₂₅₇, JP 122₂₅₈, KR 121₃₈₁, KR 122₃₈₂, SC 76₄₆₀, SC 98₄₈₂, SC 99₄₈₃, TC 102₅₈₇, TC 103₅₈₈, Noto Sans₅₉₁. + '5da2i2ja4sa2zva3zac,' + // #6436: 18 fonts: HK 108₁₃₅, JP 59₁₉₅, JP 123₂₅₉, KR 99₃₅₉, KR 123₃₈₃, SC 100₄₈₄, TC 104₅₈₉, Noto Sans₅₉₁, Bengali₆₀₁, Devanagari₆₁₅, Gurmukhi₆₂₅, Kannada₆₃₅, Khmer₆₃₈, Malayalam₆₅₀, Oriya₆₈₁, Sinhala₆₉₆, Tamil₇₁₀, Telugu₇₁₂. + '5f2h2l3vx3w4abjnjjcl1eonb,' + // #6437: 3 fonts: JP 2₁₃₈, KR 0₂₆₀, SC 1₃₈₅. + '5i4r4u,' + // #6438: 2 fonts: JP 2₁₃₈, SC 1₃₈₅. + '5i9m,' + // #6439: 2 fonts: JP 3₁₃₉, SC 1₃₈₅. + '5j9l,' + // #6440: 3 fonts: JP 3₁₃₉, SC 2₃₈₆, Noto Sans₅₉₁. + '5j9m7w,' + // #6441: 3 fonts: JP 4₁₄₀, KR 1₂₆₁, SC 2₃₈₆. + '5k4q4u,' + // #6442: 2 fonts: JP 4₁₄₀, KR 2₂₆₂. + '5k4r,' + // #6443: 2 fonts: JP 4₁₄₀, KR 107₃₆₇. + '5k8s,' + // #6444: 3 fonts: JP 5₁₄₁, KR 2₂₆₂, SC 2₃₈₆. + '5l4q4t,' + // #6445: 2 fonts: JP 5₁₄₁, KR 100₃₆₀. + '5l8k,' + // #6446: 2 fonts: JP 5₁₄₁, SC 3₃₈₇. + '5l9l,' + // #6447: 2 fonts: JP 7₁₄₃, KR 65₃₂₅. + '5n6z,' + // #6448: 2 fonts: JP 8₁₄₄, KR 65₃₂₅. + '5o6y,' + // #6449: 3 fonts: JP 8₁₄₄, KR 66₃₂₆, SC 7₃₉₁. + '5o6z2m,' + // #6450: 2 fonts: JP 9₁₄₅, KR 66₃₂₆. + '5p6y,' + // #6451: 3 fonts: JP 9₁₄₅, KR 66₃₂₆, SC 8₃₉₂. + '5p6y2n,' + // #6452: 2 fonts: JP 10₁₄₆, SC 10₃₉₄. + '5q9n,' + // #6453: 2 fonts: JP 10₁₄₆, SC 89₄₇₃. + '5q12o,' + // #6454: 2 fonts: JP 12₁₄₈, KR 67₃₂₇. + '5s6w,' + // #6455: 2 fonts: JP 12₁₄₈, KR 68₃₂₈. + '5s6x,' + // #6456: 2 fonts: JP 13₁₄₉, SC 86₄₇₀. + '5t12i,' + // #6457: 2 fonts: JP 14₁₅₀, SC 78₄₆₂. + '5u11z,' + // #6458: 2 fonts: JP 15₁₅₁, SC 16₄₀₀. + '5v9o,' + // #6459: 2 fonts: JP 15₁₅₁, SC 17₄₀₁. + '5v9p,' + // #6460: 2 fonts: JP 15₁₅₁, SC 18₄₀₂. + '5v9q,' + // #6461: 2 fonts: JP 18₁₅₄, SC 79₄₆₃. + '5y11w,' + // #6462: 2 fonts: JP 18₁₅₄, SC 90₄₇₄. + '5y12h,' + // #6463: 2 fonts: JP 19₁₅₅, KR 72₃₃₂. + '5z6u,' + // #6464: 2 fonts: JP 19₁₅₅, SC 22₄₀₆. + '5z9q,' + // #6465: 2 fonts: JP 20₁₅₆, SC 82₄₆₆. + '6a11x,' + // #6466: 2 fonts: JP 20₁₅₆, SC 84₄₆₈. + '6a11z,' + // #6467: 2 fonts: JP 20₁₅₆, SC 88₄₇₂. + '6a12d,' + // #6468: 2 fonts: JP 21₁₅₇, SC 23₄₀₇. + '6b9p,' + // #6469: 2 fonts: JP 22₁₅₈, SC 89₄₇₃. + '6c12c,' + // #6470: 3 fonts: JP 23₁₅₉, KR 74₃₃₄, SC 27₄₁₁. + '6d6s2y,' + // #6471: 2 fonts: JP 24₁₆₀, SC 89₄₇₃. + '6e12a,' + // #6472: 2 fonts: JP 25₁₆₁, KR 75₃₃₅. + '6f6r,' + // #6473: 2 fonts: JP 25₁₆₁, SC 79₄₆₃. + '6f11p,' + // #6474: 2 fonts: JP 25₁₆₁, SC 86₄₇₀. + '6f11w,' + // #6475: 2 fonts: JP 26₁₆₂, KR 76₃₃₆. + '6g6r,' + // #6476: 2 fonts: JP 26₁₆₂, SC 77₄₆₁. + '6g11m,' + // #6477: 3 fonts: JP 27₁₆₃, KR 76₃₃₆, SC 32₄₁₆. + '6h6q3b,' + // #6478: 3 fonts: JP 27₁₆₃, KR 76₃₃₆, SC 33₄₁₇. + '6h6q3c,' + // #6479: 2 fonts: JP 27₁₆₃, SC 79₄₆₃. + '6h11n,' + // #6480: 3 fonts: JP 28₁₆₄, KR 77₃₃₇, SC 35₄₁₉. + '6i6q3d,' + // #6481: 2 fonts: JP 28₁₆₄, SC 78₄₆₂. + '6i11l,' + // #6482: 3 fonts: JP 29₁₆₅, KR 77₃₃₇, SC 36₄₂₀. + '6j6p3e,' + // #6483: 3 fonts: JP 30₁₆₆, KR 78₃₃₈, SC 38₄₂₂. + '6k6p3f,' + // #6484: 2 fonts: JP 30₁₆₆, SC 85₄₆₉. + '6k11q,' + // #6485: 3 fonts: JP 32₁₆₈, KR 80₃₄₀, SC 41₄₂₅. + '6m6p3g,' + // #6486: 3 fonts: JP 32₁₆₈, KR 80₃₄₀, SC 83₄₆₇. + '6m6p4w,' + // #6487: 3 fonts: JP 33₁₆₉, KR 81₃₄₁, SC 43₄₂₇. + '6n6p3h,' + // #6488: 2 fonts: JP 33₁₆₉, SC 80₄₆₄. + '6n11i,' + // #6489: 2 fonts: JP 34₁₇₀, KR 82₃₄₂. + '6o6p,' + // #6490: 2 fonts: JP 35₁₇₁, KR 82₃₄₂. + '6p6o,' + // #6491: 2 fonts: JP 35₁₇₁, SC 90₄₇₄. + '6p11q,' + // #6492: 2 fonts: JP 36₁₇₂, KR 83₃₄₃. + '6q6o,' + // #6493: 3 fonts: JP 36₁₇₂, KR 83₃₄₃, SC 47₄₃₁. + '6q6o3j,' + // #6494: 2 fonts: JP 36₁₇₂, SC 79₄₆₃. + '6q11e,' + // #6495: 2 fonts: JP 36₁₇₂, SC 95₄₇₉. + '6q11u,' + // #6496: 3 fonts: JP 37₁₇₃, KR 83₃₄₃, SC 47₄₃₁. + '6r6n3j,' + // #6497: 3 fonts: JP 37₁₇₃, KR 84₃₄₄, SC 48₄₃₂. + '6r6o3j,' + // #6498: 3 fonts: JP 38₁₇₄, KR 84₃₄₄, SC 49₄₃₃. + '6s6n3k,' + // #6499: 2 fonts: JP 39₁₇₅, KR 85₃₄₅. + '6t6n,' + // #6500: 3 fonts: JP 39₁₇₅, KR 85₃₄₅, SC 85₄₆₉. + '6t6n4t,' + // #6501: 2 fonts: JP 39₁₇₅, KR 86₃₄₆. + '6t6o,' + // #6502: 2 fonts: JP 39₁₇₅, SC 50₄₃₄. + '6t9y,' + // #6503: 2 fonts: JP 40₁₇₆, SC 51₄₃₅. + '6u9y,' + // #6504: 2 fonts: JP 41₁₇₇, SC 81₄₆₅. + '6v11b,' + // #6505: 2 fonts: JP 41₁₇₇, SC 85₄₆₉. + '6v11f,' + // #6506: 2 fonts: JP 41₁₇₇, SC 88₄₇₂. + '6v11i,' + // #6507: 2 fonts: JP 41₁₇₇, SC 95₄₇₉. + '6v11p,' + // #6508: 3 fonts: JP 42₁₇₈, KR 88₃₄₈, SC 55₄₃₉. + '6w6n3m,' + // #6509: 2 fonts: JP 42₁₇₈, SC 56₄₄₀. + '6w10b,' + // #6510: 2 fonts: JP 42₁₇₈, SC 81₄₆₅. + '6w11a,' + // #6511: 2 fonts: JP 43₁₇₉, SC 78₄₆₂. + '6x10w,' + // #6512: 2 fonts: JP 44₁₈₀, SC 89₄₇₃. + '6y11g,' + // #6513: 3 fonts: JP 45₁₈₁, KR 89₃₄₉, SC 59₄₄₃. + '6z6l3p,' + // #6514: 2 fonts: JP 45₁₈₁, SC 79₄₆₃. + '6z10v,' + // #6515: 2 fonts: JP 46₁₈₂, SC 79₄₆₃. + '7a10u,' + // #6516: 3 fonts: JP 47₁₈₃, KR 91₃₅₁, SC 63₄₄₇. + '7b6l3r,' + // #6517: 2 fonts: JP 47₁₈₃, SC 83₄₆₇. + '7b10x,' + // #6518: 2 fonts: JP 47₁₈₃, SC 86₄₇₀. + '7b11a,' + // #6519: 2 fonts: JP 48₁₈₄, KR 91₃₅₁. + '7c6k,' + // #6520: 3 fonts: JP 48₁₈₄, KR 92₃₅₂, SC 65₄₄₉. + '7c6l3s,' + // #6521: 2 fonts: JP 48₁₈₄, SC 65₄₄₉. + '7c10e,' + // #6522: 2 fonts: JP 48₁₈₄, SC 90₄₇₄. + '7c11d,' + // #6523: 2 fonts: JP 48₁₈₄, SC 92₄₇₆. + '7c11f,' + // #6524: 2 fonts: JP 48₁₈₄, SC 95₄₇₉. + '7c11i,' + // #6525: 3 fonts: JP 49₁₈₅, KR 92₃₅₂, SC 65₄₄₉. + '7d6k3s,' + // #6526: 2 fonts: JP 49₁₈₅, SC 89₄₇₃. + '7d11b,' + // #6527: 3 fonts: JP 51₁₈₇, KR 93₃₅₃, SC 67₄₅₁. + '7f6j3t,' + // #6528: 2 fonts: JP 53₁₈₉, KR 109₃₆₉. + '7h6x,' + // #6529: 2 fonts: JP 53₁₈₉, SC 67₄₅₁. + '7h10b,' + // #6530: 3 fonts: JP 54₁₉₀, KR 94₃₅₄, SC 67₄₅₁. + '7i6h3s,' + // #6531: 2 fonts: JP 54₁₉₀, SC 68₄₅₂. + '7i10b,' + // #6532: 3 fonts: JP 55₁₉₁, KR 95₃₅₅, SC 68₄₅₂. + '7j6h3s,' + // #6533: 3 fonts: JP 55₁₉₁, Phags Pa₆₈₇, Yi₇₂₁. + '7j19b1h,' + // #6534: 4 fonts: JP 56₁₉₂, KR 96₃₅₆, SC 69₄₅₃, Symbols₇₀₂. + '7k6h3s9o,' + // #6535: 2 fonts: JP 56₁₉₂, SC 68₄₅₂. + '7k9z,' + // #6536: 2 fonts: JP 56₁₉₂, Noto Sans₅₉₁. + '7k15i,' + // #6537: 2 fonts: JP 56₁₉₂, Math₆₅₅. + '7k17u,' + // #6538: 2 fonts: JP 57₁₉₃, KR 96₃₅₆. + '7l6g,' + // #6539: 2 fonts: JP 57₁₉₃, Symbols₇₀₂. + '7l19o,' + // #6540: 4 fonts: JP 58₁₉₄, KR 97₃₅₇, SC 71₄₅₅, Symbols₇₀₂. + '7m6g3t9m,' + // #6541: 4 fonts: JP 58₁₉₄, KR 98₃₅₈, SC 72₄₅₆, Math₆₅₅. + '7m6h3t7q,' + // #6542: 3 fonts: JP 58₁₉₄, SC 71₄₅₅, Symbols₇₀₂. + '7m10a9m,' + // #6543: 3 fonts: JP 58₁₉₄, SC 72₄₅₆, Math₆₅₅. + '7m10b7q,' + // #6544: 3 fonts: JP 58₁₉₄, SC 72₄₅₆, Symbols₇₀₂. + '7m10b9l,' + // #6545: 2 fonts: JP 59₁₉₅, Symbols₇₀₂. + '7n19m,' + // #6546: 1 font: JP 60₁₉₆. + '7o,' + // #6547: 3 fonts: JP 60₁₉₆, KR 91₃₅₁, SC 81₄₆₅. + '7o5y4j,' + // #6548: 2 fonts: JP 60₁₉₆, KR 95₃₅₅. + '7o6c,' + // #6549: 1 font: JP 61₁₉₇. + '7p,' + // #6550: 2 fonts: JP 61₁₉₇, SC 60₄₄₄. + '7p9m,' + // #6551: 1 font: JP 62₁₉₈. + '7q,' + // #6552: 1 font: JP 63₁₉₉. + '7r,' + // #6553: 2 fonts: JP 63₁₉₉, SC 79₄₆₃. + '7r10d,' + // #6554: 2 fonts: JP 63₁₉₉, SC 85₄₆₉. + '7r10j,' + // #6555: 2 fonts: JP 64₂₀₀, SC 4₃₈₈. + '7s7f,' + // #6556: 2 fonts: JP 64₂₀₀, SC 7₃₉₁. + '7s7i,' + // #6557: 2 fonts: JP 64₂₀₀, SC 30₄₁₄. + '7s8f,' + // #6558: 2 fonts: JP 64₂₀₀, SC 41₄₂₅. + '7s8q,' + // #6559: 2 fonts: JP 64₂₀₀, SC 79₄₆₃. + '7s10c,' + // #6560: 2 fonts: JP 65₂₀₁, SC 29₄₁₃. + '7t8d,' + // #6561: 2 fonts: JP 65₂₀₁, SC 38₄₂₂. + '7t8m,' + // #6562: 2 fonts: JP 65₂₀₁, SC 58₄₄₂. + '7t9g,' + // #6563: 2 fonts: JP 65₂₀₁, SC 84₄₆₈. + '7t10g,' + // #6564: 2 fonts: JP 66₂₀₂, SC 6₃₉₀. + '7u7f,' + // #6565: 2 fonts: JP 66₂₀₂, SC 14₃₉₈. + '7u7n,' + // #6566: 2 fonts: JP 67₂₀₃, SC 2₃₈₆. + '7v7a,' + // #6567: 2 fonts: JP 67₂₀₃, SC 9₃₉₃. + '7v7h,' + // #6568: 2 fonts: JP 67₂₀₃, SC 37₄₂₁. + '7v8j,' + // #6569: 2 fonts: JP 67₂₀₃, SC 49₄₃₃. + '7v8v,' + // #6570: 2 fonts: JP 67₂₀₃, SC 53₄₃₇. + '7v8z,' + // #6571: 2 fonts: JP 67₂₀₃, SC 65₄₄₉. + '7v9l,' + // #6572: 1 font: JP 68₂₀₄. + '7w,' + // #6573: 2 fonts: JP 68₂₀₄, SC 6₃₉₀. + '7w7d,' + // #6574: 2 fonts: JP 68₂₀₄, SC 83₄₆₇. + '7w10c,' + // #6575: 1 font: JP 69₂₀₅. + '7x,' + // #6576: 2 fonts: JP 69₂₀₅, SC 4₃₈₈. + '7x7a,' + // #6577: 2 fonts: JP 69₂₀₅, SC 58₄₄₂. + '7x9c,' + // #6578: 2 fonts: JP 69₂₀₅, SC 64₄₄₈. + '7x9i,' + // #6579: 2 fonts: JP 69₂₀₅, SC 92₄₇₆. + '7x10k,' + // #6580: 3 fonts: JP 70₂₀₆, KR 87₃₄₇, SC 55₄₃₉. + '7y5k3n,' + // #6581: 2 fonts: JP 70₂₀₆, SC 38₄₂₂. + '7y8h,' + // #6582: 2 fonts: JP 70₂₀₆, SC 57₄₄₁. + '7y9a,' + // #6583: 2 fonts: JP 70₂₀₆, SC 63₄₄₇. + '7y9g,' + // #6584: 2 fonts: JP 70₂₀₆, SC 81₄₆₅. + '7y9y,' + // #6585: 2 fonts: JP 71₂₀₇, SC 8₃₉₂. + '7z7c,' + // #6586: 3 fonts: JP 72₂₀₈, KR 92₃₅₂, SC 64₄₄₈. + '8a5n3r,' + // #6587: 2 fonts: JP 72₂₀₈, SC 41₄₂₅. + '8a8i,' + // #6588: 2 fonts: JP 73₂₀₉, SC 13₃₉₇. + '8b7f,' + // #6589: 2 fonts: JP 73₂₀₉, SC 18₄₀₂. + '8b7k,' + // #6590: 2 fonts: JP 73₂₀₉, SC 21₄₀₅. + '8b7n,' + // #6591: 2 fonts: JP 73₂₀₉, SC 47₄₃₁. + '8b8n,' + // #6592: 2 fonts: JP 73₂₀₉, SC 62₄₄₆. + '8b9c,' + // #6593: 2 fonts: JP 73₂₀₉, SC 64₄₄₈. + '8b9e,' + // #6594: 2 fonts: JP 73₂₀₉, SC 80₄₆₄. + '8b9u,' + // #6595: 2 fonts: JP 74₂₁₀, SC 43₄₂₇. + '8c8i,' + // #6596: 2 fonts: JP 74₂₁₀, SC 58₄₄₂. + '8c8x,' + // #6597: 2 fonts: JP 75₂₁₁, SC 33₄₁₇. + '8d7x,' + // #6598: 3 fonts: JP 76₂₁₂, KR 90₃₅₀, SC 62₄₄₆. + '8e5h3r,' + // #6599: 2 fonts: JP 76₂₁₂, SC 8₃₉₂. + '8e6x,' + // #6600: 2 fonts: JP 76₂₁₂, SC 13₃₉₇. + '8e7c,' + // #6601: 2 fonts: JP 76₂₁₂, SC 21₄₀₅. + '8e7k,' + // #6602: 2 fonts: JP 76₂₁₂, SC 31₄₁₅. + '8e7u,' + // #6603: 2 fonts: JP 76₂₁₂, SC 80₄₆₄. + '8e9r,' + // #6604: 2 fonts: JP 76₂₁₂, SC 81₄₆₅. + '8e9s,' + // #6605: 2 fonts: JP 77₂₁₃, SC 13₃₉₇. + '8f7b,' + // #6606: 2 fonts: JP 77₂₁₃, SC 27₄₁₁. + '8f7p,' + // #6607: 2 fonts: JP 77₂₁₃, SC 50₄₃₄. + '8f8m,' + // #6608: 2 fonts: JP 77₂₁₃, SC 90₄₇₄. + '8f10a,' + // #6609: 3 fonts: JP 78₂₁₄, KR 66₃₂₆, SC 9₃₉₃. + '8g4h2o,' + // #6610: 2 fonts: JP 78₂₁₄, SC 49₄₃₃. + '8g8k,' + // #6611: 2 fonts: JP 78₂₁₄, SC 89₄₇₃. + '8g9y,' + // #6612: 2 fonts: JP 79₂₁₅, SC 9₃₉₃. + '8h6v,' + // #6613: 2 fonts: JP 79₂₁₅, SC 60₄₄₄. + '8h8u,' + // #6614: 2 fonts: JP 80₂₁₆, SC 63₄₄₇. + '8i8w,' + // #6615: 2 fonts: JP 80₂₁₆, SC 87₄₇₁. + '8i9u,' + // #6616: 2 fonts: JP 81₂₁₇, SC 13₃₉₇. + '8j6x,' + // #6617: 2 fonts: JP 81₂₁₇, SC 81₄₆₅. + '8j9n,' + // #6618: 2 fonts: JP 82₂₁₈, SC 4₃₈₈. + '8k6n,' + // #6619: 2 fonts: JP 82₂₁₈, SC 29₄₁₃. + '8k7m,' + // #6620: 2 fonts: JP 82₂₁₈, SC 35₄₁₉. + '8k7s,' + // #6621: 2 fonts: JP 82₂₁₈, SC 54₄₃₈. + '8k8l,' + // #6622: 2 fonts: JP 82₂₁₈, SC 86₄₇₀. + '8k9r,' + // #6623: 2 fonts: JP 82₂₁₈, SC 89₄₇₃. + '8k9u,' + // #6624: 2 fonts: JP 82₂₁₈, SC 94₄₇₈. + '8k9z,' + // #6625: 2 fonts: JP 83₂₁₉, SC 23₄₀₇. + '8l7f,' + // #6626: 2 fonts: JP 83₂₁₉, SC 27₄₁₁. + '8l7j,' + // #6627: 2 fonts: JP 83₂₁₉, SC 49₄₃₃. + '8l8f,' + // #6628: 2 fonts: JP 84₂₂₀, SC 5₃₈₉. + '8m6m,' + // #6629: 2 fonts: JP 84₂₂₀, SC 24₄₀₈. + '8m7f,' + // #6630: 2 fonts: JP 84₂₂₀, SC 65₄₄₉. + '8m8u,' + // #6631: 2 fonts: JP 85₂₂₁, SC 35₄₁₉. + '8n7p,' + // #6632: 2 fonts: JP 85₂₂₁, SC 86₄₇₀. + '8n9o,' + // #6633: 2 fonts: JP 86₂₂₂, SC 66₄₅₀. + '8o8t,' + // #6634: 2 fonts: JP 86₂₂₂, SC 88₄₇₂. + '8o9p,' + // #6635: 3 fonts: JP 87₂₂₃, KR 83₃₄₃, SC 47₄₃₁. + '8p4p3j,' + // #6636: 2 fonts: JP 87₂₂₃, SC 14₃₉₈. + '8p6s,' + // #6637: 2 fonts: JP 87₂₂₃, SC 41₄₂₅. + '8p7t,' + // #6638: 2 fonts: JP 87₂₂₃, SC 48₄₃₂. + '8p8a,' + // #6639: 2 fonts: JP 87₂₂₃, SC 49₄₃₃. + '8p8b,' + // #6640: 2 fonts: JP 87₂₂₃, SC 56₄₄₀. + '8p8i,' + // #6641: 2 fonts: JP 87₂₂₃, SC 77₄₆₁. + '8p9d,' + // #6642: 2 fonts: JP 88₂₂₄, SC 9₃₉₃. + '8q6m,' + // #6643: 2 fonts: JP 88₂₂₄, SC 31₄₁₅. + '8q7i,' + // #6644: 2 fonts: JP 88₂₂₄, SC 37₄₂₁. + '8q7o,' + // #6645: 2 fonts: JP 88₂₂₄, SC 81₄₆₅. + '8q9g,' + // #6646: 2 fonts: JP 88₂₂₄, SC 88₄₇₂. + '8q9n,' + // #6647: 2 fonts: JP 88₂₂₄, SC 92₄₇₆. + '8q9r,' + // #6648: 3 fonts: JP 89₂₂₅, KR 86₃₄₆, SC 83₄₆₇. + '8r4q4q,' + // #6649: 2 fonts: JP 89₂₂₅, SC 7₃₉₁. + '8r6j,' + // #6650: 2 fonts: JP 89₂₂₅, SC 18₄₀₂. + '8r6u,' + // #6651: 2 fonts: JP 89₂₂₅, SC 27₄₁₁. + '8r7d,' + // #6652: 2 fonts: JP 89₂₂₅, SC 42₄₂₆. + '8r7s,' + // #6653: 2 fonts: JP 89₂₂₅, SC 50₄₃₄. + '8r8a,' + // #6654: 2 fonts: JP 89₂₂₅, SC 59₄₄₃. + '8r8j,' + // #6655: 2 fonts: JP 89₂₂₅, SC 80₄₆₄. + '8r9e,' + // #6656: 2 fonts: JP 90₂₂₆, SC 13₃₉₇. + '8s6o,' + // #6657: 2 fonts: JP 90₂₂₆, SC 92₄₇₆. + '8s9p,' + // #6658: 2 fonts: JP 91₂₂₇, SC 4₃₈₈. + '8t6e,' + // #6659: 2 fonts: JP 91₂₂₇, SC 24₄₀₈. + '8t6y,' + // #6660: 2 fonts: JP 91₂₂₇, SC 27₄₁₁. + '8t7b,' + // #6661: 2 fonts: JP 91₂₂₇, SC 42₄₂₆. + '8t7q,' + // #6662: 2 fonts: JP 91₂₂₇, SC 54₄₃₈. + '8t8c,' + // #6663: 2 fonts: JP 92₂₂₈, SC 25₄₀₉. + '8u6y,' + // #6664: 2 fonts: JP 92₂₂₈, SC 50₄₃₄. + '8u7x,' + // #6665: 2 fonts: JP 92₂₂₈, SC 53₄₃₇. + '8u8a,' + // #6666: 2 fonts: JP 92₂₂₈, SC 58₄₄₂. + '8u8f,' + // #6667: 2 fonts: JP 92₂₂₈, SC 88₄₇₂. + '8u9j,' + // #6668: 2 fonts: JP 93₂₂₉, SC 14₃₉₈. + '8v6m,' + // #6669: 2 fonts: JP 93₂₂₉, SC 27₄₁₁. + '8v6z,' + // #6670: 2 fonts: JP 93₂₂₉, SC 64₄₄₈. + '8v8k,' + // #6671: 2 fonts: JP 93₂₂₉, SC 67₄₅₁. + '8v8n,' + // #6672: 2 fonts: JP 93₂₂₉, SC 87₄₇₁. + '8v9h,' + // #6673: 2 fonts: JP 93₂₂₉, SC 90₄₇₄. + '8v9k,' + // #6674: 2 fonts: JP 94₂₃₀, SC 7₃₉₁. + '8w6e,' + // #6675: 2 fonts: JP 94₂₃₀, SC 44₄₂₈. + '8w7p,' + // #6676: 2 fonts: JP 94₂₃₀, SC 50₄₃₄. + '8w7v,' + // #6677: 2 fonts: JP 94₂₃₀, SC 63₄₄₇. + '8w8i,' + // #6678: 2 fonts: JP 95₂₃₁, SC 86₄₇₀. + '8x9e,' + // #6679: 2 fonts: JP 96₂₃₂, SC 28₄₁₂. + '8y6x,' + // #6680: 2 fonts: JP 96₂₃₂, SC 41₄₂₅. + '8y7k,' + // #6681: 2 fonts: JP 96₂₃₂, SC 58₄₄₂. + '8y8b,' + // #6682: 2 fonts: JP 96₂₃₂, SC 64₄₄₈. + '8y8h,' + // #6683: 2 fonts: JP 96₂₃₂, SC 90₄₇₄. + '8y9h,' + // #6684: 2 fonts: JP 97₂₃₃, SC 9₃₉₃. + '8z6d,' + // #6685: 2 fonts: JP 97₂₃₃, SC 53₄₃₇. + '8z7v,' + // #6686: 2 fonts: JP 97₂₃₃, SC 63₄₄₇. + '8z8f,' + // #6687: 2 fonts: JP 97₂₃₃, SC 94₄₇₈. + '8z9k,' + // #6688: 2 fonts: JP 98₂₃₄, SC 6₃₉₀. + '9a5z,' + // #6689: 2 fonts: JP 98₂₃₄, SC 77₄₆₁. + '9a8s,' + // #6690: 2 fonts: JP 98₂₃₄, SC 90₄₇₄. + '9a9f,' + // #6691: 2 fonts: JP 98₂₃₄, SC 93₄₇₇. + '9a9i,' + // #6692: 2 fonts: JP 99₂₃₅, SC 9₃₉₃. + '9b6b,' + // #6693: 2 fonts: JP 99₂₃₅, SC 14₃₉₈. + '9b6g,' + // #6694: 2 fonts: JP 99₂₃₅, SC 28₄₁₂. + '9b6u,' + // #6695: 2 fonts: JP 99₂₃₅, SC 53₄₃₇. + '9b7t,' + // #6696: 2 fonts: JP 100₂₃₆, SC 50₄₃₄. + '9c7p,' + // #6697: 2 fonts: JP 100₂₃₆, SC 53₄₃₇. + '9c7s,' + // #6698: 2 fonts: JP 101₂₃₇, SC 25₄₀₉. + '9d6p,' + // #6699: 2 fonts: JP 101₂₃₇, SC 53₄₃₇. + '9d7r,' + // #6700: 2 fonts: JP 101₂₃₇, SC 54₄₃₈. + '9d7s,' + // #6701: 2 fonts: JP 101₂₃₇, SC 59₄₄₃. + '9d7x,' + // #6702: 2 fonts: JP 101₂₃₇, SC 63₄₄₇. + '9d8b,' + // #6703: 2 fonts: JP 102₂₃₈, SC 46₄₃₀. + '9e7j,' + // #6704: 2 fonts: JP 103₂₃₉, SC 22₄₀₆. + '9f6k,' + // #6705: 2 fonts: JP 103₂₃₉, SC 27₄₁₁. + '9f6p,' + // #6706: 2 fonts: JP 103₂₃₉, SC 49₄₃₃. + '9f7l,' + // #6707: 2 fonts: JP 103₂₃₉, SC 52₄₃₆. + '9f7o,' + // #6708: 2 fonts: JP 103₂₃₉, SC 79₄₆₃. + '9f8p,' + // #6709: 2 fonts: JP 104₂₄₀, SC 8₃₉₂. + '9g5v,' + // #6710: 2 fonts: JP 104₂₄₀, SC 18₄₀₂. + '9g6f,' + // #6711: 3 fonts: JP 105₂₄₁, KR 82₃₄₂, SC 46₄₃₀. + '9h3w3j,' + // #6712: 2 fonts: JP 105₂₄₁, SC 19₄₀₃. + '9h6f,' + // #6713: 2 fonts: JP 105₂₄₁, SC 21₄₀₅. + '9h6h,' + // #6714: 2 fonts: JP 105₂₄₁, SC 28₄₁₂. + '9h6o,' + // #6715: 2 fonts: JP 105₂₄₁, SC 50₄₃₄. + '9h7k,' + // #6716: 2 fonts: JP 105₂₄₁, SC 60₄₄₄. + '9h7u,' + // #6717: 2 fonts: JP 106₂₄₂, SC 9₃₉₃. + '9i5u,' + // #6718: 2 fonts: JP 106₂₄₂, SC 39₄₂₃. + '9i6y,' + // #6719: 2 fonts: JP 106₂₄₂, SC 54₄₃₈. + '9i7n,' + // #6720: 2 fonts: JP 106₂₄₂, SC 58₄₄₂. + '9i7r,' + // #6721: 2 fonts: JP 106₂₄₂, SC 64₄₄₈. + '9i7x,' + // #6722: 2 fonts: JP 106₂₄₂, SC 88₄₇₂. + '9i8v,' + // #6723: 2 fonts: JP 106₂₄₂, SC 90₄₇₄. + '9i8x,' + // #6724: 3 fonts: JP 107₂₄₃, KR 92₃₅₂, SC 65₄₄₉. + '9j4e3s,' + // #6725: 2 fonts: JP 107₂₄₃, SC 52₄₃₆. + '9j7k,' + // #6726: 2 fonts: JP 107₂₄₃, SC 60₄₄₄. + '9j7s,' + // #6727: 2 fonts: JP 107₂₄₃, SC 64₄₄₈. + '9j7w,' + // #6728: 2 fonts: JP 107₂₄₃, SC 92₄₇₆. + '9j8y,' + // #6729: 3 fonts: JP 108₂₄₄, KR 66₃₂₆, SC 8₃₉₂. + '9k3d2n,' + // #6730: 3 fonts: JP 108₂₄₄, KR 74₃₃₄, SC 28₄₁₂. + '9k3l2z,' + // #6731: 2 fonts: JP 108₂₄₄, SC 4₃₈₈. + '9k5n,' + // #6732: 2 fonts: JP 108₂₄₄, SC 24₄₀₈. + '9k6h,' + // #6733: 2 fonts: JP 108₂₄₄, SC 44₄₂₈. + '9k7b,' + // #6734: 2 fonts: JP 108₂₄₄, SC 67₄₅₁. + '9k7y,' + // #6735: 2 fonts: JP 109₂₄₅, SC 43₄₂₇. + '9l6z,' + // #6736: 2 fonts: JP 109₂₄₅, SC 44₄₂₈. + '9l7a,' + // #6737: 2 fonts: JP 109₂₄₅, SC 50₄₃₄. + '9l7g,' + // #6738: 2 fonts: JP 109₂₄₅, SC 51₄₃₅. + '9l7h,' + // #6739: 2 fonts: JP 109₂₄₅, SC 54₄₃₈. + '9l7k,' + // #6740: 2 fonts: JP 109₂₄₅, SC 60₄₄₄. + '9l7q,' + // #6741: 2 fonts: JP 109₂₄₅, SC 87₄₇₁. + '9l8r,' + // #6742: 2 fonts: JP 109₂₄₅, SC 95₄₇₉. + '9l8z,' + // #6743: 2 fonts: JP 110₂₄₆, SC 9₃₉₃. + '9m5q,' + // #6744: 2 fonts: JP 110₂₄₆, SC 15₃₉₉. + '9m5w,' + // #6745: 2 fonts: JP 110₂₄₆, SC 19₄₀₃. + '9m6a,' + // #6746: 2 fonts: JP 110₂₄₆, SC 63₄₄₇. + '9m7s,' + // #6747: 2 fonts: JP 110₂₄₆, SC 92₄₇₆. + '9m8v,' + // #6748: 2 fonts: JP 111₂₄₇, SC 7₃₉₁. + '9n5n,' + // #6749: 2 fonts: JP 111₂₄₇, SC 28₄₁₂. + '9n6i,' + // #6750: 2 fonts: JP 111₂₄₇, SC 50₄₃₄. + '9n7e,' + // #6751: 2 fonts: JP 111₂₄₇, SC 55₄₃₉. + '9n7j,' + // #6752: 3 fonts: JP 112₂₄₈, KR 77₃₃₇, SC 35₄₁₉. + '9o3k3d,' + // #6753: 2 fonts: JP 112₂₄₈, SC 50₄₃₄. + '9o7d,' + // #6754: 2 fonts: JP 112₂₄₈, SC 63₄₄₇. + '9o7q,' + // #6755: 2 fonts: JP 112₂₄₈, SC 92₄₇₆. + '9o8t,' + // #6756: 2 fonts: JP 113₂₄₉, SC 18₄₀₂. + '9p5w,' + // #6757: 2 fonts: JP 113₂₄₉, SC 28₄₁₂. + '9p6g,' + // #6758: 2 fonts: JP 113₂₄₉, SC 61₄₄₅. + '9p7n,' + // #6759: 2 fonts: JP 113₂₄₉, SC 66₄₅₀. + '9p7s,' + // #6760: 2 fonts: JP 113₂₄₉, SC 92₄₇₆. + '9p8s,' + // #6761: 2 fonts: JP 114₂₅₀, SC 52₄₃₆. + '9q7d,' + // #6762: 2 fonts: JP 114₂₅₀, SC 95₄₇₉. + '9q8u,' + // #6763: 2 fonts: JP 115₂₅₁, SC 55₄₃₉. + '9r7f,' + // #6764: 2 fonts: JP 115₂₅₁, SC 58₄₄₂. + '9r7i,' + // #6765: 2 fonts: JP 115₂₅₁, SC 95₄₇₉. + '9r8t,' + // #6766: 2 fonts: JP 116₂₅₂, SC 12₃₉₆. + '9s5n,' + // #6767: 2 fonts: JP 116₂₅₂, SC 45₄₂₉. + '9s6u,' + // #6768: 2 fonts: JP 116₂₅₂, SC 95₄₇₉. + '9s8s,' + // #6769: 2 fonts: JP 117₂₅₃, SC 43₄₂₇. + '9t6r,' + // #6770: 2 fonts: JP 117₂₅₃, SC 95₄₇₉. + '9t8r,' + // #6771: 3 fonts: JP 118₂₅₄, Noto Sans₅₉₁, Mongolian₆₆₂. + '9u12y2s,' + // #6772: 1 font: KR 0₂₆₀. + '10a,' + // #6773: 1 font: KR 1₂₆₁. + '10b,' + // #6774: 1 font: KR 66₃₂₆. + '12o,' + // #6775: 1 font: KR 67₃₂₇. + '12p,' + // #6776: 1 font: KR 68₃₂₈. + '12q,' + // #6777: 2 fonts: KR 75₃₃₅, SC 30₄₁₄. + '12x3a,' + // #6778: 1 font: KR 76₃₃₆. + '12y,' + // #6779: 2 fonts: KR 76₃₃₆, SC 31₄₁₅. + '12y3a,' + // #6780: 1 font: KR 77₃₃₇. + '12z,' + // #6781: 1 font: KR 80₃₄₀. + '13c,' + // #6782: 2 fonts: KR 80₃₄₀, SC 41₄₂₅. + '13c3g,' + // #6783: 1 font: KR 81₃₄₁. + '13d,' + // #6784: 1 font: KR 82₃₄₂. + '13e,' + // #6785: 2 fonts: KR 84₃₄₄, SC 48₄₃₂. + '13g3j,' + // #6786: 1 font: KR 90₃₅₀. + '13m,' + // #6787: 2 fonts: KR 91₃₅₁, SC 63₄₄₇. + '13n3r,' + // #6788: 2 fonts: KR 92₃₅₂, SC 65₄₄₉. + '13o3s,' + // #6789: 2 fonts: KR 93₃₅₃, SC 67₄₅₁. + '13p3t,' + // #6790: 1 font: KR 94₃₅₄. + '13q,' + // #6791: 2 fonts: KR 100₃₆₀, Symbols₇₀₂. + '13w13d,' + // #6792: 3 fonts: KR 101₃₆₁, Noto Sans₅₉₁, Math₆₅₅. + '13x8v2l,' + // #6793: 2 fonts: KR 104₃₆₄, Symbols₇₀₂. + '14a12z,' + // #6794: 4 fonts: KR 108₃₆₈, Noto Sans₅₉₁, Elbasan₆₁₆, Math₆₅₅. + '14e8oy1m,' + // #6795: 129 fonts: Noto Music₅₉₀, Noto Sans₅₉₁, Adlam₅₉₂, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Armenian₅₉₅, Avestan₅₉₆, Balinese₅₉₇, Bamum₅₉₈, Bassa Vah₅₉₉, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Carian₆₀₇, Caucasian Albanian₆₀₈, Chakma₆₀₉, Cham₆₁₀, Cherokee₆₁₁, Coptic₆₁₂, Cypriot₆₁₃, Deseret₆₁₄, Devanagari₆₁₅, Elbasan₆₁₆, Elymaic₆₁₇, Ethiopic₆₁₈, Georgian₆₁₉, Glagolitic₆₂₀, Gothic₆₂₁, Grantha₆₂₂, Gujarati₆₂₃, Gunjala Gondi₆₂₄, Gurmukhi₆₂₅, Hanunoo₆₂₆, Hatran₆₂₇, Hebrew₆₂₈, Imperial Aramaic₆₂₉, Indic Siyaq Numbers₆₃₀, Inscriptional Pahlavi₆₃₁, Inscriptional Parthian₆₃₂, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kayah Li₆₃₆, Kharoshthi₆₃₇, Khmer₆₃₈, Khojki₆₃₉, Khudawadi₆₄₀, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Linear A₆₄₄, Linear B₆₄₅, Lisu₆₄₆, Lydian₆₄₈, Mahajani₆₄₉, Malayalam₆₅₀, Mandaic₆₅₁, Manichaean₆₅₂, Marchen₆₅₃, Masaram Gondi₆₅₄, Mayan Numerals₆₅₆, Medefaidrin₆₅₇, Meetei Mayek₆₅₈, Meroitic₆₅₉, Miao₆₆₀, Modi₆₆₁, Mongolian₆₆₂, Mro₆₆₃, Multani₆₆₄, NKo₆₆₆, Nabataean₆₆₇, New Tai Lue₆₆₈, Newa₆₆₉, Nushu₆₇₀, Ogham₆₇₁, Ol Chiki₆₇₂, Old Hungarian₆₇₃, Old Italic₆₇₄, Old North Arabian₆₇₅, Old Permic₆₇₆, Old Persian₆₇₇, Old Sogdian₆₇₈, Old South Arabian₆₇₉, Old Turkic₆₈₀, Oriya₆₈₁, Osage₆₈₂, Osmanya₆₈₃, Pahawh Hmong₆₈₄, Palmyrene₆₈₅, Pau Cin Hau₆₈₆, Phoenician₆₈₈, Psalter Pahlavi₆₈₉, Rejang₆₉₀, Runic₆₉₁, Saurashtra₆₉₂, Sharada₆₉₃, Shavian₆₉₄, Siddham₆₉₅, Sinhala₆₉₆, Sogdian₆₉₇, Sora Sompeng₆₉₈, Soyombo₆₉₉, Sundanese₇₀₀, Syloti Nagri₇₀₁, Symbols₇₀₂, Syriac₇₀₃, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Takri₇₀₉, Tamil₇₁₀, Tamil Supplement₇₁₁, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Tifinagh₇₁₅, Tirhuta₇₁₆, Ugaritic₇₁₇, Vai₇₁₈, Wancho₇₁₉, Warang Citi₇₂₀, Yi₇₂₁, Zanabazar Square₇₂₂, Noto Serif Tibetan₇₂₃. + '22saaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaabaaaaaaaabaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,' + // #6796: 42 fonts: Noto Sans₅₉₁, Anatolian Hieroglyphs₅₉₃, Arabic₅₉₄, Balinese₅₉₇, Batak₆₀₀, Bengali₆₀₁, Bhaiksuki₆₀₂, Brahmi₆₀₃, Buginese₆₀₄, Buhid₆₀₅, Devanagari₆₁₅, Gujarati₆₂₃, Gurmukhi₆₂₅, Hanunoo₆₂₆, Javanese₆₃₃, Kaithi₆₃₄, Kannada₆₃₅, Kharoshthi₆₃₇, Khmer₆₃₈, Lao₆₄₁, Lepcha₆₄₂, Limbu₆₄₃, Malayalam₆₅₀, Meetei Mayek₆₅₈, Myanmar₆₆₅, Oriya₆₈₁, Phags Pa₆₈₇, Rejang₆₉₀, Saurashtra₆₉₂, Sinhala₆₉₆, Sundanese₇₀₀, Syloti Nagri₇₀₁, Tagalog₇₀₄, Tagbanwa₇₀₅, Tai Le₇₀₆, Tai Tham₇₀₇, Tai Viet₇₀₈, Tamil₇₁₀, Telugu₇₁₂, Thaana₇₁₃, Thai₇₁₄, Noto Serif Tibetan₇₂₃. + '22tbaccaaaaajhbagaabacaaghgpfcbddacaaaabbaai,' + // #6797: 4 fonts: Noto Sans₅₉₁, Arabic₅₉₄, Canadian Aboriginal₆₀₆, Mongolian₆₆₂. + '22tcl2d,' + // #6798: 3 fonts: Noto Sans₅₉₁, Arabic₅₉₄, Hebrew₆₂₈. + '22tc1h,' + // #6799: 7 fonts: Noto Sans₅₉₁, Arabic₅₉₄, Hebrew₆₂₈, NKo₆₆₆, Phags Pa₆₈₇, Syriac₇₀₃, Thaana₇₁₃. + '22tc1h1lupj,' + // #6800: 2 fonts: Noto Sans₅₉₁, Armenian₅₉₅. + '22td,' + // #6801: 2 fonts: Noto Sans₅₉₁, Avestan₅₉₆. + '22te,' + // #6802: 20 fonts: Noto Sans₅₉₁, Bengali₆₀₁, Devanagari₆₁₅, Grantha₆₂₂, Gujarati₆₂₃, Gurmukhi₆₂₅, Kannada₆₃₅, Khudawadi₆₄₀, Limbu₆₄₃, Mahajani₆₄₉, Malayalam₆₅₀, Masaram Gondi₆₅₄, Multani₆₆₄, Oriya₆₈₁, Sinhala₆₉₆, Syloti Nagri₇₀₁, Takri₇₀₉, Tamil₇₁₀, Telugu₇₁₂, Tirhuta₇₁₆. + '22tjngabjecfadjqoehabd,' + // #6803: 12 fonts: Noto Sans₅₉₁, Bengali₆₀₁, Devanagari₆₁₅, Grantha₆₂₂, Gujarati₆₂₃, Gurmukhi₆₂₅, Kannada₆₃₅, Malayalam₆₅₀, Sharada₆₉₃, Tamil₇₁₀, Telugu₇₁₂, Tirhuta₇₁₆. + '22tjngabjo1qqbd,' + // #6804: 12 fonts: Noto Sans₅₉₁, Bengali₆₀₁, Devanagari₆₁₅, Gujarati₆₂₃, Gurmukhi₆₂₅, Kannada₆₃₅, Malayalam₆₅₀, Meetei Mayek₆₅₈, Ol Chiki₆₇₂, Oriya₆₈₁, Tamil₇₁₀, Telugu₇₁₂. + '22tjnhbjohni1cb,' + // #6805: 7 fonts: Noto Sans₅₉₁, Bengali₆₀₁, Devanagari₆₁₅, Gurmukhi₆₂₅, Lisu₆₄₆, Oriya₆₈₁, Thai₇₁₄. + '22tjnju1i1g,' + // #6806: 2 fonts: Noto Sans₅₉₁, Caucasian Albanian₆₀₈. + '22tq,' + // #6807: 7 fonts: Noto Sans₅₉₁, Caucasian Albanian₆₀₈, Cherokee₆₁₁, Gothic₆₂₁, Syriac₇₀₃, Thai₇₁₄, Tifinagh₇₁₅. + '22tqcj3dka,' + // #6808: 3 fonts: Noto Sans₅₉₁, Caucasian Albanian₆₀₈, Coptic₆₁₂. + '22tqd,' + // #6809: 4 fonts: Noto Sans₅₉₁, Caucasian Albanian₆₀₈, Coptic₆₁₂, Glagolitic₆₂₀. + '22tqdh,' + // #6810: 4 fonts: Noto Sans₅₉₁, Cherokee₆₁₁, Math₆₅₅, Syriac₇₀₃. + '22tt1r1v,' + // #6811: 3 fonts: Noto Sans₅₉₁, Cherokee₆₁₁, Syriac₇₀₃. + '22tt3n,' + // #6812: 6 fonts: Noto Sans₅₉₁, Coptic₆₁₂, Elbasan₆₁₆, Glagolitic₆₂₀, Gothic₆₂₁, Math₆₅₅. + '22tudda1h,' + // #6813: 3 fonts: Noto Sans₅₉₁, Devanagari₆₁₅, Grantha₆₂₂. + '22txg,' + // #6814: 4 fonts: Noto Sans₅₉₁, Devanagari₆₁₅, Kaithi₆₃₄, Mahajani₆₄₉. + '22txso,' + // #6815: 3 fonts: Noto Sans₅₉₁, Devanagari₆₁₅, Modi₆₆₁. + '22tx1t,' + // #6816: 2 fonts: Noto Sans₅₉₁, Ethiopic₆₁₈. + '22t1a,' + // #6817: 3 fonts: Noto Sans₅₉₁, Glagolitic₆₂₀, Old Permic₆₇₆. + '22t1c2d,' + // #6818: 2 fonts: Noto Sans₅₉₁, Hebrew₆₂₈. + '22t1k,' + // #6819: 3 fonts: Noto Sans₅₉₁, Kayah Li₆₃₆, Myanmar₆₆₅. + '22t1s1c,' + // #6820: 2 fonts: Noto Sans₅₉₁, Lao₆₄₁. + '22t1x,' + // #6821: 2 fonts: Noto Sans₅₉₁, Lisu₆₄₆. + '22t2c,' + // #6822: 4 fonts: Noto Sans₅₉₁, Manichaean₆₅₂, Myanmar₆₆₅, Phags Pa₆₈₇. + '22t2imv,' + // #6823: 3 fonts: Noto Sans₅₉₁, Meroitic₆₅₉, Old Hungarian₆₇₃. + '22t2pn,' + // #6824: 2 fonts: Noto Sans₅₉₁, NKo₆₆₆. + '22t2w,' + // #6825: 2 fonts: Noto Sans₅₉₁, Newa₆₆₉. + '22t2z,' + // #6826: 2 fonts: Noto Sans₅₉₁, Old Hungarian₆₇₃. + '22t3d,' + // #6827: 3 fonts: Noto Sans₅₉₁, Old Hungarian₆₇₃, Old Turkic₆₈₀. + '22t3dg,' + // #6828: 2 fonts: Noto Sans₅₉₁, Old Permic₆₇₆. + '22t3g,' + // #6829: 2 fonts: Noto Sans₅₉₁, Oriya₆₈₁. + '22t3l,' + // #6830: 2 fonts: Noto Sans₅₉₁, Osage₆₈₂. + '22t3m,' + // #6831: 2 fonts: Noto Sans₅₉₁, Syloti Nagri₇₀₁. + '22t4f,' + // #6832: 2 fonts: Noto Sans₅₉₁, Symbols₇₀₂. + '22t4g,' + // #6833: 2 fonts: Noto Sans₅₉₁, Tamil₇₁₀. + '22t4o,' + // #6834: 2 fonts: Noto Sans₅₉₁, Thai₇₁₄. + '22t4s,' + // #6835: 7 fonts: Adlam₅₉₂, Arabic₅₉₄, Mandaic₆₅₁, Manichaean₆₅₂, Psalter Pahlavi₆₈₉, Sogdian₆₉₇, Syriac₇₀₃. + '22ub2ea1khf,' + // #6836: 5 fonts: Adlam₅₉₂, Arabic₅₉₄, NKo₆₆₆, Syriac₇₀₃, Thaana₇₁₃. + '22ub2t1kj,' + // #6837: 1 font: Anatolian Hieroglyphs₅₉₃. + '22v,' + // #6838: 2 fonts: Arabic₅₉₄, Coptic₆₁₂. + '22wr,' + // #6839: 4 fonts: Arabic₅₉₄, Indic Siyaq Numbers₆₃₀, Syriac₇₀₃, Thaana₇₁₃. + '22w1j2uj,' + // #6840: 3 fonts: Arabic₅₉₄, NKo₆₆₆, Thaana₇₁₃. + '22w2t1u,' + // #6841: 3 fonts: Arabic₅₉₄, Syriac₇₀₃, Thaana₇₁₃. + '22w4ej,' + // #6842: 2 fonts: Armenian₅₉₅, Georgian₆₁₉. + '22xx,' + // #6843: 3 fonts: Bengali₆₀₁, Chakma₆₀₉, Syloti Nagri₇₀₁. + '23dh3n,' + // #6844: 6 fonts: Bengali₆₀₁, Devanagari₆₁₅, Grantha₆₂₂, Kannada₆₃₅, Telugu₇₁₂, Tirhuta₇₁₆. + '23dngm2yd,' + // #6845: 3 fonts: Bengali₆₀₁, Devanagari₆₁₅, Kannada₆₃₅. + '23dnt,' + // #6846: 2 fonts: Bengali₆₀₁, Tirhuta₇₁₆. + '23d4k,' + // #6847: 2 fonts: Buginese₆₀₄, Javanese₆₃₃. + '23g1c,' + // #6848: 1 font: Buhid₆₀₅. + '23h,' + // #6849: 4 fonts: Buhid₆₀₅, Hanunoo₆₂₆, Tagalog₇₀₄, Tagbanwa₇₀₅. + '23hu2za,' + // #6850: 1 font: Carian₆₀₇. + '23j,' + // #6851: 3 fonts: Chakma₆₀₉, Myanmar₆₆₅, Tai Le₇₀₆. + '23l2d1o,' + // #6852: 1 font: Deseret₆₁₄. + '23q,' + // #6853: 3 fonts: Devanagari₆₁₅, Grantha₆₂₂, Kannada₆₃₅. + '23rgm,' + // #6854: 12 fonts: Devanagari₆₁₅, Gujarati₆₂₃, Gurmukhi₆₂₅, Kaithi₆₃₄, Kannada₆₃₅, Khojki₆₃₉, Khudawadi₆₄₀, Mahajani₆₄₉, Malayalam₆₅₀, Modi₆₆₁, Takri₇₀₉, Tirhuta₇₁₆. + '23rhbiadaiak1vg,' + // #6855: 11 fonts: Devanagari₆₁₅, Gujarati₆₂₃, Gurmukhi₆₂₅, Kaithi₆₃₄, Kannada₆₃₅, Khojki₆₃₉, Khudawadi₆₄₀, Mahajani₆₄₉, Modi₆₆₁, Takri₇₀₉, Tirhuta₇₁₆. + '23rhbiadail1vg,' + // #6856: 10 fonts: Devanagari₆₁₅, Gujarati₆₂₃, Gurmukhi₆₂₅, Kaithi₆₃₄, Khojki₆₃₉, Khudawadi₆₄₀, Mahajani₆₄₉, Modi₆₆₁, Takri₇₀₉, Tirhuta₇₁₆. + '23rhbieail1vg,' + // #6857: 5 fonts: Devanagari₆₁₅, Kannada₆₃₅, Malayalam₆₅₀, Tamil₇₁₀, Telugu₇₁₂. + '23rto2hb,' + // #6858: 2 fonts: Devanagari₆₁₅, Tamil₇₁₀. + '23r3q,' + // #6859: 1 font: Elbasan₆₁₆. + '23s,' + // #6860: 1 font: Elymaic₆₁₇. + '23t,' + // #6861: 2 fonts: Ethiopic₆₁₈, Math₆₅₅. + '23u1k,' + // #6862: 1 font: Gothic₆₂₁. + '23x,' + // #6863: 2 fonts: Gujarati₆₂₃, Khojki₆₃₉. + '23zp,' + // #6864: 2 fonts: Gurmukhi₆₂₅, Multani₆₆₄. + '24b1m,' + // #6865: 2 fonts: Gurmukhi₆₂₅, Symbols₇₀₂. + '24b2y,' + // #6866: 1 font: Hanunoo₆₂₆. + '24c,' + // #6867: 1 font: Indic Siyaq Numbers₆₃₀. + '24g,' + // #6868: 1 font: Lycian₆₄₇. + '24x,' + // #6869: 1 font: Mahajani₆₄₉. + '24z,' + // #6870: 2 fonts: Math₆₅₅, Old Permic₆₇₆. + '25fu,' + // #6871: 1 font: Medefaidrin₆₅₇. + '25h,' + // #6872: 1 font: Ogham₆₇₁. + '25v,' + // #6873: 1 font: Ol Chiki₆₇₂. + '25w,' + // #6874: 1 font: Old North Arabian₆₇₅. + '25z,' + // #6875: 1 font: Old Permic₆₇₆. + '26a,' + // #6876: 1 font: Old Sogdian₆₇₈. + '26c,' + // #6877: 1 font: Old South Arabian₆₇₉. + '26d,' + // #6878: 1 font: Old Turkic₆₈₀. + '26e,' + // #6879: 1 font: Palmyrene₆₈₅. + '26j,' + // #6880: 1 font: Pau Cin Hau₆₈₆. + '26k,' + // #6881: 1 font: Phags Pa₆₈₇. + '26l,' + // #6882: 1 font: Runic₆₉₁. + '26p,' + // #6883: 1 font: Sharada₆₉₃. + '26r,' + // #6884: 1 font: Shavian₆₉₄. + '26s,' + // #6885: 1 font: Sogdian₆₉₇. + '26v,' + // #6886: 1 font: Soyombo₆₉₉. + '26x,' + // #6887: 1 font: Syloti Nagri₇₀₁. + '26z,' + // #6888: 2 fonts: Symbols₇₀₂, Syriac₇₀₃. + '27aa,' + // #6889: 1 font: Thaana₇₁₃. + '27l,' + // #6890: 1 font: Vai₇₁₈. + '27q,' + // #6891: 1 font: Zanabazar Square₇₂₂. + '27u' ; -// 22287 ranges encoded in 32654 characters +// 29518 ranges encoded in 76152 characters const String encodedFontSetRanges = - '1eE' // 0-1f - '5Q' // 20 #146 - '3S' // 21 #96 - '1R' // 22 #43 - '5S' // 23 #148 - 'e1R' // 24-29 #43 - '5S' // 2a #148 - '1R' // 2b #43 - '3S' // 2c #96 - '8W' // 2d #230 - '3S' // 2e #96 - '1R' // 2f #43 - 'i8V' // 30-39 #229 - '3S' // 3a #96 - 'c1R' // 3b-3e #43 - '8X' // 3f #231 - 'd1R' // 40-44 #43 - '3T' // 45 #97 - 'b1R' // 46-48 #43 - '3T' // 49 #97 - 'd1R' // 4a-4e #43 - '3T' // 4f #97 - 'd1R' // 50-54 #43 - '3T' // 55 #97 - '1n1R' // 56-7e #43 - 'M' // 7f #12 - '1eE' // 80-9f - '5Q' // a0 #146 - 'bY' // a1-a3 #24 - 'Q' // a4 #16 - 'Y' // a5 #24 - 'Q' // a6 #16 - '1R' // a7 #43 - 'Y' // a8 #24 - '4H' // a9 #111 - 'Y' // aa #24 - '5R' // ab #147 - '2M' // ac #64 - '9T' // ad #253 - '4H' // ae #111 - 'aY' // af-b0 #24 - '2M' // b1 #64 - 'a6A' // b2-b3 #156 - 'Y' // b4 #24 - 'Q' // b5 #16 - 'bY' // b6-b8 #24 - 'Q' // b9 #16 - 'Y' // ba #24 - '5R' // bb #147 - 'bQ' // bc-be #16 - 'wY' // bf-d6 #24 - '1R' // d7 #43 - '1dY' // d8-f6 #24 - '1R' // f7 #43 - 'kY' // f8-103 #24 - 'c1E' // 104-107 #30 - 'aL' // 108-109 #11 - 'e1E' // 10a-10f #30 - 'cY' // 110-113 #24 - 'aL' // 114-115 #11 - 'c1E' // 116-119 #30 - 'aY' // 11a-11b #24 - 'aL' // 11c-11d #11 - 'e1E' // 11e-123 #30 - 'aL' // 124-125 #11 - 'a1E' // 126-127 #30 - 'aQ' // 128-129 #16 - 'aY' // 12a-12b #24 - 'aL' // 12c-12d #11 - 'c1E' // 12e-131 #30 - 'a9B' // 132-133 #235 - 'aL' // 134-135 #11 - 'a1E' // 136-137 #30 - 'L' // 138 #11 - 'e1E' // 139-13e #30 - 'aL' // 13f-140 #11 - 'a1E' // 141-142 #30 - 'aY' // 143-144 #24 - 'a1E' // 145-146 #30 - 'aY' // 147-148 #24 - 'L' // 149 #11 - 'a4I' // 14a-14b #112 - 'a5V' // 14c-14d #151 - 'aQ' // 14e-14f #16 - 'a1E' // 150-151 #30 - 'aY' // 152-153 #24 - 'a1E' // 154-155 #30 - 'a4I' // 156-157 #112 - 'c1E' // 158-15b #30 - 'aL' // 15c-15d #11 - 'c1E' // 15e-161 #30 - 'aL' // 162-163 #11 - 'a1E' // 164-165 #30 - 'aL' // 166-167 #11 - 'aQ' // 168-169 #16 - 'aY' // 16a-16b #24 - 'a5V' // 16c-16d #151 - 'p1E' // 16e-17e #30 - 'rL' // 17f-191 #11 - 'Q' // 192 #16 - 'lL' // 193-19f #11 - 'aQ' // 1a0-1a1 #16 - 'lL' // 1a2-1ae #11 - 'aQ' // 1af-1b0 #16 - '1aL' // 1b1-1cc #11 - 'a9F' // 1cd-1ce #239 - 'mQ' // 1cf-1dc #16 - 'zL' // 1dd-1f7 #11 - 'aQ' // 1f8-1f9 #16 - '1cL' // 1fa-217 #11 - 'c1E' // 218-21b #30 - 'zL' // 21c-236 #11 - '1E' // 237 #30 - 'xL' // 238-250 #11 - 'Q' // 251 #16 - 'nL' // 252-260 #11 - 'Q' // 261 #16 - '3hL' // 262-2b8 #11 - '1Z' // 2b9 #51 - 'L' // 2ba #11 - 'Q' // 2bb #16 - '9U' // 2bc #254 - 'hL' // 2bd-2c5 #11 - '1E' // 2c6 #30 - 'Y' // 2c7 #24 - 'L' // 2c8 #11 - '8Z' // 2c9 #233 - 'aQ' // 2ca-2cb #16 - 'L' // 2cc #11 - '10S' // 2cd #278 - 'hL' // 2ce-2d6 #11 - '11F' // 2d7 #291 - '1E' // 2d8 #30 - 'Y' // 2d9 #24 - 'a9A' // 2da-2db #234 - 'a1E' // 2dc-2dd #30 - 'kL' // 2de-2e9 #11 - 'aQ' // 2ea-2eb #16 - 'sL' // 2ec-2ff #11 - 'aY' // 300-301 #24 - 'a5U' // 302-303 #150 - 'Y' // 304 #24 - '10D' // 305 #263 - '1E' // 306 #30 - '1R' // 307 #43 - '5U' // 308 #150 - '6B' // 309 #157 - 'a1E' // 30a-30b #30 - 'Y' // 30c #24 - 'L' // 30d #11 - '10I' // 30e #268 - 'aL' // 30f-310 #11 - '1Z' // 311 #51 - '4I' // 312 #112 - '10Z' // 313 #285 - 'aL' // 314-315 #11 - 'a3F' // 316-317 #83 - 'gL' // 318-31f #11 - '4K' // 320 #114 - 'aL' // 321-322 #11 - '10A' // 323 #260 - '10B' // 324 #261 - '4K' // 325 #114 - 'b1E' // 326-328 #30 - 'cL' // 329-32c #11 - 'a4K' // 32d-32e #114 - '3F' // 32f #83 - '10C' // 330 #262 - '9X' // 331 #257 - 'lL' // 332-33e #11 - '1Z' // 33f #51 - 'nL' // 340-34e #11 - '9M' // 34f #246 - 'gL' // 350-357 #11 - '11B' // 358 #287 - 'L' // 359 #11 - '11A' // 35a #286 - 'bL' // 35b-35d #11 - '9Y' // 35e #258 - 'aL' // 35f-360 #11 - '1Z' // 361 #51 - 'qL' // 362-373 #11 - 'a1Z' // 374-375 #51 - 'aL' // 376-377 #11 - 'aE' // 378-379 - 'eL' // 37a-37f #11 - 'cE' // 380-383 - 'fL' // 384-38a #11 - 'E' // 38b - 'L' // 38c #11 - 'E' // 38d - 'bL' // 38e-390 #11 - 'p5Y' // 391-3a1 #154 - 'E' // 3a2 - 'f5Y' // 3a3-3a9 #154 - 'fL' // 3aa-3b0 #11 - 'x2M' // 3b1-3c9 #64 - 'fL' // 3ca-3d0 #11 - '1L' // 3d1 #37 - 'bL' // 3d2-3d4 #11 - 'a1L' // 3d5-3d6 #37 - 'bL' // 3d7-3d9 #11 - '3U' // 3da #98 - 'L' // 3db #11 - '3U' // 3dc #98 - 'L' // 3dd #11 - '3U' // 3de #98 - 'L' // 3df #11 - '3U' // 3e0 #98 - 'L' // 3e1 #11 - 'm4R' // 3e2-3ef #121 - 'a1L' // 3f0-3f1 #37 - 'aL' // 3f2-3f3 #11 - 'a1L' // 3f4-3f5 #37 - 'jL' // 3f6-400 #11 - 'Q' // 401 #16 - 'mL' // 402-40f #11 - '2kQ' // 410-44f #16 - 'L' // 450 #11 - 'Q' // 451 #16 - '1vL' // 452-482 #11 - '10K' // 483 #270 - '3V' // 484 #99 - 'aL' // 485-486 #11 - '3V' // 487 #99 - '6kL' // 488-52f #11 - 'E' // 530 - '1k3G' // 531-556 #84 - 'aE' // 557-558 - '1u3G' // 559-588 #84 - '11O' // 589 #300 - '3G' // 58a #84 - 'aE' // 58b-58c - 'b3G' // 58d-58f #84 - 'E' // 590 - '2b2H' // 591-5c7 #59 - 'gE' // 5c8-5cf - 'z2H' // 5d0-5ea #59 - 'cE' // 5eb-5ee - 'e2H' // 5ef-5f4 #59 - 'jE' // 5f5-5ff - 'd1F' // 600-604 #31 - '11K' // 605 #296 - 'e1F' // 606-60b #31 - '4M' // 60c #116 - 'm1F' // 60d-61a #31 - '4M' // 61b #116 - '1F' // 61c #31 - 'E' // 61d - '1F' // 61e #31 - '11I' // 61f #294 - '1F' // 620 #31 - '4N' // 621 #117 - 'd1F' // 622-626 #31 - '6C' // 627 #158 - 'w1F' // 628-63f #31 - '11H' // 640 #293 - 'i1F' // 641-64a #31 - 'j4N' // 64b-655 #117 - 'i1F' // 656-65f #31 - 'i11L' // 660-669 #297 - '4M' // 66a #116 - 'a11N' // 66b-66c #299 - '3W' // 66d #100 - 'a1F' // 66e-66f #31 - '4N' // 670 #117 - '3t1F' // 671-6d3 #31 - '3W' // 6d4 #100 - 'z1F' // 6d5-6ef #31 - 'i6C' // 6f0-6f9 #158 - 'e1F' // 6fa-6ff #31 - 'm5I' // 700-70d #138 - 'E' // 70e - '2g5I' // 70f-74a #138 - 'aE' // 74b-74c - 'b5I' // 74d-74f #138 - '1u1F' // 750-77f #31 - '1w14S' // 780-7b1 #382 - 'mE' // 7b2-7bf - '2f7I' // 7c0-7fa #190 - 'aE' // 7fb-7fc - 'b7I' // 7fd-7ff #190 - '2kE' // 800-83f - '1a7E' // 840-85b #186 - 'aE' // 85c-85d - '7E' // 85e #186 - '2lE' // 85f-89f - 't1F' // 8a0-8b4 #31 - 'E' // 8b5 - 'h1F' // 8b6-8be #31 - 'sE' // 8bf-8d2 - '1r1F' // 8d3-8ff #31 - '2h3E' // 900-93c #82 - '10G' // 93d #266 - 'r3E' // 93e-950 #82 - 'a9R' // 951-952 #251 - 'p3E' // 953-963 #82 - 'a9Q' // 964-965 #250 - 'i10F' // 966-96f #265 - 'o3E' // 970-97f #82 - 'c1O' // 980-983 #40 - 'E' // 984 - 'g1O' // 985-98c #40 - 'aE' // 98d-98e - 'a1O' // 98f-990 #40 - 'aE' // 991-992 - 'u1O' // 993-9a8 #40 - 'E' // 9a9 - 'f1O' // 9aa-9b0 #40 - 'E' // 9b1 - '1O' // 9b2 #40 - 'bE' // 9b3-9b5 - 'c1O' // 9b6-9b9 #40 - 'aE' // 9ba-9bb - 'h1O' // 9bc-9c4 #40 - 'aE' // 9c5-9c6 - 'a1O' // 9c7-9c8 #40 - 'aE' // 9c9-9ca - 'c1O' // 9cb-9ce #40 - 'gE' // 9cf-9d6 - '1O' // 9d7 #40 - 'cE' // 9d8-9db - 'a1O' // 9dc-9dd #40 - 'E' // 9de - 'd1O' // 9df-9e3 #40 - 'aE' // 9e4-9e5 - 'i11P' // 9e6-9ef #301 - 'c1O' // 9f0-9f3 #40 - 'c11S' // 9f4-9f7 #304 - 'f1O' // 9f8-9fe #40 - 'aE' // 9ff-a00 - 'b1Q' // a01-a03 #42 - 'E' // a04 - 'e1Q' // a05-a0a #42 - 'cE' // a0b-a0e - 'a1Q' // a0f-a10 #42 - 'aE' // a11-a12 - 'u1Q' // a13-a28 #42 - 'E' // a29 - 'f1Q' // a2a-a30 #42 - 'E' // a31 - 'a1Q' // a32-a33 #42 - 'E' // a34 - 'a1Q' // a35-a36 #42 - 'E' // a37 - 'a1Q' // a38-a39 #42 - 'aE' // a3a-a3b - '1Q' // a3c #42 - 'E' // a3d - 'd1Q' // a3e-a42 #42 - 'cE' // a43-a46 - 'a1Q' // a47-a48 #42 - 'aE' // a49-a4a - 'b1Q' // a4b-a4d #42 - 'bE' // a4e-a50 - '1Q' // a51 #42 - 'fE' // a52-a58 - 'c1Q' // a59-a5c #42 - 'E' // a5d - '1Q' // a5e #42 - 'fE' // a5f-a65 - 'i12P' // a66-a6f #327 - 'f1Q' // a70-a76 #42 - 'iE' // a77-a80 - 'b1S' // a81-a83 #44 - 'E' // a84 - 'h1S' // a85-a8d #44 - 'E' // a8e - 'b1S' // a8f-a91 #44 - 'E' // a92 - 'u1S' // a93-aa8 #44 - 'E' // aa9 - 'f1S' // aaa-ab0 #44 - 'E' // ab1 - 'a1S' // ab2-ab3 #44 - 'E' // ab4 - 'd1S' // ab5-ab9 #44 - 'aE' // aba-abb - 'i1S' // abc-ac5 #44 - 'E' // ac6 - 'b1S' // ac7-ac9 #44 - 'E' // aca - 'b1S' // acb-acd #44 - 'aE' // ace-acf - '1S' // ad0 #44 - 'nE' // ad1-adf - 'c1S' // ae0-ae3 #44 - 'aE' // ae4-ae5 - 'i12O' // ae6-aef #326 - 'a1S' // af0-af1 #44 - 'fE' // af2-af8 - 'f1S' // af9-aff #44 - 'E' // b00 - 'b1T' // b01-b03 #45 - 'E' // b04 - 'g1T' // b05-b0c #45 - 'aE' // b0d-b0e - 'a1T' // b0f-b10 #45 - 'aE' // b11-b12 - 'u1T' // b13-b28 #45 - 'E' // b29 - 'f1T' // b2a-b30 #45 - 'E' // b31 - 'a1T' // b32-b33 #45 - 'E' // b34 - 'd1T' // b35-b39 #45 - 'aE' // b3a-b3b - 'h1T' // b3c-b44 #45 - 'aE' // b45-b46 - 'a1T' // b47-b48 #45 - 'aE' // b49-b4a - 'b1T' // b4b-b4d #45 - 'fE' // b4e-b54 - 'b1T' // b55-b57 #45 - 'cE' // b58-b5b - 'a1T' // b5c-b5d #45 - 'E' // b5e - 'd1T' // b5f-b63 #45 - 'aE' // b64-b65 - 'q1T' // b66-b77 #45 - 'iE' // b78-b81 - 'a1N' // b82-b83 #39 - 'E' // b84 - 'e1N' // b85-b8a #39 - 'bE' // b8b-b8d - 'b1N' // b8e-b90 #39 - 'E' // b91 - 'c1N' // b92-b95 #39 - 'bE' // b96-b98 - 'a1N' // b99-b9a #39 - 'E' // b9b - '1N' // b9c #39 - 'E' // b9d - 'a1N' // b9e-b9f #39 - 'bE' // ba0-ba2 - 'a1N' // ba3-ba4 #39 - 'bE' // ba5-ba7 - 'a1N' // ba8-ba9 #39 - '2V' // baa #73 - 'bE' // bab-bad - 'f1N' // bae-bb4 #39 - '2V' // bb5 #73 - 'c1N' // bb6-bb9 #39 - 'cE' // bba-bbd - 'd1N' // bbe-bc2 #39 - 'bE' // bc3-bc5 - 'b1N' // bc6-bc8 #39 - 'E' // bc9 - 'c1N' // bca-bcd #39 - 'aE' // bce-bcf - '1N' // bd0 #39 - 'eE' // bd1-bd6 - '1N' // bd7 #39 - 'mE' // bd8-be5 - 'l2V' // be6-bf2 #73 - 'g1N' // bf3-bfa #39 - 'dE' // bfb-bff - 'l1W' // c00-c0c #48 - 'E' // c0d - 'b1W' // c0e-c10 #48 - 'E' // c11 - 'v1W' // c12-c28 #48 - 'E' // c29 - 'o1W' // c2a-c39 #48 - 'aE' // c3a-c3b - 'h1W' // c3c-c44 #48 - 'E' // c45 - 'b1W' // c46-c48 #48 - 'E' // c49 - 'c1W' // c4a-c4d #48 - 'fE' // c4e-c54 - 'a1W' // c55-c56 #48 - 'E' // c57 - 'b1W' // c58-c5a #48 - 'aE' // c5b-c5c - '1W' // c5d #48 - 'aE' // c5e-c5f - 'c1W' // c60-c63 #48 - 'aE' // c64-c65 - 'i1W' // c66-c6f #48 - 'fE' // c70-c76 - 'h1W' // c77-c7f #48 - 'l1U' // c80-c8c #46 - 'E' // c8d - 'b1U' // c8e-c90 #46 - 'E' // c91 - 'v1U' // c92-ca8 #46 - 'E' // ca9 - 'i1U' // caa-cb3 #46 - 'E' // cb4 - 'd1U' // cb5-cb9 #46 - 'aE' // cba-cbb - 'h1U' // cbc-cc4 #46 - 'E' // cc5 - 'b1U' // cc6-cc8 #46 - 'E' // cc9 - 'c1U' // cca-ccd #46 - 'fE' // cce-cd4 - 'a1U' // cd5-cd6 #46 - 'eE' // cd7-cdc - 'a1U' // cdd-cde #46 - 'E' // cdf - 'c1U' // ce0-ce3 #46 - 'aE' // ce4-ce5 - 'i1U' // ce6-cef #46 - 'E' // cf0 - 'b1U' // cf1-cf3 #46 - 'kE' // cf4-cff - 'l2R' // d00-d0c #69 - 'E' // d0d - 'b2R' // d0e-d10 #69 - 'E' // d11 - '1x2R' // d12-d44 #69 - 'E' // d45 - 'b2R' // d46-d48 #69 - 'E' // d49 - 'e2R' // d4a-d4f #69 - 'cE' // d50-d53 - 'o2R' // d54-d63 #69 - 'aE' // d64-d65 - 'y2R' // d66-d7f #69 - 'E' // d80 - 'b1V' // d81-d83 #47 - 'E' // d84 - 'q1V' // d85-d96 #47 - 'bE' // d97-d99 - 'w1V' // d9a-db1 #47 - 'E' // db2 - 'h1V' // db3-dbb #47 - 'E' // dbc - '1V' // dbd #47 - 'aE' // dbe-dbf - 'f1V' // dc0-dc6 #47 - 'bE' // dc7-dc9 - '1V' // dca #47 - 'cE' // dcb-dce - 'e1V' // dcf-dd4 #47 - 'E' // dd5 - '1V' // dd6 #47 - 'E' // dd7 - 'g1V' // dd8-ddf #47 - 'eE' // de0-de5 - 'i1V' // de6-def #47 - 'aE' // df0-df1 - 'b1V' // df2-df4 #47 - 'kE' // df5-e00 - '2e8K' // e01-e3a #218 - 'cE' // e3b-e3e - '1b8K' // e3f-e5b #218 - '1jE' // e5c-e80 - 'a2B' // e81-e82 #53 - 'E' // e83 - '2B' // e84 #53 - 'E' // e85 - 'd2B' // e86-e8a #53 - 'E' // e8b - 'w2B' // e8c-ea3 #53 - 'E' // ea4 - '2B' // ea5 #53 - 'E' // ea6 - 'v2B' // ea7-ebd #53 - 'aE' // ebe-ebf - 'd2B' // ec0-ec4 #53 - 'E' // ec5 - '2B' // ec6 #53 - 'E' // ec7 - 'f2B' // ec8-ece #53 - 'E' // ecf - 'i2B' // ed0-ed9 #53 - 'aE' // eda-edb - 'c2B' // edc-edf #53 - '1eE' // ee0-eff - '2s3B' // f00-f47 #79 - 'E' // f48 - '1i3B' // f49-f6c #79 - 'cE' // f6d-f70 - '1l3B' // f71-f97 #79 - 'E' // f98 - '1i3B' // f99-fbc #79 - 'E' // fbd - 'n3B' // fbe-fcc #79 - 'E' // fcd - 'l3B' // fce-fda #79 - '1jE' // fdb-fff - '2k4E' // 1000-103f #108 - 'i11X' // 1040-1049 #309 - '3g4E' // 104a-109f #108 - '1k2C' // 10a0-10c5 #54 - 'E' // 10c6 - '2C' // 10c7 #54 - 'dE' // 10c8-10cc - '2C' // 10cd #54 - 'aE' // 10ce-10cf - '1p2C' // 10d0-10fa #54 - '5Z' // 10fb #155 - 'c2C' // 10fc-10ff #54 - '9uP' // 1100-11ff #15 - '2tV' // 1200-1248 #21 - 'E' // 1249 - 'cV' // 124a-124d #21 - 'aE' // 124e-124f - 'fV' // 1250-1256 #21 - 'E' // 1257 - 'V' // 1258 #21 - 'E' // 1259 - 'cV' // 125a-125d #21 - 'aE' // 125e-125f - '1nV' // 1260-1288 #21 - 'E' // 1289 - 'cV' // 128a-128d #21 - 'aE' // 128e-128f - '1fV' // 1290-12b0 #21 - 'E' // 12b1 - 'cV' // 12b2-12b5 #21 - 'aE' // 12b6-12b7 - 'fV' // 12b8-12be #21 - 'E' // 12bf - 'V' // 12c0 #21 - 'E' // 12c1 - 'cV' // 12c2-12c5 #21 - 'aE' // 12c6-12c7 - 'nV' // 12c8-12d6 #21 - 'E' // 12d7 - '2dV' // 12d8-1310 #21 - 'E' // 1311 - 'cV' // 1312-1315 #21 - 'aE' // 1316-1317 - '2nV' // 1318-135a #21 - 'aE' // 135b-135c - '1eV' // 135d-137c #21 - 'bE' // 137d-137f - 'yV' // 1380-1399 #21 - 'eE' // 139a-139f - '3g4Q' // 13a0-13f5 #120 - 'aE' // 13f6-13f7 - 'e4Q' // 13f8-13fd #120 - 'aE' // 13fe-13ff - '24o4P' // 1400-167f #119 - '1b13N' // 1680-169c #351 - 'bE' // 169d-169f - '3j13X' // 16a0-16f8 #361 - 'fE' // 16f9-16ff - 'u8F' // 1700-1715 #213 - 'hE' // 1716-171e - '8F' // 171f #213 - 't13D' // 1720-1734 #341 - 'a11V' // 1735-1736 #307 - 'hE' // 1737-173f - 's11U' // 1740-1753 #306 - 'kE' // 1754-175f - 'l5J' // 1760-176c #139 - 'E' // 176d - 'b5J' // 176e-1770 #139 - 'E' // 1771 - 'a5J' // 1772-1773 #139 - 'kE' // 1774-177f - '3o4D' // 1780-17dd #107 - 'aE' // 17de-17df - 'i4D' // 17e0-17e9 #107 - 'eE' // 17ea-17ef - 'i4D' // 17f0-17f9 #107 - 'eE' // 17fa-17ff - '2Y' // 1800 #76 - 'b7H' // 1801-1803 #189 - '2Y' // 1804 #76 - '7H' // 1805 #189 - 's2Y' // 1806-1819 #76 - 'eE' // 181a-181f - '3j2Y' // 1820-1878 #76 - 'fE' // 1879-187f - '1p2Y' // 1880-18aa #76 - 'dE' // 18ab-18af - '2q4P' // 18b0-18f5 #119 - 'iE' // 18f6-18ff - '1d3I' // 1900-191e #86 - 'E' // 191f - 'k3I' // 1920-192b #86 - 'cE' // 192c-192f - 'k3I' // 1930-193b #86 - 'cE' // 193c-193f - '3I' // 1940 #86 - 'bE' // 1941-1943 - 'k3I' // 1944-194f #86 - '1c8G' // 1950-196d #214 - 'aE' // 196e-196f - 'd8G' // 1970-1974 #214 - 'jE' // 1975-197f - '1q4F' // 1980-19ab #109 - 'cE' // 19ac-19af - 'y4F' // 19b0-19c9 #109 - 'eE' // 19ca-19cf - 'j4F' // 19d0-19da #109 - 'bE' // 19db-19dd - 'a4F' // 19de-19df #109 - '1e4D' // 19e0-19ff #107 - '1a6J' // 1a00-1a1b #165 - 'aE' // 1a1c-1a1d - 'a6J' // 1a1e-1a1f #165 - '2j3P' // 1a20-1a5e #93 - 'E' // 1a5f - '1b3P' // 1a60-1a7c #93 - 'aE' // 1a7d-1a7e - 'j3P' // 1a7f-1a89 #93 - 'eE' // 1a8a-1a8f - 'i3P' // 1a90-1a99 #93 - 'eE' // 1a9a-1a9f - 'm3P' // 1aa0-1aad #93 - 'aE' // 1aae-1aaf - 'pL' // 1ab0-1ac0 #11 - 'cE' // 1ac1-1ac4 - 'L' // 1ac5 #11 - 'E' // 1ac6 - 'gL' // 1ac7-1ace #11 - '1vE' // 1acf-1aff - '2w6E' // 1b00-1b4b #160 - 'cE' // 1b4c-1b4f - '1r6E' // 1b50-1b7c #160 - 'bE' // 1b7d-1b7f - '2k7W' // 1b80-1bbf #204 - '1y6H' // 1bc0-1bf3 #163 - 'gE' // 1bf4-1bfb - 'c6H' // 1bfc-1bff #163 - '2c4Z' // 1c00-1c37 #129 - 'bE' // 1c38-1c3a - 'n4Z' // 1c3b-1c49 #129 - 'bE' // 1c4a-1c4c - 'b4Z' // 1c4d-1c4f #129 - '1u13O' // 1c50-1c7f #352 - 'hL' // 1c80-1c88 #11 - 'fE' // 1c89-1c8f - '1p2C' // 1c90-1cba #54 - 'aE' // 1cbb-1cbc - 'b2C' // 1cbd-1cbf #54 - 'g7W' // 1cc0-1cc7 #204 - 'gE' // 1cc8-1ccf - '6I' // 1cd0 #164 - '2A' // 1cd1 #52 - '6I' // 1cd2 #164 - '4S' // 1cd3 #122 - '2A' // 1cd4 #52 - 'a2N' // 1cd5-1cd6 #65 - '4A' // 1cd7 #104 - '2N' // 1cd8 #65 - '4A' // 1cd9 #104 - '12E' // 1cda #316 - '2A' // 1cdb #52 - 'a4A' // 1cdc-1cdd #104 - 'a2A' // 1cde-1cdf #52 - '4A' // 1ce0 #104 - '2N' // 1ce1 #65 - 'g2A' // 1ce2-1ce9 #52 - '2N' // 1cea #65 - 'a2A' // 1ceb-1cec #52 - '2N' // 1ced #65 - 'c2A' // 1cee-1cf1 #52 - '11Q' // 1cf2 #302 - '4S' // 1cf3 #122 - '12A' // 1cf4 #312 - '11R' // 1cf5 #303 - '2N' // 1cf6 #65 - '1O' // 1cf7 #40 - 'a4S' // 1cf8-1cf9 #122 - 'eE' // 1cfa-1cff - '7vL' // 1d00-1dcc #11 - '1Z' // 1dcd #51 - '1qL' // 1dce-1df9 #11 - 'E' // 1dfa - '10W' // 1dfb #282 - '2mL' // 1dfc-1e3d #11 - 'aQ' // 1e3e-1e3f #16 - '2kL' // 1e40-1e7f #11 - 'e1E' // 1e80-1e85 #30 - 'wL' // 1e86-1e9d #11 - '1E' // 1e9e #30 - 'L' // 1e9f #11 - '3cQ' // 1ea0-1ef1 #16 - 'aY' // 1ef2-1ef3 #24 - 'eQ' // 1ef4-1ef9 #16 - '1aL' // 1efa-1f15 #11 - 'aE' // 1f16-1f17 - 'eL' // 1f18-1f1d #11 - 'aE' // 1f1e-1f1f - '1kL' // 1f20-1f45 #11 - 'aE' // 1f46-1f47 - 'eL' // 1f48-1f4d #11 - 'aE' // 1f4e-1f4f - 'gL' // 1f50-1f57 #11 - 'E' // 1f58 - 'L' // 1f59 #11 - 'E' // 1f5a - 'L' // 1f5b #11 - 'E' // 1f5c - 'L' // 1f5d #11 - 'E' // 1f5e - '1dL' // 1f5f-1f7d #11 - 'aE' // 1f7e-1f7f - '1zL' // 1f80-1fb4 #11 - 'E' // 1fb5 - 'nL' // 1fb6-1fc4 #11 - 'E' // 1fc5 - 'mL' // 1fc6-1fd3 #11 - 'aE' // 1fd4-1fd5 - 'eL' // 1fd6-1fdb #11 - 'E' // 1fdc - 'rL' // 1fdd-1fef #11 - 'aE' // 1ff0-1ff1 - 'bL' // 1ff2-1ff4 #11 - 'E' // 1ff5 - 'hL' // 1ff6-1ffe #11 - 'E' // 1fff - 'L' // 2000 #11 - '3F' // 2001 #83 - 'Q' // 2002 #16 - '10H' // 2003 #267 - 'fL' // 2004-200a #11 - '9H' // 200b #241 - '9J' // 200c #243 - '9K' // 200d #244 - 'a9N' // 200e-200f #247 - '9I' // 2010 #242 - '9L' // 2011 #245 - 'Q' // 2012 #16 - 'aY' // 2013-2014 #24 - '4J' // 2015 #113 - 'Q' // 2016 #16 - 'L' // 2017 #11 - 'a5T' // 2018-2019 #149 - 'Y' // 201a #24 - '5W' // 201b #152 - 'a5T' // 201c-201d #149 - 'Y' // 201e #24 - 'L' // 201f #11 - 'a4J' // 2020-2021 #113 - 'Y' // 2022 #24 - 'L' // 2023 #11 - '9O' // 2024 #248 - '10N' // 2025 #273 - '8Y' // 2026 #232 - 'Q' // 2027 #16 - 'eL' // 2028-202d #11 - '6B' // 202e #157 - '9V' // 202f #255 - '4J' // 2030 #113 - 'L' // 2031 #11 - 'a2M' // 2032-2033 #64 - '1L' // 2034 #37 - '2M' // 2035 #64 - 'a1L' // 2036-2037 #37 - 'L' // 2038 #11 - 'aY' // 2039-203a #24 - 'Q' // 203b #16 - '10O' // 203c #274 - 'dL' // 203d-2041 #11 - 'Q' // 2042 #16 - 'L' // 2043 #11 - '9G' // 2044 #240 - 'aL' // 2045-2046 #11 - 'Q' // 2047 #16 - '10L' // 2048 #271 - '10M' // 2049 #272 - 'dL' // 204a-204e #11 - '5X' // 204f #153 - 'L' // 2050 #11 - 'Q' // 2051 #16 - 'L' // 2052 #11 - '1Z' // 2053 #51 - 'L' // 2054 #11 - '11C' // 2055 #288 - '1Z' // 2056 #51 - '1L' // 2057 #37 - 'a1Z' // 2058-2059 #51 - '10Y' // 205a #284 - 'aL' // 205b-205c #11 - '10U' // 205d #280 - '10X' // 205e #283 - 'eL' // 205f-2064 #11 - 'E' // 2065 - 'kL' // 2066-2071 #11 - 'aE' // 2072-2073 - '6A' // 2074 #156 - 'lL' // 2075-2081 #11 - 'b11E' // 2082-2084 #290 - 'iL' // 2085-208e #11 - 'E' // 208f - 'lL' // 2090-209c #11 - 'bE' // 209d-209f - 'hL' // 20a0-20a8 #11 - 'Q' // 20a9 #16 - '10P' // 20aa #275 - 'Q' // 20ab #16 - 'Y' // 20ac #24 - '10R' // 20ad #277 - 'jL' // 20ae-20b8 #11 - '9S' // 20b9 #252 - 'cL' // 20ba-20bd #11 - '5Z' // 20be #155 - 'aL' // 20bf-20c0 #11 - 'nE' // 20c1-20cf - 'jN' // 20d0-20da #13 - '13H' // 20db #345 - 'N' // 20dc #13 - '12G' // 20dd #318 - '1K' // 20de #36 - 'aS' // 20df-20e0 #18 - 'N' // 20e1 #13 - '7X' // 20e2 #205 - '14D' // 20e3 #367 - 'S' // 20e4 #18 - 'jN' // 20e5-20ef #13 - '10E' // 20f0 #264 - 'nE' // 20f1-20ff - 'Q' // 2100 #16 - 'L' // 2101 #11 - '1L' // 2102 #37 - 'Q' // 2103 #16 - 'L' // 2104 #11 - 'Q' // 2105 #16 - 'bL' // 2106-2108 #11 - 'Q' // 2109 #16 - '2M' // 210a #64 - 'c1L' // 210b-210e #37 - 'Q' // 210f #16 - 'b1L' // 2110-2112 #37 - 'Q' // 2113 #16 - 'L' // 2114 #11 - '1L' // 2115 #37 - '10J' // 2116 #269 - 'aL' // 2117-2118 #11 - 'd1L' // 2119-211d #37 - 'bL' // 211e-2120 #11 - 'Q' // 2121 #16 - '4H' // 2122 #111 - 'L' // 2123 #11 - '1L' // 2124 #37 - 'L' // 2125 #11 - 'aQ' // 2126-2127 #16 - '1L' // 2128 #37 - 'aL' // 2129-212a #11 - 'Q' // 212b #16 - 'a1L' // 212c-212d #37 - 'Q' // 212e #16 - 'b1L' // 212f-2131 #37 - 'L' // 2132 #11 - 'a1L' // 2133-2134 #37 - '2M' // 2135 #64 - 'b1L' // 2136-2138 #37 - '11G' // 2139 #292 - 'L' // 213a #11 - 'Q' // 213b #16 - 'd1L' // 213c-2140 #37 - 'cL' // 2141-2144 #11 - 'd1L' // 2145-2149 #37 - 'uL' // 214a-215f #11 - 'k1K' // 2160-216b #36 - 'cS' // 216c-216f #18 - 'k1K' // 2170-217b #36 - 'fS' // 217c-2182 #18 - '11D' // 2183 #289 - 'L' // 2184 #11 - 'cS' // 2185-2188 #18 - 'L' // 2189 #11 - 'aS' // 218a-218b #18 - 'cE' // 218c-218f - 'c6O' // 2190-2193 #170 - 'a12T' // 2194-2195 #331 - 'c12S' // 2196-2199 #330 - 'nN' // 219a-21a8 #13 - 'a13K' // 21a9-21aa #348 - 'cN' // 21ab-21ae #13 - 'M' // 21af #12 - 'gN' // 21b0-21b7 #13 - 'aT' // 21b8-21b9 #19 - 'iN' // 21ba-21c3 #13 - 'bT' // 21c4-21c6 #19 - 'cN' // 21c7-21ca #13 - 'aT' // 21cb-21cc #19 - 'bN' // 21cd-21cf #13 - 'T' // 21d0 #19 - 'N' // 21d1 #13 - 'T' // 21d2 #19 - 'N' // 21d3 #13 - 'T' // 21d4 #19 - 'pN' // 21d5-21e5 #13 - 'cZ' // 21e6-21e9 #25 - 'fM' // 21ea-21f0 #12 - 'aN' // 21f1-21f2 #13 - 'M' // 21f3 #12 - 'N' // 21f4 #13 - 'T' // 21f5 #19 - 'iN' // 21f6-21ff #13 - 'T' // 2200 #19 - 'N' // 2201 #13 - 'aT' // 2202-2203 #19 - 'N' // 2204 #13 - 'fT' // 2205-220b #19 - 'bN' // 220c-220e #13 - 'T' // 220f #19 - 'N' // 2210 #13 - 'T' // 2211 #19 - '1R' // 2212 #43 - 'T' // 2213 #19 - 'N' // 2214 #13 - 'T' // 2215 #19 - 'aN' // 2216-2217 #13 - '3K' // 2218 #88 - '13I' // 2219 #346 - 'T' // 221a #19 - 'aN' // 221b-221c #13 - 'cT' // 221d-2220 #19 - 'aN' // 2221-2222 #13 - 'T' // 2223 #19 - 'N' // 2224 #13 - 'iT' // 2225-222e #19 - 'dN' // 222f-2233 #13 - 'cT' // 2234-2237 #19 - 'dN' // 2238-223c #13 - 'T' // 223d #19 - 'dN' // 223e-2242 #13 - 'T' // 2243 #19 - 'N' // 2244 #13 - 'T' // 2245 #19 - 'aN' // 2246-2247 #13 - 'T' // 2248 #19 - 'bN' // 2249-224b #13 - 'T' // 224c #19 - 'rN' // 224d-225f #13 - 'bT' // 2260-2262 #19 - 'N' // 2263 #13 - 'cT' // 2264-2267 #19 - 'aN' // 2268-2269 #13 - 'aT' // 226a-226b #19 - 'aN' // 226c-226d #13 - 'aT' // 226e-226f #19 - 'aN' // 2270-2271 #13 - 'aT' // 2272-2273 #19 - 'aN' // 2274-2275 #13 - 'aT' // 2276-2277 #19 - 'iN' // 2278-2281 #13 - 'eT' // 2282-2287 #19 - 'aN' // 2288-2289 #13 - 'aT' // 228a-228b #19 - 'hN' // 228c-2294 #13 - 'cT' // 2295-2298 #19 - '2P' // 2299 #67 - 'eN' // 229a-229f #13 - 'T' // 22a0 #19 - 'cN' // 22a1-22a4 #13 - 'T' // 22a5 #19 - 'xN' // 22a6-22be #13 - 'T' // 22bf #19 - 'cN' // 22c0-22c3 #13 - 'b3K' // 22c4-22c6 #88 - 'rN' // 22c7-22d9 #13 - 'aT' // 22da-22db #19 - 'qN' // 22dc-22ed #13 - '12M' // 22ee #324 - 'T' // 22ef #19 - 'oN' // 22f0-22ff #13 - 'dS' // 2300-2304 #18 - 'b1K' // 2305-2307 #36 - 'c12H' // 2308-230b #319 - 'cS' // 230c-230f #18 - 'N' // 2310 #13 - 'S' // 2311 #18 - '1K' // 2312 #36 - 'bS' // 2313-2315 #18 - 'M' // 2316 #12 - 'S' // 2317 #18 - 'Z' // 2318 #25 - 'N' // 2319 #13 - 'aR' // 231a-231b #17 - 'c3J' // 231c-231f #87 - 'aN' // 2320-2321 #13 - 'aS' // 2322-2323 #18 - 'cM' // 2324-2327 #12 - 'R' // 2328 #17 - 'a1K' // 2329-232a #36 - 'M' // 232b #12 - 'iS' // 232c-2335 #18 - '2pN' // 2336-237a #13 - 'M' // 237b #12 - '3J' // 237c #87 - 'bM' // 237d-237f #12 - 'sS' // 2380-2393 #18 - '7X' // 2394 #205 - 'N' // 2395 #13 - 'dS' // 2396-239a #18 - 'sN' // 239b-23ae #13 - '3J' // 23af #87 - 'aT' // 23b0-23b1 #19 - 'dN' // 23b2-23b6 #13 - 'fE' // 23b7-23bd - 'n1K' // 23be-23cc #36 - 'S' // 23cd #18 - 'Z' // 23ce #25 - '1A' // 23cf #26 - '3J' // 23d0 #87 - 'hS' // 23d1-23d9 #18 - 'a1K' // 23da-23db #36 - 'eN' // 23dc-23e1 #13 - 'fS' // 23e2-23e8 #18 - 'a1A' // 23e9-23ea #26 - 'aX' // 23eb-23ec #23 - 'b1A' // 23ed-23ef #26 - 'U' // 23f0 #20 - 'bR' // 23f1-23f3 #17 - 'cM' // 23f4-23f7 #12 - 'b1A' // 23f8-23fa #26 - '1mM' // 23fb-2422 #12 - 'Z' // 2423 #25 - 'bM' // 2424-2426 #12 - 'xE' // 2427-243f - 'jM' // 2440-244a #12 - 'tE' // 244b-245f - 's1K' // 2460-2473 #36 - 'a6O' // 2474-2475 #170 - '2w1K' // 2476-24c1 #36 - '2X' // 24c2 #75 - '2h1K' // 24c3-24ff #36 - '6cA' // 2500-259f #0 - 'iZ' // 25a0-25a9 #25 - 'a2F' // 25aa-25ab #57 - 'bM' // 25ac-25ae #12 - '3K' // 25af #88 - 'M' // 25b0 #12 - 'aZ' // 25b1-25b2 #25 - '2P' // 25b3 #67 - 'aM' // 25b4-25b5 #12 - '2F' // 25b6 #57 - '2P' // 25b7 #67 - 'cM' // 25b8-25bb #12 - 'Z' // 25bc #25 - '2P' // 25bd #67 - 'aM' // 25be-25bf #12 - '2F' // 25c0 #57 - '2P' // 25c1 #67 - 'cM' // 25c2-25c5 #12 - 'aZ' // 25c6-25c7 #25 - 'M' // 25c8 #12 - 'Z' // 25c9 #25 - '2P' // 25ca #67 - 'Z' // 25cb #25 - '9C' // 25cc #236 - 'M' // 25cd #12 - 'eZ' // 25ce-25d3 #25 - 'mM' // 25d4-25e1 #12 - 'dZ' // 25e2-25e6 #25 - 'gM' // 25e7-25ee #12 - 'Z' // 25ef #25 - 'jM' // 25f0-25fa #12 - '13J' // 25fb #347 - 'b1A' // 25fc-25fe #26 - 'M' // 25ff #12 - 'a6T' // 2600-2601 #175 - '4V' // 2602 #125 - '6T' // 2603 #175 - '1I' // 2604 #34 - 'aZ' // 2605-2606 #25 - 'aM' // 2607-2608 #12 - 'Z' // 2609 #25 - 'cS' // 260a-260d #18 - '4V' // 260e #125 - 'Z' // 260f #25 - 'M' // 2610 #12 - '1A' // 2611 #26 - 'M' // 2612 #12 - 'S' // 2613 #18 - '1I' // 2614 #34 - '3O' // 2615 #92 - 'aZ' // 2616-2617 #25 - '1I' // 2618 #34 - 'bM' // 2619-261b #12 - 'Z' // 261c #25 - '13B' // 261d #339 - 'aZ' // 261e-261f #25 - '12J' // 2620 #321 - 'M' // 2621 #12 - 'a1A' // 2622-2623 #26 - 'aS' // 2624-2625 #18 - '2I' // 2626 #60 - 'bS' // 2627-2629 #18 - '2I' // 262a #60 - 'S' // 262b #18 - '12Q' // 262c #328 - 'S' // 262d #18 - '2I' // 262e #60 - '2X' // 262f #75 - 'gM' // 2630-2637 #12 - '14F' // 2638 #369 - 'a8A' // 2639-263a #208 - 'S' // 263b #18 - 'M' // 263c #12 - 'bS' // 263d-263f #18 - '6S' // 2640 #174 - '1K' // 2641 #36 - '6S' // 2642 #174 - 'dS' // 2643-2647 #18 - 'k2I' // 2648-2653 #60 - 'jM' // 2654-265e #12 - '1J' // 265f #35 - '2F' // 2660 #57 - 'aZ' // 2661-2662 #25 - '2F' // 2663 #57 - 'Z' // 2664 #25 - '12Y' // 2665 #336 - '2F' // 2666 #57 - 'Z' // 2667 #25 - '2F' // 2668 #57 - 'c9E' // 2669-266c #238 - 'b9D' // 266d-266f #237 - 'a14E' // 2670-2671 #368 - 'h1K' // 2672-267a #36 - '2X' // 267b #75 - 'a1K' // 267c-267d #36 - '2I' // 267e #60 - '1A' // 267f #26 - 'oM' // 2680-268f #12 - 'aS' // 2690-2691 #18 - '3N' // 2692 #91 - '2Z' // 2693 #77 - '3N' // 2694 #91 - '14H' // 2695 #371 - '14I' // 2696 #372 - '3N' // 2697 #91 - 'S' // 2698 #18 - '3N' // 2699 #91 - 'S' // 269a #18 - 'a2I' // 269b-269c #60 - 'S' // 269d #18 - 'aM' // 269e-269f #12 - '2F' // 26a0 #57 - '1I' // 26a1 #34 - 'dS' // 26a2-26a6 #18 - '14G' // 26a7 #370 - 'aS' // 26a8-26a9 #18 - 'a1A' // 26aa-26ab #26 - 'M' // 26ac #12 - 'bS' // 26ad-26af #18 - 'a3N' // 26b0-26b1 #91 - 'jS' // 26b2-26bc #18 - 'a13A' // 26bd-26be #338 - 'dM' // 26bf-26c3 #12 - 'a1I' // 26c4-26c5 #34 - 'aM' // 26c6-26c7 #12 - '1I' // 26c8 #34 - 'dM' // 26c9-26cd #12 - '2I' // 26ce #60 - 'R' // 26cf #17 - 'M' // 26d0 #12 - 'R' // 26d1 #17 - 'M' // 26d2 #12 - '14Q' // 26d3 #380 - '1A' // 26d4 #26 - 'lM' // 26d5-26e1 #12 - 'fS' // 26e2-26e8 #18 - 'a2Z' // 26e9-26ea #77 - 'dS' // 26eb-26ef #18 - '14J' // 26f0 #373 - 'a2Z' // 26f1-26f2 #77 - '7Y' // 26f3 #206 - 'a2Z' // 26f4-26f5 #77 - 'S' // 26f6 #18 - '7Z' // 26f7 #207 - '7Y' // 26f8 #206 - '7Z' // 26f9 #207 - '2Z' // 26fa #77 - 'aS' // 26fb-26fc #18 - '2Z' // 26fd #77 - 'aS' // 26fe-26ff #18 - 'aM' // 2700-2701 #12 - '4V' // 2702 #125 - 'aM' // 2703-2704 #12 - 'X' // 2705 #23 - 'aM' // 2706-2707 #12 - '8E' // 2708 #212 - 'R' // 2709 #17 - 'W' // 270a #22 - 'b2E' // 270b-270d #56 - 'M' // 270e #12 - 'R' // 270f #17 - 'aM' // 2710-2711 #12 - 'R' // 2712 #17 - 'Z' // 2713 #25 - '1A' // 2714 #26 - 'M' // 2715 #12 - '1A' // 2716 #26 - 'bM' // 2717-2719 #12 - 'Z' // 271a #25 - 'aM' // 271b-271c #12 - '12I' // 271d #320 - 'bS' // 271e-2720 #18 - '2I' // 2721 #60 - 'eM' // 2722-2727 #12 - '3D' // 2728 #81 - 'iM' // 2729-2732 #12 - 'a1A' // 2733-2734 #26 - 'gM' // 2735-273c #12 - 'Z' // 273d #25 - 'M' // 273e #12 - 'aZ' // 273f-2740 #25 - 'bM' // 2741-2743 #12 - '1I' // 2744 #34 - 'aM' // 2745-2746 #12 - '1A' // 2747 #26 - 'cM' // 2748-274b #12 - 'X' // 274c #23 - 'M' // 274d #12 - 'X' // 274e #23 - 'cM' // 274f-2752 #12 - '1A' // 2753 #26 - 'aX' // 2754-2755 #23 - 'Z' // 2756 #25 - '1A' // 2757 #26 - 'jM' // 2758-2762 #12 - '2E' // 2763 #56 - '14M' // 2764 #376 - 'pM' // 2765-2775 #12 - '1c1K' // 2776-2793 #36 - 'M' // 2794 #12 - 'bX' // 2795-2797 #23 - 'hM' // 2798-27a0 #12 - '12Z' // 27a1 #337 - 'mM' // 27a2-27af #12 - 'X' // 27b0 #23 - 'mM' // 27b1-27be #12 - 'X' // 27bf #23 - '2kN' // 27c0-27ff #13 - '9uM' // 2800-28ff #12 - '1fN' // 2900-2920 #13 - 'a3J' // 2921-2922 #87 - 'pN' // 2923-2933 #13 - 'a12U' // 2934-2935 #332 - '2vN' // 2936-2980 #13 - '3K' // 2981 #88 - '2hN' // 2982-29be #13 - '2P' // 29bf #67 - '1pN' // 29c0-29ea #13 - '3K' // 29eb #88 - 'mN' // 29ec-29f9 #13 - 'aT' // 29fa-29fb #19 - '9yN' // 29fc-2aff #13 - 'dM' // 2b00-2b04 #12 - 'b2F' // 2b05-2b07 #57 - 'eM' // 2b08-2b0d #12 - 'cN' // 2b0e-2b11 #13 - 'gM' // 2b12-2b19 #12 - 'Z' // 2b1a #25 - '14L' // 2b1b #375 - '1A' // 2b1c #26 - 'rM' // 2b1d-2b2f #12 - '1bN' // 2b30-2b4c #13 - 'bM' // 2b4d-2b4f #12 - '4G' // 2b50 #110 - 'cM' // 2b51-2b54 #12 - '1A' // 2b55 #26 - '1cM' // 2b56-2b73 #12 - 'aE' // 2b74-2b75 - '1dM' // 2b76-2b94 #12 - 'Z' // 2b95 #25 - 'E' // 2b96 - '3xM' // 2b97-2bfd #12 - 'N' // 2bfe #13 - 'M' // 2bff #12 - '1t2O' // 2c00-2c2e #66 - 'E' // 2c2f - '1t2O' // 2c30-2c5e #66 - 'E' // 2c5f - '1eL' // 2c60-2c7f #11 - '4k4R' // 2c80-2cf3 #121 - 'dE' // 2cf4-2cf8 - 'f4R' // 2cf9-2cff #121 - '1k2C' // 2d00-2d25 #54 - 'E' // 2d26 - '2C' // 2d27 #54 - 'dE' // 2d28-2d2c - '2C' // 2d2d #54 - 'aE' // 2d2e-2d2f - '2c5K' // 2d30-2d67 #140 - 'fE' // 2d68-2d6e - 'a5K' // 2d6f-2d70 #140 - 'mE' // 2d71-2d7e - '5K' // 2d7f #140 - 'vV' // 2d80-2d96 #21 - 'hE' // 2d97-2d9f - 'fV' // 2da0-2da6 #21 - 'E' // 2da7 - 'fV' // 2da8-2dae #21 - 'E' // 2daf - 'fV' // 2db0-2db6 #21 - 'E' // 2db7 - 'fV' // 2db8-2dbe #21 - 'E' // 2dbf - 'fV' // 2dc0-2dc6 #21 - 'E' // 2dc7 - 'fV' // 2dc8-2dce #21 - 'E' // 2dcf - 'fV' // 2dd0-2dd6 #21 - 'E' // 2dd7 - 'fV' // 2dd8-2dde #21 - 'E' // 2ddf - '2bL' // 2de0-2e16 #11 - '1Z' // 2e17 #51 - 'cL' // 2e18-2e1b #11 - 'a10V' // 2e1c-2e1d #281 - 'iL' // 2e1e-2e27 #11 - 'a5W' // 2e28-2e29 #152 - 'eL' // 2e2a-2e2f #11 - 'a9P' // 2e30-2e31 #249 - 'L' // 2e32 #11 - 'a1Z' // 2e33-2e34 #51 - 'dL' // 2e35-2e39 #11 - 'aQ' // 2e3a-2e3b #16 - '3F' // 2e3c #83 - 'bL' // 2e3d-2e3f #11 - '3F' // 2e40 #83 - '5X' // 2e41 #153 - '1aL' // 2e42-2e5d #11 - '1gE' // 2e5e-2e7f - 'yA' // 2e80-2e99 #0 - 'E' // 2e9a - '3jA' // 2e9b-2ef3 #0 - 'kE' // 2ef4-2eff - '8eA' // 2f00-2fd5 #0 - 'yE' // 2fd6-2fef - 'kA' // 2ff0-2ffb #0 - 'cE' // 2ffc-2fff - '6Q' // 3000 #172 - 'a6P' // 3001-3002 #171 - 'cA' // 3003-3006 #0 - '12X' // 3007 #335 - 'a6P' // 3008-3009 #171 - 'a12R' // 300a-300b #329 - 'c12W' // 300c-300f #334 - 'a6R' // 3010-3011 #173 - 'aA' // 3012-3013 #0 - 'g6R' // 3014-301b #173 - 'sA' // 301c-302f #0 - '2D' // 3030 #55 - 'kA' // 3031-303c #0 - '2D' // 303d #55 - 'aA' // 303e-303f #0 - 'E' // 3040 - '3gA' // 3041-3096 #0 - 'aE' // 3097-3098 - '3sA' // 3099-30fa #0 - '4W' // 30fb #126 - 'cA' // 30fc-30ff #0 - 'dE' // 3100-3104 - '1pA' // 3105-312f #0 - 'E' // 3130 - '1xA' // 3131-3163 #0 - 'P' // 3164 #15 - '1oA' // 3165-318e #0 - 'E' // 318f - '1qA' // 3190-31bb #0 - 'cE' // 31bc-31bf - '1iA' // 31c0-31e3 #0 - 'kE' // 31e4-31ef - '1tA' // 31f0-321e #0 - 'E' // 321f - '4nA' // 3220-3296 #0 - '2D' // 3297 #55 - 'A' // 3298 #0 - '2D' // 3299 #55 - '5oA' // 329a-332b #0 - 'E' // 332c - '8bA' // 332d-33ff #0 - 'aD' // 3400-3401 #3 - 'F' // 3402 #5 - 'aD' // 3403-3404 #3 - 'aF' // 3405-3406 #5 - '1eD' // 3407-3426 #3 - 'F' // 3427 #5 - 'cD' // 3428-342b #3 - 'F' // 342c #5 - 'D' // 342d #3 - 'F' // 342e #5 - 'eD' // 342f-3434 #3 - 'B' // 3435 #1 - 'iD' // 3436-343f #3 - 'B' // 3440 #1 - 'hD' // 3441-3449 #3 - 'B' // 344a #1 - 'D' // 344b #3 - 'B' // 344c #1 - 'vD' // 344d-3463 #3 - 'B' // 3464 #1 - 'bD' // 3465-3467 #3 - 'F' // 3468 #5 - 'D' // 3469 #3 - 'F' // 346a #5 - 'gD' // 346b-3472 #3 - 'B' // 3473 #1 - 'eD' // 3474-3479 #3 - 'B' // 347a #1 - 'aD' // 347b-347c #3 - 'aB' // 347d-347e #1 - 'hD' // 347f-3487 #3 - 'F' // 3488 #5 - 'hD' // 3489-3491 #3 - 'F' // 3492 #5 - 'B' // 3493 #1 - 'aD' // 3494-3495 #3 - 'B' // 3496 #1 - 'bD' // 3497-3499 #3 - 'K' // 349a #10 - 'iD' // 349b-34a4 #3 - 'B' // 34a5 #1 - 'hD' // 34a6-34ae #3 - 'B' // 34af #1 - 'dD' // 34b0-34b4 #3 - 'F' // 34b5 #5 - 'eD' // 34b6-34bb #3 - 'C' // 34bc #2 - 'cD' // 34bd-34c0 #3 - 'C' // 34c1 #2 - 'dD' // 34c2-34c6 #3 - 'F' // 34c7 #5 - 'B' // 34c8 #1 - 'mD' // 34c9-34d6 #3 - 'K' // 34d7 #10 - 'bD' // 34d8-34da #3 - 'F' // 34db #5 - 'bD' // 34dc-34de #3 - 'B' // 34df #1 - 'cD' // 34e0-34e3 #3 - 'B' // 34e4 #1 - 'D' // 34e5 #3 - 'B' // 34e6 #1 - 'sD' // 34e7-34fa #3 - 'B' // 34fb #1 - 'iD' // 34fc-3505 #3 - 'B' // 3506 #1 - 'mD' // 3507-3514 #3 - 'K' // 3515 #10 - 'hD' // 3516-351e #3 - 'F' // 351f #5 - 'D' // 3520 #3 - 'K' // 3521 #10 - '1aD' // 3522-353d #3 - 'A' // 353e #0 - 'qD' // 353f-3550 #3 - 'B' // 3551 #1 - 'D' // 3552 #3 - 'B' // 3553 #1 - 'dD' // 3554-3558 #3 - 'B' // 3559 #1 - 'bD' // 355a-355c #3 - 'aF' // 355d-355e #5 - 'aD' // 355f-3560 #3 - 'B' // 3561 #1 - 'D' // 3562 #3 - 'F' // 3563 #5 - 'hD' // 3564-356c #3 - 'B' // 356d #1 - 'F' // 356e #5 - 'D' // 356f #3 - 'B' // 3570 #1 - 'D' // 3571 #3 - 'B' // 3572 #1 - 'cD' // 3573-3576 #3 - 'aB' // 3577-3578 #1 - 'jD' // 3579-3583 #3 - 'B' // 3584 #1 - 'qD' // 3585-3596 #3 - 'aB' // 3597-3598 #1 - 'gD' // 3599-35a0 #3 - 'B' // 35a1 #1 - 'bD' // 35a2-35a4 #3 - 'B' // 35a5 #1 - 'F' // 35a6 #5 - 'D' // 35a7 #3 - 'F' // 35a8 #5 - 'cD' // 35a9-35ac #3 - 'B' // 35ad #1 - 'pD' // 35ae-35be #3 - 'B' // 35bf #1 - 'D' // 35c0 #3 - 'B' // 35c1 #1 - 'bD' // 35c2-35c4 #3 - 'C' // 35c5 #2 - 'D' // 35c6 #3 - 'B' // 35c7 #1 - 'aD' // 35c8-35c9 #3 - 'B' // 35ca #1 - 'bD' // 35cb-35cd #3 - 'B' // 35ce #1 - 'bD' // 35cf-35d1 #3 - 'B' // 35d2 #1 - 'bD' // 35d3-35d5 #3 - 'B' // 35d6 #1 - 'bD' // 35d7-35d9 #3 - 'F' // 35da #5 - 'B' // 35db #1 - 'D' // 35dc #3 - 'B' // 35dd #1 - 'F' // 35de #5 - 'qD' // 35df-35f0 #3 - 'bB' // 35f1-35f3 #1 - 'F' // 35f4 #5 - 'eD' // 35f5-35fa #3 - 'B' // 35fb #1 - 'aD' // 35fc-35fd #3 - 'B' // 35fe #1 - 'K' // 35ff #10 - 'dD' // 3600-3604 #3 - 'F' // 3605 #5 - 'bD' // 3606-3608 #3 - 'B' // 3609 #1 - 'iD' // 360a-3613 #3 - 'F' // 3614 #5 - 'bD' // 3615-3617 #3 - 'B' // 3618 #1 - 'D' // 3619 #3 - 'B' // 361a #1 - 'gD' // 361b-3622 #3 - 'B' // 3623 #1 - 'D' // 3624 #3 - 'B' // 3625 #1 - 'fD' // 3626-362c #3 - 'B' // 362d #1 - 'fD' // 362e-3634 #3 - 'B' // 3635 #1 - 'bD' // 3636-3638 #3 - 'B' // 3639 #1 - 'cD' // 363a-363d #3 - 'B' // 363e #1 - 'gD' // 363f-3646 #3 - 'bB' // 3647-3649 #1 - 'F' // 364a #5 - 'bD' // 364b-364d #3 - 'B' // 364e #1 - 'oD' // 364f-365e #3 - 'B' // 365f #1 - 'D' // 3660 #3 - 'B' // 3661 #1 - 'lD' // 3662-366e #3 - 'K' // 366f #10 - 'iD' // 3670-3679 #3 - 'B' // 367a #1 - 'eD' // 367b-3680 #3 - 'B' // 3681 #1 - 'nD' // 3682-3690 #3 - 'F' // 3691 #5 - 'cD' // 3692-3695 #3 - 'F' // 3696 #5 - 'aD' // 3697-3698 #3 - 'F' // 3699 #5 - 'B' // 369a #1 - 'iD' // 369b-36a4 #3 - 'B' // 36a5 #1 - 'cD' // 36a6-36a9 #3 - 'B' // 36aa #1 - 'D' // 36ab #3 - 'B' // 36ac #1 - 'bD' // 36ad-36af #3 - 'aB' // 36b0-36b1 #1 - 'bD' // 36b2-36b4 #3 - 'B' // 36b5 #1 - 'bD' // 36b6-36b8 #3 - 'B' // 36b9 #1 - 'aD' // 36ba-36bb #3 - 'B' // 36bc #1 - 'cD' // 36bd-36c0 #3 - 'B' // 36c1 #1 - 'D' // 36c2 #3 + '1eA' // 0-1f + '75Z' // 20 #1975 + '76N' // 21 #1989 + '76P' // 22 #1991 + '73Z' // 23 #1923 + '76O' // 24 #1990 + '76Y' // 25 #2000 + '77B' // 26 #2003 + 'b24K' // 27-29 #634 + '51J' // 2a #1335 + '77E' // 2b #2006 + '24K' // 2c #634 + '77H' // 2d #2009 + 'a24K' // 2e-2f #634 + 'h51J' // 30-38 #1335 + '74A' // 39 #1924 + 'a24K' // 3a-3b #634 + '76V' // 3c #1997 + '76X' // 3d #1999 + '77D' // 3e #2005 + '77G' // 3f #2008 + '76Z' // 40 #2001 + 'c27P' // 41-44 #717 + '42N' // 45 #1105 + 'a27P' // 46-47 #717 + '76D' // 48 #1979 + '76F' // 49 #1981 + 'b27P' // 4a-4c #717 + '76E' // 4d #1980 + '76C' // 4e #1978 + '42N' // 4f #1105 + 'd27P' // 50-54 #717 + '42N' // 55 #1105 + 'd27P' // 56-5a #717 + 'b24K' // 5b-5d #634 + '76U' // 5e #1996 + '77A' // 5f #2002 + '76T' // 60 #1995 + 'y27P' // 61-7a #717 + '24K' // 7b #634 + '77F' // 7c #2007 + '24K' // 7d #634 + '77C' // 7e #2004 + 'F' // 7f #5 + '1eA' // 80-9f + '76A' // a0 #1976 + '79G' // a1 #2060 + '9A' // a2 #234 + '79J' // a3 #2063 + '48U' // a4 #1268 + '79M' // a5 #2066 + '62Y' // a6 #1636 + '76R' // a7 #1993 + '79H' // a8 #2061 + '74K' // a9 #1934 + '9A' // aa #234 + '42U' // ab #1112 + '121E' // ac #3150 + '247O' // ad #6436 + '51L' // ae #1337 + '79I' // af #2062 + '79N' // b0 #2067 + '121F' // b1 #3151 + '245W' // b2 #6392 + '245V' // b3 #6391 + '79L' // b4 #2065 + '62Y' // b5 #1636 + '9A' // b6 #234 + '79P' // b7 #2069 + '9A' // b8 #234 + '48U' // b9 #1268 + '9A' // ba #234 + '42U' // bb #1112 + 'b48U' // bc-be #1268 + '79S' // bf #2072 + '9A' // c0 #234 + '21S' // c1 #564 + '9A' // c2 #234 + 'b21S' // c3-c5 #564 + 'b9A' // c6-c8 #234 + '42T' // c9 #1111 + 'a9A' // ca-cb #234 + '21S' // cc #564 + '42V' // cd #1113 + 'b9A' // ce-d0 #234 + 'b21S' // d1-d3 #564 + '9A' // d4 #234 + '21S' // d5 #564 + '42V' // d6 #1113 + '76W' // d7 #1998 + '42S' // d8 #1110 + '9A' // d9 #234 + '21S' // da #564 + '9A' // db #234 + '42S' // dc #1110 + '21S' // dd #564 + '9A' // de #234 + '79F' // df #2059 + '52O' // e0 #1366 + '27Q' // e1 #718 + '52O' // e2 #1366 + 'b27Q' // e3-e5 #718 + '52N' // e6 #1365 + '79X' // e7 #2077 + '79U' // e8 #2074 + '79W' // e9 #2076 + '52P' // ea #1367 + '42T' // eb #1111 + 'a27Q' // ec-ed #718 + '52N' // ee #1365 + '42T' // ef #1111 + '9A' // f0 #234 + 'b27Q' // f1-f3 #718 + '79T' // f4 #2073 + '21S' // f5 #564 + '27Q' // f6 #718 + '76S' // f7 #1994 + '42S' // f8 #1110 + '79R' // f9 #2071 + '27Q' // fa #718 + '9A' // fb #234 + '52P' // fc #1367 + '42V' // fd #1113 + 'a9A' // fe-ff #234 + '32D' // 100 #835 + '36V' // 101 #957 + '78T' // 102 #2047 + '78X' // 103 #2051 + 'c8C' // 104-107 #210 + 'aE' // 108-109 #4 + 'e8C' // 10a-10f #210 + '78W' // 110 #2050 + '78V' // 111 #2049 + '32D' // 112 #835 + '36V' // 113 #957 + 'aE' // 114-115 #4 + 'c8C' // 116-119 #210 + '42R' // 11a #1109 + '32D' // 11b #835 + 'aE' // 11c-11d #4 + 'e8C' // 11e-123 #210 + 'aE' // 124-125 #4 + 'a8C' // 126-127 #210 + '49J' // 128 #1283 + '62X' // 129 #1635 + '78Z' // 12a #2053 + '36V' // 12b #957 + 'aE' // 12c-12d #4 + 'b8C' // 12e-130 #210 + '42W' // 131 #1114 + 'a80C' // 132-133 #2082 + 'aE' // 134-135 #4 + 'a8C' // 136-137 #210 + 'E' // 138 #4 + 'e8C' // 139-13e #210 + 'aE' // 13f-140 #4 + 'a8C' // 141-142 #210 + '42R' // 143 #1109 + '32D' // 144 #835 + 'a8C' // 145-146 #210 + '42R' // 147 #1109 + '78U' // 148 #2048 + 'E' // 149 #4 + 'a52L' // 14a-14b #1363 + '52K' // 14c #1362 + '79A' // 14d #2054 + '21F' // 14e #551 + '121C' // 14f #3148 + 'a8C' // 150-151 #210 + 'a9A' // 152-153 #234 + 'a8C' // 154-155 #210 + 'a52L' // 156-157 #1363 + 'c8C' // 158-15b #210 + 'aE' // 15c-15d #4 + 'c8C' // 15e-161 #210 + 'aE' // 162-163 #4 + 'a8C' // 164-165 #210 + 'aE' // 166-167 #4 + '247N' // 168 #6435 + '62X' // 169 #1635 + '32D' // 16a #835 + '36V' // 16b #957 + '79B' // 16c #2055 + '52K' // 16d #1362 + 'p8C' // 16e-17e #210 + 'rE' // 17f-191 #4 + '121B' // 192 #3147 + 'lE' // 193-19f #4 + '121A' // 1a0 #3146 + '245T' // 1a1 #6389 + 'lE' // 1a2-1ae #4 + '120Z' // 1af #3145 + '245U' // 1b0 #6390 + '1aE' // 1b1-1cc #4 + '80B' // 1cd #2081 + '80A' // 1ce #2080 + '21F' // 1cf #551 + '121D' // 1d0 #3149 + '21F' // 1d1 #551 + '35S' // 1d2 #928 + '21F' // 1d3 #551 + '35S' // 1d4 #928 + '21F' // 1d5 #551 + '49K' // 1d6 #1284 + '21F' // 1d7 #551 + '49K' // 1d8 #1284 + '21F' // 1d9 #551 + '35S' // 1da #928 + '21F' // 1db #551 + '35S' // 1dc #928 + 'zE' // 1dd-1f7 #4 + '49K' // 1f8 #1284 + '35S' // 1f9 #928 + '1cE' // 1fa-217 #4 + 'c8C' // 218-21b #210 + 'zE' // 21c-236 #4 + '8C' // 237 #210 + 'xE' // 238-250 #4 + '62W' // 251 #1634 + 'nE' // 252-260 #4 + '62W' // 261 #1634 + '3hE' // 262-2b8 #4 + '17K' // 2b9 #452 + 'E' // 2ba #4 + '120W' // 2bb #3142 + '261T' // 2bc #6805 + 'hE' // 2bd-2c5 #4 + '42W' // 2c6 #1114 + '79C' // 2c7 #2056 + 'E' // 2c8 #4 + '79D' // 2c9 #2057 + '124Y' // 2ca #3248 + '135O' // 2cb #3524 + 'E' // 2cc #4 + '262J' // 2cd #6821 + 'hE' // 2ce-2d6 #4 + '262W' // 2d7 #6834 + '41V' // 2d8 #1087 + '129Z' // 2d9 #3379 + '79Z' // 2da #2079 + '261J' // 2db #6795 + '42W' // 2dc #1114 + '79E' // 2dd #2058 + 'kE' // 2de-2e9 #4 + '41T' // 2ea #1085 + '120Y' // 2eb #3144 + 'sE' // 2ec-2ff #4 + '78R' // 300 #2045 + '78Q' // 301 #2044 + '77J' // 302 #2011 + '52C' // 303 #1354 + '78P' // 304 #2043 + '262A' // 305 #6812 + '41V' // 306 #1087 + '77I' // 307 #2010 + '52C' // 308 #1354 + '71L' // 309 #1857 + 'a41V' // 30a-30b #1087 + '120X' // 30c #3143 + 'E' // 30d #4 + '262E' // 30e #6816 + 'aE' // 30f-310 #4 + '17K' // 311 #452 + '77L' // 312 #2013 + '262Q' // 313 #6828 + 'kE' // 314-31f #4 + '50K' // 320 #1310 + 'aE' // 321-322 #4 + '80E' // 323 #2084 + '261Z' // 324 #6811 + '50K' // 325 #1310 + 'a77K' // 326-327 #2012 + '41V' // 328 #1087 + 'cE' // 329-32c #4 + 'a50K' // 32d-32e #1310 + 'E' // 32f #4 + '261Y' // 330 #6810 + '261V' // 331 #6807 + 'lE' // 332-33e #4 + '17K' // 33f #452 + 'nE' // 340-34e #4 + '261M' // 34f #6798 + 'gE' // 350-357 #4 + '262S' // 358 #6830 + 'E' // 359 #4 + '262R' // 35a #6829 + 'bE' // 35b-35d #4 + '261W' // 35e #6808 + 'aE' // 35f-360 #4 + '17K' // 361 #452 + 'qE' // 362-373 #4 + 'a17K' // 374-375 #452 + 'aE' // 376-377 #4 + 'aA' // 378-379 + 'eE' // 37a-37f #4 + 'cA' // 380-383 + 'fE' // 384-38a #4 + 'A' // 38b + 'E' // 38c #4 + 'A' // 38d + 'bE' // 38e-390 #4 + 'c50G' // 391-394 #1306 + '261I' // 395 #6794 + 'k50G' // 396-3a1 #1306 + 'A' // 3a2 + 'f50G' // 3a3-3a9 #1306 + 'fE' // 3aa-3b0 #4 + '261G' // 3b1 #6792 + 'd36N' // 3b2-3b6 #949 + '71G' // 3b7 #1852 + '36N' // 3b8 #949 + '50H' // 3b9 #1307 + '71G' // 3ba #1852 + '50H' // 3bb #1307 + '36N' // 3bc #949 + '50H' // 3bd #1307 + 'c36N' // 3be-3c1 #949 + '8B' // 3c2 #209 + 'f36N' // 3c3-3c9 #949 + 'fE' // 3ca-3d0 #4 + '8B' // 3d1 #209 + 'bE' // 3d2-3d4 #4 + 'a8B' // 3d5-3d6 #209 + 'bE' // 3d7-3d9 #4 + '41W' // 3da #1088 + 'E' // 3db #4 + '41W' // 3dc #1088 + 'E' // 3dd #4 + '41W' // 3de #1088 + 'E' // 3df #4 + '41W' // 3e0 #1088 + 'E' // 3e1 #4 + 'm50R' // 3e2-3ef #1317 + 'a8B' // 3f0-3f1 #209 + 'aE' // 3f2-3f3 #4 + 'a8B' // 3f4-3f5 #209 + 'jE' // 3f6-400 #4 + '49I' // 401 #1282 + 'mE' // 402-40f #4 + '2k49I' // 410-44f #1282 + 'E' // 450 #4 + '49I' // 451 #1282 + '1vE' // 452-482 #4 + '262F' // 483 #6817 + '41X' // 484 #1089 + 'aE' // 485-486 #4 + '41X' // 487 #1089 + '6kE' // 488-52f #4 + 'A' // 530 + '1k36P' // 531-556 #951 + 'aA' // 557-558 + '1u36P' // 559-588 #951 + '263E' // 589 #6842 + '36P' // 58a #951 + 'aA' // 58b-58c + 'b36P' // 58d-58f #951 + 'A' // 590 + '2b21R' // 591-5c7 #563 + 'gA' // 5c8-5cf + 'z21R' // 5d0-5ea #563 + 'cA' // 5eb-5ee + 'e21R' // 5ef-5f4 #563 + 'jA' // 5f5-5ff + 'd4W' // 600-604 #126 + '263A' // 605 #6838 + 'e4W' // 606-60b #126 + '50M' // 60c #1312 + 'm4W' // 60d-61a #126 + '50M' // 61b #1312 + 'b4W' // 61c-61e #126 + '262Y' // 61f #6836 + '4W' // 620 #126 + '50N' // 621 #1313 + 'd4W' // 622-626 #126 + '71M' // 627 #1858 + 'w4W' // 628-63f #126 + '262X' // 640 #6835 + 'i4W' // 641-64a #126 + 'j50N' // 64b-655 #1313 + 'i4W' // 656-65f #126 + 'i263B' // 660-669 #6839 + '50M' // 66a #1312 + 'a263D' // 66b-66c #6841 + '41Y' // 66d #1090 + 'a4W' // 66e-66f #126 + '50N' // 670 #1313 + '3t4W' // 671-6d3 #126 + '41Y' // 6d4 #1090 + 'z4W' // 6d5-6ef #126 + 'i71M' // 6f0-6f9 #1858 + 'e4W' // 6fa-6ff #126 + 'm51E' // 700-70d #1330 + 'A' // 70e + '2g51E' // 70f-74a #1330 + 'aA' // 74b-74c + 'b51E' // 74d-74f #1330 + '1u4W' // 750-77f #126 + '1w264Z' // 780-7b1 #6889 + 'mA' // 7b2-7bf + '2f72K' // 7c0-7fa #1882 + 'aA' // 7fb-7fc + 'b72K' // 7fd-7ff #1882 + '2kA' // 800-83f + '1a72G' // 840-85b #1878 + 'aA' // 85c-85d + '72G' // 85e #1878 + 'pA' // 85f-86f + '1d4W' // 870-88e #126 + 'A' // 88f + 'a4W' // 890-891 #126 + 'eA' // 892-897 + '3y4W' // 898-8ff #126 + '2h36O' // 900-93c #950 + '262D' // 93d #6815 + 'r36O' // 93e-950 #950 + 'a261R' // 951-952 #6803 + 'p36O' // 953-963 #950 + 'a261Q' // 964-965 #6802 + 'i262C' // 966-96f #6814 + 'o36O' // 970-97f #950 + 'c11N' // 980-983 #299 + 'A' // 984 + 'g11N' // 985-98c #299 + 'aA' // 98d-98e + 'a11N' // 98f-990 #299 + 'aA' // 991-992 + 'u11N' // 993-9a8 #299 + 'A' // 9a9 + 'f11N' // 9aa-9b0 #299 + 'A' // 9b1 + '11N' // 9b2 #299 + 'bA' // 9b3-9b5 + 'c11N' // 9b6-9b9 #299 + 'aA' // 9ba-9bb + 'h11N' // 9bc-9c4 #299 + 'aA' // 9c5-9c6 + 'a11N' // 9c7-9c8 #299 + 'aA' // 9c9-9ca + 'c11N' // 9cb-9ce #299 + 'gA' // 9cf-9d6 + '11N' // 9d7 #299 + 'cA' // 9d8-9db + 'a11N' // 9dc-9dd #299 + 'A' // 9de + 'd11N' // 9df-9e3 #299 + 'aA' // 9e4-9e5 + 'i263F' // 9e6-9ef #6843 + 'c11N' // 9f0-9f3 #299 + 'c263I' // 9f4-9f7 #6846 + 'f11N' // 9f8-9fe #299 + 'aA' // 9ff-a00 + 'b11P' // a01-a03 #301 + 'A' // a04 + 'e11P' // a05-a0a #301 + 'cA' // a0b-a0e + 'a11P' // a0f-a10 #301 + 'aA' // a11-a12 + 'u11P' // a13-a28 #301 + 'A' // a29 + 'f11P' // a2a-a30 #301 + 'A' // a31 + 'a11P' // a32-a33 #301 + 'A' // a34 + 'a11P' // a35-a36 #301 + 'A' // a37 + 'a11P' // a38-a39 #301 + 'aA' // a3a-a3b + '11P' // a3c #301 + 'A' // a3d + 'd11P' // a3e-a42 #301 + 'cA' // a43-a46 + 'a11P' // a47-a48 #301 + 'aA' // a49-a4a + 'b11P' // a4b-a4d #301 + 'bA' // a4e-a50 + '11P' // a51 #301 + 'fA' // a52-a58 + 'c11P' // a59-a5c #301 + 'A' // a5d + '11P' // a5e #301 + 'fA' // a5f-a65 + 'i264A' // a66-a6f #6864 + 'f11P' // a70-a76 #301 + 'iA' // a77-a80 + 'b13P' // a81-a83 #353 + 'A' // a84 + 'h13P' // a85-a8d #353 + 'A' // a8e + 'b13P' // a8f-a91 #353 + 'A' // a92 + 'u13P' // a93-aa8 #353 + 'A' // aa9 + 'f13P' // aaa-ab0 #353 + 'A' // ab1 + 'a13P' // ab2-ab3 #353 + 'A' // ab4 + 'd13P' // ab5-ab9 #353 + 'aA' // aba-abb + 'i13P' // abc-ac5 #353 + 'A' // ac6 + 'b13P' // ac7-ac9 #353 + 'A' // aca + 'b13P' // acb-acd #353 + 'aA' // ace-acf + '13P' // ad0 #353 + 'nA' // ad1-adf + 'c13P' // ae0-ae3 #353 + 'aA' // ae4-ae5 + 'i263Z' // ae6-aef #6863 + 'a13P' // af0-af1 #353 + 'fA' // af2-af8 + 'f13P' // af9-aff #353 + 'A' // b00 + 'b13Q' // b01-b03 #354 + 'A' // b04 + 'g13Q' // b05-b0c #354 + 'aA' // b0d-b0e + 'a13Q' // b0f-b10 #354 + 'aA' // b11-b12 + 'u13Q' // b13-b28 #354 + 'A' // b29 + 'f13Q' // b2a-b30 #354 + 'A' // b31 + 'a13Q' // b32-b33 #354 + 'A' // b34 + 'd13Q' // b35-b39 #354 + 'aA' // b3a-b3b + 'h13Q' // b3c-b44 #354 + 'aA' // b45-b46 + 'a13Q' // b47-b48 #354 + 'aA' // b49-b4a + 'b13Q' // b4b-b4d #354 + 'fA' // b4e-b54 + 'b13Q' // b55-b57 #354 + 'cA' // b58-b5b + 'a13Q' // b5c-b5d #354 + 'A' // b5e + 'd13Q' // b5f-b63 #354 + 'aA' // b64-b65 + 'q13Q' // b66-b77 #354 + 'iA' // b78-b81 + 'a10O' // b82-b83 #274 + 'A' // b84 + 'e10O' // b85-b8a #274 + 'bA' // b8b-b8d + 'b10O' // b8e-b90 #274 + 'A' // b91 + 'c10O' // b92-b95 #274 + 'bA' // b96-b98 + 'a10O' // b99-b9a #274 + 'A' // b9b + '10O' // b9c #274 + 'A' // b9d + 'a10O' // b9e-b9f #274 + 'bA' // ba0-ba2 + 'a10O' // ba3-ba4 #274 + 'bA' // ba5-ba7 + 'a10O' // ba8-ba9 #274 + '31U' // baa #826 + 'bA' // bab-bad + 'f10O' // bae-bb4 #274 + '31U' // bb5 #826 + 'c10O' // bb6-bb9 #274 + 'cA' // bba-bbd + 'd10O' // bbe-bc2 #274 + 'bA' // bc3-bc5 + 'b10O' // bc6-bc8 #274 + 'A' // bc9 + 'c10O' // bca-bcd #274 + 'aA' // bce-bcf + '10O' // bd0 #274 + 'eA' // bd1-bd6 + '10O' // bd7 #274 + 'mA' // bd8-be5 + 'l31U' // be6-bf2 #826 + 'g10O' // bf3-bfa #274 + 'dA' // bfb-bff + 'l15D' // c00-c0c #393 + 'A' // c0d + 'b15D' // c0e-c10 #393 + 'A' // c11 + 'v15D' // c12-c28 #393 + 'A' // c29 + 'o15D' // c2a-c39 #393 + 'aA' // c3a-c3b + 'h15D' // c3c-c44 #393 + 'A' // c45 + 'b15D' // c46-c48 #393 + 'A' // c49 + 'c15D' // c4a-c4d #393 + 'fA' // c4e-c54 + 'a15D' // c55-c56 #393 + 'A' // c57 + 'b15D' // c58-c5a #393 + 'aA' // c5b-c5c + '15D' // c5d #393 + 'aA' // c5e-c5f + 'c15D' // c60-c63 #393 + 'aA' // c64-c65 + 'i15D' // c66-c6f #393 + 'fA' // c70-c76 + 'h15D' // c77-c7f #393 + 'l15B' // c80-c8c #391 + 'A' // c8d + 'b15B' // c8e-c90 #391 + 'A' // c91 + 'v15B' // c92-ca8 #391 + 'A' // ca9 + 'i15B' // caa-cb3 #391 + 'A' // cb4 + 'd15B' // cb5-cb9 #391 + 'aA' // cba-cbb + 'h15B' // cbc-cc4 #391 + 'A' // cc5 + 'b15B' // cc6-cc8 #391 + 'A' // cc9 + 'c15B' // cca-ccd #391 + 'fA' // cce-cd4 + 'a15B' // cd5-cd6 #391 + 'eA' // cd7-cdc + 'a15B' // cdd-cde #391 + 'A' // cdf + 'c15B' // ce0-ce3 #391 + 'aA' // ce4-ce5 + 'i15B' // ce6-cef #391 + 'A' // cf0 + 'b15B' // cf1-cf3 #391 + 'kA' // cf4-cff + 'l27J' // d00-d0c #711 + 'A' // d0d + 'b27J' // d0e-d10 #711 + 'A' // d11 + '1x27J' // d12-d44 #711 + 'A' // d45 + 'b27J' // d46-d48 #711 + 'A' // d49 + 'e27J' // d4a-d4f #711 + 'cA' // d50-d53 + 'o27J' // d54-d63 #711 + 'aA' // d64-d65 + 'y27J' // d66-d7f #711 + 'A' // d80 + 'b15C' // d81-d83 #392 + 'A' // d84 + 'q15C' // d85-d96 #392 + 'bA' // d97-d99 + 'w15C' // d9a-db1 #392 + 'A' // db2 + 'h15C' // db3-dbb #392 + 'A' // dbc + '15C' // dbd #392 + 'aA' // dbe-dbf + 'f15C' // dc0-dc6 #392 + 'bA' // dc7-dc9 + '15C' // dca #392 + 'cA' // dcb-dce + 'e15C' // dcf-dd4 #392 + 'A' // dd5 + '15C' // dd6 #392 + 'A' // dd7 + 'g15C' // dd8-ddf #392 + 'eA' // de0-de5 + 'i15C' // de6-def #392 + 'aA' // df0-df1 + 'b15C' // df2-df4 #392 + 'kA' // df5-e00 + '2e73D' // e01-e3a #1901 + 'cA' // e3b-e3e + '1b73D' // e3f-e5b #1901 + '1jA' // e5c-e80 + 'a17M' // e81-e82 #454 + 'A' // e83 + '17M' // e84 #454 + 'A' // e85 + 'd17M' // e86-e8a #454 + 'A' // e8b + 'w17M' // e8c-ea3 #454 + 'A' // ea4 + '17M' // ea5 #454 + 'A' // ea6 + 'v17M' // ea7-ebd #454 + 'aA' // ebe-ebf + 'd17M' // ec0-ec4 #454 + 'A' // ec5 + '17M' // ec6 #454 + 'A' // ec7 + 'f17M' // ec8-ece #454 + 'A' // ecf + 'i17M' // ed0-ed9 #454 + 'aA' // eda-edb + 'c17M' // edc-edf #454 + '1eA' // ee0-eff + '2s31Y' // f00-f47 #830 + 'A' // f48 + '1i31Y' // f49-f6c #830 + 'cA' // f6d-f70 + '1l31Y' // f71-f97 #830 + 'A' // f98 + '1i31Y' // f99-fbc #830 + 'A' // fbd + 'n31Y' // fbe-fcc #830 + 'A' // fcd + 'l31Y' // fce-fda #830 + '1jA' // fdb-fff + '2k42D' // 1000-103f #1095 + 'i263N' // 1040-1049 #6851 + '3g42D' // 104a-109f #1095 + '1k19P' // 10a0-10c5 #509 + 'A' // 10c6 + '19P' // 10c7 #509 + 'dA' // 10c8-10cc + '19P' // 10cd #509 + 'aA' // 10ce-10cf + '1p19P' // 10d0-10fa #509 + '71K' // 10fb #1856 + 'c19P' // 10fc-10ff #509 + '9uA' // 1100-11ff + '2t3R' // 1200-1248 #95 + 'A' // 1249 + 'c3R' // 124a-124d #95 + 'aA' // 124e-124f + 'f3R' // 1250-1256 #95 + 'A' // 1257 + '3R' // 1258 #95 + 'A' // 1259 + 'c3R' // 125a-125d #95 + 'aA' // 125e-125f + '1n3R' // 1260-1288 #95 + 'A' // 1289 + 'c3R' // 128a-128d #95 + 'aA' // 128e-128f + '1f3R' // 1290-12b0 #95 + 'A' // 12b1 + 'c3R' // 12b2-12b5 #95 + 'aA' // 12b6-12b7 + 'f3R' // 12b8-12be #95 + 'A' // 12bf + '3R' // 12c0 #95 + 'A' // 12c1 + 'c3R' // 12c2-12c5 #95 + 'aA' // 12c6-12c7 + 'n3R' // 12c8-12d6 #95 + 'A' // 12d7 + '2d3R' // 12d8-1310 #95 + 'A' // 1311 + 'c3R' // 1312-1315 #95 + 'aA' // 1316-1317 + '2n3R' // 1318-135a #95 + 'aA' // 135b-135c + '1e3R' // 135d-137c #95 + 'bA' // 137d-137f + 'y3R' // 1380-1399 #95 + 'eA' // 139a-139f + '3g50Q' // 13a0-13f5 #1316 + 'aA' // 13f6-13f7 + 'e50Q' // 13f8-13fd #1316 + 'aA' // 13fe-13ff + '24o50P' // 1400-167f #1315 + '1b264I' // 1680-169c #6872 + 'bA' // 169d-169f + '3j264S' // 16a0-16f8 #6882 + 'fA' // 16f9-16ff + 'u72Y' // 1700-1715 #1896 + 'hA' // 1716-171e + '72Y' // 171f #1896 + 't264C' // 1720-1734 #6866 + 'a263L' // 1735-1736 #6849 + 'hA' // 1737-173f + 's263K' // 1740-1753 #6848 + 'kA' // 1754-175f + 'l51F' // 1760-176c #1331 + 'A' // 176d + 'b51F' // 176e-1770 #1331 + 'A' // 1771 + 'a51F' // 1772-1773 #1331 + 'kA' // 1774-177f + '3o42C' // 1780-17dd #1094 + 'aA' // 17de-17df + 'i42C' // 17e0-17e9 #1094 + 'eA' // 17ea-17ef + 'i42C' // 17f0-17f9 #1094 + 'eA' // 17fa-17ff + '31X' // 1800 #829 + 'b72J' // 1801-1803 #1881 + '31X' // 1804 #829 + '72J' // 1805 #1881 + 's31X' // 1806-1819 #829 + 'eA' // 181a-181f + '3j31X' // 1820-1878 #829 + 'fA' // 1879-187f + '1p31X' // 1880-18aa #829 + 'dA' // 18ab-18af + '2q50P' // 18b0-18f5 #1315 + 'iA' // 18f6-18ff + '1d36Q' // 1900-191e #952 + 'A' // 191f + 'k36Q' // 1920-192b #952 + 'cA' // 192c-192f + 'k36Q' // 1930-193b #952 + 'cA' // 193c-193f + '36Q' // 1940 #952 + 'bA' // 1941-1943 + 'k36Q' // 1944-194f #952 + '1c72Z' // 1950-196d #1897 + 'aA' // 196e-196f + 'd72Z' // 1970-1974 #1897 + 'jA' // 1975-197f + '1q42E' // 1980-19ab #1096 + 'cA' // 19ac-19af + 'y42E' // 19b0-19c9 #1096 + 'eA' // 19ca-19cf + 'j42E' // 19d0-19da #1096 + 'bA' // 19db-19dd + 'a42E' // 19de-19df #1096 + '1e42C' // 19e0-19ff #1094 + '1a71T' // 1a00-1a1b #1865 + 'aA' // 1a1c-1a1d + 'a71T' // 1a1e-1a1f #1865 + '2j36T' // 1a20-1a5e #955 + 'A' // 1a5f + '1b36T' // 1a60-1a7c #955 + 'aA' // 1a7d-1a7e + 'j36T' // 1a7f-1a89 #955 + 'eA' // 1a8a-1a8f + 'i36T' // 1a90-1a99 #955 + 'eA' // 1a9a-1a9f + 'm36T' // 1aa0-1aad #955 + 'aA' // 1aae-1aaf + 'pE' // 1ab0-1ac0 #4 + 'cA' // 1ac1-1ac4 + 'E' // 1ac5 #4 + 'A' // 1ac6 + 'gE' // 1ac7-1ace #4 + '1vA' // 1acf-1aff + '2w71O' // 1b00-1b4b #1860 + 'cA' // 1b4c-1b4f + '1r71O' // 1b50-1b7c #1860 + 'bA' // 1b7d-1b7f + '2k72X' // 1b80-1bbf #1895 + '1y71R' // 1bc0-1bf3 #1863 + 'gA' // 1bf4-1bfb + 'c71R' // 1bfc-1bff #1863 + '2c50V' // 1c00-1c37 #1321 + 'bA' // 1c38-1c3a + 'n50V' // 1c3b-1c49 #1321 + 'bA' // 1c4a-1c4c + 'b50V' // 1c4d-1c4f #1321 + '1u264J' // 1c50-1c7f #6873 + 'hE' // 1c80-1c88 #4 + 'fA' // 1c89-1c8f + '1p19P' // 1c90-1cba #509 + 'aA' // 1cbb-1cbc + 'b19P' // 1cbd-1cbf #509 + 'g72X' // 1cc0-1cc7 #1895 + 'gA' // 1cc8-1ccf + '71S' // 1cd0 #1864 + '17L' // 1cd1 #453 + '71S' // 1cd2 #1864 + '50S' // 1cd3 #1318 + '17L' // 1cd4 #453 + 'a27H' // 1cd5-1cd6 #709 + '42B' // 1cd7 #1093 + '27H' // 1cd8 #709 + '42B' // 1cd9 #1093 + '263T' // 1cda #6857 + '17L' // 1cdb #453 + 'a42B' // 1cdc-1cdd #1093 + 'a17L' // 1cde-1cdf #453 + '42B' // 1ce0 #1093 + '27H' // 1ce1 #709 + 'g17L' // 1ce2-1ce9 #453 + '27H' // 1cea #709 + 'a17L' // 1ceb-1cec #453 + '27H' // 1ced #709 + 'c17L' // 1cee-1cf1 #453 + '263G' // 1cf2 #6844 + '50S' // 1cf3 #1318 + '263P' // 1cf4 #6853 + '263H' // 1cf5 #6845 + '27H' // 1cf6 #709 + '11N' // 1cf7 #299 + 'a50S' // 1cf8-1cf9 #1318 + 'eA' // 1cfa-1cff + '7vE' // 1d00-1dcc #4 + '17K' // 1dcd #452 + '1qE' // 1dce-1df9 #4 + 'A' // 1dfa + '262N' // 1dfb #6825 + '2mE' // 1dfc-1e3d #4 + 'a21F' // 1e3e-1e3f #551 + '2kE' // 1e40-1e7f #4 + 'e8C' // 1e80-1e85 #210 + 'wE' // 1e86-1e9d #4 + '8C' // 1e9e #210 + 'E' // 1e9f #4 + '15Z' // 1ea0 #415 + '69E' // 1ea1 #1798 + '15Z' // 1ea2 #415 + '69E' // 1ea3 #1798 + 'e15Z' // 1ea4-1ea9 #415 + '11B' // 1eaa #287 + 'b15Z' // 1eab-1ead #415 + '120H' // 1eae #3127 + '15Z' // 1eaf #415 + '11B' // 1eb0 #287 + '15Z' // 1eb1 #415 + '11B' // 1eb2 #287 + '15Z' // 1eb3 #415 + '11B' // 1eb4 #287 + '15Z' // 1eb5 #415 + '11B' // 1eb6 #287 + '15Z' // 1eb7 #415 + '11B' // 1eb8 #287 + '15Z' // 1eb9 #415 + '11B' // 1eba #287 + '15Z' // 1ebb #415 + '11B' // 1ebc #287 + 'a15Z' // 1ebd-1ebe #415 + '69F' // 1ebf #1799 + 'c11A' // 1ec0-1ec3 #286 + '11B' // 1ec4 #287 + 'a11A' // 1ec5-1ec6 #286 + '69F' // 1ec7 #1799 + '11B' // 1ec8 #287 + '11A' // 1ec9 #286 + '35W' // 1eca #932 + 'b11A' // 1ecb-1ecd #286 + '11B' // 1ece #287 + 'b11A' // 1ecf-1ed1 #286 + '35W' // 1ed2 #932 + '11A' // 1ed3 #286 + '62U' // 1ed4 #1632 + '11A' // 1ed5 #286 + '11B' // 1ed6 #287 + 'f11A' // 1ed7-1edd #286 + '35W' // 1ede #932 + '11A' // 1edf #286 + '11B' // 1ee0 #287 + '11A' // 1ee1 #286 + '11B' // 1ee2 #287 + '11A' // 1ee3 #286 + '62U' // 1ee4 #1632 + 'b11A' // 1ee5-1ee7 #286 + '35W' // 1ee8 #932 + '11A' // 1ee9 #286 + '35W' // 1eea #932 + '11A' // 1eeb #286 + '11B' // 1eec #287 + '11A' // 1eed #286 + '11B' // 1eee #287 + 'b11A' // 1eef-1ef1 #286 + '78Y' // 1ef2 #2052 + '78S' // 1ef3 #2046 + 'b49J' // 1ef4-1ef6 #1283 + '62T' // 1ef7 #1631 + '49J' // 1ef8 #1283 + '62T' // 1ef9 #1631 + '1aE' // 1efa-1f15 #4 + 'aA' // 1f16-1f17 + 'eE' // 1f18-1f1d #4 + 'aA' // 1f1e-1f1f + '1kE' // 1f20-1f45 #4 + 'aA' // 1f46-1f47 + 'eE' // 1f48-1f4d #4 + 'aA' // 1f4e-1f4f + 'gE' // 1f50-1f57 #4 + 'A' // 1f58 + 'E' // 1f59 #4 + 'A' // 1f5a + 'E' // 1f5b #4 + 'A' // 1f5c + 'E' // 1f5d #4 + 'A' // 1f5e + '1dE' // 1f5f-1f7d #4 + 'aA' // 1f7e-1f7f + '1zE' // 1f80-1fb4 #4 + 'A' // 1fb5 + 'nE' // 1fb6-1fc4 #4 + 'A' // 1fc5 + 'mE' // 1fc6-1fd3 #4 + 'aA' // 1fd4-1fd5 + 'eE' // 1fd6-1fdb #4 + 'A' // 1fdc + 'rE' // 1fdd-1fef #4 + 'aA' // 1ff0-1ff1 + 'bE' // 1ff2-1ff4 #4 + 'A' // 1ff5 + 'hE' // 1ff6-1ffe #4 + 'A' // 1fff + 'aE' // 2000-2001 #4 + '120M' // 2002 #3132 + '120S' // 2003 #3138 + 'dE' // 2004-2008 #4 + '71J' // 2009 #1855 + 'E' // 200a #4 + '261K' // 200b #6796 + '80D' // 200c #2083 + '73K' // 200d #1908 + 'a261N' // 200e-200f #6799 + '120U' // 2010 #3140 + '120R' // 2011 #3137 + '120Q' // 2012 #3136 + 'a42U' // 2013-2014 #1112 + '120V' // 2015 #3141 + '120N' // 2016 #3133 + 'E' // 2017 #4 + 'a79Y' // 2018-2019 #2078 + '52M' // 201a #1364 + '71H' // 201b #1853 + 'a79O' // 201c-201d #2068 + '52M' // 201e #1364 + 'E' // 201f #4 + '120I' // 2020 #3128 + '62V' // 2021 #1633 + '77N' // 2022 #2015 + 'E' // 2023 #4 + '261O' // 2024 #6800 + '120T' // 2025 #3139 + '79Q' // 2026 #2070 + '216U' // 2027 #5636 + 'eE' // 2028-202d #4 + '71L' // 202e #1857 + '261L' // 202f #6797 + '62V' // 2030 #1633 + 'E' // 2031 #4 + '120L' // 2032 #3131 + '120J' // 2033 #3129 + '8B' // 2034 #209 + '120O' // 2035 #3134 + 'a8B' // 2036-2037 #209 + 'E' // 2038 #4 + 'a79V' // 2039-203a #2075 + '206L' // 203b #5367 + '74W' // 203c #1946 + 'dE' // 203d-2041 #4 + '120P' // 2042 #3135 + 'E' // 2043 #4 + '80F' // 2044 #2085 + 'aE' // 2045-2046 #4 + '41T' // 2047 #1085 + '260L' // 2048 #6771 + '74U' // 2049 #1944 + 'dE' // 204a-204e #4 + '71I' // 204f #1854 + 'E' // 2050 #4 + '41T' // 2051 #1085 + 'E' // 2052 #4 + '17K' // 2053 #452 + 'E' // 2054 #4 + '262T' // 2055 #6831 + '17K' // 2056 #452 + '8B' // 2057 #209 + 'a17K' // 2058-2059 #452 + '262P' // 205a #6827 + 'aE' // 205b-205c #4 + '262L' // 205d #6823 + '262O' // 205e #6826 + 'eE' // 205f-2064 #4 + 'A' // 2065 + 'kE' // 2066-2071 #4 + 'aA' // 2072-2073 + '120K' // 2074 #3130 + 'lE' // 2075-2081 #4 + 'b262V' // 2082-2084 #6833 + 'iE' // 2085-208e #4 + 'A' // 208f + 'lE' // 2090-209c #4 + 'bA' // 209d-209f + 'hE' // 20a0-20a8 #4 + '119B' // 20a9 #3095 + '262G' // 20aa #6818 + '119A' // 20ab #3094 + '79K' // 20ac #2064 + '262I' // 20ad #6820 + 'jE' // 20ae-20b8 #4 + '261S' // 20b9 #6804 + 'cE' // 20ba-20bd #4 + '71K' // 20be #1856 + 'aE' // 20bf-20c0 #4 + 'nA' // 20c1-20cf + 'jM' // 20d0-20da #12 + '264G' // 20db #6870 + 'M' // 20dc #12 + '119O' // 20dd #3108 + '251T' // 20de #6545 + 'a2M' // 20df-20e0 #64 + 'M' // 20e1 #12 + '52J' // 20e2 #1361 + '74J' // 20e3 #1933 + '2M' // 20e4 #64 + 'jM' // 20e5-20ef #12 + '262B' // 20f0 #6813 + 'nA' // 20f1-20ff + '41T' // 2100 #1085 + 'E' // 2101 #4 + '8B' // 2102 #209 + '154U' // 2103 #4024 + 'E' // 2104 #4 + '119N' // 2105 #3107 + 'bE' // 2106-2108 #4 + '40Y' // 2109 #1064 + '70T' // 210a #1839 + 'c8B' // 210b-210e #209 + '41R' // 210f #1083 + 'b8B' // 2110-2112 #209 + '119C' // 2113 #3096 + 'E' // 2114 #4 + '8B' // 2115 #209 + '118Z' // 2116 #3093 + 'aE' // 2117-2118 #4 + 'd8B' // 2119-211d #209 + 'bE' // 211e-2120 #4 + '40Y' // 2121 #1064 + '51L' // 2122 #1337 + 'E' // 2123 #4 + '8B' // 2124 #209 + 'E' // 2125 #4 + '40Y' // 2126 #1064 + '41R' // 2127 #1083 + '8B' // 2128 #209 + 'aE' // 2129-212a #4 + '40Y' // 212b #1064 + 'a8B' // 212c-212d #209 + '41R' // 212e #1083 + 'b8B' // 212f-2131 #209 + 'E' // 2132 #4 + 'a8B' // 2133-2134 #209 + '70T' // 2135 #1839 + 'b8B' // 2136-2138 #209 + '74Z' // 2139 #1949 + 'E' // 213a #4 + '41R' // 213b #1083 + 'd8B' // 213c-2140 #209 + 'cE' // 2141-2144 #4 + 'd8B' // 2145-2149 #209 + 'uE' // 214a-215f #4 + '261F' // 2160 #6791 + '261H' // 2161 #6793 + 'g71F' // 2162-2169 #1851 + 'e2M' // 216a-216f #64 + 'i71F' // 2170-2179 #1851 + 'h2M' // 217a-2182 #64 + '262U' // 2183 #6832 + 'E' // 2184 #4 + 'c2M' // 2185-2188 #64 + 'E' // 2189 #4 + 'a2M' // 218a-218b #64 + 'cA' // 218c-218f + '161Z' // 2190 #4211 + '155V' // 2191 #4051 + '206E' // 2192 #5360 + '155U' // 2193 #4050 + '73Y' // 2194 #1922 + '73X' // 2195 #1921 + 'a51P' // 2196-2197 #1341 + '74V' // 2198 #1945 + '51P' // 2199 #1341 + 'nM' // 219a-21a8 #12 + 'a75A' // 21a9-21aa #1950 + 'cM' // 21ab-21ae #12 + 'F' // 21af #5 + 'gM' // 21b0-21b7 #12 + 'a21N' // 21b8-21b9 #559 + 'iM' // 21ba-21c3 #12 + '8I' // 21c4 #216 + '12N' // 21c5 #325 + '21N' // 21c6 #559 + 'cM' // 21c7-21ca #12 + '21N' // 21cb #559 + '48T' // 21cc #1267 + 'bM' // 21cd-21cf #12 + '8I' // 21d0 #216 + 'M' // 21d1 #12 + '120G' // 21d2 #3126 + 'M' // 21d3 #12 + '119Q' // 21d4 #3110 + 'pM' // 21d5-21e5 #12 + 'c77Z' // 21e6-21e9 #2027 + 'fF' // 21ea-21f0 #5 + 'aM' // 21f1-21f2 #12 + 'F' // 21f3 #5 + 'M' // 21f4 #12 + '21N' // 21f5 #559 + 'iM' // 21f6-21ff #12 + '62S' // 2200 #1630 + 'M' // 2201 #12 + '8I' // 2202 #216 + '12N' // 2203 #325 + 'M' // 2204 #12 + '48T' // 2205 #1267 + '8I' // 2206 #216 + '119Z' // 2207 #3119 + '8I' // 2208 #216 + '12N' // 2209 #325 + '21N' // 220a #559 + '12N' // 220b #325 + 'bM' // 220c-220e #12 + '8I' // 220f #216 + 'M' // 2210 #12 + '8I' // 2211 #216 + '76Q' // 2212 #1992 + '12N' // 2213 #325 + 'M' // 2214 #12 + '119D' // 2215 #3097 + 'aM' // 2216-2217 #12 + '32C' // 2218 #834 + '76M' // 2219 #1988 + '119I' // 221a #3102 + 'aM' // 221b-221c #12 + '8I' // 221d #216 + '119X' // 221e #3117 + '120B' // 221f #3121 + '8I' // 2220 #216 + 'aM' // 2221-2222 #12 + '124N' // 2223 #3237 + 'M' // 2224 #12 + '8I' // 2225 #216 + '12N' // 2226 #325 + '119P' // 2227 #3109 + '119M' // 2228 #3106 + '119S' // 2229 #3112 + 'a8I' // 222a-222b #216 + '12N' // 222c #325 + '21N' // 222d #559 + '8I' // 222e #216 + 'dM' // 222f-2233 #12 + 'c8I' // 2234-2237 #216 + 'dM' // 2238-223c #12 + '8I' // 223d #216 + 'dM' // 223e-2242 #12 + '12N' // 2243 #325 + 'M' // 2244 #12 + '12N' // 2245 #325 + 'aM' // 2246-2247 #12 + '8I' // 2248 #216 + 'bM' // 2249-224b #12 + '251R' // 224c #6543 + 'rM' // 224d-225f #12 + '8I' // 2260 #216 + '62R' // 2261 #1629 + '12N' // 2262 #325 + 'M' // 2263 #12 + '119J' // 2264 #3103 + '119K' // 2265 #3104 + '130P' // 2266 #3395 + '136Q' // 2267 #3552 + 'aM' // 2268-2269 #12 + '120C' // 226a #3122 + '62S' // 226b #1630 + 'aM' // 226c-226d #12 + 'a8I' // 226e-226f #216 + 'aM' // 2270-2271 #12 + 'a12N' // 2272-2273 #325 + 'aM' // 2274-2275 #12 + 'a12N' // 2276-2277 #325 + 'iM' // 2278-2281 #12 + '62R' // 2282 #1629 + '8I' // 2283 #216 + 'c12N' // 2284-2287 #325 + 'aM' // 2288-2289 #12 + 'a12N' // 228a-228b #325 + 'hM' // 228c-2294 #12 + '8I' // 2295 #216 + '119L' // 2296 #3105 + '251P' // 2297 #6541 + '21N' // 2298 #559 + '76K' // 2299 #1986 + 'eM' // 229a-229f #12 + '21N' // 22a0 #559 + 'cM' // 22a1-22a4 #12 + '8I' // 22a5 #216 + 'xM' // 22a6-22be #12 + '48T' // 22bf #1267 + 'cM' // 22c0-22c3 #12 + 'b32C' // 22c4-22c6 #834 + 'rM' // 22c7-22d9 #12 + 'a12N' // 22da-22db #325 + 'qM' // 22dc-22ed #12 + '263X' // 22ee #6861 + '172M' // 22ef #4484 + 'oM' // 22f0-22ff #12 + 'd2M' // 2300-2304 #64 + '41S' // 2305 #1084 + '70S' // 2306 #1838 + '251S' // 2307 #6544 + 'c31W' // 2308-230b #828 + 'c2M' // 230c-230f #64 + 'M' // 2310 #12 + '2M' // 2311 #64 + '119V' // 2312 #3115 + 'b2M' // 2313-2315 #64 + 'F' // 2316 #5 + '2M' // 2317 #64 + '78A' // 2318 #2028 + 'M' // 2319 #12 + 'a2E' // 231a-231b #56 + 'c31W' // 231c-231f #828 + 'aM' // 2320-2321 #12 + 'a2M' // 2322-2323 #64 + 'cF' // 2324-2327 #5 + '2E' // 2328 #56 + 'a41S' // 2329-232a #1084 + 'F' // 232b #5 + 'i2M' // 232c-2335 #64 + '2pM' // 2336-237a #12 + 'F' // 237b #5 + '31W' // 237c #828 + 'bF' // 237d-237f #5 + 's2M' // 2380-2393 #64 + '52J' // 2394 #1361 + 'M' // 2395 #12 + 'd2M' // 2396-239a #64 + 'sM' // 239b-23ae #12 + '31W' // 23af #828 + 'a21N' // 23b0-23b1 #559 + 'dM' // 23b2-23b6 #12 + 'fA' // 23b7-23bd + 'n41S' // 23be-23cc #1084 + '2M' // 23cd #64 + '52I' // 23ce #1360 + '4M' // 23cf #116 + '31W' // 23d0 #828 + 'h2M' // 23d1-23d9 #64 + 'a41S' // 23da-23db #1084 + 'eM' // 23dc-23e1 #12 + 'f2M' // 23e2-23e8 #64 + 'a4M' // 23e9-23ea #116 + 'a3S' // 23eb-23ec #96 + 'b4M' // 23ed-23ef #116 + '2S' // 23f0 #70 + 'b2E' // 23f1-23f3 #56 + 'cF' // 23f4-23f7 #5 + 'b4M' // 23f8-23fa #116 + '1mF' // 23fb-2422 #5 + '52I' // 2423 #1360 + 'bF' // 2424-2426 #5 + 'xA' // 2427-243f + 'jF' // 2440-244a #5 + 'tA' // 244b-245f + '120F' // 2460 #3125 + '120E' // 2461 #3124 + '120D' // 2462 #3123 + '120A' // 2463 #3120 + '119Y' // 2464 #3118 + '119W' // 2465 #3116 + '119U' // 2466 #3114 + '119T' // 2467 #3113 + '119R' // 2468 #3111 + 'h119H' // 2469-2471 #3101 + 'a70S' // 2472-2473 #1838 + '119G' // 2474 #3100 + '119E' // 2475 #3098 + '119F' // 2476 #3099 + 'a118U' // 2477-2478 #3088 + 'h35R' // 2479-2481 #927 + 'e251O' // 2482-2487 #6540 + 'i118V' // 2488-2491 #3089 + 'i251Q' // 2492-249b #6542 + 'y36L' // 249c-24b5 #947 + 'a26W' // 24b6-24b7 #698 + '35R' // 24b8 #927 + 'h26W' // 24b9-24c1 #698 + '74T' // 24c2 #1943 + 'b26W' // 24c3-24c5 #698 + '36L' // 24c6 #947 + '35R' // 24c7 #927 + '26W' // 24c8 #698 + '35R' // 24c9 #927 + '26W' // 24ca #698 + '36L' // 24cb #947 + '26W' // 24cc #698 + '36L' // 24cd #947 + '26W' // 24ce #698 + '36L' // 24cf #947 + '35R' // 24d0 #927 + '21D' // 24d1 #549 + '118S' // 24d2 #3086 + '21D' // 24d3 #549 + '35Q' // 24d4 #926 + 'b21D' // 24d5-24d7 #549 + '118R' // 24d8 #3085 + 'c21D' // 24d9-24dc #549 + 'a35Q' // 24dd-24de #926 + '21D' // 24df #549 + '70R' // 24e0 #1837 + 'a21D' // 24e1-24e2 #549 + '35Q' // 24e3 #926 + 'a21D' // 24e4-24e5 #549 + '35Q' // 24e6 #926 + '21D' // 24e7 #549 + '35Q' // 24e8 #926 + '21D' // 24e9 #549 + '48S' // 24ea #1266 + 'i70R' // 24eb-24f4 #1837 + '48S' // 24f5 #1266 + 'h251N' // 24f6-24fe #6539 + '48S' // 24ff #1266 + '189E' // 2500 #4918 + '118Y' // 2501 #3092 + '196F' // 2502 #5101 + '10Z' // 2503 #285 + 'e62P' // 2504-2509 #1627 + '118T' // 250a #3087 + '62P' // 250b #1627 + 'a10Z' // 250c-250d #285 + '16D' // 250e #419 + 'b10Z' // 250f-2511 #285 + '16D' // 2512 #419 + '10Z' // 2513 #285 + '118X' // 2514 #3091 + '10Z' // 2515 #285 + '16D' // 2516 #419 + '118W' // 2517 #3090 + '10Z' // 2518 #285 + 'a16D' // 2519-251a #419 + '10Z' // 251b #285 + '62Q' // 251c #1628 + '10Z' // 251d #285 + 'a16D' // 251e-251f #419 + '10Z' // 2520 #285 + 'a16D' // 2521-2522 #419 + '62Q' // 2523 #1628 + '10Z' // 2524 #285 + 'a16D' // 2525-2526 #419 + '50D' // 2527 #1303 + '10Z' // 2528 #285 + 'a16D' // 2529-252a #419 + 'a10Z' // 252b-252c #285 + 'a16D' // 252d-252e #419 + '10Z' // 252f #285 + 'b16D' // 2530-2532 #419 + 'a10Z' // 2533-2534 #285 + '50D' // 2535 #1303 + '16D' // 2536 #419 + '10Z' // 2537 #285 + 'b50D' // 2538-253a #1303 + 'a10Z' // 253b-253c #285 + 'c16D' // 253d-2540 #419 + '48R' // 2541 #1265 + '50C' // 2542 #1302 + 'b48R' // 2543-2545 #1265 + 'b50C' // 2546-2548 #1302 + '251M' // 2549 #6538 + '50C' // 254a #1302 + '48R' // 254b #1265 + 'c36K' // 254c-254f #946 + 'a23X' // 2550-2551 #621 + 'a26V' // 2552-2553 #697 + '23X' // 2554 #621 + 'a26V' // 2555-2556 #697 + '23X' // 2557 #621 + 'a26V' // 2558-2559 #697 + 'a23X' // 255a-255b #621 + '26V' // 255c #697 + '23X' // 255d #621 + '26V' // 255e #697 + 'a23X' // 255f-2560 #621 + '26V' // 2561 #697 + 'a23X' // 2562-2563 #621 + '26V' // 2564 #697 + '23X' // 2565 #621 + 'a23W' // 2566-2567 #620 + '40X' // 2568 #1063 + 'a23W' // 2569-256a #620 + '40X' // 256b #1063 + 'd23W' // 256c-2570 #620 + '64X' // 2571 #1687 + '23W' // 2572 #620 + '135H' // 2573 #3517 + '40X' // 2574 #1063 + 'c36K' // 2575-2578 #946 + '23W' // 2579 #620 + 'e36K' // 257a-257f #946 + 'j23W' // 2580-258a #620 + '64X' // 258b #1687 + 'e23W' // 258c-2591 #620 + '118Q' // 2592 #3084 + 'b23W' // 2593-2595 #620 + '36K' // 2596 #946 + '40X' // 2597 #1063 + 'g36K' // 2598-259f #946 + '78E' // 25a0 #2032 + '77X' // 25a1 #2025 + '78N' // 25a2 #2041 + '77T' // 25a3 #2021 + '52G' // 25a4 #1358 + '52H' // 25a5 #1359 + 'b52G' // 25a6-25a8 #1358 + '42P' // 25a9 #1107 + 'a74G' // 25aa-25ab #1930 + 'bF' // 25ac-25ae #5 + '32C' // 25af #834 + 'F' // 25b0 #5 + '52H' // 25b1 #1359 + '78M' // 25b2 #2040 + '76L' // 25b3 #1987 + 'aF' // 25b4-25b5 #5 + '74I' // 25b6 #1932 + '76I' // 25b7 #1984 + 'cF' // 25b8-25bb #5 + '78K' // 25bc #2038 + '76J' // 25bd #1985 + 'aF' // 25be-25bf #5 + '74F' // 25c0 #1929 + '52B' // 25c1 #1353 + 'cF' // 25c2-25c5 #5 + '78H' // 25c6 #2035 + '77Y' // 25c7 #2026 + 'F' // 25c8 #5 + '42P' // 25c9 #1107 + '52B' // 25ca #1353 + '78F' // 25cb #2033 + '76G' // 25cc #1982 + 'F' // 25cd #5 + '78G' // 25ce #2034 + '78I' // 25cf #2036 + 'a42P' // 25d0-25d1 #1107 + 'a77V' // 25d2-25d3 #2023 + 'mF' // 25d4-25e1 #5 + 'a77U' // 25e2-25e3 #2022 + 'a52F' // 25e4-25e5 #1357 + '42O' // 25e6 #1106 + 'gF' // 25e7-25ee #5 + '77W' // 25ef #2024 + 'jF' // 25f0-25fa #5 + '74B' // 25fb #1925 + 'b4M' // 25fc-25fe #116 + 'F' // 25ff #5 + '75V' // 2600 #1971 + '51W' // 2601 #1348 + '75M' // 2602 #1962 + '51W' // 2603 #1348 + '5Z' // 2604 #155 + '78L' // 2605 #2039 + '78J' // 2606 #2037 + 'aF' // 2607-2608 #5 + '52F' // 2609 #1357 + 'c2M' // 260a-260d #64 + '75L' // 260e #1961 + '42O' // 260f #1106 + 'F' // 2610 #5 + '4M' // 2611 #116 + 'F' // 2612 #5 + '2M' // 2613 #64 + '5Z' // 2614 #155 + '32A' // 2615 #832 + 'a42Q' // 2616-2617 #1108 + '5Z' // 2618 #155 + 'bF' // 2619-261b #5 + '77Q' // 261c #2018 + '75Y' // 261d #1974 + '77R' // 261e #2019 + '42O' // 261f #1106 + '73N' // 2620 #1911 + 'F' // 2621 #5 + 'a4M' // 2622-2623 #116 + 'a2M' // 2624-2625 #64 + '17N' // 2626 #455 + 'b2M' // 2627-2629 #64 + '17N' // 262a #455 + '2M' // 262b #64 + '264B' // 262c #6865 + '2M' // 262d #64 + '17N' // 262e #455 + '74R' // 262f #1941 + 'gF' // 2630-2637 #5 + '75B' // 2638 #1951 + 'a52A' // 2639-263a #1352 + '2M' // 263b #64 + 'F' // 263c #5 + 'b2M' // 263d-263f #64 + '73S' // 2640 #1916 + '118L' // 2641 #3079 + '73R' // 2642 #1915 + 'd2M' // 2643-2647 #64 + 'k17N' // 2648-2653 #455 + 'jF' // 2654-265e #5 + '6J' // 265f #165 + '51K' // 2660 #1336 + '78D' // 2661 #2031 + '77S' // 2662 #2020 + '74D' // 2663 #1927 + '52E' // 2664 #1356 + '73V' // 2665 #1919 + '74E' // 2666 #1928 + '52E' // 2667 #1356 + '51K' // 2668 #1336 + '118N' // 2669 #3081 + '155N' // 266a #4043 + '118P' // 266b #3083 + '118O' // 266c #3082 + '118J' // 266d #3077 + '118K' // 266e #3078 + '118M' // 266f #3080 + 'a264Y' // 2670-2671 #6888 + 'h50B' // 2672-267a #1301 + '74S' // 267b #1942 + 'a50B' // 267c-267d #1301 + '17N' // 267e #455 + '4M' // 267f #116 + 'oF' // 2680-268f #5 + 'a2M' // 2690-2691 #64 + '31Z' // 2692 #831 + '27M' // 2693 #714 + '31Z' // 2694 #831 + '73T' // 2695 #1917 + '75G' // 2696 #1956 + '31Z' // 2697 #831 + '2M' // 2698 #64 + '31Z' // 2699 #831 + '2M' // 269a #64 + 'a17N' // 269b-269c #455 + '2M' // 269d #64 + 'aF' // 269e-269f #5 + '74H' // 26a0 #1931 + '5Z' // 26a1 #155 + 'd2M' // 26a2-26a6 #64 + '73L' // 26a7 #1909 + 'a2M' // 26a8-26a9 #64 + 'a4M' // 26aa-26ab #116 + 'F' // 26ac #5 + 'b2M' // 26ad-26af #64 + 'a31Z' // 26b0-26b1 #831 + 'j2M' // 26b2-26bc #64 + 'a75O' // 26bd-26be #1964 + 'dF' // 26bf-26c3 #5 + 'a5Z' // 26c4-26c5 #155 + 'aF' // 26c6-26c7 #5 + '5Z' // 26c8 #155 + 'dF' // 26c9-26cd #5 + '17N' // 26ce #455 + '2E' // 26cf #56 + 'F' // 26d0 #5 + '2E' // 26d1 #56 + 'F' // 26d2 #5 + '75J' // 26d3 #1959 + '4M' // 26d4 #116 + 'lF' // 26d5-26e1 #5 + 'f2M' // 26e2-26e8 #64 + 'a27M' // 26e9-26ea #714 + 'd2M' // 26eb-26ef #64 + '75W' // 26f0 #1972 + 'a27M' // 26f1-26f2 #714 + '51S' // 26f3 #1344 + 'a27M' // 26f4-26f5 #714 + '2M' // 26f6 #64 + '51Y' // 26f7 #1350 + '51S' // 26f8 #1344 + '51Y' // 26f9 #1350 + '27M' // 26fa #714 + 'a2M' // 26fb-26fc #64 + '27M' // 26fd #714 + 'a2M' // 26fe-26ff #64 + 'aF' // 2700-2701 #5 + '75K' // 2702 #1960 + 'aF' // 2703-2704 #5 + '3S' // 2705 #96 + 'aF' // 2706-2707 #5 + '51T' // 2708 #1345 + '2E' // 2709 #56 + '3O' // 270a #92 + 'b17O' // 270b-270d #456 + 'F' // 270e #5 + '2E' // 270f #56 + 'aF' // 2710-2711 #5 + '2E' // 2712 #56 + '78C' // 2713 #2030 + '4M' // 2714 #116 + 'F' // 2715 #5 + '4M' // 2716 #116 + 'bF' // 2717-2719 #5 + '52D' // 271a #1355 + 'aF' // 271b-271c #5 + '17N' // 271d #455 + 'b2M' // 271e-2720 #64 + '17N' // 2721 #455 + 'eF' // 2722-2727 #5 + '27N' // 2728 #715 + 'iF' // 2729-2732 #5 + 'a4M' // 2733-2734 #116 + 'gF' // 2735-273c #5 + '52D' // 273d #1355 + 'F' // 273e #5 + '78B' // 273f #2029 + '77P' // 2740 #2017 + 'bF' // 2741-2743 #5 + '5Z' // 2744 #155 + 'aF' // 2745-2746 #5 + '4M' // 2747 #116 + 'cF' // 2748-274b #5 + '3S' // 274c #96 + 'F' // 274d #5 + '3S' // 274e #96 + 'cF' // 274f-2752 #5 + '4M' // 2753 #116 + 'a3S' // 2754-2755 #96 + '77O' // 2756 #2016 + '4M' // 2757 #116 + 'jF' // 2758-2762 #5 + '17O' // 2763 #456 + '73Q' // 2764 #1914 + 'pF' // 2765-2775 #5 + 'h62O' // 2776-277e #1626 + '50A' // 277f #1300 + 'b118I' // 2780-2782 #3076 + 'f50B' // 2783-2789 #1301 + 'b62O' // 278a-278c #1626 + '50A' // 278d #1300 + '251I' // 278e #6534 + 'd50A' // 278f-2793 #1300 + 'F' // 2794 #5 + 'b3S' // 2795-2797 #96 + 'hF' // 2798-27a0 #5 + '73W' // 27a1 #1920 + 'mF' // 27a2-27af #5 + '3S' // 27b0 #96 + 'mF' // 27b1-27be #5 + '3S' // 27bf #96 + '2kM' // 27c0-27ff #12 + '9u76B' // 2800-28ff #1977 + '1fM' // 2900-2920 #12 + 'a31W' // 2921-2922 #828 + 'pM' // 2923-2933 #12 + 'a74Q' // 2934-2935 #1940 + '2vM' // 2936-2980 #12 + '32C' // 2981 #834 + '2hM' // 2982-29be #12 + '76H' // 29bf #1983 + '1pM' // 29c0-29ea #12 + '32C' // 29eb #834 + 'mM' // 29ec-29f9 #12 + 'a251L' // 29fa-29fb #6537 + '9yM' // 29fc-2aff #12 + 'dF' // 2b00-2b04 #5 + 'b74C' // 2b05-2b07 #1926 + 'eF' // 2b08-2b0d #5 + 'cM' // 2b0e-2b11 #12 + 'gF' // 2b12-2b19 #5 + '42Q' // 2b1a #1108 + '73P' // 2b1b #1913 + '4M' // 2b1c #116 + 'rF' // 2b1d-2b2f #5 + '1bM' // 2b30-2b4c #12 + 'bF' // 2b4d-2b4f #5 + '36U' // 2b50 #956 + 'cF' // 2b51-2b54 #5 + '4M' // 2b55 #116 + '1cF' // 2b56-2b73 #5 + 'aA' // 2b74-2b75 + '1dF' // 2b76-2b94 #5 + '42Q' // 2b95 #1108 + 'A' // 2b96 + '3xF' // 2b97-2bfd #5 + 'M' // 2bfe #12 + 'F' // 2bff #5 + '1t27I' // 2c00-2c2e #710 + 'A' // 2c2f + '1t27I' // 2c30-2c5e #710 + 'A' // 2c5f + '1eE' // 2c60-2c7f #4 + '4k50R' // 2c80-2cf3 #1317 + 'dA' // 2cf4-2cf8 + 'f50R' // 2cf9-2cff #1317 + '1k19P' // 2d00-2d25 #509 + 'A' // 2d26 + '19P' // 2d27 #509 + 'dA' // 2d28-2d2c + '19P' // 2d2d #509 + 'aA' // 2d2e-2d2f + '2c51G' // 2d30-2d67 #1332 + 'fA' // 2d68-2d6e + 'a51G' // 2d6f-2d70 #1332 + 'mA' // 2d71-2d7e + '51G' // 2d7f #1332 + 'v3R' // 2d80-2d96 #95 + 'hA' // 2d97-2d9f + 'f3R' // 2da0-2da6 #95 + 'A' // 2da7 + 'f3R' // 2da8-2dae #95 + 'A' // 2daf + 'f3R' // 2db0-2db6 #95 + 'A' // 2db7 + 'f3R' // 2db8-2dbe #95 + 'A' // 2dbf + 'f3R' // 2dc0-2dc6 #95 + 'A' // 2dc7 + 'f3R' // 2dc8-2dce #95 + 'A' // 2dcf + 'f3R' // 2dd0-2dd6 #95 + 'A' // 2dd7 + 'f3R' // 2dd8-2dde #95 + 'A' // 2ddf + '2bE' // 2de0-2e16 #4 + '17K' // 2e17 #452 + 'cE' // 2e18-2e1b #4 + 'a262M' // 2e1c-2e1d #6824 + 'iE' // 2e1e-2e27 #4 + 'a71H' // 2e28-2e29 #1853 + 'eE' // 2e2a-2e2f #4 + 'a261P' // 2e30-2e31 #6801 + 'E' // 2e32 #4 + 'a17K' // 2e33-2e34 #452 + 'dE' // 2e35-2e39 #4 + 'a251K' // 2e3a-2e3b #6536 + 'dE' // 2e3c-2e40 #4 + '71I' // 2e41 #1854 + '1aE' // 2e42-2e5d #4 + '1gA' // 2e5e-2e7f + 'y21M' // 2e80-2e99 #558 + 'A' // 2e9a + '1e21M' // 2e9b-2eba #558 + '251J' // 2ebb #6535 + '2c21M' // 2ebc-2ef3 #558 + 'kA' // 2ef4-2eff + '62N' // 2f00 #1625 + 'b21M' // 2f01-2f03 #558 + '41Q' // 2f04 #1082 + '21M' // 2f05 #558 + '41Q' // 2f06 #1082 + '21M' // 2f07 #558 + '62N' // 2f08 #1625 + '21M' // 2f09 #558 + 'a41Q' // 2f0a-2f0b #1082 + 'd21M' // 2f0c-2f10 #558 + '41Q' // 2f11 #1082 + '118H' // 2f12 #3075 + '21M' // 2f13 #558 + '6O' // 2f14 #170 + 'a4I' // 2f15-2f16 #112 + 'a6O' // 2f17-2f18 #170 + 'b4I' // 2f19-2f1b #112 + 'a6O' // 2f1c-2f1d #170 + '4I' // 2f1e #112 + 'a6O' // 2f1f-2f20 #170 + 'a4I' // 2f21-2f22 #112 + '6O' // 2f23 #170 + '48Q' // 2f24 #1264 + 'a6O' // 2f25-2f26 #170 + '4I' // 2f27 #112 + '6O' // 2f28 #170 + '118G' // 2f29 #3074 + '4I' // 2f2a #112 + '6O' // 2f2b #170 + '4I' // 2f2c #112 + '49Z' // 2f2d #1299 + '4I' // 2f2e #112 + '48Q' // 2f2f #1264 + '49Z' // 2f30 #1299 + 'a6O' // 2f31-2f32 #170 + 'd4I' // 2f33-2f37 #112 + '6O' // 2f38 #170 + 'b4I' // 2f39-2f3b #112 + '48Q' // 2f3c #1264 + 'a6O' // 2f3d-2f3e #170 + '62J' // 2f3f #1621 + '6O' // 2f40 #170 + '4I' // 2f41 #112 + '62J' // 2f42 #1621 + 'a6O' // 2f43-2f44 #170 + '62I' // 2f45 #1620 + 'f6O' // 2f46-2f4c #170 + 'a4I' // 2f4d-2f4e #112 + 'c6O' // 2f4f-2f52 #170 + '4I' // 2f53 #112 + 'd6O' // 2f54-2f58 #170 + '4I' // 2f59 #112 + 'h6O' // 2f5a-2f62 #170 + 'a62I' // 2f63-2f64 #1620 + 'a6O' // 2f65-2f66 #170 + 'a4I' // 2f67-2f68 #112 + 'g6O' // 2f69-2f70 #170 + '4I' // 2f71 #112 + 'a6O' // 2f72-2f73 #170 + '49Z' // 2f74 #1299 + 'a6O' // 2f75-2f76 #170 + '4I' // 2f77 #112 + '6O' // 2f78 #170 + '4I' // 2f79 #112 + 'b6O' // 2f7a-2f7c #170 + '14S' // 2f7d #382 + '4I' // 2f7e #112 + 'c14S' // 2f7f-2f82 #382 + '48N' // 2f83 #1261 + 'g14S' // 2f84-2f8b #382 + 'a4I' // 2f8c-2f8d #112 + '14S' // 2f8e #382 + '48N' // 2f8f #1261 + '14S' // 2f90 #382 + '4I' // 2f91 #112 + 'e14S' // 2f92-2f97 #382 + '4I' // 2f98 #112 + 'g14S' // 2f99-2fa0 #382 + '4I' // 2fa1 #112 + 'a14S' // 2fa2-2fa3 #382 + '4I' // 2fa4 #112 + 'd14S' // 2fa5-2fa9 #382 + 'a4I' // 2faa-2fab #112 + 'e14S' // 2fac-2fb1 #382 + '4I' // 2fb2 #112 + 'h14S' // 2fb3-2fbb #382 + '251G' // 2fbc #6532 + 'c4I' // 2fbd-2fc0 #112 + 'i14S' // 2fc1-2fca #382 + 'a4I' // 2fcb-2fcc #112 + 'g14S' // 2fcd-2fd4 #382 + '4I' // 2fd5 #112 + 'yA' // 2fd6-2fef + 'k4I' // 2ff0-2ffb #112 + 'cA' // 2ffc-2fff + '239K' // 3000 #6224 + '247L' // 3001 #6433 + '247M' // 3002 #6434 + '117T' // 3003 #3061 + '48O' // 3004 #1262 + '118C' // 3005 #3070 + '117U' // 3006 #3062 + '117Z' // 3007 #3067 + '182B' // 3008 #4733 + '182C' // 3009 #4734 + '233T' // 300a #6077 + '233U' // 300b #6078 + 'a245R' // 300c-300d #6387 + '214B' // 300e #5565 + '206I' // 300f #5364 + 'a239J' // 3010-3011 #6223 + '118A' // 3012 #3068 + '48N' // 3013 #1261 + 'a154W' // 3014-3015 #4026 + 'a117N' // 3016-3017 #3055 + 'a117P' // 3018-3019 #3057 + 'a251H' // 301a-301b #6533 + '126G' // 301c #3282 + '124U' // 301d #3244 + '129S' // 301e #3372 + '251W' // 301f #6548 + '117O' // 3020 #3056 + '62K' // 3021 #1622 + '48O' // 3022 #1262 + 'a62K' // 3023-3024 #1622 + '48O' // 3025 #1262 + 'a4I' // 3026-3027 #112 + 'g13J' // 3028-302f #347 + '74O' // 3030 #1938 + 'b13J' // 3031-3033 #347 + 'a251F' // 3034-3035 #6531 + '49X' // 3036 #1297 + 'e13J' // 3037-303c #347 + '74P' // 303d #1939 + 'a13J' // 303e-303f #347 + 'A' // 3040 + '35P' // 3041 #925 + '7C' // 3042 #184 + '35P' // 3043 #925 + '49E' // 3044 #1278 + '35P' // 3045 #925 + '17H' // 3046 #449 + '35P' // 3047 #925 + '21E' // 3048 #550 + '35P' // 3049 #925 + '7C' // 304a #184 + '14P' // 304b #379 + 'a17H' // 304c-304d #449 + '7B' // 304e #183 + '17H' // 304f #449 + '7B' // 3050 #183 + '143R' // 3051 #3735 + '118E' // 3052 #3072 + '7C' // 3053 #184 + '26X' // 3054 #699 + '17H' // 3055 #449 + '17G' // 3056 #448 + '14P' // 3057 #379 + '131I' // 3058 #3414 + '17H' // 3059 #449 + '7B' // 305a #183 + '26Y' // 305b #700 + '15Y' // 305c #414 + '26Y' // 305d #700 + '17G' // 305e #448 + '14P' // 305f #379 + '16A' // 3060 #416 + '7C' // 3061 #184 + '15Y' // 3062 #414 + '17H' // 3063 #449 + '16A' // 3064 #416 + '7B' // 3065 #183 + 'b17H' // 3066-3068 #449 + '26Y' // 3069 #700 + 'a14P' // 306a-306b #379 + '17G' // 306c #448 + '118F' // 306d #3073 + '68A' // 306e #1768 + '17H' // 306f #449 + 'c7B' // 3070-3073 #183 + '17G' // 3074 #448 + 'a7B' // 3075-3076 #183 + '17G' // 3077 #448 + 'a7B' // 3078-3079 #183 + '15Y' // 307a #414 + '7B' // 307b #183 + '15Y' // 307c #414 + '17G' // 307d #448 + '14P' // 307e #379 + '7C' // 307f #184 + '118D' // 3080 #3071 + '149Q' // 3081 #3890 + '7C' // 3082 #184 + 'a21E' // 3083-3084 #550 + '7B' // 3085 #183 + '26X' // 3086 #699 + '7B' // 3087 #183 + '16A' // 3088 #416 + '17H' // 3089 #449 + 'a14P' // 308a-308b #379 + '7C' // 308c #184 + '26X' // 308d #699 + '15Y' // 308e #414 + '21E' // 308f #550 + '62L' // 3090 #1623 + '15Y' // 3091 #414 + '7C' // 3092 #184 + '14P' // 3093 #379 + 'b13J' // 3094-3096 #347 + 'aA' // 3097-3098 + 'a48P' // 3099-309a #1263 + '117R' // 309b #3059 + '117X' // 309c #3065 + 'a48P' // 309d-309e #1263 + 'a13J' // 309f-30a0 #347 + '7B' // 30a1 #183 + '14P' // 30a2 #379 + '16A' // 30a3 #416 + '49E' // 30a4 #1278 + '15Y' // 30a5 #414 + '16A' // 30a6 #416 + '26Y' // 30a7 #700 + '7C' // 30a8 #184 + '7B' // 30a9 #183 + '16A' // 30aa #416 + '7C' // 30ab #184 + '26X' // 30ac #699 + '7C' // 30ad #184 + '7B' // 30ae #183 + '14P' // 30af #379 + '7C' // 30b0 #184 + 'a7B' // 30b1-30b2 #183 + '7C' // 30b3 #184 + '7B' // 30b4 #183 + '16A' // 30b5 #416 + '7B' // 30b6 #183 + '7C' // 30b7 #184 + '17H' // 30b8 #449 + '14P' // 30b9 #379 + '26Y' // 30ba #700 + '26X' // 30bb #699 + '17G' // 30bc #448 + '7B' // 30bd #183 + '17G' // 30be #448 + '17H' // 30bf #449 + '21E' // 30c0 #550 + '7C' // 30c1 #184 + '15Y' // 30c2 #414 + '14P' // 30c3 #379 + '26X' // 30c4 #699 + '15Y' // 30c5 #414 + '7C' // 30c6 #184 + '16A' // 30c7 #416 + '14P' // 30c8 #379 + '7C' // 30c9 #184 + 'a16A' // 30ca-30cb #416 + '17G' // 30cc #448 + 'a26X' // 30cd-30ce #699 + '26Y' // 30cf #700 + '16A' // 30d0 #416 + '21E' // 30d1 #550 + '7B' // 30d2 #183 + '21E' // 30d3 #550 + '7B' // 30d4 #183 + '7C' // 30d5 #184 + '21E' // 30d6 #550 + '7C' // 30d7 #184 + '17G' // 30d8 #448 + 'd7B' // 30d9-30dd #183 + '7C' // 30de #184 + '21E' // 30df #550 + 'a7C' // 30e0-30e1 #184 + '137L' // 30e2 #3573 + '16A' // 30e3 #416 + '7B' // 30e4 #183 + '16A' // 30e5 #416 + '7B' // 30e6 #183 + '21E' // 30e7 #550 + '17G' // 30e8 #448 + 'b14P' // 30e9-30eb #379 + 'a7C' // 30ec-30ed #184 + '117G' // 30ee #3048 + '26Y' // 30ef #700 + '15Y' // 30f0 #414 + '62L' // 30f1 #1623 + '15Y' // 30f2 #414 + '68A' // 30f3 #1768 + '15Y' // 30f4 #414 + '117Y' // 30f5 #3066 + '118B' // 30f6 #3069 + 'c13J' // 30f7-30fa #347 + '216T' // 30fb #5635 + '49E' // 30fc #1278 + 'a48P' // 30fd-30fe #1263 + '13J' // 30ff #347 + 'dA' // 3100-3104 + 'a31I' // 3105-3106 #814 + '63K' // 3107 #1648 + '31I' // 3108 #814 + '141M' // 3109 #3678 + 'a31I' // 310a-310b #814 + '63K' // 310c #1648 + 'e31I' // 310d-3112 #814 + 'a41E' // 3113-3114 #1070 + 'b31I' // 3115-3117 #814 + '41E' // 3118 #1070 + '31I' // 3119 #814 + 'l41E' // 311a-3126 #1070 + '245Z' // 3127 #6395 + '41E' // 3128 #1070 + '245Y' // 3129 #6394 + 'e13J' // 312a-312f #347 + 'A' // 3130 + '26U' // 3131 #696 + '49Y' // 3132 #1298 + '49X' // 3133 #1297 + '26U' // 3134 #696 + '49X' // 3135 #1297 + '13K' // 3136 #348 + '48L' // 3137 #1259 + '13K' // 3138 #348 + '26U' // 3139 #696 + 'f13K' // 313a-3140 #348 + 'a26U' // 3141-3142 #696 + 'a13K' // 3143-3144 #348 + '48L' // 3145 #1259 + '49Y' // 3146 #1298 + '62G' // 3147 #1618 + '26U' // 3148 #696 + '49Y' // 3149 #1298 + '70Q' // 314a #1836 + '117M' // 314b #3054 + '70Q' // 314c #1836 + '117J' // 314d #3051 + '117L' // 314e #3053 + '117I' // 314f #3050 + '13K' // 3150 #348 + '70P' // 3151 #1835 + '13K' // 3152 #348 + '117H' // 3153 #3049 + 'b13K' // 3154-3156 #348 + '70P' // 3157 #1835 + 'c13K' // 3158-315b #348 + '48L' // 315c #1259 + 'b13K' // 315d-315f #348 + '117K' // 3160 #3052 + '26U' // 3161 #696 + '13K' // 3162 #348 + '26U' // 3163 #696 + '261E' // 3164 #6790 + '1a13K' // 3165-3180 #348 + '117F' // 3181 #3047 + 'c13K' // 3182-3185 #348 + '251E' // 3186 #6530 + 'e13K' // 3187-318c #348 + '62G' // 318d #1618 + '13K' // 318e #348 + 'A' // 318f + 'a13J' // 3190-3191 #347 + '48M' // 3192 #1260 + '62H' // 3193 #1619 + 'a13J' // 3194-3195 #347 + 'b48M' // 3196-3198 #1260 + 'c13J' // 3199-319c #347 + '62H' // 319d #1619 + 'a48M' // 319e-319f #1260 + '1a13J' // 31a0-31bb #347 + 'cA' // 31bc-31bf + 'g13J' // 31c0-31c7 #347 + '1a12M' // 31c8-31e3 #324 + 'kA' // 31e4-31ef + 'o12M' // 31f0-31ff #324 + '1a17J' // 3200-321b #451 + '251C' // 321c #6528 + 'a12M' // 321d-321e #324 + 'A' // 321f + 'f62F' // 3220-3226 #1617 + 'b251D' // 3227-3229 #6529 + 'f12M' // 322a-3230 #324 + '117S' // 3231 #3060 + 'f12M' // 3232-3238 #324 + '17J' // 3239 #451 + 'v12M' // 323a-3250 #324 + 'i17J' // 3251-325a #451 + 'd12M' // 325b-325f #324 + 'g17J' // 3260-3267 #451 + '117D' // 3268 #3045 + 'r17J' // 3269-327b #451 + 'a12M' // 327c-327d #324 + 'a17J' // 327e-327f #451 + '12M' // 3280 #324 + '31H' // 3281 #813 + 'g12M' // 3282-3289 #324 + '17J' // 328a #451 + '117E' // 328b #3046 + 'd17J' // 328c-3290 #451 + 'a31H' // 3291-3292 #813 + '12M' // 3293 #324 + '17J' // 3294 #451 + 'a31H' // 3295-3296 #813 + '51O' // 3297 #1340 + '12M' // 3298 #324 + '51O' // 3299 #1340 + 'b12M' // 329a-329c #324 + '31H' // 329d #813 + '17J' // 329e #451 + '31H' // 329f #813 + 'b12M' // 32a0-32a2 #324 + '62F' // 32a3 #1617 + '31H' // 32a4 #813 + '17J' // 32a5 #451 + 'n12M' // 32a6-32b4 #324 + '1f27D' // 32b5-32d5 #705 + '35O' // 32d6 #924 + 'i27D' // 32d7-32e0 #705 + '35O' // 32e1 #924 + '1w27D' // 32e2-3313 #705 + '35O' // 3314 #924 + 'l27D' // 3315-3321 #705 + '35O' // 3322 #924 + 'h27D' // 3323-332b #705 + 'A' // 332c + '3c27D' // 332d-337e #705 + '35O' // 337f #924 + 'd70O' // 3380-3384 #1834 + 'b27D' // 3385-3387 #705 + 'c70O' // 3388-338b #1834 + 'a70N' // 338c-338d #1833 + 'a117C' // 338e-338f #3044 + 'd70N' // 3390-3394 #1833 + '251B' // 3395 #6527 + 'e21L' // 3396-339b #557 + '117Q' // 339c #3058 + '117W' // 339d #3064 + '116Z' // 339e #3041 + 'a21L' // 339f-33a0 #557 + '117V' // 33a1 #3063 + '1g21L' // 33a2-33c3 #557 + '116Y' // 33c4 #3040 + 'f21L' // 33c5-33cb #557 + 'bT' // 33cc-33ce #19 + 'a21L' // 33cf-33d0 #557 + 'a117B' // 33d1-33d2 #3043 + '21L' // 33d3 #557 + 'T' // 33d4 #19 + '70M' // 33d5 #1832 + '21L' // 33d6 #557 + 'T' // 33d7 #19 + '21L' // 33d8 #557 + 'aT' // 33d9-33da #19 + 'b21L' // 33db-33dd #557 + '1gT' // 33de-33ff #19 + 'aA' // 3400-3401 + 'T' // 3402 #19 + 'aA' // 3403-3404 + 'aT' // 3405-3406 #19 + '1eA' // 3407-3426 + 'T' // 3427 #19 + 'cA' // 3428-342b + 'T' // 342c #19 + 'A' // 342d + 'T' // 342e #19 + 'dA' // 342f-3433 + '7J' // 3434 #191 + '7A' // 3435 #182 + 'iA' // 3436-343f + '62M' // 3440 #1624 + 'fA' // 3441-3447 + 'a3G' // 3448-3449 #84 + '7A' // 344a #182 + 'A' // 344b + '7A' // 344c #182 + 'vA' // 344d-3463 + '7A' // 3464 #182 + 'bA' // 3465-3467 + 'T' // 3468 #19 + 'A' // 3469 + 'T' // 346a #19 + 'gA' // 346b-3472 + '7A' // 3473 #182 + 'eA' // 3474-3479 + '7A' // 347a #182 + 'aA' // 347b-347c + 'a7A' // 347d-347e #182 + 'hA' // 347f-3487 + 'T' // 3488 #19 + 'hA' // 3489-3491 + 'T' // 3492 #19 + '7A' // 3493 #182 + 'aA' // 3494-3495 + '7A' // 3496 #182 + 'mA' // 3497-34a4 + '7A' // 34a5 #182 + 'hA' // 34a6-34ae + '7A' // 34af #182 + 'dA' // 34b0-34b4 + 'T' // 34b5 #19 + 'eA' // 34b6-34bb + '62E' // 34bc #1616 + 'cA' // 34bd-34c0 + '62E' // 34c1 #1616 + 'dA' // 34c2-34c6 + 'T' // 34c7 #19 + '7A' // 34c8 #182 + 'qA' // 34c9-34da + 'T' // 34db #19 + '7J' // 34dc #191 + 'aA' // 34dd-34de + '7A' // 34df #182 + 'cA' // 34e0-34e3 + '7A' // 34e4 #182 + 'A' // 34e5 + '7A' // 34e6 #182 + 'fA' // 34e7-34ed + '7J' // 34ee #191 + 'kA' // 34ef-34fa + '7A' // 34fb #182 + 'iA' // 34fc-3505 + '7A' // 3506 #182 + 'wA' // 3507-351e + 'T' // 351f #19 + '1cA' // 3520-353d + '117A' // 353e #3042 + 'qA' // 353f-3550 + '62M' // 3551 #1624 + 'A' // 3552 + '7A' // 3553 #182 + 'dA' // 3554-3558 + '7A' // 3559 #182 + 'bA' // 355a-355c + '70M' // 355d #1832 + 'T' // 355e #19 + 'aA' // 355f-3560 + '7A' // 3561 #182 + 'A' // 3562 + 'T' // 3563 #19 + 'aA' // 3564-3565 + '7J' // 3566 #191 + 'eA' // 3567-356c + '7A' // 356d #182 + 'T' // 356e #19 + 'A' // 356f + '7A' // 3570 #182 + 'A' // 3571 + 'I' // 3572 #8 + 'aA' // 3573-3574 + '7J' // 3575 #191 + 'A' // 3576 + 'aI' // 3577-3578 #8 + 'jA' // 3579-3583 + 'I' // 3584 #8 + 'lA' // 3585-3591 + '7J' // 3592 #191 + 'cA' // 3593-3596 + 'aI' // 3597-3598 #8 + 'fA' // 3599-359f + '7J' // 35a0 #191 + '116X' // 35a1 #3039 + 'bA' // 35a2-35a4 + 'I' // 35a5 #8 + 'T' // 35a6 #19 + 'A' // 35a7 + 'T' // 35a8 #19 + 'cA' // 35a9-35ac + '62D' // 35ad #1615 + 'pA' // 35ae-35be + 'I' // 35bf #8 + 'A' // 35c0 + 'I' // 35c1 #8 + 'bA' // 35c2-35c4 + '26T' // 35c5 #695 + 'A' // 35c6 + 'I' // 35c7 #8 + 'aA' // 35c8-35c9 + 'I' // 35ca #8 + 'bA' // 35cb-35cd + '62D' // 35ce #1615 + 'bA' // 35cf-35d1 + 'I' // 35d2 #8 + 'bA' // 35d3-35d5 + 'I' // 35d6 #8 + 'bA' // 35d7-35d9 + 'T' // 35da #19 + 'I' // 35db #8 + 'A' // 35dc + 'I' // 35dd #8 + 'T' // 35de #19 + 'qA' // 35df-35f0 + 'bI' // 35f1-35f3 #8 + 'T' // 35f4 #19 + 'eA' // 35f5-35fa + 'I' // 35fb #8 + 'aA' // 35fc-35fd + 'I' // 35fe #8 + 'eA' // 35ff-3604 + 'T' // 3605 #19 + 'bA' // 3606-3608 + 'I' // 3609 #8 + 'iA' // 360a-3613 + 'T' // 3614 #19 + 'bA' // 3615-3617 + 'I' // 3618 #8 + 'A' // 3619 + 'I' // 361a #8 + 'gA' // 361b-3622 + 'I' // 3623 #8 + 'A' // 3624 + 'I' // 3625 #8 + 'fA' // 3626-362c + 'I' // 362d #8 + 'fA' // 362e-3634 + 'I' // 3635 #8 + 'bA' // 3636-3638 + 'I' // 3639 #8 + 'cA' // 363a-363d + 'I' // 363e #8 + 'gA' // 363f-3646 + 'bI' // 3647-3649 #8 + 'T' // 364a #19 + 'bA' // 364b-364d + 'I' // 364e #8 + 'oA' // 364f-365e + 'I' // 365f #8 + 'A' // 3660 + 'I' // 3661 #8 + 'wA' // 3662-3679 + 'I' // 367a #8 + 'eA' // 367b-3680 + 'I' // 3681 #8 + 'nA' // 3682-3690 + 'T' // 3691 #19 + 'cA' // 3692-3695 + 'T' // 3696 #19 + 'aA' // 3697-3698 + 'T' // 3699 #19 + 'I' // 369a #8 + 'fA' // 369b-36a1 + '7J' // 36a2 #191 + 'aA' // 36a3-36a4 + 'I' // 36a5 #8 + 'cA' // 36a6-36a9 + 'I' // 36aa #8 + '7J' // 36ab #191 + '35N' // 36ac #923 + 'bA' // 36ad-36af + 'aI' // 36b0-36b1 #8 + 'bA' // 36b2-36b4 + 'I' // 36b5 #8 + 'bA' // 36b6-36b8 + 'I' // 36b9 #8 + 'aA' // 36ba-36bb + 'I' // 36bc #8 + 'cA' // 36bd-36c0 + 'I' // 36c1 #8 + 'A' // 36c2 'bI' // 36c3-36c5 #8 - 'D' // 36c6 #3 - 'aB' // 36c7-36c8 #1 - 'eD' // 36c9-36ce #3 - 'F' // 36cf #5 - 'bD' // 36d0-36d2 #3 - 'aB' // 36d3-36d4 #1 - 'D' // 36d5 #3 - 'B' // 36d6 #1 - 'eD' // 36d7-36dc #3 - 'B' // 36dd #1 - 'bD' // 36de-36e0 #3 - 'aB' // 36e1-36e2 #1 - 'aD' // 36e3-36e4 #3 - 'B' // 36e5 #1 - 'I' // 36e6 #8 - 'mD' // 36e7-36f4 #3 - 'B' // 36f5 #1 - 'jD' // 36f6-3700 #3 - 'B' // 3701 #1 - 'D' // 3702 #3 - 'B' // 3703 #1 - 'cD' // 3704-3707 #3 - 'B' // 3708 #1 - 'D' // 3709 #3 - 'B' // 370a #1 - 'aD' // 370b-370c #3 - 'B' // 370d #1 - 'mD' // 370e-371b #3 - 'B' // 371c #1 - 'dD' // 371d-3721 #3 - 'B' // 3722 #1 - 'I' // 3723 #8 - 'D' // 3724 #3 - 'B' // 3725 #1 - 'eD' // 3726-372b #3 - 'aB' // 372c-372d #1 - 'D' // 372e #3 - 'K' // 372f #10 - 'B' // 3730 #1 - 'D' // 3731 #3 - 'aB' // 3732-3733 #1 - 'eD' // 3734-3739 #3 - 'I' // 373a #8 - 'dD' // 373b-373f #3 - 'B' // 3740 #1 - 'aD' // 3741-3742 #3 - 'B' // 3743 #1 - '1bD' // 3744-3760 #3 - 'F' // 3761 #5 - 'C' // 3762 #2 - 'gD' // 3763-376a #3 - 'aF' // 376b-376c #5 - 'aD' // 376d-376e #3 - 'B' // 376f #1 - 'dD' // 3770-3774 #3 - 'F' // 3775 #5 - 'vD' // 3776-378c #3 - 'F' // 378d #5 - 'hD' // 378e-3796 #3 - 'B' // 3797 #1 - 'gD' // 3798-379f #3 - 'B' // 37a0 #1 - 'wD' // 37a1-37b8 #3 - 'B' // 37b9 #1 - 'aD' // 37ba-37bb #3 - 'K' // 37bc #10 - 'D' // 37bd #3 - 'B' // 37be #1 - 'aD' // 37bf-37c0 #3 - 'F' // 37c1 #5 - 'sD' // 37c2-37d5 #3 - 'B' // 37d6 #1 - 'jD' // 37d7-37e1 #3 - 'F' // 37e2 #5 - 'dD' // 37e3-37e7 #3 - 'F' // 37e8 #5 - 'hD' // 37e9-37f1 #3 - 'B' // 37f2 #1 - 'D' // 37f3 #3 - 'F' // 37f4 #5 - 'bD' // 37f5-37f7 #3 - 'B' // 37f8 #1 - 'aD' // 37f9-37fa #3 - 'B' // 37fb #1 - 'D' // 37fc #3 - 'F' // 37fd #5 - 'aD' // 37fe-37ff #3 - 'F' // 3800 #5 - 'jD' // 3801-380b #3 - 'K' // 380c #10 - 'aD' // 380d-380e #3 - 'B' // 380f #1 - 'gD' // 3810-3817 #3 - 'K' // 3818 #10 - 'B' // 3819 #1 - 'eD' // 381a-381f #3 - 'B' // 3820 #1 - 'kD' // 3821-382c #3 - 'B' // 382d #1 - 'D' // 382e #3 - 'F' // 382f #5 - 'eD' // 3830-3835 #3 - 'C' // 3836 #2 - 'D' // 3837 #3 - 'B' // 3838 #1 - 'fD' // 3839-383f #3 - 'F' // 3840 #5 - 'zD' // 3841-385b #3 - 'F' // 385c #5 - 'cD' // 385d-3860 #3 - 'F' // 3861 #5 - 'D' // 3862 #3 - 'B' // 3863 #1 - 'pD' // 3864-3874 #3 - 'B' // 3875 #1 - 'lD' // 3876-3882 #3 - 'K' // 3883 #10 - '1aD' // 3884-389f #3 - 'B' // 38a0 #1 - 'F' // 38a1 #5 - 'jD' // 38a2-38ac #3 - 'F' // 38ad #5 - 'kD' // 38ae-38b9 #3 - 'K' // 38ba #10 - 'gD' // 38bb-38c2 #3 - 'B' // 38c3 #1 - 'gD' // 38c4-38cb #3 - 'B' // 38cc #1 - 'cD' // 38cd-38d0 #3 - 'B' // 38d1 #1 - 'aD' // 38d2-38d3 #3 - 'B' // 38d4 #1 - 'qD' // 38d5-38e6 #3 - 'K' // 38e7 #10 - 'qD' // 38e8-38f9 #3 - 'C' // 38fa #2 - 'aD' // 38fb-38fc #3 - 'K' // 38fd #10 - 'iD' // 38fe-3907 #3 - 'B' // 3908 #1 - 'jD' // 3909-3913 #3 - 'B' // 3914 #1 - 'aD' // 3915-3916 #3 - 'F' // 3917 #5 - 'aD' // 3918-3919 #3 - 'F' // 391a #5 - 'kD' // 391b-3926 #3 - 'B' // 3927 #1 - 'iD' // 3928-3931 #3 - 'B' // 3932 #1 - 'kD' // 3933-393e #3 - 'B' // 393f #1 - 'lD' // 3940-394c #3 - 'B' // 394d #1 - 'qD' // 394e-395f #3 - 'K' // 3960 #10 - 'aD' // 3961-3962 #3 - 'B' // 3963 #1 - 'D' // 3964 #3 - 'K' // 3965 #10 - 'hD' // 3966-396e #3 - 'F' // 396f #5 - 'gD' // 3970-3977 #3 - 'B' // 3978 #1 - 'fD' // 3979-397f #3 - 'B' // 3980 #1 - 'aD' // 3981-3982 #3 - 'K' // 3983 #10 - 'dD' // 3984-3988 #3 - 'aB' // 3989-398a #1 - 'dD' // 398b-398f #3 - 'K' // 3990 #10 - 'D' // 3991 #3 - 'B' // 3992 #1 - 'eD' // 3993-3998 #3 - 'B' // 3999 #1 - 'D' // 399a #3 - 'B' // 399b #1 - 'dD' // 399c-39a0 #3 - 'B' // 39a1 #1 - 'aD' // 39a2-39a3 #3 - 'C' // 39a4 #2 - 'K' // 39a5 #10 - 'oD' // 39a6-39b5 #3 - 'K' // 39b6 #10 - 'D' // 39b7 #3 - 'C' // 39b8 #2 - '1hD' // 39b9-39db #3 - 'B' // 39dc #1 - 'dD' // 39dd-39e1 #3 - 'B' // 39e2 #1 - 'aD' // 39e3-39e4 #3 - 'B' // 39e5 #1 - 'eD' // 39e6-39eb #3 - 'B' // 39ec #1 - 'jD' // 39ed-39f7 #3 - 'B' // 39f8 #1 - 'aD' // 39f9-39fa #3 - 'B' // 39fb #1 - 'aD' // 39fc-39fd #3 - 'B' // 39fe #1 - 'aD' // 39ff-3a00 #3 - 'B' // 3a01 #1 - 'D' // 3a02 #3 - 'B' // 3a03 #1 - 'aD' // 3a04-3a05 #3 - 'B' // 3a06 #1 - 'oD' // 3a07-3a16 #3 - 'aB' // 3a17-3a18 #1 - 'oD' // 3a19-3a28 #3 - 'aB' // 3a29-3a2a #1 - 'hD' // 3a2b-3a33 #3 - 'B' // 3a34 #1 - 'cD' // 3a35-3a38 #3 - 'K' // 3a39 #10 - 'pD' // 3a3a-3a4a #3 - 'B' // 3a4b #1 - 'eD' // 3a4c-3a51 #3 - 'B' // 3a52 #1 - 'cD' // 3a53-3a56 #3 - 'B' // 3a57 #1 - 'cD' // 3a58-3a5b #3 - 'C' // 3a5c #2 - 'D' // 3a5d #3 - 'B' // 3a5e #1 - 'fD' // 3a5f-3a65 #3 - 'aB' // 3a66-3a67 #1 - 'eD' // 3a68-3a6d #3 - 'F' // 3a6e #5 - 'cD' // 3a6f-3a72 #3 - 'F' // 3a73 #5 - 'pD' // 3a74-3a84 #3 - 'F' // 3a85 #5 - 'pD' // 3a86-3a96 #3 - 'B' // 3a97 #1 - 'kD' // 3a98-3aa3 #3 - 'K' // 3aa4 #10 - 'eD' // 3aa5-3aaa #3 - 'B' // 3aab #1 - 'pD' // 3aac-3abc #3 - 'B' // 3abd #1 - 'eD' // 3abe-3ac3 #3 - 'F' // 3ac4 #5 - 'eD' // 3ac5-3aca #3 - 'F' // 3acb #5 - 'iD' // 3acc-3ad5 #3 - 'aF' // 3ad6-3ad7 #5 - 'aD' // 3ad8-3ad9 #3 - '7S' // 3ada #200 - 'D' // 3adb #3 - 'K' // 3adc #10 - 'D' // 3add #3 - 'B' // 3ade #1 - 'D' // 3adf #3 - 'B' // 3ae0 #1 - 'hD' // 3ae1-3ae9 #3 - 'F' // 3aea #5 - 'dD' // 3aeb-3aef #3 - 'B' // 3af0 #1 - 'D' // 3af1 #3 - 'B' // 3af2 #1 - 'F' // 3af3 #5 - 'D' // 3af4 #3 - 'B' // 3af5 #1 - 'K' // 3af6 #10 - 'cD' // 3af7-3afa #3 - 'B' // 3afb #1 - 'fD' // 3afc-3b02 #3 - 'K' // 3b03 #10 - 'iD' // 3b04-3b0d #3 - 'C' // 3b0e #2 - 'iD' // 3b0f-3b18 #3 - 'B' // 3b19 #1 - 'F' // 3b1a #5 - 'D' // 3b1b #3 - 'F' // 3b1c #5 - 'dD' // 3b1d-3b21 #3 - 'C' // 3b22 #2 - 'K' // 3b23 #10 - 'fD' // 3b24-3b2a #3 - 'B' // 3b2b #1 - 'hD' // 3b2c-3b34 #3 - 'F' // 3b35 #5 - 'bD' // 3b36-3b38 #3 - 'B' // 3b39 #1 - 'gD' // 3b3a-3b41 #3 - 'B' // 3b42 #1 - 'tD' // 3b43-3b57 #3 - 'B' // 3b58 #1 - 'fD' // 3b59-3b5f #3 - 'B' // 3b60 #1 - 'kD' // 3b61-3b6c #3 - 'F' // 3b6d #5 - 'bD' // 3b6e-3b70 #3 - 'aB' // 3b71-3b72 #1 - 'cD' // 3b73-3b76 #3 - 'F' // 3b77 #5 - 'D' // 3b78 #3 - 'K' // 3b79 #10 - 'D' // 3b7a #3 - 'aB' // 3b7b-3b7c #1 - 'bD' // 3b7d-3b7f #3 - 'B' // 3b80 #1 - 'eD' // 3b81-3b86 #3 - 'aF' // 3b87-3b88 #5 - 'cD' // 3b89-3b8c #3 - 'F' // 3b8d #5 - 'fD' // 3b8e-3b94 #3 - 'aB' // 3b95-3b96 #1 - 'aD' // 3b97-3b98 #3 - 'B' // 3b99 #1 - 'fD' // 3b9a-3ba0 #3 - 'B' // 3ba1 #1 - 'aD' // 3ba2-3ba3 #3 - 'F' // 3ba4 #5 - 'pD' // 3ba5-3bb5 #3 - 'F' // 3bb6 #5 - 'dD' // 3bb7-3bbb #3 - 'B' // 3bbc #1 - 'D' // 3bbd #3 - 'B' // 3bbe #1 - 'bD' // 3bbf-3bc1 #3 - 'B' // 3bc2 #1 - 'F' // 3bc3 #5 - 'B' // 3bc4 #1 - 'gD' // 3bc5-3bcc #3 - 'F' // 3bcd #5 - 'hD' // 3bce-3bd6 #3 - 'B' // 3bd7 #1 - 'dD' // 3bd8-3bdc #3 - 'B' // 3bdd #1 - 'mD' // 3bde-3beb #3 - 'B' // 3bec #1 - 'bD' // 3bed-3bef #3 - 'F' // 3bf0 #5 - 'D' // 3bf1 #3 - 'B' // 3bf2 #1 - 'A' // 3bf3 #0 - 'B' // 3bf4 #1 - 'wD' // 3bf5-3c0c #3 - 'B' // 3c0d #1 - 'D' // 3c0e #3 - 'F' // 3c0f #5 - 'D' // 3c10 #3 - 'B' // 3c11 #1 - 'aD' // 3c12-3c13 #3 - 'K' // 3c14 #10 - 'B' // 3c15 #1 - 'aD' // 3c16-3c17 #3 - 'B' // 3c18 #1 - 'jD' // 3c19-3c23 #3 - 'K' // 3c24 #10 - 'D' // 3c25 #3 - 'F' // 3c26 #5 - 'eD' // 3c27-3c2c #3 - 'K' // 3c2d #10 - '1kD' // 3c2e-3c53 #3 - 'B' // 3c54 #1 - '2aD' // 3c55-3c8a #3 - 'B' // 3c8b #1 - '1vD' // 3c8c-3cbc #3 - 'aK' // 3cbd-3cbe #10 - 'cD' // 3cbf-3cc2 #3 - 'F' // 3cc3 #5 - 'fD' // 3cc4-3cca #3 - 'B' // 3ccb #1 - 'D' // 3ccc #3 - 'B' // 3ccd #1 - 'bD' // 3cce-3cd0 #3 - 'B' // 3cd1 #1 - 'F' // 3cd2 #5 - 'bD' // 3cd3-3cd5 #3 - 'B' // 3cd6 #1 - 'dD' // 3cd7-3cdb #3 - 'B' // 3cdc #1 - 'mD' // 3cdd-3cea #3 - 'B' // 3ceb #1 - 'bD' // 3cec-3cee #3 - 'B' // 3cef #1 - 'kD' // 3cf0-3cfb #3 - 'K' // 3cfc #10 - 'sD' // 3cfd-3d10 #3 - 'F' // 3d11 #5 - 'aB' // 3d12-3d13 #1 - 'bD' // 3d14-3d16 #3 - 'K' // 3d17 #10 - 'dD' // 3d18-3d1c #3 - 'B' // 3d1d #1 - 'F' // 3d1e #5 - 'qD' // 3d1f-3d30 #3 - 'F' // 3d31 #5 - 'B' // 3d32 #1 - 'gD' // 3d33-3d3a #3 - 'B' // 3d3b #1 - 'iD' // 3d3c-3d45 #3 - 'B' // 3d46 #1 - 'dD' // 3d47-3d4b #3 - 'B' // 3d4c #1 - 'D' // 3d4d #3 - 'C' // 3d4e #2 - 'aD' // 3d4f-3d50 #3 - 'B' // 3d51 #1 - 'lD' // 3d52-3d5e #3 - 'I' // 3d5f #8 - 'aD' // 3d60-3d61 #3 - 'B' // 3d62 #1 - 'D' // 3d63 #3 - 'F' // 3d64 #5 - 'cD' // 3d65-3d68 #3 - 'aB' // 3d69-3d6a #1 - 'cD' // 3d6b-3d6e #3 - 'B' // 3d6f #1 - 'dD' // 3d70-3d74 #3 - 'B' // 3d75 #1 - 'fD' // 3d76-3d7c #3 - 'B' // 3d7d #1 - 'fD' // 3d7e-3d84 #3 - 'B' // 3d85 #1 - 'aD' // 3d86-3d87 #3 - 'B' // 3d88 #1 - 'D' // 3d89 #3 - 'B' // 3d8a #1 - 'cD' // 3d8b-3d8e #3 - 'B' // 3d8f #1 - 'D' // 3d90 #3 - 'B' // 3d91 #1 - 'gD' // 3d92-3d99 #3 - 'F' // 3d9a #5 - 'iD' // 3d9b-3da4 #3 - 'B' // 3da5 #1 - 'fD' // 3da6-3dac #3 - 'B' // 3dad #1 - 'eD' // 3dae-3db3 #3 - 'B' // 3db4 #1 - 'fD' // 3db5-3dbb #3 - 'K' // 3dbc #10 - 'aD' // 3dbd-3dbe #3 - 'B' // 3dbf #1 - 'F' // 3dc0 #5 - 'D' // 3dc1 #3 - 'K' // 3dc2 #10 - 'bD' // 3dc3-3dc5 #3 - 'aB' // 3dc6-3dc7 #1 - 'D' // 3dc8 #3 - 'B' // 3dc9 #1 - 'aD' // 3dca-3dcb #3 - 'C' // 3dcc #2 - 'B' // 3dcd #1 - 'dD' // 3dce-3dd2 #3 - 'B' // 3dd3 #1 - 'F' // 3dd4 #5 - 'eD' // 3dd5-3dda #3 - 'B' // 3ddb #1 - 'jD' // 3ddc-3de6 #3 - 'aB' // 3de7-3de8 #1 - 'aD' // 3de9-3dea #3 - 'B' // 3deb #1 - 'fD' // 3dec-3df2 #3 - 'aB' // 3df3-3df4 #1 - 'aD' // 3df5-3df6 #3 - 'B' // 3df7 #1 - 'cD' // 3df8-3dfb #3 - 'aB' // 3dfc-3dfd #1 - 'fD' // 3dfe-3e04 #3 - 'F' // 3e05 #5 - 'B' // 3e06 #1 - '2cD' // 3e07-3e3e #3 - 'F' // 3e3f #5 - 'C' // 3e40 #2 - 'aD' // 3e41-3e42 #3 - 'B' // 3e43 #1 - 'cD' // 3e44-3e47 #3 - 'B' // 3e48 #1 - 'kD' // 3e49-3e54 #3 - 'B' // 3e55 #1 - 'iD' // 3e56-3e5f #3 - 'F' // 3e60 #5 - 'dD' // 3e61-3e65 #3 - 'F' // 3e66 #5 - 'D' // 3e67 #3 - 'F' // 3e68 #5 - 'jD' // 3e69-3e73 #3 - 'B' // 3e74 #1 - 'mD' // 3e75-3e82 #3 - 'F' // 3e83 #5 - 'eD' // 3e84-3e89 #3 - 'F' // 3e8a #5 - 'hD' // 3e8b-3e93 #3 - 'F' // 3e94 #5 - 'rD' // 3e95-3ea7 #3 - 'bB' // 3ea8-3eaa #1 - 'aD' // 3eab-3eac #3 - 'B' // 3ead #1 - 'bD' // 3eae-3eb0 #3 - 'B' // 3eb1 #1 - 'eD' // 3eb2-3eb7 #3 - 'B' // 3eb8 #1 - 'eD' // 3eb9-3ebe #3 - 'B' // 3ebf #1 - 'aD' // 3ec0-3ec1 #3 - 'B' // 3ec2 #1 - 'D' // 3ec3 #3 - 'K' // 3ec4 #10 - 'aD' // 3ec5-3ec6 #3 - 'B' // 3ec7 #1 - 'aD' // 3ec8-3ec9 #3 - 'B' // 3eca #1 - 'D' // 3ecb #3 - 'B' // 3ecc #1 - 'bD' // 3ecd-3ecf #3 - 'aB' // 3ed0-3ed1 #1 - 'cD' // 3ed2-3ed5 #3 - 'aB' // 3ed6-3ed7 #1 - 'aD' // 3ed8-3ed9 #3 - 'C' // 3eda #2 - 'B' // 3edb #1 - 'aD' // 3edc-3edd #3 - 'B' // 3ede #1 - 'aD' // 3edf-3ee0 #3 - 'aB' // 3ee1-3ee2 #1 - 'cD' // 3ee3-3ee6 #3 - 'B' // 3ee7 #1 - 'D' // 3ee8 #3 - 'B' // 3ee9 #1 - 'D' // 3eea #3 - 'aB' // 3eeb-3eec #1 - 'K' // 3eed #10 - 'aD' // 3eee-3eef #3 - 'B' // 3ef0 #1 - 'aD' // 3ef1-3ef2 #3 - 'aB' // 3ef3-3ef4 #1 - 'dD' // 3ef5-3ef9 #3 - 'B' // 3efa #1 - 'D' // 3efb #3 - 'B' // 3efc #1 - 'K' // 3efd #10 - 'D' // 3efe #3 - 'aB' // 3eff-3f00 #1 - 'bD' // 3f01-3f03 #3 - 'I' // 3f04 #8 - 'D' // 3f05 #3 - 'aB' // 3f06-3f07 #1 - 'eD' // 3f08-3f0d #3 - 'B' // 3f0e #1 - '2oD' // 3f0f-3f52 #3 - 'B' // 3f53 #1 - 'bD' // 3f54-3f56 #3 - 'F' // 3f57 #5 - 'aB' // 3f58-3f59 #1 - 'hD' // 3f5a-3f62 #3 - 'B' // 3f63 #1 - 'mD' // 3f64-3f71 #3 - 'F' // 3f72 #5 - 'aD' // 3f73-3f74 #3 - 'F' // 3f75 #5 - 'D' // 3f76 #3 - 'F' // 3f77 #5 - 'cD' // 3f78-3f7b #3 - 'B' // 3f7c #1 - 'uD' // 3f7d-3f92 #3 - 'B' // 3f93 #1 - 'yD' // 3f94-3fad #3 - 'F' // 3fae #5 - 'aD' // 3faf-3fb0 #3 - 'F' // 3fb1 #5 - 'mD' // 3fb2-3fbf #3 - 'B' // 3fc0 #1 - 'fD' // 3fc1-3fc7 #3 - 'B' // 3fc8 #1 - 'F' // 3fc9 #5 - 'lD' // 3fca-3fd6 #3 - 'C' // 3fd7 #2 - 'cD' // 3fd8-3fdb #3 - 'C' // 3fdc #2 - 'gD' // 3fdd-3fe4 #3 - 'B' // 3fe5 #1 - 'fD' // 3fe6-3fec #3 - 'B' // 3fed #1 - 'jD' // 3fee-3ff8 #3 - 'aB' // 3ff9-3ffa #1 - 'hD' // 3ffb-4003 #3 - 'B' // 4004 #1 - 'cD' // 4005-4008 #3 - 'B' // 4009 #1 - 'rD' // 400a-401c #3 - 'B' // 401d #1 - 'pD' // 401e-402e #3 - 'K' // 402f #10 - 'cD' // 4030-4033 #3 - 'K' // 4034 #10 - 'cD' // 4035-4038 #3 - 'C' // 4039 #2 - 'jD' // 403a-4044 #3 - 'B' // 4045 #1 - 'lD' // 4046-4052 #3 - 'B' // 4053 #1 - 'bD' // 4054-4056 #3 - 'B' // 4057 #1 - 'F' // 4058 #5 - 'hD' // 4059-4061 #3 - 'I' // 4062 #8 - 'aD' // 4063-4064 #3 - 'B' // 4065 #1 - 'cD' // 4066-4069 #3 - 'B' // 406a #1 - 'cD' // 406b-406e #3 - 'B' // 406f #1 - 'D' // 4070 #3 - 'B' // 4071 #1 - '1fD' // 4072-4092 #3 - 'F' // 4093 #5 - 'sD' // 4094-40a7 #3 - 'B' // 40a8 #1 - 'K' // 40a9 #10 - 'iD' // 40aa-40b3 #3 - 'B' // 40b4 #1 - 'eD' // 40b5-40ba #3 - 'B' // 40bb #1 - 'bD' // 40bc-40be #3 - 'B' // 40bf #1 - 'gD' // 40c0-40c7 #3 - 'B' // 40c8 #1 - 'K' // 40c9 #10 - 'mD' // 40ca-40d7 #3 - 'B' // 40d8 #1 - 'eD' // 40d9-40de #3 - 'B' // 40df #1 - 'wD' // 40e0-40f7 #3 - 'B' // 40f8 #1 - 'D' // 40f9 #3 - 'B' // 40fa #1 - 'fD' // 40fb-4101 #3 - 'B' // 4102 #1 - 'C' // 4103 #2 - 'B' // 4104 #1 - 'F' // 4105 #5 - 'bD' // 4106-4108 #3 - 'B' // 4109 #1 - 'cD' // 410a-410d #3 - 'B' // 410e #1 - '1gD' // 410f-4130 #3 - 'aB' // 4131-4132 #1 - 'cD' // 4133-4136 #3 - 'K' // 4137 #10 - 'oD' // 4138-4147 #3 - 'F' // 4148 #5 - 'eD' // 4149-414e #3 - 'F' // 414f #5 - 'rD' // 4150-4162 #3 - 'F' // 4163 #5 - 'bD' // 4164-4166 #3 - 'B' // 4167 #1 - 'cD' // 4168-416b #3 - 'B' // 416c #1 - 'D' // 416d #3 - 'B' // 416e #1 - 'lD' // 416f-417b #3 - 'B' // 417c #1 - 'aD' // 417d-417e #3 - 'B' // 417f #1 - 'D' // 4180 #3 - 'B' // 4181 #1 - 'mD' // 4182-418f #3 - 'B' // 4190 #1 - 'zD' // 4191-41ab #3 - 'K' // 41ac #10 - 'dD' // 41ad-41b1 #3 - 'B' // 41b2 #1 - 'D' // 41b3 #3 - 'F' // 41b4 #5 - 'iD' // 41b5-41be #3 - 'F' // 41bf #5 - 'cD' // 41c0-41c3 #3 - 'B' // 41c4 #1 - 'dD' // 41c5-41c9 #3 - 'B' // 41ca #1 - 'cD' // 41cb-41ce #3 - 'B' // 41cf #1 - 'jD' // 41d0-41da #3 - 'B' // 41db #1 - 'iD' // 41dc-41e5 #3 - 'F' // 41e6 #5 - 'eD' // 41e7-41ec #3 - 'B' // 41ed #1 - 'F' // 41ee #5 - 'B' // 41ef #1 - 'bD' // 41f0-41f2 #3 - 'F' // 41f3 #5 - 'dD' // 41f4-41f8 #3 - 'B' // 41f9 #1 - 'lD' // 41fa-4206 #3 - 'F' // 4207 #5 - 'eD' // 4208-420d #3 - 'F' // 420e #5 - 'aD' // 420f-4210 #3 - 'B' // 4211 #1 - 'pD' // 4212-4222 #3 - 'B' // 4223 #1 - '1aD' // 4224-423f #3 - 'B' // 4240 #1 - 'wD' // 4241-4258 #3 - 'K' // 4259 #10 - 'eD' // 425a-425f #3 - 'B' // 4260 #1 - 'bD' // 4261-4263 #3 - 'F' // 4264 #5 - 'dD' // 4265-4269 #3 - 'B' // 426a #1 - 'jD' // 426b-4275 #3 - 'B' // 4276 #1 - 'bD' // 4277-4279 #3 - 'B' // 427a #1 - 'pD' // 427b-428b #3 - 'B' // 428c #1 - 'eD' // 428d-4292 #3 - 'F' // 4293 #5 - 'B' // 4294 #1 - 'lD' // 4295-42a1 #3 - 'B' // 42a2 #1 - 'qD' // 42a3-42b4 #3 - 'B' // 42b5 #1 - 'bD' // 42b6-42b8 #3 - 'B' // 42b9 #1 - 'aD' // 42ba-42bb #3 - 'B' // 42bc #1 - 'hD' // 42bd-42c5 #3 - 'F' // 42c6 #5 - 'nD' // 42c7-42d5 #3 - 'F' // 42d6 #5 - 'eD' // 42d7-42dc #3 - 'F' // 42dd #5 - 'uD' // 42de-42f3 #3 - 'B' // 42f4 #1 - 'eD' // 42f5-42fa #3 - 'aB' // 42fb-42fc #1 - 'dD' // 42fd-4301 #3 - 'F' // 4302 #5 - 'fD' // 4303-4309 #3 - 'B' // 430a #1 - '1eD' // 430b-432a #3 - 'C' // 432b #2 - 'vD' // 432c-4342 #3 - 'F' // 4343 #5 - '1oD' // 4344-436d #3 - 'B' // 436e #1 - '1mD' // 436f-4396 #3 - 'B' // 4397 #1 - 'aD' // 4398-4399 #3 - 'B' // 439a #1 - '1dD' // 439b-43b9 #3 - 'B' // 43ba #1 - 'K' // 43bb #10 - 'dD' // 43bc-43c0 #3 - 'B' // 43c1 #1 - 'dD' // 43c2-43c6 #3 - 'K' // 43c7 #10 - 'pD' // 43c8-43d8 #3 - 'B' // 43d9 #1 - 'dD' // 43da-43de #3 - 'B' // 43df #1 - 'fD' // 43e0-43e6 #3 - 'K' // 43e7 #10 - 'aD' // 43e8-43e9 #3 - 'K' // 43ea #10 - 'aD' // 43eb-43ec #3 - 'B' // 43ed #1 - 'F' // 43ee #5 - 'D' // 43ef #3 - 'C' // 43f0 #2 - 'D' // 43f1 #3 - 'B' // 43f2 #1 - 'mD' // 43f3-4400 #3 - 'aB' // 4401-4402 #1 - 'dD' // 4403-4407 #3 - 'F' // 4408 #5 - 'bD' // 4409-440b #3 - 'F' // 440c #5 - 'eD' // 440d-4412 #3 - 'B' // 4413 #1 - 'bD' // 4414-4416 #3 - 'F' // 4417 #5 - 'cD' // 4418-441b #3 - 'F' // 441c #5 - 'dD' // 441d-4421 #3 - 'F' // 4422 #5 - 'aD' // 4423-4424 #3 - 'B' // 4425 #1 - 'fD' // 4426-442c #3 - 'B' // 442d #1 - '1gD' // 442e-444f #3 - 'K' // 4450 #10 - 'aD' // 4451-4452 #3 - 'F' // 4453 #5 - 'fD' // 4454-445a #3 - 'F' // 445b #5 - 'yD' // 445c-4475 #3 - 'F' // 4476 #5 - 'bD' // 4477-4479 #3 - 'C' // 447a #2 - 'sD' // 447b-448e #3 - 'B' // 448f #1 - 'D' // 4490 #3 - 'C' // 4491 #2 - 'lD' // 4492-449e #3 - 'aB' // 449f-44a0 #1 - 'D' // 44a1 #3 - 'B' // 44a2 #1 - 'lD' // 44a3-44af #3 - 'B' // 44b0 #1 - 'aD' // 44b1-44b2 #3 - 'F' // 44b3 #5 - 'bD' // 44b4-44b6 #3 - 'B' // 44b7 #1 - 'dD' // 44b8-44bc #3 - 'B' // 44bd #1 - 'F' // 44be #5 - 'D' // 44bf #3 - 'B' // 44c0 #1 - 'aD' // 44c1-44c2 #3 - 'B' // 44c3 #1 - 'D' // 44c4 #3 - 'B' // 44c5 #1 - 'gD' // 44c6-44cd #3 - 'B' // 44ce #1 - 'dD' // 44cf-44d3 #3 - 'F' // 44d4 #5 - 'gD' // 44d5-44dc #3 - 'bB' // 44dd-44df #1 - 'D' // 44e0 #3 - 'B' // 44e1 #1 - 'aD' // 44e2-44e3 #3 - 'B' // 44e4 #1 - 'cD' // 44e5-44e8 #3 - 'cB' // 44e9-44ec #1 - 'fD' // 44ed-44f3 #3 - 'B' // 44f4 #1 - 'mD' // 44f5-4502 #3 - 'aB' // 4503-4504 #1 - 'bD' // 4505-4507 #3 - 'F' // 4508 #5 - 'B' // 4509 #1 - 'D' // 450a #3 - 'B' // 450b #1 - 'D' // 450c #3 - 'F' // 450d #5 - 'cD' // 450e-4511 #3 - 'K' // 4512 #10 - 'bD' // 4513-4515 #3 - 'B' // 4516 #1 - 'cD' // 4517-451a #3 - 'B' // 451b #1 - 'D' // 451c #3 - 'B' // 451d #1 - 'fD' // 451e-4524 #3 - 'F' // 4525 #5 - 'D' // 4526 #3 - 'B' // 4527 #1 - 'eD' // 4528-452d #3 - 'B' // 452e #1 - 'cD' // 452f-4532 #3 - 'B' // 4533 #1 - 'aD' // 4534-4535 #3 - 'B' // 4536 #1 - 'cD' // 4537-453a #3 - 'B' // 453b #1 - 'D' // 453c #3 - 'B' // 453d #1 - 'D' // 453e #3 - 'B' // 453f #1 - 'bD' // 4540-4542 #3 - 'C' // 4543 #2 - 'lD' // 4544-4550 #3 - 'aB' // 4551-4552 #1 - 'aD' // 4553-4554 #3 - 'B' // 4555 #1 - 'aD' // 4556-4557 #3 - 'B' // 4558 #1 - 'bD' // 4559-455b #3 - 'B' // 455c #1 - 'cD' // 455d-4560 #3 - 'aB' // 4561-4562 #1 - 'fD' // 4563-4569 #3 - 'B' // 456a #1 - 'aD' // 456b-456c #3 - 'B' // 456d #1 - 'hD' // 456e-4576 #3 - 'aB' // 4577-4578 #1 - 'D' // 4579 #3 - 'F' // 457a #5 - 'iD' // 457b-4584 #3 - 'B' // 4585 #1 - 'vD' // 4586-459c #3 - 'F' // 459d #5 - 'gD' // 459e-45a5 #3 - 'B' // 45a6 #1 - 'kD' // 45a7-45b2 #3 - 'B' // 45b3 #1 - 'cD' // 45b4-45b7 #3 - 'F' // 45b8 #5 - 'dD' // 45b9-45bd #3 - 'F' // 45be #5 - 'zD' // 45bf-45d9 #3 - 'B' // 45da #1 - 'iD' // 45db-45e4 #3 - 'F' // 45e5 #5 - 'bD' // 45e6-45e8 #3 - 'B' // 45e9 #1 - 'C' // 45ea #2 - 'fD' // 45eb-45f1 #3 - 'K' // 45f2 #10 - 'oD' // 45f3-4602 #3 - 'B' // 4603 #1 - 'aD' // 4604-4605 #3 - 'B' // 4606 #1 - 'gD' // 4607-460e #3 - 'C' // 460f #2 - 'F' // 4610 #5 - 'cD' // 4611-4614 #3 - 'B' // 4615 #1 - 'D' // 4616 #3 - 'B' // 4617 #1 - 'K' // 4618 #10 - '1mD' // 4619-4640 #3 - 'F' // 4641 #5 - 'xD' // 4642-465a #3 - 'B' // 465b #1 - 'hD' // 465c-4664 #3 - 'F' // 4665 #5 - 'sD' // 4666-4679 #3 - 'B' // 467a #1 - 'dD' // 467b-467f #3 - 'B' // 4680 #1 - '1eD' // 4681-46a0 #3 - 'C' // 46a1 #2 - 'kD' // 46a2-46ad #3 - 'C' // 46ae #2 - 'F' // 46af #5 - 'fD' // 46b0-46b6 #3 - 'K' // 46b7 #10 - 'bD' // 46b8-46ba #3 - 'B' // 46bb #1 - 'aD' // 46bc-46bd #3 - 'K' // 46be #10 - 'oD' // 46bf-46ce #3 - 'aB' // 46cf-46d0 #1 - 'bD' // 46d1-46d3 #3 - 'K' // 46d4 #10 - 'bD' // 46d5-46d7 #3 - 'K' // 46d8 #10 - 'cD' // 46d9-46dc #3 - 'K' // 46dd #10 - 'vD' // 46de-46f4 #3 - 'B' // 46f5 #1 - 'D' // 46f6 #3 - 'B' // 46f7 #1 - 'sD' // 46f8-470b #3 - 'F' // 470c #5 - 'eD' // 470d-4712 #3 - 'B' // 4713 #1 - 'cD' // 4714-4717 #3 - 'B' // 4718 #1 - 'eD' // 4719-471e #3 - 'F' // 471f #5 - 'lD' // 4720-472c #3 - 'K' // 472d #10 - 'gD' // 472e-4735 #3 - 'B' // 4736 #1 - 'lD' // 4737-4743 #3 - 'B' // 4744 #1 - 'hD' // 4745-474d #3 - 'aB' // 474e-474f #1 - 'sD' // 4750-4763 #3 - 'F' // 4764 #5 - 'fD' // 4765-476b #3 - 'K' // 476c #10 - 'nD' // 476d-477b #3 - 'B' // 477c #1 - 'K' // 477d #10 - 'yD' // 477e-4797 #3 - 'B' // 4798 #1 - 'eD' // 4799-479e #3 - 'K' // 479f #10 - 'eD' // 47a0-47a5 #3 - 'B' // 47a6 #1 - '1sD' // 47a7-47d4 #3 - 'B' // 47d5 #1 - 'oD' // 47d6-47e5 #3 - 'F' // 47e6 #5 - 'eD' // 47e7-47ec #3 - 'B' // 47ed #1 - 'eD' // 47ee-47f3 #3 - 'B' // 47f4 #1 - 'gD' // 47f5-47fc #3 - 'F' // 47fd #5 - 'aD' // 47fe-47ff #3 - 'B' // 4800 #1 - 'iD' // 4801-480a #3 - 'B' // 480b #1 - 'iD' // 480c-4815 #3 - 'F' // 4816 #5 - 'fD' // 4817-481d #3 - 'F' // 481e #5 - 'wD' // 481f-4836 #3 - 'B' // 4837 #1 - 'kD' // 4838-4843 #3 - 'F' // 4844 #5 - 'hD' // 4845-484d #3 - 'F' // 484e #5 - 'mD' // 484f-485c #3 - 'B' // 485d #1 - 'dD' // 485e-4862 #3 - 'K' // 4863 #10 - 'lD' // 4864-4870 #3 - 'B' // 4871 #1 - 'pD' // 4872-4882 #3 - 'K' // 4883 #10 - 'qD' // 4884-4895 #3 - 'K' // 4896 #10 - 'cD' // 4897-489a #3 - 'B' // 489b #1 - 'iD' // 489c-48a5 #3 - 'K' // 48a6 #10 - 'eD' // 48a7-48ac #3 - 'aB' // 48ad-48ae #1 - 'eD' // 48af-48b4 #3 - 'F' // 48b5 #5 - 'yD' // 48b6-48cf #3 - 'B' // 48d0 #1 - 'kD' // 48d1-48dc #3 - 'B' // 48dd #1 - 'nD' // 48de-48ec #3 - 'B' // 48ed #1 - 'dD' // 48ee-48f2 #3 - 'B' // 48f3 #1 - 'eD' // 48f4-48f9 #3 - 'B' // 48fa #1 - 'jD' // 48fb-4905 #3 - 'B' // 4906 #1 - 'iD' // 4907-4910 #3 - 'B' // 4911 #1 - 'kD' // 4912-491d #3 - 'B' // 491e #1 - 'eD' // 491f-4924 #3 - 'I' // 4925 #8 - 'cD' // 4926-4929 #3 - 'B' // 492a #1 - 'aD' // 492b-492c #3 - 'B' // 492d #1 - 'D' // 492e #3 - 'aB' // 492f-4930 #1 - 'cD' // 4931-4934 #3 - 'B' // 4935 #1 - 'eD' // 4936-493b #3 - 'B' // 493c #1 - 'D' // 493d #3 - 'B' // 493e #1 - 'eD' // 493f-4944 #3 - 'B' // 4945 #1 - 'jD' // 4946-4950 #3 - 'B' // 4951 #1 - 'D' // 4952 #3 - 'B' // 4953 #1 - 'pD' // 4954-4964 #3 - 'B' // 4965 #1 - 'cD' // 4966-4969 #3 - 'B' // 496a #1 - 'fD' // 496b-4971 #3 - 'B' // 4972 #1 - 'uD' // 4973-4988 #3 - 'B' // 4989 #1 - 'sD' // 498a-499d #3 - 'K' // 499e #10 - 'aD' // 499f-49a0 #3 - 'B' // 49a1 #1 - 'bD' // 49a2-49a4 #3 - 'K' // 49a5 #10 - 'D' // 49a6 #3 - 'B' // 49a7 #1 - 'gD' // 49a8-49af #3 - 'F' // 49b0 #5 - 'yD' // 49b1-49ca #3 - 'K' // 49cb #10 - 'rD' // 49cc-49de #3 - 'B' // 49df #1 - 'dD' // 49e0-49e4 #3 - 'B' // 49e5 #1 - 'D' // 49e6 #3 - 'C' // 49e7 #2 - 'qD' // 49e8-49f9 #3 - 'F' // 49fa #5 - 'hD' // 49fb-4a03 #3 - 'F' // 4a04 #5 - 'iD' // 4a05-4a0e #3 - 'B' // 4a0f #1 - 'aD' // 4a10-4a11 #3 - 'K' // 4a12 #10 - 'iD' // 4a13-4a1c #3 - 'B' // 4a1d #1 - 'eD' // 4a1e-4a23 #3 - 'B' // 4a24 #1 - 'cD' // 4a25-4a28 #3 - 'F' // 4a29 #5 - 'bD' // 4a2a-4a2c #3 - 'K' // 4a2d #10 - 'fD' // 4a2e-4a34 #3 - 'B' // 4a35 #1 - '3qD' // 4a36-4a95 #3 - 'B' // 4a96 #1 - 'lD' // 4a97-4aa3 #3 - 'B' // 4aa4 #1 - 'nD' // 4aa5-4ab3 #3 - 'B' // 4ab4 #1 - 'bD' // 4ab5-4ab7 #3 - 'I' // 4ab8 #8 - 'bD' // 4ab9-4abb #3 - 'F' // 4abc #5 - 'sD' // 4abd-4ad0 #3 - 'B' // 4ad1 #1 - 'lD' // 4ad2-4ade #3 - 'K' // 4adf #10 - 'cD' // 4ae0-4ae3 #3 - 'B' // 4ae4 #1 - 'bD' // 4ae5-4ae7 #3 - 'K' // 4ae8 #10 - 'qD' // 4ae9-4afa #3 - 'K' // 4afb #10 - 'bD' // 4afc-4afe #3 - 'B' // 4aff #1 - 'oD' // 4b00-4b0f #3 - 'B' // 4b10 #1 - 'gD' // 4b11-4b18 #3 - 'B' // 4b19 #1 - 'eD' // 4b1a-4b1f #3 - 'B' // 4b20 #1 - 'jD' // 4b21-4b2b #3 - 'B' // 4b2c #1 - 'iD' // 4b2d-4b36 #3 - 'B' // 4b37 #1 - 'F' // 4b38 #5 - 'aD' // 4b39-4b3a #3 - 'F' // 4b3b #5 - 'vD' // 4b3c-4b52 #3 - 'K' // 4b53 #10 - 'zD' // 4b54-4b6e #3 - 'aB' // 4b6f-4b70 #1 - 'K' // 4b71 #10 - 'B' // 4b72 #1 - 'gD' // 4b73-4b7a #3 - 'B' // 4b7b #1 - 'aD' // 4b7c-4b7d #3 - 'C' // 4b7e #2 - 'nD' // 4b7f-4b8d #3 - 'B' // 4b8e #1 - 'D' // 4b8f #3 - 'B' // 4b90 #1 - 'aD' // 4b91-4b92 #3 - 'B' // 4b93 #1 - 'aD' // 4b94-4b95 #3 - 'aB' // 4b96-4b97 #1 - 'dD' // 4b98-4b9c #3 - 'B' // 4b9d #1 - '1dD' // 4b9e-4bbc #3 - 'aB' // 4bbd-4bbe #1 - 'D' // 4bbf #3 - 'B' // 4bc0 #1 - 'D' // 4bc1 #3 - 'F' // 4bc2 #5 - 'fD' // 4bc3-4bc9 #3 - 'F' // 4bca #5 - 'fD' // 4bcb-4bd1 #3 - 'F' // 4bd2 #5 - 'tD' // 4bd3-4be7 #3 - 'F' // 4be8 #5 - 'zD' // 4be9-4c03 #3 - 'B' // 4c04 #1 - 'aD' // 4c05-4c06 #3 - 'B' // 4c07 #1 - 'eD' // 4c08-4c0d #3 - 'B' // 4c0e #1 - 'gD' // 4c0f-4c16 #3 - 'F' // 4c17 #5 - 'gD' // 4c18-4c1f #3 - 'F' // 4c20 #5 - 'pD' // 4c21-4c31 #3 - 'B' // 4c32 #1 - 'dD' // 4c33-4c37 #3 - 'F' // 4c38 #5 - 'aD' // 4c39-4c3a #3 - 'B' // 4c3b #1 - 'aD' // 4c3c-4c3d #3 - 'B' // 4c3e #1 - 'D' // 4c3f #3 - 'B' // 4c40 #1 - 'eD' // 4c41-4c46 #3 - 'B' // 4c47 #1 - 'nD' // 4c48-4c56 #3 - 'B' // 4c57 #1 - 'bD' // 4c58-4c5a #3 - 'B' // 4c5b #1 - 'pD' // 4c5c-4c6c #3 - 'B' // 4c6d #1 - 'hD' // 4c6e-4c76 #3 - 'B' // 4c77 #1 - 'bD' // 4c78-4c7a #3 - 'B' // 4c7b #1 - 'D' // 4c7c #3 - 'B' // 4c7d #1 - 'bD' // 4c7e-4c80 #3 - 'B' // 4c81 #1 - 'bD' // 4c82-4c84 #3 - 'B' // 4c85 #1 - '1cD' // 4c86-4ca3 #3 - 'B' // 4ca4 #1 - 'hD' // 4ca5-4cad #3 - 'B' // 4cae #1 - 'D' // 4caf #3 - 'B' // 4cb0 #1 - 'eD' // 4cb1-4cb6 #3 - 'B' // 4cb7 #1 - 'kD' // 4cb8-4cc3 #3 - 'F' // 4cc4 #5 - 'gD' // 4cc5-4ccc #3 - 'B' // 4ccd #1 - 'bD' // 4cce-4cd0 #3 - 'F' // 4cd1 #5 - 'lD' // 4cd2-4cde #3 - 'aK' // 4cdf-4ce0 #10 - 'C' // 4ce1 #2 - 'B' // 4ce2 #1 - 'iD' // 4ce3-4cec #3 - 'B' // 4ced #1 - 'xD' // 4cee-4d06 #3 - 'C' // 4d07 #2 - 'D' // 4d08 #3 - 'B' // 4d09 #1 - 'eD' // 4d0a-4d0f #3 - 'B' // 4d10 #1 - 'iD' // 4d11-4d1a #3 - 'K' // 4d1b #10 - 'wD' // 4d1c-4d33 #3 - 'B' // 4d34 #1 - '2lD' // 4d35-4d75 #3 - 'B' // 4d76 #1 - 'C' // 4d77 #2 - 'pD' // 4d78-4d88 #3 - 'B' // 4d89 #1 - 'fD' // 4d8a-4d90 #3 - 'B' // 4d91 #1 - 'iD' // 4d92-4d9b #3 - 'B' // 4d9c #1 - 'xD' // 4d9d-4db5 #3 - 'iE' // 4db6-4dbf - '2kM' // 4dc0-4dff #12 - 'aA' // 4e00-4e01 #0 - 'F' // 4e02 #5 - 'A' // 4e03 #0 - 'C' // 4e04 #2 - 'F' // 4e05 #5 - 'D' // 4e06 #3 - 'dA' // 4e07-4e0b #0 - 'C' // 4e0c #2 - 'aA' // 4e0d-4e0e #0 - 'aC' // 4e0f-4e10 #2 - 'A' // 4e11 #0 - 'F' // 4e12 #5 - 'D' // 4e13 #3 - 'bA' // 4e14-4e16 #0 - 'F' // 4e17 #5 - 'aA' // 4e18-4e19 #0 - 'B' // 4e1a #1 - 'D' // 4e1b #3 - 'B' // 4e1c #1 - 'D' // 4e1d #3 - 'aA' // 4e1e-4e1f #0 - 'D' // 4e20 #3 - 'C' // 4e21 #2 - 'B' // 4e22 #1 - 'F' // 4e23 #5 - 'A' // 4e24 #0 - 'D' // 4e25 #3 - 'A' // 4e26 #0 - 'D' // 4e27 #3 - 'A' // 4e28 #0 - 'F' // 4e29 #5 - 'C' // 4e2a #2 - 'bA' // 4e2b-4e2d #0 - 'aC' // 4e2e-4e2f #2 - 'bA' // 4e30-4e32 #0 - 'B' // 4e33 #1 - 'D' // 4e34 #3 - 'F' // 4e35 #5 - 'A' // 4e36 #0 - 'C' // 4e37 #2 - 'aA' // 4e38-4e39 #0 - 'D' // 4e3a #3 - 'A' // 4e3b #0 - 'C' // 4e3c #2 - 'B' // 4e3d #1 - 'D' // 4e3e #3 - 'A' // 4e3f #0 - 'aF' // 4e40-4e41 #5 - 'aA' // 4e42-4e43 #0 - 'F' // 4e44 #5 - 'A' // 4e45 #0 - 'D' // 4e46 #3 - 'aC' // 4e47-4e48 #2 - 'B' // 4e49 #1 - 'D' // 4e4a #3 - 'A' // 4e4b #0 - 'D' // 4e4c #3 - 'bA' // 4e4d-4e4f #0 - 'D' // 4e50 #3 - 'F' // 4e51 #5 - 'aB' // 4e52-4e53 #1 - 'D' // 4e54 #3 - 'F' // 4e55 #5 - 'A' // 4e56 #0 - 'J' // 4e57 #9 - 'cA' // 4e58-4e5b #0 - 'C' // 4e5c #2 - 'bA' // 4e5d-4e5f #0 - 'aD' // 4e60-4e61 #3 - 'aF' // 4e62-4e63 #5 - 'bD' // 4e64-4e66 #3 - 'K' // 4e67 #10 - 'F' // 4e68 #5 - 'C' // 4e69 #2 - 'B' // 4e6a #1 - 'bK' // 4e6b-4e6d #10 - 'bD' // 4e6e-4e70 #3 - 'J' // 4e71 #9 - 'D' // 4e72 #3 - 'A' // 4e73 #0 - 'aF' // 4e74-4e75 #5 - 'aK' // 4e76-4e77 #10 - 'B' // 4e78 #1 - 'F' // 4e79 #5 - 'bK' // 4e7a-4e7c #10 - 'D' // 4e7d #3 - 'A' // 4e7e #0 - 'C' // 4e7f #2 - 'A' // 4e80 #0 - 'B' // 4e81 #1 - 'A' // 4e82 #0 - 'aB' // 4e83-4e84 #1 - 'aA' // 4e85-4e86 #0 - 'B' // 4e87 #1 - 'aA' // 4e88-4e89 #0 - 'F' // 4e8a #5 - 'aA' // 4e8b-4e8c #0 - 'C' // 4e8d #2 - 'A' // 4e8e #0 - 'aK' // 4e8f-4e90 #10 - 'aA' // 4e91-4e92 #0 - 'B' // 4e93 #1 - 'aA' // 4e94-4e95 #0 - 'aF' // 4e96-4e97 #5 - 'aA' // 4e98-4e99 #0 - 'B' // 4e9a #1 - 'A' // 4e9b #0 - 'J' // 4e9c #9 - 'F' // 4e9d #5 - 'dA' // 4e9e-4ea2 #0 - 'B' // 4ea3 #1 - 'bA' // 4ea4-4ea6 #0 - 'D' // 4ea7 #3 - 'A' // 4ea8 #0 - 'aD' // 4ea9-4eaa #3 - 'cA' // 4eab-4eae #0 - 'F' // 4eaf #5 - 'J' // 4eb0 #9 - 'aD' // 4eb1-4eb2 #3 - 'A' // 4eb3 #0 - 'K' // 4eb4 #10 - 'D' // 4eb5 #3 - 'A' // 4eb6 #0 - 'B' // 4eb7 #1 - 'D' // 4eb8 #3 - 'bA' // 4eb9-4ebb #0 - 'C' // 4ebc #2 - 'aD' // 4ebd-4ebe #3 - 'B' // 4ebf #1 - 'aA' // 4ec0-4ec1 #0 - 'aC' // 4ec2-4ec3 #2 - 'A' // 4ec4 #0 - 'D' // 4ec5 #3 - 'aA' // 4ec6-4ec7 #0 - 'C' // 4ec8 #2 - 'B' // 4ec9 #1 - 'aA' // 4eca-4ecb #0 - 'D' // 4ecc #3 - 'A' // 4ecd #0 - 'C' // 4ece #2 - 'aF' // 4ecf-4ed0 #5 - 'bD' // 4ed1-4ed3 #3 - 'eA' // 4ed4-4ed9 #0 - 'C' // 4eda #2 - 'F' // 4edb #5 - 'B' // 4edc #1 - 'bA' // 4edd-4edf #0 - 'F' // 4ee0 #5 - 'A' // 4ee1 #0 - 'F' // 4ee2 #5 - 'bA' // 4ee3-4ee5 #0 - 'aD' // 4ee6-4ee7 #3 - 'C' // 4ee8 #2 - 'aB' // 4ee9-4eea #1 - 'C' // 4eeb #2 - 'D' // 4eec #3 - 'F' // 4eed #5 - 'A' // 4eee #0 - 'F' // 4eef #5 - 'A' // 4ef0 #0 - 'C' // 4ef1 #2 - 'aA' // 4ef2-4ef3 #0 - 'B' // 4ef4 #1 - 'bA' // 4ef5-4ef7 #0 - 'B' // 4ef8 #1 - 'aD' // 4ef9-4efa #3 - 'A' // 4efb #0 - 'F' // 4efc #5 - 'A' // 4efd #0 - 'F' // 4efe #5 - 'bA' // 4eff-4f01 #0 - 'aC' // 4f02-4f03 #2 - 'aB' // 4f04-4f05 #1 - 'aD' // 4f06-4f07 #3 - 'C' // 4f08 #2 - 'bA' // 4f09-4f0b #0 - 'F' // 4f0c #5 - 'dA' // 4f0d-4f11 #0 - 'C' // 4f12 #2 - 'aB' // 4f13-4f14 #1 - 'C' // 4f15 #2 - 'F' // 4f16 #5 - 'C' // 4f17 #2 - 'B' // 4f18 #1 - 'C' // 4f19 #2 - 'A' // 4f1a #0 - 'D' // 4f1b #3 - 'F' // 4f1c #5 - 'A' // 4f1d #0 - 'cD' // 4f1e-4f21 #3 - 'B' // 4f22 #1 - 'dD' // 4f23-4f27 #3 - 'aB' // 4f28-4f29 #1 - 'D' // 4f2a #3 - 'F' // 4f2b #5 - 'aB' // 4f2c-4f2d #1 - 'F' // 4f2e #5 - 'aA' // 4f2f-4f30 #0 - 'F' // 4f31 #5 - 'B' // 4f32 #1 - 'C' // 4f33 #2 - 'A' // 4f34 #0 - 'F' // 4f35 #5 - 'A' // 4f36 #0 - 'C' // 4f37 #2 - 'A' // 4f38 #0 - 'C' // 4f39 #2 - 'A' // 4f3a #0 - 'C' // 4f3b #2 - 'bA' // 4f3c-4f3e #0 - 'B' // 4f3f #1 - 'F' // 4f40 #5 - 'B' // 4f41 #1 - 'aA' // 4f42-4f43 #0 - 'D' // 4f44 #3 - 'B' // 4f45 #1 - 'cA' // 4f46-4f49 #0 - 'D' // 4f4a #3 - 'A' // 4f4b #0 - 'C' // 4f4c #2 - 'dA' // 4f4d-4f51 #0 - 'C' // 4f52 #2 - 'dA' // 4f53-4f57 #0 - 'C' // 4f58 #2 - 'fA' // 4f59-4f5f #0 - 'C' // 4f60 #2 - 'aB' // 4f61-4f62 #1 - 'aC' // 4f63-4f64 #2 - 'aD' // 4f65-4f66 #3 - 'B' // 4f67 #1 - 'D' // 4f68 #3 - 'aA' // 4f69-4f6a #0 - 'B' // 4f6b #1 - 'C' // 4f6c #2 - 'D' // 4f6d #3 - 'C' // 4f6e #2 - 'aA' // 4f6f-4f70 #0 - 'F' // 4f71 #5 - 'B' // 4f72 #1 - 'A' // 4f73 #0 - 'I' // 4f74 #8 - 'C' // 4f75 #2 - 'A' // 4f76 #0 - 'C' // 4f77 #2 - 'gA' // 4f78-4f7f #0 - 'I' // 4f80 #8 - 'A' // 4f81 #0 - 'C' // 4f82 #2 - 'aA' // 4f83-4f84 #0 - 'C' // 4f85 #2 - 'A' // 4f86 #0 - 'B' // 4f87 #1 - 'cA' // 4f88-4f8b #0 - 'F' // 4f8c #5 - 'A' // 4f8d #0 - 'J' // 4f8e #9 - 'cA' // 4f8f-4f92 #0 - 'F' // 4f93 #5 - 'A' // 4f94 #0 - 'B' // 4f95 #1 - 'bA' // 4f96-4f98 #0 - 'F' // 4f99 #5 - 'aA' // 4f9a-4f9b #0 - 'I' // 4f9c #8 - 'A' // 4f9d #0 - 'C' // 4f9e #2 - 'bF' // 4f9f-4fa1 #5 - 'B' // 4fa2 #1 - 'dD' // 4fa3-4fa7 #3 - 'B' // 4fa8 #1 - 'aD' // 4fa9-4faa #3 - 'C' // 4fab #2 - 'D' // 4fac #3 - 'F' // 4fad #5 - 'aA' // 4fae-4faf #0 - 'B' // 4fb0 #1 - 'D' // 4fb1 #3 - 'A' // 4fb2 #0 - 'aB' // 4fb3-4fb4 #1 - 'aA' // 4fb5-4fb6 #0 - 'C' // 4fb7 #2 - 'D' // 4fb8 #3 - 'A' // 4fb9 #0 - 'B' // 4fba #1 - 'A' // 4fbb #0 - 'F' // 4fbc #5 - 'C' // 4fbd #2 - 'F' // 4fbe #5 - 'A' // 4fbf #0 - 'C' // 4fc0 #2 - 'dA' // 4fc1-4fc5 #0 - 'F' // 4fc6 #5 - 'B' // 4fc7 #1 - 'C' // 4fc8 #2 - 'aA' // 4fc9-4fca #0 - 'C' // 4fcb #2 - 'eA' // 4fcc-4fd1 #0 - 'J' // 4fd2 #9 - 'aA' // 4fd3-4fd4 #0 - 'D' // 4fd5 #3 - 'B' // 4fd6 #1 - 'aA' // 4fd7-4fd8 #0 - 'I' // 4fd9 #8 - 'aA' // 4fda-4fdb #0 - 'C' // 4fdc #2 - 'A' // 4fdd #0 - 'I' // 4fde #8 - 'bA' // 4fdf-4fe1 #0 - 'F' // 4fe2 #5 - 'J' // 4fe3 #9 - 'aC' // 4fe4-4fe5 #2 - 'F' // 4fe6 #5 - 'dD' // 4fe7-4feb #3 - 'B' // 4fec #1 - 'D' // 4fed #3 - 'cA' // 4fee-4ff1 #0 - 'C' // 4ff2 #2 - 'A' // 4ff3 #0 - 'I' // 4ff4 #8 - 'aA' // 4ff5-4ff6 #0 - 'B' // 4ff7 #1 - 'A' // 4ff8 #0 - 'B' // 4ff9 #1 - 'A' // 4ffa #0 - 'D' // 4ffb #3 - 'F' // 4ffc #5 - 'C' // 4ffd #2 - 'A' // 4ffe #0 - 'F' // 4fff #5 - 'A' // 5000 #0 - 'F' // 5001 #5 - 'J' // 5002 #9 - 'B' // 5003 #1 - 'F' // 5004 #5 - 'bA' // 5005-5007 #0 - 'B' // 5008 #1 - 'A' // 5009 #0 - 'F' // 500a #5 - 'A' // 500b #0 - 'C' // 500c #2 - 'A' // 500d #0 - 'C' // 500e #2 - 'A' // 500f #0 - 'F' // 5010 #5 - 'cA' // 5011-5014 #0 - 'B' // 5015 #1 - 'A' // 5016 #0 - 'C' // 5017 #2 - 'bA' // 5018-501a #0 - 'C' // 501b #2 - 'A' // 501c #0 - 'F' // 501d #5 - 'aA' // 501e-501f #0 - 'B' // 5020 #1 - 'bA' // 5021-5023 #0 - 'J' // 5024 #9 - 'iA' // 5025-502e #0 - 'B' // 502f #1 - 'A' // 5030 #0 - 'B' // 5031 #1 - 'F' // 5032 #5 - 'C' // 5033 #2 - 'B' // 5034 #1 - 'C' // 5035 #2 - 'F' // 5036 #5 - 'B' // 5037 #1 - 'D' // 5038 #3 - 'F' // 5039 #5 - 'D' // 503a #3 - 'A' // 503b #0 - 'B' // 503c #1 - 'bD' // 503d-503f #3 - 'aC' // 5040-5041 #2 - 'F' // 5042 #5 - 'A' // 5043 #0 - 'K' // 5044 #10 - 'aC' // 5045-5046 #2 - 'cA' // 5047-504a #0 - 'B' // 504b #1 - 'C' // 504c #2 - 'B' // 504d #1 - 'aA' // 504e-504f #0 - 'F' // 5050 #5 - 'C' // 5051 #2 - 'F' // 5052 #5 - 'A' // 5053 #0 - 'D' // 5054 #3 - 'aA' // 5055-5056 #0 - 'C' // 5057 #2 - 'I' // 5058 #8 - 'J' // 5059 #9 - 'A' // 505a #0 - 'B' // 505b #1 - 'A' // 505c #0 - 'aB' // 505d-505e #1 - 'C' // 505f #2 - 'A' // 5060 #0 - 'B' // 5061 #1 - 'A' // 5062 #0 - 'C' // 5063 #2 - 'B' // 5064 #1 - 'aA' // 5065-5066 #0 - 'F' // 5067 #5 - 'aB' // 5068-5069 #1 - 'A' // 506a #0 - 'B' // 506b #1 - 'aC' // 506c-506d #2 - 'aB' // 506e-506f #1 - 'A' // 5070 #0 - 'F' // 5071 #5 - 'A' // 5072 #0 - 'B' // 5073 #1 - 'bA' // 5074-5076 #0 - 'C' // 5077 #2 - 'J' // 5078 #9 - 'D' // 5079 #3 - 'B' // 507a #1 - 'aD' // 507b-507c #3 - 'C' // 507d #2 - 'aD' // 507e-507f #3 - 'A' // 5080 #0 - 'C' // 5081 #2 - 'B' // 5082 #1 - 'A' // 5083 #0 - 'F' // 5084 #5 - 'A' // 5085 #0 - 'F' // 5086 #5 - 'B' // 5087 #1 - 'C' // 5088 #2 - 'D' // 5089 #3 - 'F' // 508a #5 - 'I' // 508b #8 - 'B' // 508c #1 - 'A' // 508d #0 - 'C' // 508e #2 - 'F' // 508f #5 - 'C' // 5090 #2 - 'aA' // 5091-5092 #0 - 'F' // 5093 #5 - 'A' // 5094 #0 - 'C' // 5095 #2 - 'A' // 5096 #0 - 'D' // 5097 #3 - 'cA' // 5098-509b #0 - 'C' // 509c #2 - 'I' // 509d #8 - 'A' // 509e #0 - 'bF' // 509f-50a1 #5 - 'A' // 50a2 #0 - 'C' // 50a3 #2 - 'aD' // 50a4-50a5 #3 - 'B' // 50a6 #1 - 'bD' // 50a7-50a9 #3 - 'F' // 50aa #5 - 'D' // 50ab #3 - 'aA' // 50ac-50ad #0 - 'I' // 50ae #8 - 'bC' // 50af-50b1 #2 - 'cA' // 50b2-50b5 #0 - 'B' // 50b6 #1 - 'A' // 50b7 #0 - 'B' // 50b8 #1 - 'F' // 50b9 #5 - 'aC' // 50ba-50bb #2 - 'B' // 50bc #1 - 'aA' // 50bd-50be #0 - 'I' // 50bf #8 - 'F' // 50c0 #5 - 'B' // 50c1 #1 - 'A' // 50c2 #0 - 'F' // 50c3 #5 - 'aA' // 50c4-50c5 #0 - 'B' // 50c6 #1 - 'C' // 50c7 #2 - 'B' // 50c8 #1 - 'aA' // 50c9-50ca #0 - 'B' // 50cb #1 - 'F' // 50cc #5 - 'aC' // 50cd-50ce #2 - 'A' // 50cf #0 - 'C' // 50d0 #2 - 'A' // 50d1 #0 - 'D' // 50d2 #3 - 'C' // 50d3 #2 - 'bA' // 50d4-50d6 #0 - 'B' // 50d7 #1 - 'F' // 50d8 #5 - 'C' // 50d9 #2 - 'A' // 50da #0 - 'I' // 50db #8 - 'F' // 50dc #5 - 'C' // 50dd #2 - 'J' // 50de #9 - 'C' // 50df #2 - 'B' // 50e0 #1 - 'C' // 50e1 #2 - 'J' // 50e2 #9 - 'aC' // 50e3-50e4 #2 - 'bA' // 50e5-50e7 #0 - 'C' // 50e8 #2 - 'A' // 50e9 #0 - 'B' // 50ea #1 - 'D' // 50eb #3 - 'I' // 50ec #8 - 'aA' // 50ed-50ee #0 - 'bC' // 50ef-50f1 #2 - 'F' // 50f2 #5 - 'aC' // 50f3-50f4 #2 - 'A' // 50f5 #0 - 'C' // 50f6 #2 - 'D' // 50f7 #3 - 'B' // 50f8 #1 - 'A' // 50f9 #0 - 'F' // 50fa #5 - 'A' // 50fb #0 - 'aB' // 50fc-50fd #1 - 'A' // 50fe #0 - 'I' // 50ff #8 - 'dA' // 5100-5104 #0 - 'B' // 5105 #1 - 'aA' // 5106-5107 #0 - 'C' // 5108 #2 - 'A' // 5109 #0 - 'B' // 510a #1 - 'aA' // 510b-510c #0 - 'aC' // 510d-510e #2 - 'D' // 510f #3 - 'A' // 5110 #0 - 'B' // 5111 #1 - 'A' // 5112 #0 - 'I' // 5113 #8 - 'aA' // 5114-5115 #0 - 'F' // 5116 #5 - 'aA' // 5117-5118 #0 - 'F' // 5119 #5 - 'A' // 511a #0 - 'J' // 511b #9 - 'A' // 511c #0 - 'aF' // 511d-511e #5 - 'A' // 511f #0 - 'B' // 5120 #1 - 'A' // 5121 #0 - 'I' // 5122 #8 - 'F' // 5123 #5 - 'aI' // 5124-5125 #8 - 'B' // 5126 #1 - 'J' // 5127 #9 - 'F' // 5128 #5 - 'B' // 5129 #1 - 'A' // 512a #0 - 'I' // 512b #8 - 'F' // 512c #5 - 'C' // 512d #2 - 'B' // 512e #1 - 'F' // 512f #5 - 'B' // 5130 #1 - 'bA' // 5131-5133 #0 - 'C' // 5134 #2 - 'A' // 5135 #0 - 'D' // 5136 #3 - 'eA' // 5137-513c #0 - 'B' // 513d #1 - 'D' // 513e #3 - 'bA' // 513f-5141 #0 - 'F' // 5142 #5 - 'fA' // 5143-5149 #0 - 'F' // 514a #5 - 'bA' // 514b-514d #0 - 'J' // 514e #9 - 'F' // 514f #5 - 'J' // 5150 #9 - 'B' // 5151 #1 - 'A' // 5152 #0 - 'F' // 5153 #5 - 'aA' // 5154-5155 #0 - 'I' // 5156 #8 - 'A' // 5157 #0 - 'F' // 5158 #5 - 'B' // 5159 #1 - 'A' // 515a #0 - 'B' // 515b #1 - 'A' // 515c #0 - 'aB' // 515d-515e #1 - 'aC' // 515f-5160 #2 - 'B' // 5161 #1 - 'A' // 5162 #0 - 'B' // 5163 #1 - 'F' // 5164 #5 - 'A' // 5165 #0 - 'F' // 5166 #5 - 'gA' // 5167-516e #0 - 'aD' // 516f-5170 #3 - 'A' // 5171 #0 - 'D' // 5172 #3 - 'F' // 5173 #5 - 'C' // 5174 #2 - 'cA' // 5175-5178 #0 - 'C' // 5179 #2 - 'D' // 517a #3 - 'F' // 517b #5 - 'A' // 517c #0 - 'D' // 517d #3 - 'F' // 517e #5 - 'D' // 517f #3 - 'A' // 5180 #0 - 'D' // 5181 #3 - 'A' // 5182 #0 - 'bF' // 5183-5185 #5 - 'A' // 5186 #0 - 'aB' // 5187-5188 #1 - 'aA' // 5189-518a #0 - 'F' // 518b #5 - 'J' // 518c #9 - 'A' // 518d #0 - 'F' // 518e #5 - 'A' // 518f #0 - 'F' // 5190 #5 - 'bA' // 5191-5193 #0 - 'B' // 5194 #1 - 'cA' // 5195-5198 #0 - 'J' // 5199 #9 - 'B' // 519a #1 - 'D' // 519b #3 - 'B' // 519c #1 - 'F' // 519d #5 - 'I' // 519e #8 - 'D' // 519f #3 - 'A' // 51a0 #0 - 'F' // 51a1 #5 - 'A' // 51a2 #0 - 'J' // 51a3 #9 - 'aA' // 51a4-51a5 #0 - 'F' // 51a6 #5 - 'B' // 51a7 #1 - 'C' // 51a8 #2 - 'F' // 51a9 #5 - 'bA' // 51aa-51ac #0 - 'F' // 51ad #5 - 'B' // 51ae #1 - 'D' // 51af #3 - 'bA' // 51b0-51b2 #0 - 'bC' // 51b3-51b5 #2 - 'aA' // 51b6-51b7 #0 - 'C' // 51b8 #2 - 'B' // 51b9 #1 - 'F' // 51ba #5 - 'D' // 51bb #3 - 'C' // 51bc #2 - 'aA' // 51bd-51be #0 - 'F' // 51bf #5 - 'aD' // 51c0-51c1 #3 - 'F' // 51c2 #5 - 'C' // 51c3 #2 - 'bA' // 51c4-51c6 #0 - 'B' // 51c7 #1 - 'C' // 51c8 #2 - 'dA' // 51c9-51cd #0 - 'B' // 51ce #1 - 'C' // 51cf #2 - 'B' // 51d0 #1 - 'C' // 51d1 #2 - 'A' // 51d2 #0 - 'C' // 51d3 #2 - 'A' // 51d4 #0 - 'F' // 51d5 #5 - 'J' // 51d6 #9 - 'B' // 51d7 #1 - 'C' // 51d8 #2 - 'aD' // 51d9-51da #3 - 'cA' // 51db-51de #0 - 'C' // 51df #2 - 'aA' // 51e0-51e1 #0 - 'C' // 51e2 #2 - 'D' // 51e3 #3 - 'B' // 51e4 #1 - 'bF' // 51e5-51e7 #5 - 'D' // 51e8 #3 - 'J' // 51e9 #9 - 'F' // 51ea #5 - 'D' // 51eb #3 - 'F' // 51ec #5 - 'A' // 51ed #0 - 'F' // 51ee #5 - 'D' // 51ef #3 - 'aA' // 51f0-51f1 #0 - 'F' // 51f2 #5 - 'cA' // 51f3-51f6 #0 - 'F' // 51f7 #5 - 'bA' // 51f8-51fa #0 - 'D' // 51fb #3 - 'B' // 51fc #1 - 'A' // 51fd #0 - 'C' // 51fe #2 - 'D' // 51ff #3 - 'cA' // 5200-5203 #0 - 'F' // 5204 #5 - 'C' // 5205 #2 - 'bA' // 5206-5208 #0 - 'B' // 5209 #1 - 'A' // 520a #0 - 'C' // 520b #2 - 'B' // 520c #1 - 'D' // 520d #3 - 'A' // 520e #0 - 'D' // 520f #3 - 'B' // 5210 #1 - 'A' // 5211 #0 - 'C' // 5212 #2 - 'A' // 5213 #0 - 'aF' // 5214-5215 #5 - 'aA' // 5216-5217 #0 - 'F' // 5218 #5 - 'bD' // 5219-521b #3 - 'B' // 521c #1 - 'A' // 521d #0 - 'cB' // 521e-5221 #1 - 'F' // 5222 #5 - 'D' // 5223 #3 - 'cA' // 5224-5227 #0 - 'C' // 5228 #2 - 'aA' // 5229-522a #0 - 'F' // 522b #5 - 'aD' // 522c-522d #3 - 'A' // 522e #0 - 'D' // 522f #3 - 'cA' // 5230-5233 #0 - 'B' // 5234 #1 - 'C' // 5235 #2 - 'bA' // 5236-5238 #0 - 'J' // 5239 #9 - 'aA' // 523a-523b #0 - 'C' // 523c #2 - 'cD' // 523d-5240 #3 - 'B' // 5241 #1 - 'D' // 5242 #3 - 'aA' // 5243-5244 #0 - 'F' // 5245 #5 - 'I' // 5246 #8 - 'A' // 5247 #0 - 'D' // 5248 #3 - 'dA' // 5249-524d #0 - 'B' // 524e #1 - 'C' // 524f #2 - 'aD' // 5250-5251 #3 - 'B' // 5252 #1 - 'D' // 5253 #3 - 'cA' // 5254-5257 #0 - 'F' // 5258 #5 - 'B' // 5259 #1 - 'aA' // 525a-525b #0 - 'C' // 525c #2 - 'bA' // 525d-525f #0 - 'C' // 5260 #2 - 'A' // 5261 #0 - 'B' // 5262 #1 - 'cF' // 5263-5266 #5 - 'D' // 5267 #3 - 'B' // 5268 #1 - 'aA' // 5269-526a #0 - 'B' // 526b #1 - 'C' // 526c #2 - 'B' // 526d #1 - 'C' // 526e #2 - 'A' // 526f #0 - 'aF' // 5270-5271 #5 - 'A' // 5272 #0 - 'C' // 5273 #2 - 'aA' // 5274-5275 #0 - 'D' // 5276 #3 - 'A' // 5277 #0 - 'aC' // 5278-5279 #2 - 'I' // 527a #8 - 'aB' // 527b-527c #1 - 'A' // 527d #0 - 'D' // 527e #3 - 'A' // 527f #0 - 'C' // 5280 #2 - 'B' // 5281 #1 - 'aA' // 5282-5283 #0 - 'C' // 5284 #2 - 'F' // 5285 #5 - 'D' // 5286 #3 - 'bA' // 5287-5289 #0 - 'C' // 528a #2 - 'B' // 528b #1 - 'C' // 528c #2 - 'A' // 528d #0 - 'D' // 528e #3 - 'aB' // 528f-5290 #1 - 'A' // 5291 #0 - 'J' // 5292 #9 - 'A' // 5293 #0 - 'C' // 5294 #2 - 'F' // 5295 #5 - 'C' // 5296 #2 - 'aA' // 5297-5298 #0 - 'B' // 5299 #1 - 'C' // 529a #2 - 'A' // 529b #0 - 'F' // 529c #5 - 'aD' // 529d-529e #3 - 'aA' // 529f-52a0 #0 - 'B' // 52a1 #1 - 'D' // 52a2 #3 - 'aA' // 52a3-52a4 #0 - 'F' // 52a5 #5 - 'C' // 52a6 #2 - 'J' // 52a7 #9 - 'B' // 52a8 #1 - 'dA' // 52a9-52ad #0 - 'I' // 52ae #8 - 'bF' // 52af-52b1 #5 - 'aD' // 52b2-52b3 #3 - 'F' // 52b4 #5 - 'C' // 52b5 #2 - 'bF' // 52b6-52b8 #5 - 'A' // 52b9 #0 - 'F' // 52ba #5 - 'aC' // 52bb-52bc #2 - 'F' // 52bd #5 - 'A' // 52be #0 - 'D' // 52bf #3 - 'C' // 52c0 #2 - 'A' // 52c1 #0 - 'B' // 52c2 #1 - 'A' // 52c3 #0 - 'F' // 52c4 #5 - 'A' // 52c5 #0 - 'F' // 52c6 #5 - 'A' // 52c7 #0 - 'F' // 52c8 #5 - 'A' // 52c9 #0 - 'F' // 52ca #5 - 'D' // 52cb #3 - 'aA' // 52cc-52cd #0 - 'D' // 52ce #3 - 'F' // 52cf #5 - 'aC' // 52d0-52d1 #2 - 'A' // 52d2 #0 - 'B' // 52d3 #1 - 'F' // 52d4 #5 - 'aA' // 52d5-52d6 #0 - 'C' // 52d7 #2 - 'aA' // 52d8-52d9 #0 - 'D' // 52da #3 - 'A' // 52db #0 - 'F' // 52dc #5 - 'gA' // 52dd-52e4 #0 - 'F' // 52e5 #5 - 'A' // 52e6 #0 - 'aF' // 52e7-52e8 #5 - 'C' // 52e9 #2 - 'F' // 52ea #5 - 'B' // 52eb #1 - 'F' // 52ec #5 - 'K' // 52ed #10 - 'D' // 52ee #3 - 'B' // 52ef #1 - 'aC' // 52f0-52f1 #2 - 'J' // 52f2 #9 - 'A' // 52f3 #0 - 'C' // 52f4 #2 - 'A' // 52f5 #0 - 'F' // 52f6 #5 - 'C' // 52f7 #2 - 'cA' // 52f8-52fb #0 - 'B' // 52fc #1 - 'D' // 52fd #3 - 'aA' // 52fe-52ff #0 - 'J' // 5300 #9 - 'A' // 5301 #0 - 'aJ' // 5302-5303 #9 - 'D' // 5304 #3 - 'A' // 5305 #0 - 'C' // 5306 #2 - 'F' // 5307 #5 - 'A' // 5308 #0 - 'B' // 5309 #1 - 'A' // 530a #0 - 'C' // 530b #2 - 'J' // 530c #9 - 'A' // 530d #0 - 'B' // 530e #1 - 'aA' // 530f-5310 #0 - 'C' // 5311 #2 - 'B' // 5312 #1 - 'F' // 5313 #5 - 'D' // 5314 #3 - 'bA' // 5315-5317 #0 - 'F' // 5318 #5 - 'aA' // 5319-531a #0 - 'F' // 531b #5 - 'aC' // 531c-531d #2 - 'F' // 531e #5 - 'C' // 531f #2 - 'aA' // 5320-5321 #0 - 'B' // 5322 #1 - 'A' // 5323 #0 - 'C' // 5324 #2 - 'F' // 5325 #5 - 'D' // 5326 #3 - 'A' // 5327 #0 - 'aF' // 5328-5329 #5 - 'A' // 532a #0 - 'F' // 532b #5 - 'aC' // 532c-532d #2 - 'D' // 532e #3 - 'A' // 532f #0 - 'C' // 5330 #2 - 'A' // 5331 #0 - 'aC' // 5332-5333 #2 - 'B' // 5334 #1 - 'F' // 5335 #5 - 'K' // 5336 #10 - 'B' // 5337 #1 - 'aA' // 5338-5339 #0 - 'J' // 533a #9 - 'A' // 533b #0 - 'C' // 533c #2 - 'dA' // 533d-5341 #0 - 'C' // 5342 #2 - 'A' // 5343 #0 - 'I' // 5344 #8 - 'A' // 5345 #0 - 'F' // 5346 #5 - 'cA' // 5347-534a #0 - 'F' // 534b #5 - 'C' // 534c #2 - 'A' // 534d #0 - 'B' // 534e #1 - 'aD' // 534f-5350 #3 - 'cA' // 5351-5354 #0 - 'aD' // 5355-5356 #3 - 'A' // 5357 #0 - 'aF' // 5358-5359 #5 - 'A' // 535a #0 - 'F' // 535b #5 - 'A' // 535c #0 - 'B' // 535d #1 - 'A' // 535e #0 - 'B' // 535f #1 - 'aA' // 5360-5361 #0 - 'D' // 5362 #3 - 'C' // 5363 #2 - 'A' // 5364 #0 - 'F' // 5365 #5 - 'A' // 5366 #0 - 'C' // 5367 #2 - 'K' // 5368 #10 - 'A' // 5369 #0 - 'aD' // 536a-536b #3 - 'A' // 536c #0 - 'C' // 536d #2 - 'gA' // 536e-5375 #0 - 'D' // 5376 #3 - 'bA' // 5377-5379 #0 - 'J' // 537a #9 - 'A' // 537b #0 - 'B' // 537c #1 - 'bA' // 537d-537f #0 - 'aD' // 5380-5381 #3 - 'A' // 5382 #0 - 'F' // 5383 #5 - 'A' // 5384 #0 - 'aD' // 5385-5386 #3 - 'bF' // 5387-5389 #5 - 'B' // 538a #1 - 'bD' // 538b-538d #3 - 'A' // 538e #0 - 'B' // 538f #1 - 'aD' // 5390-5391 #3 - 'B' // 5392 #1 - 'A' // 5393 #0 - 'C' // 5394 #2 - 'D' // 5395 #3 - 'A' // 5396 #0 - 'B' // 5397 #1 - 'A' // 5398 #0 - 'C' // 5399 #2 - 'A' // 539a #0 - 'D' // 539b #3 - 'B' // 539c #1 - 'A' // 539d #0 - 'B' // 539e #1 - 'aA' // 539f-53a0 #0 - 'F' // 53a1 #5 - 'B' // 53a2 #1 - 'D' // 53a3 #3 - 'C' // 53a4 #2 - 'aA' // 53a5-53a6 #0 - 'B' // 53a7 #1 - 'aC' // 53a8-53a9 #2 - 'A' // 53aa #0 - 'C' // 53ab #2 - 'B' // 53ac #1 - 'aA' // 53ad-53ae #0 - 'F' // 53af #5 - 'C' // 53b0 #2 - 'D' // 53b1 #3 - 'A' // 53b2 #0 - 'J' // 53b3 #9 - 'C' // 53b4 #2 - 'F' // 53b5 #5 - 'A' // 53b6 #0 - 'aF' // 53b7-53b8 #5 - 'I' // 53b9 #8 - 'F' // 53ba #5 - 'A' // 53bb #0 - 'D' // 53bc #3 - 'F' // 53bd #5 - 'aD' // 53be-53bf #3 - 'F' // 53c0 #5 - 'C' // 53c1 #2 - 'aA' // 53c2-53c3 #0 - 'F' // 53c4 #5 - 'A' // 53c5 #0 - 'aD' // 53c6-53c7 #3 - 'eA' // 53c8-53cd #0 - 'aF' // 53ce-53cf #5 - 'aB' // 53d0-53d1 #1 - 'C' // 53d2 #2 - 'F' // 53d3 #5 - 'A' // 53d4 #0 - 'F' // 53d5 #5 - 'aA' // 53d6-53d7 #0 - 'B' // 53d8 #1 - 'A' // 53d9 #0 - 'C' // 53da #2 - 'A' // 53db #0 - 'D' // 53dc #3 - 'aF' // 53dd-53de #5 - 'A' // 53df #0 - 'C' // 53e0 #2 - 'eA' // 53e1-53e6 #0 - 'F' // 53e7 #5 - 'kA' // 53e8-53f3 #0 - 'J' // 53f4 #9 - 'cA' // 53f5-53f8 #0 - 'D' // 53f9 #3 - 'F' // 53fa #5 - 'aB' // 53fb-53fc #1 - 'D' // 53fd #3 - 'B' // 53fe #1 - 'aD' // 53ff-5400 #3 - 'A' // 5401 #0 - 'F' // 5402 #5 - 'aA' // 5403-5404 #0 - 'D' // 5405 #3 - 'aB' // 5406-5407 #1 - 'iA' // 5408-5411 #0 - 'aC' // 5412-5413 #2 - 'B' // 5414 #1 - 'D' // 5415 #3 - 'B' // 5416 #1 - 'D' // 5417 #3 - 'aB' // 5418-5419 #1 - 'C' // 541a #2 - 'A' // 541b #0 - 'B' // 541c #1 - 'A' // 541d #0 - 'C' // 541e #2 - 'aA' // 541f-5420 #0 - 'C' // 5421 #2 - 'D' // 5422 #3 - 'B' // 5423 #1 - 'C' // 5424 #2 - 'B' // 5425 #1 - 'A' // 5426 #0 - 'aC' // 5427-5428 #2 - 'A' // 5429 #0 - 'C' // 542a #2 - 'aA' // 542b-542c #0 - 'C' // 542d #2 - 'A' // 542e #0 - 'C' // 542f #2 - 'B' // 5430 #1 - 'A' // 5431 #0 - 'B' // 5432 #1 - 'A' // 5433 #0 - 'aC' // 5434-5435 #2 - 'A' // 5436 #0 - 'B' // 5437 #1 - 'aA' // 5438-5439 #0 - 'D' // 543a #3 - 'cA' // 543b-543e #0 - 'C' // 543f #2 - 'A' // 5440 #0 - 'B' // 5441 #1 - 'A' // 5442 #0 - 'C' // 5443 #2 - 'F' // 5444 #5 - 'B' // 5445 #1 - 'A' // 5446 #0 - 'C' // 5447 #2 - 'A' // 5448 #0 - 'F' // 5449 #5 - 'A' // 544a #0 - 'B' // 544b #1 - 'aC' // 544c-544d #2 - 'A' // 544e #0 - 'C' // 544f #2 - 'D' // 5450 #3 - 'J' // 5451 #9 - 'aD' // 5452-5453 #3 - 'B' // 5454 #1 - 'F' // 5455 #5 - 'fD' // 5456-545c #3 - 'K' // 545d #10 - 'F' // 545e #5 - 'J' // 545f #9 - 'aB' // 5460-5461 #1 - 'A' // 5462 #0 - 'B' // 5463 #1 - 'A' // 5464 #0 - 'B' // 5465 #1 - 'A' // 5466 #0 - 'C' // 5467 #2 - 'A' // 5468 #0 - 'C' // 5469 #2 - 'aA' // 546a-546b #0 - 'aC' // 546c-546d #2 - 'F' // 546e #5 - 'B' // 546f #1 - 'aA' // 5470-5471 #0 - 'B' // 5472 #1 - 'A' // 5473 #0 - 'C' // 5474 #2 - 'aA' // 5475-5476 #0 - 'C' // 5477 #2 - 'B' // 5478 #1 - 'D' // 5479 #3 - 'B' // 547a #1 - 'bA' // 547b-547d #0 - 'B' // 547e #1 - 'aA' // 547f-5480 #0 - 'C' // 5481 #2 - 'B' // 5482 #1 - 'F' // 5483 #5 - 'A' // 5484 #0 - 'C' // 5485 #2 - 'A' // 5486 #0 - 'I' // 5487 #8 - 'C' // 5488 #2 - 'aF' // 5489-548a #5 - 'eA' // 548b-5490 #0 - 'aC' // 5491-5492 #2 - 'aB' // 5493-5494 #1 - 'C' // 5495 #2 - 'A' // 5496 #0 - 'aB' // 5497-5498 #1 - 'D' // 5499 #3 - 'B' // 549a #1 - 'D' // 549b #3 - 'C' // 549c #2 - 'D' // 549d #3 - 'B' // 549e #1 - 'F' // 549f #5 - 'A' // 54a0 #0 - 'C' // 54a1 #2 - 'A' // 54a2 #0 - 'B' // 54a3 #1 - 'aA' // 54a4-54a5 #0 - 'aC' // 54a6-54a7 #2 - 'A' // 54a8 #0 - 'aC' // 54a9-54aa #2 - 'aA' // 54ab-54ac #0 - 'aC' // 54ad-54ae #2 - 'A' // 54af #0 - 'B' // 54b0 #1 - 'C' // 54b1 #2 - 'aA' // 54b2-54b3 #0 - 'B' // 54b4 #1 - 'D' // 54b5 #3 - 'B' // 54b6 #1 - 'C' // 54b7 #2 - 'A' // 54b8 #0 - 'aC' // 54b9-54ba #2 - 'bA' // 54bb-54bd #0 - 'C' // 54be #2 - 'eA' // 54bf-54c4 #0 - 'B' // 54c5 #1 - 'cA' // 54c6-54c9 #0 - 'F' // 54ca #5 - 'aB' // 54cb-54cc #1 - 'aC' // 54cd-54ce #2 - 'aB' // 54cf-54d0 #1 - 'dD' // 54d1-54d5 #3 - 'B' // 54d6 #1 - 'D' // 54d7 #3 - 'F' // 54d8 #5 - 'D' // 54d9 #3 - 'B' // 54da #1 - 'bD' // 54db-54dd #3 - 'B' // 54de #1 - 'D' // 54df #3 - 'C' // 54e0 #2 - 'A' // 54e1 #0 - 'C' // 54e2 #2 - 'aB' // 54e3-54e4 #1 - 'aA' // 54e5-54e6 #0 - 'B' // 54e7 #1 - 'aA' // 54e8-54e9 #0 - 'C' // 54ea #2 - 'B' // 54eb #1 - 'F' // 54ec #5 - 'aA' // 54ed-54ee #0 - 'C' // 54ef #2 - 'D' // 54f0 #3 - 'aA' // 54f1-54f2 #0 - 'C' // 54f3 #2 - 'aD' // 54f4-54f5 #3 - 'F' // 54f6 #5 - 'aB' // 54f7-54f8 #1 - 'D' // 54f9 #3 - 'A' // 54fa #0 - 'B' // 54fb #1 - 'C' // 54fc #2 - 'A' // 54fd #0 - 'F' // 54fe #5 - 'A' // 54ff #0 - 'F' // 5500 #5 - 'C' // 5501 #2 - 'aB' // 5502-5503 #1 - 'A' // 5504 #0 - 'C' // 5505 #2 - 'aA' // 5506-5507 #0 - 'C' // 5508 #2 - 'A' // 5509 #0 - 'aB' // 550a-550b #1 - 'aC' // 550c-550d #2 - 'bA' // 550e-5510 #0 - 'bB' // 5511-5513 #1 - 'A' // 5514 #0 - 'aF' // 5515-5516 #5 - 'aB' // 5517-5518 #1 - 'D' // 5519 #3 - 'B' // 551a #1 - 'D' // 551b #3 - 'K' // 551c #10 - 'D' // 551d #3 - 'B' // 551e #1 - 'cD' // 551f-5522 #3 - 'B' // 5523 #1 - 'D' // 5524 #3 - 'aB' // 5525-5526 #1 - 'C' // 5527 #2 - 'B' // 5528 #1 - 'D' // 5529 #3 - 'C' // 552a #2 - 'A' // 552b #0 - 'aB' // 552c-552d #1 - 'aA' // 552e-552f #0 - 'B' // 5530 #1 - 'A' // 5531 #0 - 'C' // 5532 #2 - 'A' // 5533 #0 - 'B' // 5534 #1 - 'A' // 5535 #0 - 'C' // 5536 #2 - 'B' // 5537 #1 - 'C' // 5538 #2 - 'A' // 5539 #0 - 'D' // 553a #3 - 'C' // 553b #2 - 'A' // 553c #0 - 'F' // 553d #5 - 'A' // 553e #0 - 'B' // 553f #1 - 'A' // 5540 #0 - 'C' // 5541 #2 - 'K' // 5542 #10 - 'B' // 5543 #1 - 'A' // 5544 #0 - 'C' // 5545 #2 - 'A' // 5546 #0 - 'C' // 5547 #2 - 'B' // 5548 #1 - 'C' // 5549 #2 - 'A' // 554a #0 - 'B' // 554b #1 - 'F' // 554c #5 - 'C' // 554d #2 - 'B' // 554e #1 - 'A' // 554f #0 - 'aC' // 5550-5551 #2 - 'B' // 5552 #1 - 'A' // 5553 #0 - 'D' // 5554 #3 - 'B' // 5555 #1 - 'aA' // 5556-5557 #0 - 'F' // 5558 #5 - 'D' // 5559 #3 - 'aF' // 555a-555b #5 - 'A' // 555c #0 - 'C' // 555d #2 - 'A' // 555e #0 - 'B' // 555f #1 - 'F' // 5560 #5 - 'C' // 5561 #2 - 'B' // 5562 #1 - 'A' // 5563 #0 - 'C' // 5564 #2 - 'B' // 5565 #1 - 'C' // 5566 #2 - 'aD' // 5567-5568 #3 - 'bB' // 5569-556b #1 - 'dD' // 556c-5570 #3 - 'bB' // 5571-5573 #1 - 'D' // 5574 #3 - 'bB' // 5575-5577 #1 - 'D' // 5578 #3 - 'B' // 5579 #1 - 'D' // 557a #3 - 'fA' // 557b-5581 #0 - 'C' // 5582 #2 - 'aA' // 5583-5584 #0 - 'D' // 5585 #3 - 'aA' // 5586-5587 #0 - 'C' // 5588 #2 - 'bA' // 5589-558b #0 - 'aB' // 558c-558d #1 - 'aC' // 558e-558f #2 - 'B' // 5590 #1 - 'A' // 5591 #0 - 'C' // 5592 #2 - 'aA' // 5593-5594 #0 - 'B' // 5595 #1 - 'D' // 5596 #3 - 'F' // 5597 #5 - 'bA' // 5598-559a #0 - 'D' // 559b #3 - 'aA' // 559c-559d #0 - 'J' // 559e #9 - 'A' // 559f #0 - 'D' // 55a0 #3 - 'aB' // 55a1-55a2 #1 - 'aA' // 55a3-55a4 #0 - 'aB' // 55a5-55a6 #1 - 'eA' // 55a7-55ac #0 - 'C' // 55ad #2 - 'A' // 55ae #0 - 'D' // 55af #3 - 'A' // 55b0 #0 - 'B' // 55b1 #1 - 'C' // 55b2 #2 - 'bB' // 55b3-55b5 #1 - 'F' // 55b6 #5 - 'aD' // 55b7-55b8 #3 - 'cB' // 55b9-55bc #1 - 'aD' // 55bd-55be #3 - 'C' // 55bf #2 - 'B' // 55c0 #1 - 'C' // 55c1 #2 - 'B' // 55c2 #1 - 'A' // 55c3 #0 - 'C' // 55c4 #2 - 'A' // 55c5 #0 - 'C' // 55c6 #2 - 'A' // 55c7 #0 - 'B' // 55c8 #1 - 'A' // 55c9 #0 - 'B' // 55ca #1 - 'aC' // 55cb-55cc #2 - 'B' // 55cd #1 - 'C' // 55ce #2 - 'aB' // 55cf-55d0 #1 - 'A' // 55d1 #0 - 'aC' // 55d2-55d3 #2 - 'A' // 55d4 #0 - 'aB' // 55d5-55d6 #1 - 'aC' // 55d7-55d8 #2 - 'B' // 55d9 #1 - 'bA' // 55da-55dc #0 - 'aC' // 55dd-55de #2 - 'A' // 55df #0 - 'K' // 55e0 #10 - 'B' // 55e1 #1 - 'bA' // 55e2-55e4 #0 - 'cB' // 55e5-55e8 #1 - 'C' // 55e9 #2 - 'B' // 55ea #1 - 'D' // 55eb #3 - 'C' // 55ec #2 - 'D' // 55ed #3 - 'C' // 55ee #2 - 'aB' // 55ef-55f0 #1 - 'C' // 55f1 #2 - 'B' // 55f2 #1 - 'aD' // 55f3-55f4 #3 - 'B' // 55f5 #1 - 'C' // 55f6 #2 - 'A' // 55f7 #0 - 'F' // 55f8 #5 - 'C' // 55f9 #2 - 'bB' // 55fa-55fc #1 - 'bA' // 55fd-55ff #0 - 'bB' // 5600-5602 #1 - 'D' // 5603 #3 - 'I' // 5604 #8 - 'C' // 5605 #2 - 'A' // 5606 #0 - 'F' // 5607 #5 - 'aA' // 5608-5609 #0 - 'F' // 560a #5 - 'D' // 560b #3 - 'I' // 560c #8 - 'cA' // 560d-5610 #0 - 'C' // 5611 #2 - 'A' // 5612 #0 - 'B' // 5613 #1 - 'A' // 5614 #0 - 'B' // 5615 #1 - 'aA' // 5616-5617 #0 - 'aF' // 5618-5619 #5 - 'D' // 561a #3 - 'C' // 561b #2 - 'cB' // 561c-561f #1 - 'C' // 5620 #2 - 'bB' // 5621-5623 #1 - 'D' // 5624 #3 - 'B' // 5625 #1 - 'D' // 5626 #3 - 'B' // 5627 #1 - 'F' // 5628 #5 - 'A' // 5629 #0 - 'B' // 562a #1 - 'D' // 562b #3 - 'A' // 562c #0 - 'aB' // 562d-562e #1 - 'A' // 562f #0 - 'C' // 5630 #2 - 'F' // 5631 #5 - 'A' // 5632 #0 - 'C' // 5633 #2 - 'A' // 5634 #0 - 'C' // 5635 #2 - 'cA' // 5636-5639 #0 - 'B' // 563a #1 - 'A' // 563b #0 - 'F' // 563c #5 - 'C' // 563d #2 - 'B' // 563e #1 - 'A' // 563f #0 - 'C' // 5640 #2 - 'aA' // 5641-5642 #0 - 'C' // 5643 #2 - 'F' // 5644 #5 - 'B' // 5645 #1 - 'C' // 5646 #2 - 'F' // 5647 #5 - 'B' // 5648 #1 - 'A' // 5649 #0 - 'B' // 564a #1 - 'J' // 564b #9 - 'C' // 564c #2 - 'bA' // 564d-564f #0 - 'C' // 5650 #2 - 'D' // 5651 #3 - 'B' // 5652 #1 - 'A' // 5653 #0 - 'C' // 5654 #2 - 'aD' // 5655-5656 #3 - 'cB' // 5657-565a #1 - 'F' // 565b #5 - 'D' // 565c #3 - 'B' // 565d #1 - 'C' // 565e #2 - 'D' // 565f #3 - 'cC' // 5660-5663 #2 - 'A' // 5664 #0 - 'I' // 5665 #8 - 'C' // 5666 #2 - 'D' // 5667 #3 - 'eA' // 5668-566d #0 - 'B' // 566e #1 - 'A' // 566f #0 - 'B' // 5670 #1 - 'C' // 5671 #2 - 'A' // 5672 #0 - 'B' // 5673 #1 - 'A' // 5674 #0 - 'F' // 5675 #5 - 'A' // 5676 #0 - 'B' // 5677 #1 - 'A' // 5678 #0 - 'B' // 5679 #1 - 'A' // 567a #0 - 'aB' // 567b-567c #1 - 'D' // 567d #3 - 'aB' // 567e-567f #1 - 'A' // 5680 #0 - 'bB' // 5681-5683 #1 - 'A' // 5684 #0 - 'C' // 5685 #2 - 'aA' // 5686-5687 #0 - 'F' // 5688 #5 - 'B' // 5689 #1 - 'bC' // 568a-568c #2 - 'aB' // 568d-568e #1 - 'A' // 568f #0 - 'B' // 5690 #1 - 'D' // 5691 #3 - 'aB' // 5692-5693 #1 - 'F' // 5694 #5 - 'C' // 5695 #2 - 'D' // 5696 #3 - 'aB' // 5697-5698 #1 - 'aA' // 5699-569a #0 - 'D' // 569b #3 - 'B' // 569c #1 - 'bC' // 569d-569f #2 - 'F' // 56a0 #5 - 'B' // 56a1 #1 - 'F' // 56a2 #5 - 'D' // 56a3 #3 - 'B' // 56a4 #1 - 'A' // 56a5 #0 - 'C' // 56a6 #2 - 'A' // 56a7 #0 - 'C' // 56a8 #2 - 'F' // 56a9 #5 - 'B' // 56aa #1 - 'C' // 56ab #2 - 'A' // 56ac #0 - 'C' // 56ad #2 - 'A' // 56ae #0 - 'B' // 56af #1 - 'D' // 56b0 #3 - 'aC' // 56b1-56b2 #2 - 'aA' // 56b3-56b4 #0 - 'B' // 56b5 #1 - 'A' // 56b6 #0 - 'C' // 56b7 #2 - 'D' // 56b8 #3 - 'B' // 56b9 #1 - 'aD' // 56ba-56bb #3 - 'A' // 56bc #0 - 'B' // 56bd #1 - 'C' // 56be #2 - 'B' // 56bf #1 - 'cA' // 56c0-56c3 #0 - 'D' // 56c4 #3 - 'C' // 56c5 #2 - 'B' // 56c6 #1 - 'D' // 56c7 #3 - 'bA' // 56c8-56ca #0 - 'aC' // 56cb-56cc #2 - 'A' // 56cd #0 - 'bF' // 56ce-56d0 #5 - 'A' // 56d1 #0 - 'D' // 56d2 #3 - 'C' // 56d3 #2 - 'B' // 56d4 #1 - 'D' // 56d5 #3 - 'B' // 56d6 #1 - 'A' // 56d7 #0 - 'aF' // 56d8-56d9 #5 - 'aA' // 56da-56db #0 - 'F' // 56dc #5 - 'C' // 56dd #2 - 'bA' // 56de-56e0 #0 - 'C' // 56e1 #2 - 'B' // 56e2 #1 - 'J' // 56e3 #9 - 'aC' // 56e4-56e5 #2 - 'J' // 56e6 #9 - 'A' // 56e7 #0 - 'F' // 56e8 #5 - 'D' // 56e9 #3 - 'B' // 56ea #1 - 'A' // 56eb #0 - 'D' // 56ec #3 - 'aA' // 56ed-56ee #0 - 'B' // 56ef #1 - 'A' // 56f0 #0 - 'C' // 56f1 #2 - 'F' // 56f2 #5 - 'J' // 56f3 #9 - 'aD' // 56f4-56f5 #3 - 'F' // 56f6 #5 - 'A' // 56f7 #0 - 'D' // 56f8 #3 - 'aA' // 56f9-56fa #0 - 'B' // 56fb #1 - 'D' // 56fc #3 - 'A' // 56fd #0 - 'D' // 56fe #3 - 'A' // 56ff #0 - 'C' // 5700 #2 - 'cA' // 5701-5704 #0 - 'aD' // 5705-5706 #3 - 'dA' // 5707-570b #0 - 'C' // 570c #2 - 'A' // 570d #0 - 'D' // 570e #3 - 'F' // 570f #5 - 'D' // 5710 #3 - 'F' // 5711 #5 - 'aA' // 5712-5713 #0 - 'B' // 5714 #1 - 'C' // 5715 #2 - 'A' // 5716 #0 - 'D' // 5717 #3 - 'A' // 5718 #0 - 'D' // 5719 #3 - 'aC' // 571a-571b #2 - 'A' // 571c #0 - 'C' // 571d #2 - 'B' // 571e #1 - 'A' // 571f #0 - 'C' // 5720 #2 - 'F' // 5721 #5 - 'aC' // 5722-5723 #2 - 'F' // 5724 #5 - 'J' // 5725 #9 - 'aF' // 5726-5727 #5 - 'bA' // 5728-572a #0 - 'D' // 572b #3 - 'bA' // 572c-572e #0 - 'C' // 572f #2 - 'A' // 5730 #0 - 'D' // 5731 #3 - 'B' // 5732 #1 - 'aC' // 5733-5734 #2 - 'aD' // 5735-5736 #3 - 'aF' // 5737-5738 #5 - 'aD' // 5739-573a #3 - 'A' // 573b #0 - 'D' // 573c #3 - 'C' // 573d #2 - 'A' // 573e #0 - 'C' // 573f #2 - 'A' // 5740 #0 - 'I' // 5741 #8 - 'A' // 5742 #0 - 'B' // 5743 #1 - 'D' // 5744 #3 - 'aC' // 5745-5746 #2 - 'A' // 5747 #0 - 'D' // 5748 #3 - 'B' // 5749 #1 - 'A' // 574a #0 - 'B' // 574b #1 - 'eA' // 574c-5751 #0 - 'C' // 5752 #2 - 'D' // 5753 #3 - 'B' // 5754 #1 - 'aD' // 5755-5756 #3 - 'B' // 5757 #1 - 'D' // 5758 #3 - 'F' // 5759 #5 - 'D' // 575a #3 - 'B' // 575b #1 - 'bD' // 575c-575e #3 - 'C' // 575f #2 - 'D' // 5760 #3 - 'A' // 5761 #0 - 'C' // 5762 #2 - 'D' // 5763 #3 - 'A' // 5764 #0 - 'F' // 5765 #5 - 'dA' // 5766-576a #0 - 'C' // 576b #2 - 'D' // 576c #3 - 'C' // 576d #2 - 'J' // 576e #9 - 'bA' // 576f-5771 #0 - 'B' // 5772 #1 - 'A' // 5773 #0 - 'C' // 5774 #2 - 'A' // 5775 #0 - 'B' // 5776 #1 - 'A' // 5777 #0 - 'K' // 5778 #10 - 'F' // 5779 #5 - 'C' // 577a #2 - 'aA' // 577b-577c #0 - 'B' // 577d #1 - 'aC' // 577e-577f #2 - 'B' // 5780 #1 - 'F' // 5781 #5 - 'A' // 5782 #0 - 'C' // 5783 #2 - 'cD' // 5784-5787 #3 - 'A' // 5788 #0 - 'F' // 5789 #5 - 'B' // 578a #1 - 'aA' // 578b-578c #0 - 'B' // 578d #1 - 'D' // 578e #3 - 'aB' // 578f-5790 #1 - 'aD' // 5791-5792 #3 - 'A' // 5793 #0 - 'C' // 5794 #2 - 'A' // 5795 #0 - 'D' // 5796 #3 - 'C' // 5797 #2 - 'B' // 5798 #1 - 'aC' // 5799-579a #2 - 'B' // 579b #1 - 'aC' // 579c-579d #2 - 'A' // 579e #0 - 'C' // 579f #2 - 'A' // 57a0 #0 - 'C' // 57a1 #2 - 'bA' // 57a2-57a4 #0 - 'B' // 57a5 #1 - 'D' // 57a6 #3 - 'C' // 57a7 #2 - 'aF' // 57a8-57a9 #5 - 'C' // 57aa #2 - 'D' // 57ab #3 - 'F' // 57ac #5 - 'D' // 57ad #3 - 'C' // 57ae #2 - 'D' // 57af #3 - 'F' // 57b0 #5 - 'aD' // 57b1-57b2 #3 - 'C' // 57b3 #2 - 'bB' // 57b4-57b6 #1 - 'D' // 57b7 #3 - 'A' // 57b8 #0 - 'cB' // 57b9-57bc #1 - 'A' // 57bd #0 - 'aB' // 57be-57bf #1 - 'F' // 57c0 #5 - 'aB' // 57c1-57c2 #1 - 'A' // 57c3 #0 - 'B' // 57c4 #1 - 'D' // 57c5 #3 - 'bA' // 57c6-57c8 #0 - 'K' // 57c9 #10 - 'D' // 57ca #3 - 'A' // 57cb #0 - 'C' // 57cc #2 - 'D' // 57cd #3 - 'aA' // 57ce-57cf #0 - 'B' // 57d0 #1 - 'K' // 57d1 #10 - 'A' // 57d2 #0 - 'F' // 57d3 #5 - 'aC' // 57d4-57d5 #2 - 'F' // 57d6 #5 - 'C' // 57d7 #2 - 'cD' // 57d8-57db #3 - 'A' // 57dc #0 - 'aC' // 57dd-57de #2 - 'aA' // 57df-57e0 #0 - 'C' // 57e1 #2 - 'B' // 57e2 #1 - 'C' // 57e3 #2 - 'A' // 57e4 #0 - 'B' // 57e5 #1 - 'aC' // 57e6-57e7 #2 - 'D' // 57e8 #3 - 'A' // 57e9 #0 - 'aD' // 57ea-57eb #3 - 'B' // 57ec #1 - 'A' // 57ed #0 - 'I' // 57ee #8 - 'B' // 57ef #1 - 'A' // 57f0 #0 - 'aB' // 57f1-57f2 #1 - 'I' // 57f3 #8 - 'A' // 57f4 #0 - 'C' // 57f5 #2 - 'aA' // 57f6-57f7 #0 - 'C' // 57f8 #2 - 'dA' // 57f9-57fd #0 - 'C' // 57fe #2 - 'F' // 57ff #5 - 'A' // 5800 #0 - 'B' // 5801 #1 - 'dA' // 5802-5806 #0 - 'B' // 5807 #1 - 'cA' // 5808-580b #0 - 'aC' // 580c-580d #2 - 'B' // 580e #1 - 'D' // 580f #3 - 'B' // 5810 #1 - 'D' // 5811 #3 - 'B' // 5812 #1 - 'D' // 5813 #3 - 'B' // 5814 #1 - 'F' // 5815 #5 - 'D' // 5816 #3 - 'K' // 5817 #10 - 'B' // 5818 #1 - 'A' // 5819 #0 - 'D' // 581a #3 - 'C' // 581b #2 - 'B' // 581c #1 - 'aA' // 581d-581e #0 - 'F' // 581f #5 - 'aA' // 5820-5821 #0 - 'B' // 5822 #1 - 'I' // 5823 #8 - 'A' // 5824 #0 - 'B' // 5825 #1 - 'aA' // 5826-5827 #0 - 'aB' // 5828-5829 #1 - 'A' // 582a #0 - 'D' // 582b #3 - 'B' // 582c #1 - 'A' // 582d #0 - 'B' // 582e #1 - 'bA' // 582f-5831 #0 - 'C' // 5832 #2 - 'B' // 5833 #1 - 'aA' // 5834-5835 #0 - 'bB' // 5836-5838 #1 - 'C' // 5839 #2 - 'A' // 583a #0 - 'B' // 583b #1 - 'D' // 583c #3 - 'C' // 583d #2 - 'D' // 583e #3 - 'C' // 583f #2 - 'A' // 5840 #0 - 'F' // 5841 #5 - 'aD' // 5842-5843 #3 - 'B' // 5844 #1 - 'aD' // 5845-5846 #3 - 'aB' // 5847-5848 #1 - 'dA' // 5849-584d #0 - 'B' // 584e #1 - 'A' // 584f #0 - 'J' // 5850 #9 - 'aA' // 5851-5852 #0 - 'B' // 5853 #1 - 'A' // 5854 #0 - 'C' // 5855 #2 - 'D' // 5856 #3 - 'cA' // 5857-585a #0 - 'bB' // 585b-585d #1 - 'A' // 585e #0 - 'C' // 585f #2 - 'D' // 5860 #3 - 'J' // 5861 #9 - 'A' // 5862 #0 - 'B' // 5863 #1 - 'A' // 5864 #0 - 'B' // 5865 #1 - 'D' // 5866 #3 - 'F' // 5867 #5 - 'C' // 5868 #2 - 'A' // 5869 #0 - 'D' // 586a #3 - 'C' // 586b #2 - 'B' // 586c #1 - 'C' // 586d #2 - 'D' // 586e #3 - 'B' // 586f #1 - 'F' // 5870 #5 - 'B' // 5871 #1 - 'C' // 5872 #2 - 'aB' // 5873-5874 #1 - 'A' // 5875 #0 - 'B' // 5876 #1 - 'D' // 5877 #3 - 'F' // 5878 #5 - 'A' // 5879 #0 - 'aB' // 587a-587b #1 - 'A' // 587c #0 - 'I' // 587d #8 - 'A' // 587e #0 - 'C' // 587f #2 - 'aA' // 5880-5881 #0 - 'B' // 5882 #1 - 'A' // 5883 #0 - 'D' // 5884 #3 - 'A' // 5885 #0 - 'B' // 5886 #1 - 'aC' // 5887-5888 #2 - 'aA' // 5889-588a #0 - 'C' // 588b #2 - 'aJ' // 588c-588d #9 - 'B' // 588e #1 - 'C' // 588f #2 - 'A' // 5890 #0 - 'aB' // 5891-5892 #1 - 'A' // 5893 #0 - 'C' // 5894 #2 - 'D' // 5895 #3 - 'C' // 5896 #2 - 'F' // 5897 #5 - 'C' // 5898 #2 - 'aB' // 5899-589a #1 - 'D' // 589b #3 - 'cA' // 589c-589f #0 - 'C' // 58a0 #2 - 'A' // 58a1 #0 - 'F' // 58a2 #5 - 'I' // 58a3 #8 - 'D' // 58a4 #3 - 'B' // 58a5 #1 - 'C' // 58a6 #2 - 'B' // 58a7 #1 - 'aA' // 58a8-58a9 #0 - 'C' // 58aa #2 - 'A' // 58ab #0 - 'B' // 58ac #1 - 'D' // 58ad #3 - 'A' // 58ae #0 - 'B' // 58af #1 - 'I' // 58b0 #8 - 'A' // 58b1 #0 - 'F' // 58b2 #5 - 'A' // 58b3 #0 - 'D' // 58b4 #3 - 'aB' // 58b5-58b6 #1 - 'D' // 58b7 #3 - 'aF' // 58b8-58b9 #5 - 'aA' // 58ba-58bb #0 - 'C' // 58bc #2 - 'B' // 58bd #1 - 'A' // 58be #0 - 'B' // 58bf #1 - 'D' // 58c0 #3 - 'A' // 58c1 #0 - 'C' // 58c2 #2 - 'J' // 58c3 #9 - 'F' // 58c4 #5 - 'A' // 58c5 #0 - 'B' // 58c6 #1 - 'A' // 58c7 #0 - 'C' // 58c8 #2 - 'B' // 58c9 #1 - 'F' // 58ca #5 - 'B' // 58cb #1 - 'aF' // 58cc-58cd #5 - 'A' // 58ce #0 - 'B' // 58cf #1 - 'C' // 58d0 #2 - 'A' // 58d1 #0 - 'C' // 58d2 #2 - 'bA' // 58d3-58d5 #0 - 'C' // 58d6 #2 - 'F' // 58d7 #5 - 'bA' // 58d8-58da #0 - 'B' // 58db #1 - 'cA' // 58dc-58df #0 - 'C' // 58e0 #2 - 'J' // 58e1 #9 - 'C' // 58e2 #2 - 'B' // 58e3 #1 - 'A' // 58e4 #0 - 'F' // 58e5 #5 - 'D' // 58e6 #3 - 'aB' // 58e7-58e8 #1 - 'C' // 58e9 #2 - 'D' // 58ea #3 - 'aA' // 58eb-58ec #0 - 'D' // 58ed #3 - 'J' // 58ee #9 - 'aA' // 58ef-58f0 #0 - 'F' // 58f1 #5 - 'A' // 58f2 #0 - 'aC' // 58f3-58f4 #2 - 'aD' // 58f5-58f6 #3 - 'F' // 58f7 #5 - 'D' // 58f8 #3 - 'bA' // 58f9-58fb #0 - 'C' // 58fc #2 - 'A' // 58fd #0 - 'aB' // 58fe-58ff #1 - 'aD' // 5900-5901 #3 - 'A' // 5902 #0 - 'aB' // 5903-5904 #1 - 'C' // 5905 #2 - 'A' // 5906 #0 - 'B' // 5907 #1 - 'K' // 5908 #10 - 'F' // 5909 #5 - 'A' // 590a #0 - 'F' // 590b #5 - 'aC' // 590c-590d #2 - 'B' // 590e #1 - 'A' // 590f #0 - 'J' // 5910 #9 - 'B' // 5911 #1 - 'C' // 5912 #2 - 'F' // 5913 #5 - 'bA' // 5914-5916 #0 - 'B' // 5917 #1 - 'F' // 5918 #5 - 'aA' // 5919-591a #0 - 'J' // 591b #9 - 'A' // 591c #0 - 'C' // 591d #2 - 'D' // 591e #3 - 'C' // 591f #2 - 'B' // 5920 #1 - 'F' // 5921 #5 - 'A' // 5922 #0 - 'F' // 5923 #5 - 'aA' // 5924-5925 #0 - 'D' // 5926 #3 - 'A' // 5927 #0 - 'F' // 5928 #5 - 'fA' // 5929-592f #0 - 'F' // 5930 #5 - 'aA' // 5931-5932 #0 - 'F' // 5933 #5 - 'B' // 5934 #1 - 'aF' // 5935-5936 #5 - 'aA' // 5937-5938 #0 - 'F' // 5939 #5 - 'aD' // 593a-593b #3 - 'B' // 593c #1 - 'J' // 593d #9 - 'A' // 593e #0 - 'F' // 593f #5 - 'B' // 5940 #1 - 'aD' // 5941-5942 #3 - 'F' // 5943 #5 - 'A' // 5944 #0 - 'B' // 5945 #1 - 'F' // 5946 #5 - 'bA' // 5947-5949 #0 - 'B' // 594a #1 - 'D' // 594b #3 - 'K' // 594c #10 - 'D' // 594d #3 - 'cA' // 594e-5951 #0 - 'F' // 5952 #5 - 'bA' // 5953-5955 #0 - 'D' // 5956 #3 - 'aA' // 5957-5958 #0 - 'F' // 5959 #5 - 'A' // 595a #0 - 'F' // 595b #5 - 'I' // 595c #8 - 'bF' // 595d-595f #5 - 'A' // 5960 #0 - 'C' // 5961 #2 - 'A' // 5962 #0 - 'F' // 5963 #5 - 'D' // 5964 #3 - 'C' // 5965 #2 - 'D' // 5966 #3 - 'A' // 5967 #0 - 'F' // 5968 #5 - 'eA' // 5969-596e #0 - 'F' // 596f #5 - 'aB' // 5970-5971 #1 - 'bA' // 5972-5974 #0 - 'C' // 5975 #2 - 'A' // 5976 #0 - 'B' // 5977 #1 - 'A' // 5978 #0 - 'C' // 5979 #2 - 'D' // 597a #3 - 'aC' // 597b-597c #2 - 'A' // 597d #0 - 'bB' // 597e-5980 #1 - 'C' // 5981 #2 - 'bA' // 5982-5984 #0 - 'B' // 5985 #1 - 'bD' // 5986-5988 #3 - 'B' // 5989 #1 - 'A' // 598a #0 - 'F' // 598b #5 - 'J' // 598c #9 - 'A' // 598d #0 - 'C' // 598e #2 - 'aB' // 598f-5990 #1 - 'K' // 5991 #10 - 'aA' // 5992-5993 #0 - 'B' // 5994 #1 - 'F' // 5995 #5 - 'aA' // 5996-5997 #0 - 'B' // 5998 #1 - 'A' // 5999 #0 - 'B' // 599a #1 - 'F' // 599b #5 - 'D' // 599c #3 - 'A' // 599d #0 - 'B' // 599e #1 - 'C' // 599f #2 - 'bB' // 59a0-59a2 #1 - 'bA' // 59a3-59a5 #0 - 'B' // 59a6 #1 - 'aA' // 59a7-59a8 #0 - 'bD' // 59a9-59ab #3 - 'A' // 59ac #0 - 'F' // 59ad #5 - 'C' // 59ae #2 - 'A' // 59af #0 - 'C' // 59b0 #2 - 'B' // 59b1 #1 - 'A' // 59b2 #0 - 'C' // 59b3 #2 - 'B' // 59b4 #1 - 'aI' // 59b5-59b6 #8 - 'C' // 59b7 #2 - 'I' // 59b8 #8 - 'A' // 59b9 #0 - 'C' // 59ba #2 - 'A' // 59bb #0 - 'C' // 59bc #2 - 'B' // 59bd #1 - 'A' // 59be #0 - 'I' // 59bf #8 - 'B' // 59c0 #1 - 'A' // 59c1 #0 - 'D' // 59c2 #3 - 'A' // 59c3 #0 - 'C' // 59c4 #2 - 'B' // 59c5 #1 - 'A' // 59c6 #0 - 'B' // 59c7 #1 - 'cA' // 59c8-59cb #0 - 'B' // 59cc #1 - 'A' // 59cd #0 - 'aB' // 59ce-59cf #1 - 'dA' // 59d0-59d4 #0 - 'D' // 59d5 #3 - 'B' // 59d6 #1 - 'D' // 59d7 #3 - 'B' // 59d8 #1 - 'aA' // 59d9-59da #0 - 'B' // 59db #1 - 'bA' // 59dc-59de #0 - 'F' // 59df #5 - 'aB' // 59e0-59e1 #1 - 'K' // 59e2 #10 - 'cA' // 59e3-59e6 #0 - 'F' // 59e7 #5 - 'A' // 59e8 #0 - 'B' // 59e9 #1 - 'bA' // 59ea-59ec #0 - 'B' // 59ed #1 - 'A' // 59ee #0 - 'C' // 59ef #2 - 'I' // 59f0 #8 - 'C' // 59f1 #2 - 'A' // 59f2 #0 - 'B' // 59f3 #1 - 'C' // 59f4 #2 - 'B' // 59f5 #1 - 'C' // 59f6 #2 - 'aA' // 59f7-59f8 #0 - 'aI' // 59f9-59fa #8 - 'A' // 59fb #0 - 'I' // 59fc #8 - 'aB' // 59fd-59fe #1 - 'A' // 59ff #0 - 'C' // 5a00 #2 - 'A' // 5a01 #0 - 'B' // 5a02 #1 - 'A' // 5a03 #0 - 'F' // 5a04 #5 - 'cD' // 5a05-5a08 #3 - 'A' // 5a09 #0 - 'I' // 5a0a #8 - 'B' // 5a0b #1 - 'C' // 5a0c #2 - 'A' // 5a0d #0 - 'F' // 5a0e #5 - 'B' // 5a0f #1 - 'D' // 5a10 #3 - 'A' // 5a11 #0 - 'C' // 5a12 #2 - 'A' // 5a13 #0 - 'D' // 5a14 #3 - 'aB' // 5a15-5a16 #1 - 'C' // 5a17 #2 - 'A' // 5a18 #0 - 'I' // 5a19 #8 - 'C' // 5a1a #2 - 'aA' // 5a1b-5a1c #0 - 'D' // 5a1d #3 - 'C' // 5a1e #2 - 'aA' // 5a1f-5a20 #0 - 'B' // 5a21 #1 - 'D' // 5a22 #3 - 'A' // 5a23 #0 - 'C' // 5a24 #2 - 'A' // 5a25 #0 - 'D' // 5a26 #3 - 'A' // 5a27 #0 - 'F' // 5a28 #5 - 'A' // 5a29 #0 - 'C' // 5a2a #2 - 'I' // 5a2b #8 - 'B' // 5a2c #1 - 'A' // 5a2d #0 - 'B' // 5a2e #1 - 'aF' // 5a2f-5a30 #5 - 'aD' // 5a31-5a32 #3 - 'B' // 5a33 #1 - 'D' // 5a34 #3 - 'aA' // 5a35-5a36 #0 - 'bB' // 5a37-5a39 #1 - 'aD' // 5a3a-5a3b #3 - 'A' // 5a3c #0 - 'aB' // 5a3d-5a3e #1 - 'K' // 5a3f #10 - 'aA' // 5a40-5a41 #0 - 'aB' // 5a42-5a43 #1 - 'aC' // 5a44-5a45 #2 - 'aA' // 5a46-5a47 #0 - 'C' // 5a48 #2 - 'A' // 5a49 #0 - 'B' // 5a4a #1 - 'K' // 5a4b #10 - 'A' // 5a4c #0 - 'B' // 5a4d #1 - 'aD' // 5a4e-5a4f #3 - 'A' // 5a50 #0 - 'I' // 5a51 #8 - 'bB' // 5a52-5a54 #1 - 'C' // 5a55 #2 - 'cB' // 5a56-5a59 #1 - 'A' // 5a5a #0 - 'bB' // 5a5b-5a5d #1 - 'C' // 5a5e #2 - 'B' // 5a5f #1 - 'I' // 5a60 #8 - 'B' // 5a61 #1 - 'aA' // 5a62-5a63 #0 - 'B' // 5a64 #1 - 'C' // 5a65 #2 - 'aA' // 5a66-5a67 #0 - 'B' // 5a68 #1 - 'I' // 5a69 #8 - 'A' // 5a6a #0 - 'B' // 5a6b #1 - 'C' // 5a6c #2 - 'A' // 5a6d #0 - 'B' // 5a6e #1 - 'D' // 5a6f #3 - 'aB' // 5a70-5a71 #1 - 'K' // 5a72 #10 - 'cD' // 5a73-5a76 #3 - 'A' // 5a77 #0 - 'aB' // 5a78-5a79 #1 - 'aC' // 5a7a-5a7b #2 - 'aB' // 5a7c-5a7d #1 - 'C' // 5a7e #2 - 'A' // 5a7f #0 - 'D' // 5a80 #3 - 'bB' // 5a81-5a83 #1 - 'A' // 5a84 #0 - 'D' // 5a85 #3 - 'B' // 5a86 #1 - 'D' // 5a87 #3 - 'B' // 5a88 #1 - 'D' // 5a89 #3 - 'B' // 5a8a #1 - 'C' // 5a8b #2 - 'B' // 5a8c #1 - 'K' // 5a8d #10 - 'aB' // 5a8e-5a8f #1 - 'A' // 5a90 #0 - 'B' // 5a91 #1 - 'aA' // 5a92-5a93 #0 - 'B' // 5a94 #1 - 'I' // 5a95 #8 - 'C' // 5a96 #2 - 'B' // 5a97 #1 - 'D' // 5a98 #3 - 'C' // 5a99 #2 - 'aA' // 5a9a-5a9b #0 - 'C' // 5a9c #2 - 'B' // 5a9d #1 - 'aA' // 5a9e-5a9f #0 - 'C' // 5aa0 #2 - 'B' // 5aa1 #1 - 'A' // 5aa2 #0 - 'D' // 5aa3 #3 - 'I' // 5aa4 #8 - 'aB' // 5aa5-5aa6 #1 - 'A' // 5aa7 #0 - 'D' // 5aa8 #3 - 'B' // 5aa9 #1 - 'I' // 5aaa #8 - 'B' // 5aab #1 - 'C' // 5aac #2 - 'D' // 5aad #3 - 'bB' // 5aae-5ab0 #1 - 'aC' // 5ab1-5ab2 #2 - 'A' // 5ab3 #0 - 'B' // 5ab4 #1 - 'A' // 5ab5 #0 - 'aB' // 5ab6-5ab7 #1 - 'C' // 5ab8 #2 - 'B' // 5ab9 #1 - 'eA' // 5aba-5abf #0 - 'B' // 5ac0 #1 - 'aA' // 5ac1-5ac2 #0 - 'B' // 5ac3 #1 - 'A' // 5ac4 #0 - 'D' // 5ac5 #3 - 'C' // 5ac6 #2 - 'B' // 5ac7 #1 - 'aA' // 5ac8-5ac9 #0 - 'B' // 5aca #1 - 'aA' // 5acb-5acc #0 - 'aB' // 5acd-5ace #1 - 'C' // 5acf #2 - 'F' // 5ad0 #5 - 'B' // 5ad1 #1 - 'D' // 5ad2 #3 - 'B' // 5ad3 #1 - 'D' // 5ad4 #3 - 'I' // 5ad5 #8 - 'aA' // 5ad6-5ad7 #0 - 'B' // 5ad8 #1 - 'I' // 5ad9 #8 - 'A' // 5ada #0 - 'I' // 5adb #8 - 'C' // 5adc #2 - 'I' // 5add #8 - 'aB' // 5ade-5adf #1 - 'aA' // 5ae0-5ae1 #0 - 'I' // 5ae2 #8 - 'A' // 5ae3 #0 - 'I' // 5ae4 #8 - 'aA' // 5ae5-5ae6 #0 - 'D' // 5ae7 #3 - 'B' // 5ae8 #1 - 'A' // 5ae9 #0 - 'C' // 5aea #2 - 'I' // 5aeb #8 - 'B' // 5aec #1 - 'I' // 5aed #8 - 'A' // 5aee #0 - 'K' // 5aef #10 - 'C' // 5af0 #2 - 'D' // 5af1 #3 - 'bB' // 5af2-5af4 #1 - 'C' // 5af5 #2 - 'A' // 5af6 #0 - 'bB' // 5af7-5af9 #1 - 'aA' // 5afa-5afb #0 - 'D' // 5afc #3 - 'A' // 5afd #0 - 'aB' // 5afe-5aff #1 - 'J' // 5b00 #9 - 'C' // 5b01 #2 - 'aB' // 5b02-5b03 #1 - 'D' // 5b04 #3 - 'I' // 5b05 #8 - 'D' // 5b06 #3 - 'B' // 5b07 #1 - 'aA' // 5b08-5b09 #0 - 'D' // 5b0a #3 - 'aA' // 5b0b-5b0c #0 - 'B' // 5b0d #1 - 'D' // 5b0e #3 - 'bB' // 5b0f-5b11 #1 - 'D' // 5b12 #3 - 'bB' // 5b13-5b15 #1 - 'A' // 5b16 #0 - 'C' // 5b17 #2 - 'D' // 5b18 #3 - 'A' // 5b19 #0 - 'B' // 5b1a #1 - 'A' // 5b1b #0 - 'D' // 5b1c #3 - 'C' // 5b1d #2 - 'bB' // 5b1e-5b20 #1 - 'C' // 5b21 #2 - 'F' // 5b22 #5 - 'aB' // 5b23-5b24 #1 - 'A' // 5b25 #0 - 'aB' // 5b26-5b27 #1 - 'I' // 5b28 #8 - 'D' // 5b29 #3 - 'A' // 5b2a #0 - 'B' // 5b2b #1 - 'C' // 5b2c #2 - 'A' // 5b2d #0 - 'aB' // 5b2e-5b2f #1 - 'A' // 5b30 #0 - 'D' // 5b31 #3 - 'A' // 5b32 #0 - 'D' // 5b33 #3 - 'A' // 5b34 #0 - 'D' // 5b35 #3 - 'F' // 5b36 #5 - 'D' // 5b37 #3 - 'C' // 5b38 #2 - 'bD' // 5b39-5b3b #3 - 'aB' // 5b3c-5b3d #1 - 'A' // 5b3e #0 - 'I' // 5b3f #8 - 'A' // 5b40 #0 - 'C' // 5b41 #2 - 'D' // 5b42 #3 - 'A' // 5b43 #0 - 'B' // 5b44 #1 - 'A' // 5b45 #0 - 'bB' // 5b46-5b48 #1 - 'D' // 5b49 #3 - 'B' // 5b4a #1 - 'C' // 5b4b #2 - 'A' // 5b4c #0 - 'bB' // 5b4d-5b4f #1 - 'aA' // 5b50-5b51 #0 - 'F' // 5b52 #5 - 'B' // 5b53 #1 - 'dA' // 5b54-5b58 #0 - 'D' // 5b59 #3 - 'cA' // 5b5a-5b5d #0 - 'F' // 5b5e #5 - 'A' // 5b5f #0 - 'D' // 5b60 #3 - 'K' // 5b61 #10 - 'B' // 5b62 #1 - 'cA' // 5b63-5b66 #0 - 'D' // 5b67 #3 - 'C' // 5b68 #2 - 'A' // 5b69 #0 - 'D' // 5b6a #3 - 'A' // 5b6b #0 - 'aB' // 5b6c-5b6d #1 - 'C' // 5b6e #2 - 'F' // 5b6f #5 - 'aA' // 5b70-5b71 #0 - 'B' // 5b72 #1 - 'C' // 5b73 #2 - 'B' // 5b74 #1 - 'aA' // 5b75-5b76 #0 - 'B' // 5b77 #1 - 'A' // 5b78 #0 - 'D' // 5b79 #3 - 'A' // 5b7a #0 - 'B' // 5b7b #1 - 'A' // 5b7c #0 - 'C' // 5b7d #2 - 'F' // 5b7e #5 - 'cA' // 5b7f-5b82 #0 - 'aC' // 5b83-5b84 #2 - 'A' // 5b85 #0 - 'F' // 5b86 #5 - 'bA' // 5b87-5b89 #0 - 'J' // 5b8a #9 - 'aA' // 5b8b-5b8c #0 - 'F' // 5b8d #5 - 'C' // 5b8e #2 - 'A' // 5b8f #0 - 'C' // 5b90 #2 - 'F' // 5b91 #5 - 'B' // 5b92 #1 - 'A' // 5b93 #0 - 'F' // 5b94 #5 - 'hA' // 5b95-5b9d #0 - 'B' // 5b9e #1 - 'A' // 5b9f #0 - 'aD' // 5ba0-5ba1 #3 - 'dA' // 5ba2-5ba6 #0 - 'B' // 5ba7 #1 - 'C' // 5ba8 #2 - 'F' // 5ba9 #5 - 'B' // 5baa #1 - 'D' // 5bab #3 - 'A' // 5bac #0 - 'C' // 5bad #2 - 'A' // 5bae #0 - 'F' // 5baf #5 - 'A' // 5bb0 #0 - 'aF' // 5bb1-5bb2 #5 - 'cA' // 5bb3-5bb6 #0 - 'C' // 5bb7 #2 - 'aA' // 5bb8-5bb9 #0 - 'F' // 5bba #5 - 'D' // 5bbb #3 - 'F' // 5bbc #5 - 'aD' // 5bbd-5bbe #3 - 'aA' // 5bbf-5bc0 #0 - 'C' // 5bc1 #2 - 'eA' // 5bc2-5bc7 #0 - 'D' // 5bc8 #3 - 'F' // 5bc9 #5 - 'aB' // 5bca-5bcb #1 - 'A' // 5bcc #0 - 'aC' // 5bcd-5bce #2 - 'F' // 5bcf #5 - 'A' // 5bd0 #0 - 'B' // 5bd1 #1 - 'bA' // 5bd2-5bd4 #0 - 'B' // 5bd5 #1 - 'bA' // 5bd6-5bd8 #0 - 'C' // 5bd9 #2 - 'F' // 5bda #5 - 'A' // 5bdb #0 - 'D' // 5bdc #3 - 'F' // 5bdd #5 - 'aA' // 5bde-5bdf #0 - 'C' // 5be0 #2 - 'aA' // 5be1-5be2 #0 - 'B' // 5be3 #1 - 'eA' // 5be4-5be9 #0 - 'B' // 5bea #1 - 'aA' // 5beb-5bec #0 - 'K' // 5bed #10 - 'bA' // 5bee-5bf0 #0 - 'C' // 5bf1 #2 - 'B' // 5bf2 #1 - 'C' // 5bf3 #2 - 'F' // 5bf4 #5 - 'aA' // 5bf5-5bf6 #0 - 'D' // 5bf7 #3 - 'A' // 5bf8 #0 - 'K' // 5bf9 #10 - 'A' // 5bfa #0 - 'aD' // 5bfb-5bfc #3 - 'aF' // 5bfd-5bfe #5 - 'A' // 5bff #0 - 'D' // 5c00 #3 - 'A' // 5c01 #0 - 'F' // 5c02 #5 - 'C' // 5c03 #2 - 'aA' // 5c04-5c05 #0 - 'J' // 5c06 #9 - 'hA' // 5c07-5c0f #0 - 'B' // 5c10 #1 - 'A' // 5c11 #0 - 'aC' // 5c12-5c13 #2 - 'A' // 5c14 #0 - 'B' // 5c15 #1 - 'A' // 5c16 #0 - 'F' // 5c17 #5 - 'D' // 5c18 #3 - 'J' // 5c19 #9 - 'C' // 5c1a #2 - 'D' // 5c1b #3 - 'B' // 5c1c #1 - 'D' // 5c1d #3 - 'C' // 5c1e #2 - 'aA' // 5c1f-5c20 #0 - 'D' // 5c21 #3 - 'bA' // 5c22-5c24 #0 - 'B' // 5c25 #1 - 'F' // 5c26 #5 - 'D' // 5c27 #3 - 'A' // 5c28 #0 - 'F' // 5c29 #5 - 'C' // 5c2a #2 - 'J' // 5c2b #9 - 'C' // 5c2c #2 - 'aF' // 5c2d-5c2e #5 - 'D' // 5c2f #3 - 'C' // 5c30 #2 - 'A' // 5c31 #0 - 'F' // 5c32 #5 - 'B' // 5c33 #1 - 'D' // 5c34 #3 - 'aF' // 5c35-5c36 #5 - 'B' // 5c37 #1 - 'dA' // 5c38-5c3c #0 - 'J' // 5c3d #9 - 'cA' // 5c3e-5c41 #0 - 'aD' // 5c42-5c43 #3 - 'B' // 5c44 #1 - 'aA' // 5c45-5c46 #0 - 'I' // 5c47 #8 - 'A' // 5c48 #0 - 'B' // 5c49 #1 - 'C' // 5c4a #2 - 'A' // 5c4b #0 - 'B' // 5c4c #1 - 'aA' // 5c4d-5c4e #0 - 'C' // 5c4f #2 - 'aA' // 5c50-5c51 #0 - 'D' // 5c52 #3 - 'C' // 5c53 #2 - 'B' // 5c54 #1 - 'A' // 5c55 #0 - 'B' // 5c56 #1 - 'D' // 5c57 #3 - 'B' // 5c58 #1 - 'C' // 5c59 #2 - 'F' // 5c5a #5 - 'J' // 5c5b #9 - 'C' // 5c5c #2 - 'B' // 5c5d #1 - 'C' // 5c5e #2 - 'F' // 5c5f #5 - 'A' // 5c60 #0 - 'F' // 5c61 #5 - 'A' // 5c62 #0 - 'C' // 5c63 #2 - 'aA' // 5c64-5c65 #0 - 'D' // 5c66 #3 - 'C' // 5c67 #2 - 'A' // 5c68 #0 - 'C' // 5c69 #2 - 'B' // 5c6a #1 - 'D' // 5c6b #3 - 'A' // 5c6c #0 - 'aC' // 5c6d-5c6e #2 - 'A' // 5c6f #0 - 'F' // 5c70 #5 - 'A' // 5c71 #0 - 'D' // 5c72 #3 - 'I' // 5c73 #8 - 'C' // 5c74 #2 - 'aF' // 5c75-5c76 #5 - 'D' // 5c77 #3 - 'B' // 5c78 #1 - 'aA' // 5c79-5c7a #0 - 'aC' // 5c7b-5c7c #2 - 'F' // 5c7d #5 - 'B' // 5c7e #1 - 'cD' // 5c7f-5c82 #3 - '13C' // 5c83 #340 - 'D' // 5c84 #3 - 'aB' // 5c85-5c86 #1 - 'F' // 5c87 #5 - 'A' // 5c88 #0 - 'B' // 5c89 #1 - 'A' // 5c8a #0 - 'B' // 5c8b #1 - 'A' // 5c8c #0 - 'B' // 5c8d #1 - 'D' // 5c8e #3 - 'cA' // 5c8f-5c92 #0 - 'B' // 5c93 #1 - 'A' // 5c94 #0 - 'B' // 5c95 #1 - 'bD' // 5c96-5c98 #3 - 'aB' // 5c99-5c9a #1 - 'D' // 5c9b #3 - 'B' // 5c9c #1 - 'A' // 5c9d #0 - 'B' // 5c9e #1 - 'aC' // 5c9f-5ca0 #2 - 'A' // 5ca1 #0 - 'C' // 5ca2 #2 - 'A' // 5ca3 #0 - 'B' // 5ca4 #1 - 'I' // 5ca5 #8 - 'gA' // 5ca6-5cad #0 - 'bB' // 5cae-5cb0 #1 - 'A' // 5cb1 #0 - 'F' // 5cb2 #5 - 'A' // 5cb3 #0 - 'F' // 5cb4 #5 - 'A' // 5cb5 #0 - 'C' // 5cb6 #2 - 'aA' // 5cb7-5cb8 #0 - 'D' // 5cb9 #3 - 'A' // 5cba #0 - 'aF' // 5cbb-5cbc #5 - 'D' // 5cbd #3 - 'J' // 5cbe #9 - 'D' // 5cbf #3 - 'K' // 5cc0 #10 - 'aB' // 5cc1-5cc2 #1 - 'aD' // 5cc3-5cc4 #3 - 'F' // 5cc5 #5 - 'B' // 5cc6 #1 - 'C' // 5cc7 #2 - 'B' // 5cc8 #1 - 'C' // 5cc9 #2 - 'B' // 5cca #1 - 'A' // 5ccb #0 - 'B' // 5ccc #1 - 'D' // 5ccd #3 - 'aB' // 5cce-5ccf #1 - 'C' // 5cd0 #2 - 'B' // 5cd1 #1 - 'A' // 5cd2 #0 - 'cB' // 5cd3-5cd6 #1 - 'C' // 5cd7 #2 - 'B' // 5cd8 #1 - 'A' // 5cd9 #0 - 'aB' // 5cda-5cdb #1 - 'D' // 5cdc #3 - 'F' // 5cdd #5 - 'aB' // 5cde-5cdf #1 - 'J' // 5ce0 #9 - 'F' // 5ce1 #5 - 'bD' // 5ce2-5ce4 #3 - 'B' // 5ce5 #1 - 'F' // 5ce6 #5 - 'D' // 5ce7 #3 - 'aA' // 5ce8-5ce9 #0 - 'C' // 5cea #2 - 'D' // 5ceb #3 - 'B' // 5cec #1 - 'A' // 5ced #0 - 'C' // 5cee #2 - 'bA' // 5cef-5cf1 #0 - 'F' // 5cf2 #5 - 'D' // 5cf3 #3 - 'A' // 5cf4 #0 - 'C' // 5cf5 #2 - 'A' // 5cf6 #0 - 'bB' // 5cf7-5cf9 #1 - 'F' // 5cfa #5 - 'A' // 5cfb #0 - 'B' // 5cfc #1 - 'A' // 5cfd #0 - 'D' // 5cfe #3 - 'aB' // 5cff-5d00 #1 - 'C' // 5d01 #2 - 'cD' // 5d02-5d05 #3 - 'aA' // 5d06-5d07 #0 - 'bD' // 5d08-5d0a #3 - 'C' // 5d0b #2 - 'B' // 5d0c #1 - 'aA' // 5d0d-5d0e #0 - 'B' // 5d0f #1 - 'aA' // 5d10-5d11 #0 - 'C' // 5d12 #2 - 'D' // 5d13 #3 - 'eA' // 5d14-5d19 #0 - 'C' // 5d1a #2 - 'A' // 5d1b #0 - 'D' // 5d1c #3 - 'C' // 5d1d #2 - 'B' // 5d1e #1 - 'A' // 5d1f #0 - 'C' // 5d20 #2 - 'D' // 5d21 #3 - 'A' // 5d22 #0 - 'C' // 5d23 #2 - 'A' // 5d24 #0 - 'B' // 5d25 #1 - 'aA' // 5d26-5d27 #0 - 'B' // 5d28 #1 - 'A' // 5d29 #0 - 'D' // 5d2a #3 - 'F' // 5d2b #5 - 'B' // 5d2c #1 - 'D' // 5d2d #3 - 'bB' // 5d2e-5d30 #1 - 'C' // 5d31 #2 - 'aB' // 5d32-5d33 #1 - 'A' // 5d34 #0 - 'cB' // 5d35-5d38 #1 - 'C' // 5d39 #2 - 'B' // 5d3a #1 - 'D' // 5d3b #3 - 'B' // 5d3c #1 - 'A' // 5d3d #0 - 'B' // 5d3e #1 - 'C' // 5d3f #2 - 'B' // 5d40 #1 - 'I' // 5d41 #8 - 'A' // 5d42 #0 - 'C' // 5d43 #2 - 'K' // 5d44 #10 - 'B' // 5d45 #1 - 'bC' // 5d46-5d48 #2 - 'B' // 5d49 #1 - 'C' // 5d4a #2 - 'aA' // 5d4b-5d4c #0 - 'D' // 5d4d #3 - 'A' // 5d4e #0 - 'D' // 5d4f #3 - 'A' // 5d50 #0 - 'aC' // 5d51-5d52 #2 - 'J' // 5d53 #9 - 'K' // 5d54 #10 - 'C' // 5d55 #2 - 'aB' // 5d56-5d57 #1 - 'D' // 5d58 #3 - 'C' // 5d59 #2 - 'D' // 5d5a #3 - 'B' // 5d5b #1 - 'F' // 5d5c #5 - 'D' // 5d5d #3 - 'B' // 5d5e #1 - 'bF' // 5d5f-5d61 #5 - 'C' // 5d62 #2 - 'B' // 5d63 #1 - 'F' // 5d64 #5 - 'B' // 5d65 #1 - 'D' // 5d66 #3 - 'aB' // 5d67-5d68 #1 - 'A' // 5d69 #0 - 'F' // 5d6a #5 - 'B' // 5d6b #1 - 'A' // 5d6c #0 - 'F' // 5d6d #5 - 'D' // 5d6e #3 - 'A' // 5d6f #0 - 'C' // 5d70 #2 - 'I' // 5d71 #8 - 'B' // 5d72 #1 - 'F' // 5d73 #5 - 'B' // 5d74 #1 - 'D' // 5d75 #3 - 'F' // 5d76 #5 - 'aB' // 5d77-5d78 #1 - 'aC' // 5d79-5d7a #2 - 'bB' // 5d7b-5d7d #1 - 'aC' // 5d7e-5d7f #2 - 'B' // 5d80 #1 - 'aA' // 5d81-5d82 #0 - 'F' // 5d83 #5 - 'A' // 5d84 #0 - 'B' // 5d85 #1 - 'I' // 5d86 #8 - 'A' // 5d87 #0 - 'C' // 5d88 #2 - 'B' // 5d89 #1 - 'C' // 5d8a #2 - 'A' // 5d8b #0 - 'F' // 5d8c #5 - 'aB' // 5d8d-5d8e #1 - 'D' // 5d8f #3 - 'F' // 5d90 #5 - 'D' // 5d91 #3 - 'A' // 5d92 #0 - 'C' // 5d93 #2 - 'aA' // 5d94-5d95 #0 - 'D' // 5d96 #3 - 'C' // 5d97 #2 - 'D' // 5d98 #3 - 'A' // 5d99 #0 - 'B' // 5d9a #1 - 'F' // 5d9b #5 - 'B' // 5d9c #1 - 'A' // 5d9d #0 - 'B' // 5d9e #1 - 'C' // 5d9f #2 - 'A' // 5da0 #0 - 'B' // 5da1 #1 - 'A' // 5da2 #0 - 'D' // 5da3 #3 - 'C' // 5da4 #2 - 'aD' // 5da5-5da6 #3 - 'A' // 5da7 #0 - 'aB' // 5da8-5da9 #1 - 'I' // 5daa #8 - 'A' // 5dab #0 - 'C' // 5dac #2 - 'B' // 5dad #1 - 'A' // 5dae #0 - 'B' // 5daf #1 - 'A' // 5db0 #0 - 'B' // 5db1 #1 - 'C' // 5db2 #2 - 'D' // 5db3 #3 - 'C' // 5db4 #2 - 'aB' // 5db5-5db6 #1 - 'aA' // 5db7-5db8 #0 - 'C' // 5db9 #2 - 'A' // 5dba #0 - 'D' // 5dbb #3 - 'aA' // 5dbc-5dbd #0 - 'K' // 5dbe #10 - 'D' // 5dbf #3 - 'bB' // 5dc0-5dc2 #1 - 'C' // 5dc3 #2 - 'aD' // 5dc4-5dc5 #3 - 'B' // 5dc6 #1 - 'C' // 5dc7 #2 - 'D' // 5dc8 #3 - 'A' // 5dc9 #0 - 'D' // 5dca #3 - 'A' // 5dcb #0 - 'F' // 5dcc #5 - 'A' // 5dcd #0 - 'F' // 5dce #5 - 'B' // 5dcf #1 - 'F' // 5dd0 #5 - 'aA' // 5dd1-5dd2 #0 - 'J' // 5dd3 #9 - 'aB' // 5dd4-5dd5 #1 - 'A' // 5dd6 #0 - 'aC' // 5dd7-5dd8 #2 - 'F' // 5dd9 #5 - 'K' // 5dda #10 - 'A' // 5ddb #0 - 'D' // 5ddc #3 - 'aA' // 5ddd-5dde #0 - 'B' // 5ddf #1 - 'bA' // 5de0-5de2 #0 - 'aF' // 5de3-5de4 #5 - 'cA' // 5de5-5de8 #0 - 'F' // 5de9 #5 - 'D' // 5dea #3 - 'A' // 5deb #0 - 'aD' // 5dec-5ded #3 - 'A' // 5dee #0 - 'D' // 5def #3 - 'B' // 5df0 #1 - 'dA' // 5df1-5df5 #0 - 'D' // 5df6 #3 - 'A' // 5df7 #0 - 'J' // 5df8 #9 - 'A' // 5df9 #0 - 'D' // 5dfa #3 - 'J' // 5dfb #9 - 'D' // 5dfc #3 - 'aA' // 5dfd-5dfe #0 - 'C' // 5dff #2 - 'F' // 5e00 #5 - 'D' // 5e01 #3 - 'aA' // 5e02-5e03 #0 - 'B' // 5e04 #1 - 'D' // 5e05 #3 - 'A' // 5e06 #0 - 'F' // 5e07 #5 - 'D' // 5e08 #3 - 'I' // 5e09 #8 - 'B' // 5e0a #1 - 'C' // 5e0b #2 - 'A' // 5e0c #0 - 'F' // 5e0d #5 - 'B' // 5e0e #1 - 'aD' // 5e0f-5e10 #3 - 'A' // 5e11 #0 - 'C' // 5e12 #2 - 'D' // 5e13 #3 - 'C' // 5e14 #2 - 'aA' // 5e15-5e16 #0 - 'B' // 5e17 #1 - 'C' // 5e18 #2 - 'bA' // 5e19-5e1b #0 - 'D' // 5e1c #3 - 'A' // 5e1d #0 - 'D' // 5e1e #3 - 'C' // 5e1f #2 - 'A' // 5e20 #0 - 'cB' // 5e21-5e24 #1 - 'A' // 5e25 #0 - 'aD' // 5e26-5e27 #3 - 'A' // 5e28 #0 - 'B' // 5e29 #1 - 'D' // 5e2a #3 - 'A' // 5e2b #0 - 'D' // 5e2c #3 - 'A' // 5e2d #0 - 'C' // 5e2e #2 - 'aF' // 5e2f-5e30 #5 - 'D' // 5e31 #3 - 'F' // 5e32 #5 - 'A' // 5e33 #0 - 'B' // 5e34 #1 - 'F' // 5e35 #5 - 'bA' // 5e36-5e38 #0 - 'cD' // 5e39-5e3c #3 - 'A' // 5e3d #0 - 'C' // 5e3e #2 - 'K' // 5e3f #10 - 'A' // 5e40 #0 - 'aB' // 5e41-5e42 #1 - 'bA' // 5e43-5e45 #0 - 'D' // 5e46 #3 - 'J' // 5e47 #9 - 'B' // 5e48 #1 - 'F' // 5e49 #5 - 'B' // 5e4a #1 - 'C' // 5e4b #2 - 'A' // 5e4c #0 - 'B' // 5e4d #1 - 'A' // 5e4e #0 - 'B' // 5e4f #1 - 'aF' // 5e50-5e51 #5 - 'D' // 5e52 #3 - 'B' // 5e53 #1 - 'aA' // 5e54-5e55 #0 - 'F' // 5e56 #5 - 'C' // 5e57 #2 - 'A' // 5e58 #0 - 'B' // 5e59 #1 - 'D' // 5e5a #3 - 'aC' // 5e5b-5e5c #2 - 'B' // 5e5d #1 - 'aA' // 5e5e-5e5f #0 - 'B' // 5e60 #1 - 'bA' // 5e61-5e63 #0 - 'F' // 5e64 #5 - 'D' // 5e65 #3 - 'aB' // 5e66-5e67 #1 - 'A' // 5e68 #0 - 'B' // 5e69 #1 - 'bA' // 5e6a-5e6c #0 - 'aC' // 5e6d-5e6e #2 - 'B' // 5e6f #1 - 'A' // 5e70 #0 - 'K' // 5e71 #10 - 'bA' // 5e72-5e74 #0 - 'C' // 5e75 #2 - 'A' // 5e76 #0 - 'J' // 5e77 #9 - 'hA' // 5e78-5e80 #0 - 'F' // 5e81 #5 - 'B' // 5e82 #1 - 'aA' // 5e83-5e84 #0 - 'D' // 5e85 #3 - 'B' // 5e86 #1 - 'A' // 5e87 #0 - 'aB' // 5e88-5e89 #1 - 'aA' // 5e8a-5e8b #0 - 'aB' // 5e8c-5e8d #1 - 'F' // 5e8e #5 - 'A' // 5e8f #0 - 'aD' // 5e90-5e91 #3 - 'B' // 5e92 #1 - 'aD' // 5e93-5e94 #3 - 'bA' // 5e95-5e97 #0 - 'D' // 5e98 #3 - 'C' // 5e99 #2 - 'A' // 5e9a #0 - 'B' // 5e9b #1 - 'A' // 5e9c #0 - 'bD' // 5e9d-5e9f #3 - 'A' // 5ea0 #0 - 'D' // 5ea1 #3 - 'C' // 5ea2 #2 - 'B' // 5ea3 #1 - 'C' // 5ea4 #2 - 'cA' // 5ea5-5ea8 #0 - 'D' // 5ea9 #3 - 'C' // 5eaa #2 - 'A' // 5eab #0 - 'C' // 5eac #2 - 'A' // 5ead #0 - 'B' // 5eae #1 - 'D' // 5eaf #3 - 'B' // 5eb0 #1 - 'C' // 5eb1 #2 - 'B' // 5eb2 #1 - 'A' // 5eb3 #0 - 'B' // 5eb4 #1 - 'cA' // 5eb5-5eb8 #0 - 'C' // 5eb9 #2 - 'bD' // 5eba-5ebc #3 - 'aA' // 5ebd-5ebe #0 - 'F' // 5ebf #5 - 'D' // 5ec0 #3 - 'aA' // 5ec1-5ec2 #0 - 'F' // 5ec3 #5 - 'aB' // 5ec4-5ec5 #1 - 'C' // 5ec6 #2 - 'B' // 5ec7 #1 - 'cA' // 5ec8-5ecb #0 - 'C' // 5ecc #2 - 'B' // 5ecd #1 - 'C' // 5ece #2 - 'J' // 5ecf #9 - 'aA' // 5ed0-5ed1 #0 - 'C' // 5ed2 #2 - 'A' // 5ed3 #0 - 'C' // 5ed4 #2 - 'aA' // 5ed5-5ed6 #0 - 'aB' // 5ed7-5ed8 #1 - 'bA' // 5ed9-5edb #0 - 'C' // 5edc #2 - 'fA' // 5edd-5ee3 #0 - 'D' // 5ee4 #3 - 'A' // 5ee5 #0 - 'B' // 5ee6 #1 - 'I' // 5ee7 #8 - 'aA' // 5ee8-5ee9 #0 - 'D' // 5eea #3 - 'F' // 5eeb #5 - 'A' // 5eec #0 - 'D' // 5eed #3 - 'aB' // 5eee-5eef #1 - 'F' // 5ef0 #5 - 'A' // 5ef1 #0 - 'B' // 5ef2 #1 - 'aA' // 5ef3-5ef4 #0 - 'D' // 5ef5 #3 - 'aA' // 5ef6-5ef7 #0 - 'aC' // 5ef8-5ef9 #2 - 'aA' // 5efa-5efb #0 - 'C' // 5efc #2 - 'F' // 5efd #5 - 'aA' // 5efe-5eff #0 - 'F' // 5f00 #5 - 'A' // 5f01 #0 - 'C' // 5f02 #2 - 'J' // 5f03 #9 - 'A' // 5f04 #0 - 'B' // 5f05 #1 - 'F' // 5f06 #5 - 'aA' // 5f07-5f08 #0 - 'F' // 5f09 #5 - 'aA' // 5f0a-5f0b #0 - 'bC' // 5f0c-5f0e #2 - 'A' // 5f0f #0 - 'F' // 5f10 #5 - 'J' // 5f11 #9 - 'I' // 5f12 #8 - 'bA' // 5f13-5f15 #0 - 'F' // 5f16 #5 - 'aA' // 5f17-5f18 #0 - 'F' // 5f19 #5 - 'B' // 5f1a #1 - 'A' // 5f1b #0 - 'F' // 5f1c #5 - 'C' // 5f1d #2 - 'F' // 5f1e #5 - 'A' // 5f1f #0 - 'D' // 5f20 #3 - 'F' // 5f21 #5 - 'A' // 5f22 #0 - 'aC' // 5f23-5f24 #2 - 'bA' // 5f25-5f27 #0 - 'C' // 5f28 #2 - 'A' // 5f29 #0 - 'D' // 5f2a #3 - 'aF' // 5f2b-5f2c #5 - 'A' // 5f2d #0 - 'C' // 5f2e #2 - 'F' // 5f2f #5 - 'C' // 5f30 #2 - 'A' // 5f31 #0 - 'D' // 5f32 #3 - 'B' // 5f33 #1 - 'J' // 5f34 #9 - 'A' // 5f35 #0 - 'C' // 5f36 #2 - 'A' // 5f37 #0 - 'C' // 5f38 #2 - 'D' // 5f39 #3 - 'A' // 5f3a #0 - 'C' // 5f3b #2 - 'A' // 5f3c #0 - 'F' // 5f3d #5 - 'J' // 5f3e #9 - 'F' // 5f3f #5 - 'A' // 5f40 #0 - 'F' // 5f41 #5 - 'D' // 5f42 #3 - 'B' // 5f43 #1 - 'aC' // 5f44-5f45 #2 - 'I' // 5f46 #8 - 'F' // 5f47 #5 - 'A' // 5f48 #0 - 'B' // 5f49 #1 - 'A' // 5f4a #0 - 'B' // 5f4b #1 - 'A' // 5f4c #0 - 'C' // 5f4d #2 - 'A' // 5f4e #0 - 'B' // 5f4f #1 - 'aA' // 5f50-5f51 #0 - 'D' // 5f52 #3 - 'J' // 5f53 #9 - 'A' // 5f54 #0 - 'D' // 5f55 #3 - 'cA' // 5f56-5f59 #0 - 'D' // 5f5a #3 - 'J' // 5f5b #9 - 'C' // 5f5c #2 - 'A' // 5f5d #0 - 'B' // 5f5e #1 - 'D' // 5f5f #3 - 'F' // 5f60 #5 - 'aA' // 5f61-5f62 #0 - 'C' // 5f63 #2 - 'aA' // 5f64-5f65 #0 - 'J' // 5f66 #9 - 'A' // 5f67 #0 - 'D' // 5f68 #3 - 'dA' // 5f69-5f6d #0 - 'D' // 5f6e #3 - 'C' // 5f6f #2 - 'aA' // 5f70-5f71 #0 - 'C' // 5f72 #2 - 'A' // 5f73 #0 - 'C' // 5f74 #2 - 'F' // 5f75 #5 - 'B' // 5f76 #1 - 'A' // 5f77 #0 - 'C' // 5f78 #2 - 'A' // 5f79 #0 - 'F' // 5f7a #5 - 'B' // 5f7b #1 - 'A' // 5f7c #0 - 'aC' // 5f7d-5f7e #2 - 'cA' // 5f7f-5f82 #0 - 'C' // 5f83 #2 - 'F' // 5f84 #5 - 'A' // 5f85 #0 - 'B' // 5f86 #1 - 'eA' // 5f87-5f8c #0 - 'F' // 5f8d #5 - 'D' // 5f8e #3 - 'F' // 5f8f #5 - 'bA' // 5f90-5f92 #0 - 'F' // 5f93 #5 - 'aD' // 5f94-5f95 #3 - 'C' // 5f96 #2 - 'bA' // 5f97-5f99 #0 - 'D' // 5f9a #3 - 'B' // 5f9b #1 - 'A' // 5f9c #0 - 'F' // 5f9d #5 - 'A' // 5f9e #0 - 'B' // 5f9f #1 - 'aA' // 5fa0-5fa1 #0 - 'F' // 5fa2 #5 - 'K' // 5fa3 #10 - 'C' // 5fa4 #2 - 'aB' // 5fa5-5fa6 #1 - 'cA' // 5fa7-5faa #0 - 'C' // 5fab #2 - 'cA' // 5fac-5faf #0 - 'F' // 5fb0 #5 - 'C' // 5fb1 #2 - 'B' // 5fb2 #1 - 'J' // 5fb3 #9 - 'F' // 5fb4 #5 - 'A' // 5fb5 #0 - 'B' // 5fb6 #1 - 'A' // 5fb7 #0 - 'F' // 5fb8 #5 - 'A' // 5fb9 #0 - 'aB' // 5fba-5fbb #1 - 'aA' // 5fbc-5fbd #0 - 'dB' // 5fbe-5fc2 #1 - 'bA' // 5fc3-5fc5 #0 - 'D' // 5fc6 #3 - 'F' // 5fc7 #5 - 'J' // 5fc8 #9 - 'A' // 5fc9 #0 - 'D' // 5fca #3 - 'F' // 5fcb #5 - 'aA' // 5fcc-5fcd #0 - 'K' // 5fce #10 - 'B' // 5fcf #1 - 'A' // 5fd0 #0 - 'C' // 5fd1 #2 - 'A' // 5fd2 #0 - 'J' // 5fd3 #9 - 'C' // 5fd4 #2 - 'I' // 5fd5 #8 - 'cA' // 5fd6-5fd9 #0 - 'D' // 5fda #3 - 'B' // 5fdb #1 - 'J' // 5fdc #9 - 'aA' // 5fdd-5fde #0 - 'I' // 5fdf #8 - 'aA' // 5fe0-5fe1 #0 - 'F' // 5fe2 #5 - 'B' // 5fe3 #1 - 'A' // 5fe4 #0 - 'B' // 5fe5 #1 - 'aD' // 5fe6-5fe7 #3 - 'A' // 5fe8 #0 - 'F' // 5fe9 #5 - 'C' // 5fea #2 - 'A' // 5feb #0 - 'F' // 5fec #5 - 'bA' // 5fed-5fef #0 - 'F' // 5ff0 #5 - 'A' // 5ff1 #0 - 'F' // 5ff2 #5 - 'C' // 5ff3 #2 - 'B' // 5ff4 #1 - 'A' // 5ff5 #0 - 'F' // 5ff6 #5 - 'B' // 5ff7 #1 - 'A' // 5ff8 #0 - 'D' // 5ff9 #3 - 'C' // 5ffa #2 - 'A' // 5ffb #0 - 'J' // 5ffc #9 - 'A' // 5ffd #0 - 'D' // 5ffe #3 - 'A' // 5fff #0 - 'B' // 6000 #1 - 'eD' // 6001-6006 #3 - 'F' // 6007 #5 - 'D' // 6008 #3 - 'B' // 6009 #1 - 'A' // 600a #0 - 'aB' // 600b-600c #1 - 'A' // 600d #0 - 'C' // 600e #2 - 'A' // 600f #0 - 'C' // 6010 #2 - 'B' // 6011 #1 - 'A' // 6012 #0 - 'C' // 6013 #2 - 'cA' // 6014-6017 #0 - 'F' // 6018 #5 - 'A' // 6019 #0 - 'C' // 601a #2 - 'bA' // 601b-601d #0 - 'B' // 601e #1 - 'F' // 601f #5 - 'aA' // 6020-6021 #0 - 'C' // 6022 #2 - 'B' // 6023 #1 - 'C' // 6024 #2 - 'eA' // 6025-602a #0 - 'C' // 602b #2 - 'B' // 602c #1 - 'C' // 602d #2 - 'B' // 602e #1 - 'A' // 602f #0 - 'K' // 6030 #10 - 'C' // 6031 #2 - 'B' // 6032 #1 - 'A' // 6033 #0 - 'B' // 6034 #1 - 'C' // 6035 #2 - 'D' // 6036 #3 - 'B' // 6037 #1 - 'D' // 6038 #3 - 'B' // 6039 #1 - 'F' // 603a #5 - 'B' // 603b #1 - 'cD' // 603c-603f #3 - 'C' // 6040 #2 - 'bA' // 6041-6043 #0 - 'aB' // 6044-6045 #1 - 'aA' // 6046-6047 #0 - 'J' // 6048 #9 - 'C' // 6049 #2 - 'aA' // 604a-604b #0 - 'C' // 604c #2 - 'A' // 604d #0 - 'aD' // 604e-604f #3 - 'A' // 6050 #0 - 'F' // 6051 #5 - 'A' // 6052 #0 - 'B' // 6053 #1 - 'C' // 6054 #2 - 'A' // 6055 #0 - 'aF' // 6056-6057 #5 - 'B' // 6058 #1 - 'aA' // 6059-605a #0 - 'B' // 605b #1 - 'D' // 605c #3 - 'A' // 605d #0 - 'B' // 605e #1 - 'A' // 605f #0 - 'J' // 6060 #9 - 'F' // 6061 #5 - 'cA' // 6062-6065 #0 - 'B' // 6066 #1 - 'C' // 6067 #2 - 'eA' // 6068-606d #0 - 'B' // 606e #1 - 'aA' // 606f-6070 #0 - 'F' // 6071 #5 - 'B' // 6072 #1 - 'aD' // 6073-6074 #3 - 'A' // 6075 #0 - 'D' // 6076 #3 - 'C' // 6077 #2 - 'eD' // 6078-607d #3 - 'aC' // 607e-607f #2 - 'B' // 6080 #1 - 'A' // 6081 #0 - 'F' // 6082 #5 - 'cA' // 6083-6086 #0 - 'B' // 6087 #1 - 'C' // 6088 #2 - 'aA' // 6089-608a #0 - 'J' // 608b #9 - 'aA' // 608c-608d #0 - 'C' // 608e #2 - 'K' // 608f #10 - 'B' // 6090 #1 - 'F' // 6091 #5 - 'A' // 6092 #0 - 'F' // 6093 #5 - 'cA' // 6094-6097 #0 - 'F' // 6098 #5 - 'D' // 6099 #3 - 'aA' // 609a-609b #0 - 'B' // 609c #1 - 'aC' // 609d-609e #2 - 'aA' // 609f-60a0 #0 - 'D' // 60a1 #3 - 'bA' // 60a2-60a4 #0 - 'F' // 60a5 #5 - 'C' // 60a6 #2 - 'A' // 60a7 #0 - 'C' // 60a8 #2 - 'F' // 60a9 #5 - 'J' // 60aa #9 - 'dD' // 60ab-60af #3 - 'fA' // 60b0-60b6 #0 - 'C' // 60b7 #2 - 'A' // 60b8 #0 - 'aB' // 60b9-60ba #1 - 'cA' // 60bb-60be #0 - 'bB' // 60bf-60c1 #1 - 'F' // 60c2 #5 - 'B' // 60c3 #1 - 'cA' // 60c4-60c7 #0 - 'C' // 60c8 #2 - 'A' // 60c9 #0 - 'C' // 60ca #2 - 'A' // 60cb #0 - 'aB' // 60cc-60cd #1 - 'C' // 60ce #2 - 'A' // 60cf #0 - 'D' // 60d0 #3 - 'A' // 60d1 #0 - 'D' // 60d2 #3 - 'A' // 60d3 #0 - 'C' // 60d4 #2 - 'A' // 60d5 #0 - 'D' // 60d6 #3 - 'I' // 60d7 #8 - 'jA' // 60d8-60e2 #0 - 'C' // 60e3 #2 - 'B' // 60e4 #1 - 'F' // 60e5 #5 - 'B' // 60e6 #1 - 'aC' // 60e7-60e8 #2 - 'B' // 60e9 #1 - 'cD' // 60ea-60ed #3 - 'F' // 60ee #5 - 'D' // 60ef #3 - 'dA' // 60f0-60f4 #0 - 'C' // 60f5 #2 - 'fA' // 60f6-60fc #0 - 'C' // 60fd #2 - 'aB' // 60fe-60ff #1 - 'aA' // 6100-6101 #0 - 'F' // 6102 #5 - 'A' // 6103 #0 - 'aB' // 6104-6105 #1 - 'A' // 6106 #0 - 'C' // 6107 #2 - 'aA' // 6108-6109 #0 - 'C' // 610a #2 - 'B' // 610b #1 - 'C' // 610c #2 - 'bA' // 610d-610f #0 - 'C' // 6110 #2 - 'F' // 6111 #5 - 'aC' // 6112-6113 #2 - 'aA' // 6114-6115 #0 - 'C' // 6116 #2 - 'F' // 6117 #5 - 'B' // 6118 #1 - 'C' // 6119 #2 - 'bA' // 611a-611c #0 - 'B' // 611d #1 - 'J' // 611e #9 - 'A' // 611f #0 - 'C' // 6120 #2 - 'F' // 6121 #5 - 'A' // 6122 #0 - 'B' // 6123 #1 - 'bD' // 6124-6126 #3 - 'aA' // 6127-6128 #0 - 'B' // 6129 #1 - 'F' // 612a #5 - 'aA' // 612b-612c #0 - 'K' // 612d #10 - 'aB' // 612e-612f #1 - 'A' // 6130 #0 - 'F' // 6131 #5 - 'B' // 6132 #1 - 'D' // 6133 #3 - 'A' // 6134 #0 - 'F' // 6135 #5 - 'C' // 6136 #2 - 'A' // 6137 #0 - 'D' // 6138 #3 - 'aF' // 6139-613a #5 - 'B' // 613b #1 - 'J' // 613c #9 - 'C' // 613d #2 - 'aA' // 613e-613f #0 - 'B' // 6140 #1 - 'C' // 6141 #2 - 'A' // 6142 #0 - 'D' // 6143 #3 - 'A' // 6144 #0 - 'C' // 6145 #2 - 'bA' // 6146-6148 #0 - 'C' // 6149 #2 - 'cA' // 614a-614d #0 - 'C' // 614e #2 - 'I' // 614f #8 - 'B' // 6150 #1 - 'D' // 6151 #3 - 'I' // 6152 #8 - 'A' // 6153 #0 - 'I' // 6154 #8 - 'A' // 6155 #0 - 'B' // 6156 #1 - 'D' // 6157 #3 - 'bA' // 6158-615a #0 - 'B' // 615b #1 - 'I' // 615c #8 - 'A' // 615d #0 - 'C' // 615e #2 - 'aA' // 615f-6160 #0 - 'I' // 6161 #8 - 'bA' // 6162-6164 #0 - 'C' // 6165 #2 - 'B' // 6166 #1 - 'aA' // 6167-6168 #0 - 'D' // 6169 #3 - 'I' // 616a #8 - 'A' // 616b #0 - 'C' // 616c #2 - 'D' // 616d #3 - 'A' // 616e #0 - 'C' // 616f #2 - 'aA' // 6170-6171 #0 - 'C' // 6172 #2 - 'dA' // 6173-6177 #0 - 'F' // 6178 #5 - 'B' // 6179 #1 - 'I' // 617a #8 - 'F' // 617b #5 - 'bA' // 617c-617e #0 - 'F' // 617f #5 - 'C' // 6180 #2 - 'bA' // 6181-6183 #0 - 'F' // 6184 #5 - 'aD' // 6185-6186 #3 - 'C' // 6187 #2 - 'D' // 6188 #3 - 'B' // 6189 #1 - 'A' // 618a #0 - 'C' // 618b #2 - 'B' // 618c #1 - 'aA' // 618d-618e #0 - 'D' // 618f #3 - 'dA' // 6190-6194 #0 - 'B' // 6195 #1 - 'A' // 6196 #0 - 'F' // 6197 #5 - 'bA' // 6198-619a #0 - 'B' // 619b #1 - 'aC' // 619c-619d #2 - 'D' // 619e #3 - 'C' // 619f #2 - 'F' // 61a0 #5 - 'aB' // 61a1-61a2 #1 - 'D' // 61a3 #3 - 'A' // 61a4 #0 - 'F' // 61a5 #5 - 'D' // 61a6 #3 - 'bA' // 61a7-61a9 #0 - 'C' // 61aa #2 - 'aA' // 61ab-61ac #0 - 'C' // 61ad #2 - 'A' // 61ae #0 - 'I' // 61af #8 - 'aB' // 61b0-61b1 #1 - 'A' // 61b2 #0 - 'bB' // 61b3-61b5 #1 - 'A' // 61b6 #0 - 'B' // 61b7 #1 - 'A' // 61b8 #0 - 'C' // 61b9 #2 - 'A' // 61ba #0 - 'K' // 61bb #10 - 'A' // 61bc #0 - 'K' // 61bd #10 - 'A' // 61be #0 - 'B' // 61bf #1 - 'bC' // 61c0-61c2 #2 - 'A' // 61c3 #0 - 'D' // 61c4 #3 - 'B' // 61c5 #1 - 'fA' // 61c6-61cc #0 - 'C' // 61cd #2 - 'F' // 61ce #5 - 'A' // 61cf #0 - 'C' // 61d0 #2 - 'aD' // 61d1-61d2 #3 - 'B' // 61d3 #1 - 'D' // 61d4 #3 - 'J' // 61d5 #9 - 'B' // 61d6 #1 - 'K' // 61d7 #10 - 'B' // 61d8 #1 - 'D' // 61d9 #3 - 'B' // 61da #1 - 'D' // 61db #3 - 'aF' // 61dc-61dd #5 - 'aA' // 61de-61df #0 - 'B' // 61e0 #1 - 'F' // 61e1 #5 - 'C' // 61e2 #2 - 'A' // 61e3 #0 - 'B' // 61e4 #1 - 'C' // 61e5 #2 - 'A' // 61e6 #0 - 'bC' // 61e7-61e9 #2 - 'aB' // 61ea-61eb #1 - 'F' // 61ec #5 - 'C' // 61ed #2 - 'B' // 61ee #1 - 'F' // 61ef #5 - 'aB' // 61f0-61f1 #1 - 'A' // 61f2 #0 - 'D' // 61f3 #3 - 'F' // 61f4 #5 - 'C' // 61f5 #2 - 'bA' // 61f6-61f8 #0 - 'B' // 61f9 #1 - 'A' // 61fa #0 - 'B' // 61fb #1 - 'dA' // 61fc-6200 #0 - 'C' // 6201 #2 - 'D' // 6202 #3 - 'aC' // 6203-6204 #2 - 'aD' // 6205-6206 #3 - 'aA' // 6207-6208 #0 - 'C' // 6209 #2 - 'A' // 620a #0 - 'D' // 620b #3 - 'bA' // 620c-620e #0 - 'D' // 620f #3 - 'bA' // 6210-6212 #0 - 'F' // 6213 #5 - 'bA' // 6214-6216 #0 - 'D' // 6217 #3 - 'K' // 6218 #10 - 'B' // 6219 #1 - 'A' // 621a #0 - 'C' // 621b #2 - 'aF' // 621c-621d #5 - 'J' // 621e #9 - 'A' // 621f #0 - 'C' // 6220 #2 - 'aA' // 6221-6222 #0 - 'C' // 6223 #2 - 'aB' // 6224-6225 #1 - 'J' // 6226 #9 - 'A' // 6227 #0 - 'D' // 6228 #3 - 'aA' // 6229-622a #0 - 'C' // 622b #2 - 'B' // 622c #1 - 'I' // 622d #8 - 'A' // 622e #0 - 'F' // 622f #5 - 'A' // 6230 #0 - 'J' // 6231 #9 - 'bA' // 6232-6234 #0 - 'K' // 6235 #10 - 'A' // 6236 #0 - 'B' // 6237 #1 - 'F' // 6238 #5 - 'A' // 6239 #0 - 'B' // 623a #1 - 'F' // 623b #5 - 'D' // 623c #3 - 'C' // 623d #2 - 'cA' // 623e-6241 #0 - 'C' // 6242 #2 - 'A' // 6243 #0 - 'F' // 6244 #5 - 'D' // 6245 #3 - 'C' // 6246 #2 - 'bA' // 6247-6249 #0 - 'I' // 624a #8 - 'cA' // 624b-624e #0 - 'D' // 624f #3 - 'C' // 6250 #2 - 'bA' // 6251-6253 #0 - 'C' // 6254 #2 - 'aF' // 6255-6256 #5 - 'K' // 6257 #10 - 'A' // 6258 #0 - 'B' // 6259 #1 - 'C' // 625a #2 - 'aA' // 625b-625c #0 - 'D' // 625d #3 - 'A' // 625e #0 - 'D' // 625f #3 - 'aC' // 6260-6261 #2 - 'B' // 6262 #1 - 'A' // 6263 #0 - 'C' // 6264 #2 - 'aB' // 6265-6266 #1 - 'D' // 6267 #3 - 'A' // 6268 #0 - 'cD' // 6269-626c #3 - 'C' // 626d #2 - 'A' // 626e #0 - 'C' // 626f #2 - 'B' // 6270 #1 - 'A' // 6271 #0 - 'B' // 6272 #1 - 'A' // 6273 #0 - 'B' // 6274 #1 - 'D' // 6275 #3 - 'A' // 6276 #0 - 'B' // 6277 #1 - 'D' // 6278 #3 - 'aA' // 6279-627a #0 - 'C' // 627b #2 - 'A' // 627c #0 - 'C' // 627d #2 - 'bA' // 627e-6280 #0 - 'B' // 6281 #1 - 'C' // 6282 #2 - 'aA' // 6283-6284 #0 - 'C' // 6285 #2 - 'I' // 6286 #8 - 'aB' // 6287-6288 #1 - 'aA' // 6289-628a #0 - 'D' // 628b #3 - 'B' // 628c #1 - 'F' // 628d #5 - 'C' // 628e #2 - 'A' // 628f #0 - 'C' // 6290 #2 - 'aA' // 6291-6292 #0 - 'C' // 6293 #2 - 'dA' // 6294-6298 #0 - 'F' // 6299 #5 - 'D' // 629a #3 - 'J' // 629b #9 - 'F' // 629c #5 - 'B' // 629d #1 - 'F' // 629e #5 - 'dD' // 629f-62a3 #3 - 'B' // 62a4 #1 - 'D' // 62a5 #3 - 'A' // 62a6 #0 - 'D' // 62a7 #3 - 'A' // 62a8 #0 - 'aB' // 62a9-62aa #1 - 'aA' // 62ab-62ac #0 - 'B' // 62ad #1 - 'I' // 62ae #8 - 'aB' // 62af-62b0 #1 - 'A' // 62b1 #0 - 'K' // 62b2 #10 - 'C' // 62b3 #2 - 'B' // 62b4 #1 - 'A' // 62b5 #0 - 'C' // 62b6 #2 - 'F' // 62b7 #5 - 'B' // 62b8 #1 - 'A' // 62b9 #0 - 'F' // 62ba #5 - 'C' // 62bb #2 - 'aA' // 62bc-62bd #0 - 'aC' // 62be-62bf #2 - 'D' // 62c0 #3 - 'B' // 62c1 #1 - 'A' // 62c2 #0 - 'B' // 62c3 #1 - 'fA' // 62c4-62ca #0 - 'I' // 62cb #8 - 'aA' // 62cc-62cd #0 - 'C' // 62ce #2 - 'jA' // 62cf-62d9 #0 - 'C' // 62da #2 - 'aA' // 62db-62dc #0 - 'F' // 62dd #5 - 'D' // 62de #3 - 'B' // 62df #1 - 'F' // 62e0 #5 - 'J' // 62e1 #9 - 'bD' // 62e2-62e4 #3 - 'B' // 62e5 #1 - 'cD' // 62e6-62e9 #3 - 'F' // 62ea #5 - 'B' // 62eb #1 - 'cA' // 62ec-62ef #0 - 'B' // 62f0 #1 - 'A' // 62f1 #0 - 'C' // 62f2 #2 - 'A' // 62f3 #0 - 'C' // 62f4 #2 - 'bA' // 62f5-62f7 #0 - 'cB' // 62f8-62fb #1 - 'C' // 62fc #2 - 'bA' // 62fd-62ff #0 - 'B' // 6300 #1 - 'aA' // 6301-6302 #0 - 'C' // 6303 #2 - 'F' // 6304 #5 - 'aD' // 6305-6306 #3 - 'A' // 6307 #0 - 'C' // 6308 #2 - 'A' // 6309 #0 - 'F' // 630a #5 - 'C' // 630b #2 - 'A' // 630c #0 - 'C' // 630d #2 - 'aB' // 630e-630f #1 - 'aA' // 6310-6311 #0 - 'K' // 6312 #10 - 'C' // 6313 #2 - 'aB' // 6314-6315 #1 - 'C' // 6316 #2 - 'D' // 6317 #3 - 'C' // 6318 #2 - 'F' // 6319 #5 - 'D' // 631a #3 - 'F' // 631b #5 - 'bD' // 631c-631e #3 - 'F' // 631f #5 - 'fD' // 6320-6326 #3 - 'F' // 6327 #5 - 'A' // 6328 #0 - 'C' // 6329 #2 - 'aA' // 632a-632b #0 - 'B' // 632c #1 - 'C' // 632d #2 - 'B' // 632e #1 - 'A' // 632f #0 - 'D' // 6330 #3 - 'B' // 6331 #1 - 'C' // 6332 #2 - 'aB' // 6333-6334 #1 - 'aC' // 6335-6336 #2 - 'aB' // 6337-6338 #1 - 'bA' // 6339-633b #0 - 'C' // 633c #2 - 'aA' // 633d-633e #0 - 'F' // 633f #5 - 'B' // 6340 #1 - 'C' // 6341 #2 - 'bA' // 6342-6344 #0 - 'B' // 6345 #1 - 'A' // 6346 #0 - 'aB' // 6347-6348 #1 - 'A' // 6349 #0 - 'aC' // 634a-634b #2 - 'dA' // 634c-6350 #0 - 'B' // 6351 #1 - 'F' // 6352 #5 - 'J' // 6353 #9 - 'C' // 6354 #2 - 'A' // 6355 #0 - 'B' // 6356 #1 - 'A' // 6357 #0 - 'aC' // 6358-6359 #2 - 'I' // 635a #8 - 'aF' // 635b-635c #5 - 'B' // 635d #1 - 'eD' // 635e-6363 #3 - 'B' // 6364 #1 - 'C' // 6365 #2 - 'F' // 6366 #5 - 'bA' // 6367-6369 #0 - 'D' // 636a #3 - 'A' // 636b #0 - 'aC' // 636c-636d #2 - 'A' // 636e #0 - 'aB' // 636f-6370 #1 - 'aA' // 6371-6372 #0 - 'D' // 6373 #3 - 'F' // 6374 #5 - 'C' // 6375 #2 - 'aA' // 6376-6377 #0 - 'C' // 6378 #2 - 'B' // 6379 #1 - 'aA' // 637a-637b #0 - 'aC' // 637c-637d #2 - 'D' // 637e #3 - 'aA' // 637f-6380 #0 - 'B' // 6381 #1 - 'C' // 6382 #2 - 'aA' // 6383-6384 #0 - 'B' // 6385 #1 - 'D' // 6386 #3 - 'cA' // 6387-638a #0 - 'B' // 638b #1 - 'A' // 638c #0 - 'B' // 638d #1 - 'aA' // 638e-638f #0 - 'C' // 6390 #2 - 'B' // 6391 #1 - 'A' // 6392 #0 - 'D' // 6393 #3 - 'C' // 6394 #2 - 'F' // 6395 #5 - 'A' // 6396 #0 - 'B' // 6397 #1 - 'A' // 6398 #0 - 'C' // 6399 #2 - 'F' // 639a #5 - 'A' // 639b #0 - 'I' // 639c #8 - 'B' // 639d #1 - 'C' // 639e #2 - 'cA' // 639f-63a2 #0 - 'aC' // 63a3-63a4 #2 - 'A' // 63a5 #0 - 'F' // 63a6 #5 - 'cA' // 63a7-63aa #0 - 'C' // 63ab #2 - 'A' // 63ac #0 - 'bC' // 63ad-63af #2 - 'aB' // 63b0-63b1 #1 - 'F' // 63b2 #5 - 'D' // 63b3 #3 - 'aF' // 63b4-63b5 #5 - 'bD' // 63b6-63b8 #3 - 'B' // 63b9 #1 - 'D' // 63ba #3 - 'F' // 63bb #5 - 'D' // 63bc #3 - 'C' // 63bd #2 - 'A' // 63be #0 - 'D' // 63bf #3 - 'A' // 63c0 #0 - 'C' // 63c1 #2 - 'B' // 63c2 #1 - 'aA' // 63c3-63c4 #0 - 'C' // 63c5 #2 - 'A' // 63c6 #0 - 'B' // 63c7 #1 - 'C' // 63c8 #2 - 'A' // 63c9 #0 - 'cB' // 63ca-63cd #1 - 'C' // 63ce #2 - 'aA' // 63cf-63d0 #0 - 'C' // 63d1 #2 - 'A' // 63d2 #0 - 'C' // 63d3 #2 - 'F' // 63d4 #5 - 'C' // 63d5 #2 - 'A' // 63d6 #0 - 'bB' // 63d7-63d9 #1 - 'aA' // 63da-63db #0 - 'C' // 63dc #2 - 'aB' // 63dd-63de #1 - 'I' // 63df #8 - 'aA' // 63e0-63e1 #0 - 'B' // 63e2 #1 - 'A' // 63e3 #0 - 'B' // 63e4 #1 - 'C' // 63e5 #2 - 'bB' // 63e6-63e8 #1 - 'A' // 63e9 #0 - 'C' // 63ea #2 - 'A' // 63eb #0 - 'F' // 63ec #5 - 'aA' // 63ed-63ee #0 - 'bB' // 63ef-63f1 #1 - 'A' // 63f2 #0 - 'C' // 63f3 #2 - 'bA' // 63f4-63f6 #0 - 'J' // 63f7 #9 - 'aC' // 63f8-63f9 #2 - 'F' // 63fa #5 - 'aB' // 63fb-63fc #1 - 'D' // 63fd #3 - 'B' // 63fe #1 - 'fD' // 63ff-6405 #3 - 'A' // 6406 #0 - 'B' // 6407 #1 - 'D' // 6408 #3 - 'A' // 6409 #0 - 'C' // 640a #2 - 'aB' // 640b-640c #1 - 'A' // 640d #0 - 'B' // 640e #1 - 'A' // 640f #0 - 'C' // 6410 #2 - 'D' // 6411 #3 - 'A' // 6412 #0 - 'C' // 6413 #2 - 'A' // 6414 #0 - 'B' // 6415 #1 - 'bA' // 6416-6418 #0 - 'D' // 6419 #3 - 'aB' // 641a-641b #1 - 'A' // 641c #0 - 'D' // 641d #3 - 'C' // 641e #2 - 'B' // 641f #1 - 'A' // 6420 #0 - 'B' // 6421 #1 - 'A' // 6422 #0 - 'B' // 6423 #1 - 'aA' // 6424-6425 #0 - 'C' // 6426 #2 - 'B' // 6427 #1 - 'A' // 6428 #0 - 'F' // 6429 #5 - 'A' // 642a #0 - 'I' // 642b #8 - 'aA' // 642c-642d #0 - 'B' // 642e #1 - 'aA' // 642f-6430 #0 - 'D' // 6431 #3 - 'aB' // 6432-6433 #1 - 'A' // 6434 #0 - 'C' // 6435 #2 - 'A' // 6436 #0 - 'bB' // 6437-6439 #1 - 'A' // 643a #0 - 'B' // 643b #1 - 'D' // 643c #3 - 'C' // 643d #2 - 'A' // 643e #0 - 'C' // 643f #2 - 'aB' // 6440-6441 #1 - 'F' // 6442 #5 - 'B' // 6443 #1 - 'fD' // 6444-644a #3 - 'C' // 644b #2 - 'D' // 644c #3 - 'B' // 644d #1 - 'C' // 644e #2 - 'F' // 644f #5 - 'B' // 6450 #1 - 'cC' // 6451-6454 #2 - 'bD' // 6455-6457 #3 - 'A' // 6458 #0 - 'B' // 6459 #1 - 'C' // 645a #2 - 'A' // 645b #0 - 'aC' // 645c-645d #2 - 'I' // 645e #8 - 'C' // 645f #2 - 'A' // 6460 #0 - 'C' // 6461 #2 - 'D' // 6462 #3 - 'F' // 6463 #5 - 'D' // 6464 #3 - 'aB' // 6465-6466 #1 - 'A' // 6467 #0 - 'B' // 6468 #1 - 'A' // 6469 #0 - 'D' // 646a #3 - 'aB' // 646b-646c #1 - 'A' // 646d #0 - 'B' // 646e #1 - 'A' // 646f #0 - 'bB' // 6470-6472 #1 - 'A' // 6473 #0 - 'C' // 6474 #2 - 'B' // 6475 #1 - 'C' // 6476 #2 - 'B' // 6477 #1 - 'cA' // 6478-647b #0 - 'B' // 647c #1 - 'A' // 647d #0 - 'D' // 647e #3 - 'B' // 647f #1 - 'aD' // 6480-6481 #3 - 'B' // 6482 #1 - 'F' // 6483 #5 - 'D' // 6484 #3 - 'A' // 6485 #0 - 'D' // 6486 #3 - 'C' // 6487 #2 - 'A' // 6488 #0 - 'dB' // 6489-648d #1 - 'D' // 648e #3 - 'C' // 648f #2 - 'cA' // 6490-6493 #0 - 'D' // 6494 #3 - 'A' // 6495 #0 - 'aB' // 6496-6497 #1 - 'C' // 6498 #2 - 'aA' // 6499-649a #0 - 'J' // 649b #9 - 'B' // 649c #1 - 'bA' // 649d-649f #0 - 'B' // 64a0 #1 - 'F' // 64a1 #5 - 'B' // 64a2 #1 - 'C' // 64a3 #2 - 'aA' // 64a4-64a5 #0 - 'C' // 64a6 #2 - 'D' // 64a7 #3 - 'F' // 64a8 #5 - 'A' // 64a9 #0 - 'D' // 64aa #3 - 'A' // 64ab #0 - 'C' // 64ac #2 - 'aA' // 64ad-64ae #0 - 'B' // 64af #1 - 'A' // 64b0 #0 - 'B' // 64b1 #1 - 'A' // 64b2 #0 - 'C' // 64b3 #2 - 'B' // 64b4 #1 - 'D' // 64b5 #3 - 'B' // 64b6 #1 - 'aD' // 64b7-64b8 #3 - 'F' // 64b9 #5 - 'D' // 64ba #3 - 'aA' // 64bb-64bc #0 - 'C' // 64bd #2 - 'aA' // 64be-64bf #0 - 'B' // 64c0 #1 - 'A' // 64c1 #0 - 'C' // 64c2 #2 - 'B' // 64c3 #1 - 'aA' // 64c4-64c5 #0 - 'D' // 64c6 #3 - 'A' // 64c7 #0 - 'D' // 64c8 #3 - 'aA' // 64c9-64ca #0 - 'C' // 64cb #2 - 'F' // 64cc #5 - 'aA' // 64cd-64ce #0 - 'B' // 64cf #1 - 'A' // 64d0 #0 - 'F' // 64d1 #5 - 'A' // 64d2 #0 - 'B' // 64d3 #1 - 'A' // 64d4 #0 - 'J' // 64d5 #9 - 'B' // 64d6 #1 - 'aA' // 64d7-64d8 #0 - 'B' // 64d9 #1 - 'A' // 64da #0 - 'B' // 64db #1 - 'D' // 64dc #3 - 'B' // 64dd #1 - 'aD' // 64de-64df #3 - 'cA' // 64e0-64e3 #0 - 'C' // 64e4 #2 - 'bA' // 64e5-64e7 #0 - 'B' // 64e8 #1 - 'aC' // 64e9-64ea #2 - 'B' // 64eb #1 - 'aA' // 64ec-64ed #0 - 'D' // 64ee #3 - 'A' // 64ef #0 - 'C' // 64f0 #2 - 'aA' // 64f1-64f2 #0 - 'B' // 64f3 #1 - 'A' // 64f4 #0 - 'aF' // 64f5-64f6 #5 - 'C' // 64f7 #2 - 'B' // 64f8 #1 - 'D' // 64f9 #3 - 'A' // 64fa #0 - 'C' // 64fb #2 - 'B' // 64fc #1 - 'C' // 64fd #2 - 'A' // 64fe #0 - 'C' // 64ff #2 - 'A' // 6500 #0 - 'C' // 6501 #2 - 'K' // 6502 #10 - 'B' // 6503 #1 - 'A' // 6504 #0 - 'F' // 6505 #5 - 'B' // 6506 #1 - 'I' // 6507 #8 - 'F' // 6508 #5 - 'C' // 6509 #2 - 'A' // 650a #0 - 'D' // 650b #3 - 'bB' // 650c-650e #1 - 'A' // 650f #0 - 'aB' // 6510-6511 #1 - 'D' // 6512 #3 - 'C' // 6513 #2 - 'A' // 6514 #0 - 'B' // 6515 #1 - 'C' // 6516 #2 - 'B' // 6517 #1 - 'aA' // 6518-6519 #0 - 'D' // 651a #3 - 'aC' // 651b-651c #2 - 'A' // 651d #0 - 'aC' // 651e-651f #2 - 'aB' // 6520-6521 #1 - 'bA' // 6522-6524 #0 - 'B' // 6525 #1 - 'C' // 6526 #2 - 'aD' // 6527-6528 #3 - 'C' // 6529 #2 - 'bA' // 652a-652c #0 - 'B' // 652d #1 - 'C' // 652e #2 - 'A' // 652f #0 - 'B' // 6530 #1 - 'F' // 6531 #5 - 'A' // 6532 #0 - 'B' // 6533 #1 - 'eA' // 6534-6539 #0 - 'F' // 653a #5 - 'A' // 653b #0 - 'F' // 653c #5 - 'bA' // 653d-653f #0 - 'D' // 6540 #3 - 'B' // 6541 #1 - 'D' // 6542 #3 - 'A' // 6543 #0 - 'F' // 6544 #5 - 'A' // 6545 #0 - 'B' // 6546 #1 - 'F' // 6547 #5 - 'aA' // 6548-6549 #0 - 'B' // 654a #1 - 'aD' // 654b-654c #3 - 'A' // 654d #0 - 'J' // 654e #9 - 'A' // 654f #0 - 'F' // 6550 #5 - 'A' // 6551 #0 - 'J' // 6552 #9 - 'B' // 6553 #1 - 'eA' // 6554-6559 #0 - 'B' // 655a #1 - 'D' // 655b #3 - 'B' // 655c #1 - 'aA' // 655d-655e #0 - 'C' // 655f #2 - 'F' // 6560 #5 - 'D' // 6561 #3 - 'aA' // 6562-6563 #0 - 'aB' // 6564-6565 #1 - 'A' // 6566 #0 - 'C' // 6567 #2 - 'B' // 6568 #1 - 'D' // 6569 #3 - 'B' // 656a #1 - 'C' // 656b #2 - 'A' // 656c #0 - 'I' // 656d #8 - 'D' // 656e #3 - 'B' // 656f #1 - 'F' // 6570 #5 - 'D' // 6571 #3 - 'A' // 6572 #0 - 'B' // 6573 #1 - 'aA' // 6574-6575 #0 - 'B' // 6576 #1 - 'aA' // 6577-6578 #0 - 'B' // 6579 #1 - 'C' // 657a #2 - 'aB' // 657b-657c #1 - 'F' // 657d #5 - 'K' // 657e #10 - 'aB' // 657f-6580 #1 - 'bA' // 6581-6583 #0 - 'C' // 6584 #2 - 'A' // 6585 #0 - 'B' // 6586 #1 - 'A' // 6587 #0 - 'C' // 6588 #2 - 'A' // 6589 #0 - 'F' // 658a #5 - 'B' // 658b #1 - 'A' // 658c #0 - 'D' // 658d #3 - 'F' // 658e #5 - 'D' // 658f #3 - 'aA' // 6590-6591 #0 - 'C' // 6592 #2 - 'D' // 6593 #3 - 'B' // 6594 #1 - 'C' // 6595 #2 - 'B' // 6596 #1 - 'A' // 6597 #0 - 'F' // 6598 #5 - 'A' // 6599 #0 - 'D' // 659a #3 - 'bA' // 659b-659d #0 - 'B' // 659e #1 - 'A' // 659f #0 - 'C' // 65a0 #2 - 'A' // 65a1 #0 - 'B' // 65a2 #1 - 'F' // 65a3 #5 - 'aA' // 65a4-65a5 #0 - 'F' // 65a6 #5 - 'A' // 65a7 #0 - 'B' // 65a8 #1 - 'D' // 65a9 #3 - 'B' // 65aa #1 - 'aA' // 65ab-65ac #0 - 'J' // 65ad #9 - 'C' // 65ae #2 - 'aA' // 65af-65b0 #0 - 'K' // 65b1 #10 - 'A' // 65b2 #0 - 'C' // 65b3 #2 - 'F' // 65b4 #5 - 'C' // 65b5 #2 - 'B' // 65b6 #1 - 'A' // 65b7 #0 - 'C' // 65b8 #2 - 'A' // 65b9 #0 - 'D' // 65ba #3 - 'B' // 65bb #1 - 'aA' // 65bc-65bd #0 - 'C' // 65be #2 - 'A' // 65bf #0 - 'D' // 65c0 #3 - 'eA' // 65c1-65c6 #0 - 'D' // 65c7 #3 - 'aF' // 65c8-65c9 #5 - 'K' // 65ca #10 - 'aA' // 65cb-65cc #0 - 'B' // 65cd #1 - 'C' // 65ce #2 - 'A' // 65cf #0 - 'C' // 65d0 #2 - 'B' // 65d1 #1 - 'A' // 65d2 #0 - 'B' // 65d3 #1 - 'C' // 65d4 #2 - 'D' // 65d5 #3 - 'C' // 65d6 #2 - 'A' // 65d7 #0 - 'aF' // 65d8-65d9 #5 - 'B' // 65da #1 - 'C' // 65db #2 - 'D' // 65dc #3 - 'aB' // 65dd-65de #1 - 'C' // 65df #2 - 'aA' // 65e0-65e1 #0 - 'C' // 65e2 #2 - 'A' // 65e3 #0 - 'D' // 65e4 #3 - 'aA' // 65e5-65e6 #0 - 'F' // 65e7 #5 - 'aA' // 65e8-65e9 #0 - 'aD' // 65ea-65eb #3 - 'aA' // 65ec-65ed #0 - 'aB' // 65ee-65ef #1 - 'C' // 65f0 #2 - 'aA' // 65f1-65f2 #0 - 'B' // 65f3 #1 - 'A' // 65f4 #0 - 'C' // 65f5 #2 - 'bD' // 65f6-65f8 #3 - 'F' // 65f9 #5 - 'bA' // 65fa-65fc #0 - 'I' // 65fd #8 - 'F' // 65fe #5 - 'aA' // 65ff-6600 #0 - 'D' // 6601 #3 - 'aA' // 6602-6603 #0 - 'C' // 6604 #2 - 'B' // 6605 #1 - 'aA' // 6606-6607 #0 - 'C' // 6608 #2 - 'aA' // 6609-660a #0 - 'B' // 660b #1 - 'cA' // 660c-660f #0 - 'I' // 6610 #8 - 'A' // 6611 #0 - 'C' // 6612 #2 - 'bA' // 6613-6615 #0 - 'F' // 6616 #5 - 'D' // 6617 #3 - 'B' // 6618 #1 - 'aD' // 6619-661a #3 - 'K' // 661b #10 - 'A' // 661c #0 - 'C' // 661d #2 - 'cA' // 661e-6621 #0 - 'C' // 6622 #2 - 'bA' // 6623-6625 #0 - 'C' // 6626 #2 - 'aA' // 6627-6628 #0 - 'aF' // 6629-662a #5 - 'A' // 662b #0 - 'F' // 662c #5 - 'A' // 662d #0 - 'C' // 662e #2 - 'bA' // 662f-6631 #0 - 'B' // 6632 #1 - 'C' // 6633 #2 - 'bA' // 6634-6636 #0 - 'J' // 6637 #9 - 'D' // 6638 #3 - 'C' // 6639 #2 - 'A' // 663a #0 - 'J' // 663b #9 - 'F' // 663c #5 - 'aD' // 663d-663e #3 - 'aF' // 663f-6640 #5 - 'cA' // 6641-6644 #0 - 'C' // 6645 #2 - 'F' // 6646 #5 - 'B' // 6647 #1 - 'aA' // 6648-6649 #0 - 'C' // 664a #2 - 'aA' // 664b-664c #0 - 'B' // 664d #1 - 'J' // 664e #9 - 'A' // 664f #0 - 'K' // 6650 #10 - 'A' // 6651 #0 - 'C' // 6652 #2 - 'B' // 6653 #1 - 'bD' // 6654-6656 #3 - 'C' // 6657 #2 - 'F' // 6658 #5 - 'bA' // 6659-665b #0 - 'C' // 665c #2 - 'eA' // 665d-6662 #0 - 'C' // 6663 #2 - 'dA' // 6664-6668 #0 - 'J' // 6669 #9 - 'C' // 666a #2 - 'aA' // 666b-666c #0 - 'J' // 666d #9 - 'bA' // 666e-6670 #0 - 'aB' // 6671-6672 #1 - 'aA' // 6673-6674 #0 - 'F' // 6675 #5 - 'eA' // 6676-667b #0 - 'C' // 667c #2 - 'I' // 667d #8 - 'C' // 667e #2 - 'J' // 667f #9 - 'C' // 6680 #2 - 'F' // 6681 #5 - 'D' // 6682 #3 - 'F' // 6683 #5 - 'A' // 6684 #0 - 'aB' // 6685-6686 #1 - 'bA' // 6687-6689 #0 - 'B' // 668a #1 - 'aA' // 668b-668c #0 - 'C' // 668d #2 - 'A' // 668e #0 - 'D' // 668f #3 - 'aA' // 6690-6691 #0 - 'C' // 6692 #2 - 'D' // 6693 #3 - 'aB' // 6694-6695 #1 - 'bA' // 6696-6698 #0 - 'C' // 6699 #2 - 'A' // 669a #0 - 'aF' // 669b-669c #5 - 'A' // 669d #0 - 'K' // 669e #10 - 'C' // 669f #2 - 'A' // 66a0 #0 - 'B' // 66a1 #1 - 'A' // 66a2 #0 - 'K' // 66a3 #10 - 'C' // 66a4 #2 - 'D' // 66a5 #3 - 'F' // 66a6 #5 - 'D' // 66a7 #3 - 'bB' // 66a8-66aa #1 - 'A' // 66ab #0 - 'K' // 66ac #10 - 'C' // 66ad #2 - 'A' // 66ae #0 - 'aB' // 66af-66b0 #1 - 'dA' // 66b1-66b5 #0 - 'aB' // 66b6-66b7 #1 - 'aA' // 66b8-66b9 #0 - 'I' // 66ba #8 - 'A' // 66bb #0 - 'F' // 66bc #5 - 'B' // 66bd #1 - 'bA' // 66be-66c0 #0 - 'J' // 66c1 #9 - 'aF' // 66c2-66c3 #5 - 'A' // 66c4 #0 - 'K' // 66c5 #10 - 'cA' // 66c6-66c9 #0 - 'aB' // 66ca-66cb #1 - 'C' // 66cc #2 - 'B' // 66cd #1 - 'aC' // 66ce-66cf #2 - 'aD' // 66d0-66d1 #3 - 'B' // 66d2 #1 - 'K' // 66d3 #10 - 'J' // 66d4 #9 - 'D' // 66d5 #3 - 'A' // 66d6 #0 - 'D' // 66d7 #3 - 'I' // 66d8 #8 - 'dA' // 66d9-66dd #0 - 'I' // 66de #8 - 'F' // 66df #5 - 'A' // 66e0 #0 - 'aD' // 66e1-66e2 #3 - 'I' // 66e3 #8 - 'B' // 66e4 #1 - 'D' // 66e5 #3 - 'A' // 66e6 #0 - 'B' // 66e7 #1 - 'aA' // 66e8-66e9 #0 - 'K' // 66ea #10 - 'C' // 66eb #2 - 'A' // 66ec #0 - 'B' // 66ed #1 - 'A' // 66ee #0 - 'K' // 66ef #10 - 'A' // 66f0 #0 - 'B' // 66f1 #1 - 'bA' // 66f2-66f4 #0 - 'F' // 66f5 #5 - 'B' // 66f6 #1 - 'bA' // 66f7-66f9 #0 - 'J' // 66fa #9 - 'F' // 66fb #5 - 'A' // 66fc #0 - 'F' // 66fd #5 - 'bA' // 66fe-6700 #0 - 'C' // 6701 #2 - 'B' // 6702 #1 - 'A' // 6703 #0 - 'I' // 6704 #8 - 'A' // 6705 #0 - 'D' // 6706 #3 - 'F' // 6707 #5 - 'aA' // 6708-6709 #0 - 'I' // 670a #8 - 'bA' // 670b-670d #0 - 'aC' // 670e-670f #2 - 'A' // 6710 #0 - 'D' // 6711 #3 - 'aC' // 6712-6713 #2 - 'aA' // 6714-6715 #0 - 'C' // 6716 #2 - 'A' // 6717 #0 - 'B' // 6718 #1 - 'C' // 6719 #2 - 'D' // 671a #3 - 'A' // 671b #0 - 'F' // 671c #5 - 'cA' // 671d-6720 #0 - 'B' // 6721 #1 - 'A' // 6722 #0 - 'I' // 6723 #8 - 'D' // 6724 #3 - 'C' // 6725 #2 - 'bA' // 6726-6728 #0 - 'D' // 6729 #3 - 'dA' // 672a-672e #0 - 'aD' // 672f-6730 #3 - 'A' // 6731 #0 - 'D' // 6732 #3 - 'aA' // 6733-6734 #0 - 'C' // 6735 #2 - 'A' // 6736 #0 - 'F' // 6737 #5 - 'C' // 6738 #2 - 'B' // 6739 #1 - 'A' // 673a #0 - 'aB' // 673b-673c #1 - 'aA' // 673d-673e #0 - 'C' // 673f #2 - 'D' // 6740 #3 - 'F' // 6741 #5 - 'D' // 6742 #3 - 'F' // 6743 #5 - 'B' // 6744 #1 - 'aA' // 6745-6746 #0 - 'aC' // 6747-6748 #2 - 'A' // 6749 #0 - 'D' // 674a #3 - 'I' // 674b #8 - 'A' // 674c #0 - 'C' // 674d #2 - 'cA' // 674e-6751 #0 - 'D' // 6752 #3 - 'A' // 6753 #0 - 'F' // 6754 #5 - 'C' // 6755 #2 - 'A' // 6756 #0 - 'B' // 6757 #1 - 'D' // 6758 #3 - 'C' // 6759 #2 - 'B' // 675a #1 - 'D' // 675b #3 - 'dA' // 675c-6760 #0 - 'aC' // 6761-6762 #2 - 'aF' // 6763-6764 #5 - 'J' // 6765 #9 - 'F' // 6766 #5 - 'B' // 6767 #1 - 'aD' // 6768-6769 #3 - 'A' // 676a #0 - 'B' // 676b #1 - 'aA' // 676c-676d #0 - 'C' // 676e #2 - 'dA' // 676f-6773 #0 - 'C' // 6774 #2 - 'A' // 6775 #0 - 'C' // 6776 #2 - 'A' // 6777 #0 - 'bB' // 6778-677a #1 - 'aA' // 677b-677c #0 - 'B' // 677d #1 - 'aA' // 677e-677f #0 - 'F' // 6780 #5 - 'C' // 6781 #2 - 'B' // 6782 #1 - 'I' // 6783 #8 - 'aC' // 6784-6785 #2 - 'B' // 6786 #1 - 'A' // 6787 #0 - 'D' // 6788 #3 - 'A' // 6789 #0 - 'D' // 678a #3 - 'aA' // 678b-678c #0 - 'B' // 678d #1 - 'C' // 678e #2 - 'aA' // 678f-6790 #0 - 'C' // 6791 #2 - 'aA' // 6792-6793 #0 - 'B' // 6794 #1 - 'A' // 6795 #0 - 'F' // 6796 #5 - 'cA' // 6797-679a #0 - 'F' // 679b #5 - 'aA' // 679c-679d #0 - 'D' // 679e #3 - 'B' // 679f #1 - 'C' // 67a0 #2 - 'aF' // 67a1-67a2 #5 - 'D' // 67a3 #3 - 'C' // 67a4 #2 - 'D' // 67a5 #3 - 'F' // 67a6 #5 - 'aD' // 67a7-67a8 #3 - 'F' // 67a9 #5 - 'aD' // 67aa-67ab #3 - 'B' // 67ac #1 - 'D' // 67ad #3 - 'B' // 67ae #1 - 'aA' // 67af-67b0 #0 - 'C' // 67b1 #2 - 'aA' // 67b2-67b3 #0 - 'aC' // 67b4-67b5 #2 - 'bA' // 67b6-67b8 #0 - 'C' // 67b9 #2 - 'B' // 67ba #1 - 'C' // 67bb #2 - 'aF' // 67bc-67bd #5 - 'J' // 67be #9 - 'B' // 67bf #1 - 'C' // 67c0 #2 - 'A' // 67c1 #0 - 'aC' // 67c2-67c3 #2 - 'aA' // 67c4-67c5 #0 - 'C' // 67c6 #2 - 'D' // 67c7 #3 - 'aC' // 67c8-67c9 #2 - 'A' // 67ca #0 - 'bB' // 67cb-67cd #1 - 'C' // 67ce #2 - 'eA' // 67cf-67d4 #0 - 'D' // 67d5 #3 - 'I' // 67d6 #8 - 'cA' // 67d7-67da #0 - 'aC' // 67db-67dc #2 - 'aA' // 67dd-67de #0 - 'I' // 67df #8 - 'D' // 67e0 #3 - 'F' // 67e1 #5 - 'A' // 67e2 #0 - 'B' // 67e3 #1 - 'C' // 67e4 #2 - 'B' // 67e5 #1 - 'aC' // 67e6-67e7 #2 - 'D' // 67e8 #3 - 'A' // 67e9 #0 - 'aB' // 67ea-67eb #1 - 'A' // 67ec #0 - 'B' // 67ed #1 - 'C' // 67ee #2 - 'bA' // 67ef-67f1 #0 - 'C' // 67f2 #2 - 'cA' // 67f3-67f6 #0 - 'C' // 67f7 #2 - 'B' // 67f8 #1 - 'A' // 67f9 #0 - 'C' // 67fa #2 - 'J' // 67fb #9 - 'C' // 67fc #2 - 'D' // 67fd #3 - 'aA' // 67fe-67ff #0 - 'B' // 6800 #1 - 'aC' // 6801-6802 #2 - 'aA' // 6803-6804 #0 - 'F' // 6805 #5 - 'fD' // 6806-680c #3 - 'B' // 680d #1 - 'aD' // 680e-680f #3 - 'A' // 6810 #0 - 'D' // 6811 #3 - 'I' // 6812 #8 - 'A' // 6813 #0 - 'C' // 6814 #2 - 'D' // 6815 #3 - 'aA' // 6816-6817 #0 - 'C' // 6818 #2 - 'F' // 6819 #5 - 'bB' // 681a-681c #1 - 'aA' // 681d-681e #0 - 'C' // 681f #2 - 'B' // 6820 #1 - 'aA' // 6821-6822 #0 - 'aD' // 6823-6824 #3 - 'aB' // 6825-6826 #1 - 'F' // 6827 #5 - 'aC' // 6828-6829 #2 - 'A' // 682a #0 - 'C' // 682b #2 - 'F' // 682c #5 - 'C' // 682d #2 - 'I' // 682e #8 - 'A' // 682f #0 - 'F' // 6830 #5 - 'aA' // 6831-6832 #0 - 'C' // 6833 #2 - 'A' // 6834 #0 - 'bB' // 6835-6837 #1 - 'aA' // 6838-6839 #0 - 'B' // 683a #1 - 'bA' // 683b-683d #0 - 'C' // 683e #2 - 'F' // 683f #5 - 'dA' // 6840-6844 #0 - 'C' // 6845 #2 - 'A' // 6846 #0 - 'B' // 6847 #1 - 'aA' // 6848-6849 #0 - 'C' // 684a #2 - 'B' // 684b #1 - 'aC' // 684c-684d #2 - 'A' // 684e #0 - 'B' // 684f #1 - 'aA' // 6850-6851 #0 - 'F' // 6852 #5 - 'aA' // 6853-6854 #0 - 'C' // 6855 #2 - 'B' // 6856 #1 - 'bF' // 6857-6859 #5 - 'D' // 685a #3 - 'aF' // 685b-685c #5 - 'C' // 685d #2 - 'D' // 685e #3 - 'F' // 685f #5 - 'bD' // 6860-6862 #3 - 'F' // 6863 #5 - 'D' // 6864 #3 - 'B' // 6865 #1 - 'D' // 6866 #3 - 'F' // 6867 #5 - 'bD' // 6868-686a #3 - 'C' // 686b #2 - 'D' // 686c #3 - 'I' // 686d #8 - 'C' // 686e #2 - 'A' // 686f #0 - 'F' // 6870 #5 - 'aC' // 6871-6872 #2 - 'D' // 6873 #3 - 'A' // 6874 #0 - 'C' // 6875 #2 - 'aA' // 6876-6877 #0 - 'B' // 6878 #1 - 'C' // 6879 #2 - 'F' // 687a #5 - 'aC' // 687b-687c #2 - 'B' // 687d #1 - 'aA' // 687e-687f #0 - 'B' // 6880 #1 - 'A' // 6881 #0 - 'C' // 6882 #2 - 'A' // 6883 #0 - 'C' // 6884 #2 - 'aA' // 6885-6886 #0 - 'B' // 6887 #1 - 'C' // 6888 #2 - 'cB' // 6889-688c #1 - 'aF' // 688d-688e #5 - 'A' // 688f #0 - 'C' // 6890 #2 - 'aB' // 6891-6892 #1 - 'aA' // 6893-6894 #0 - 'D' // 6895 #3 - 'C' // 6896 #2 - 'A' // 6897 #0 - 'C' // 6898 #2 - 'aF' // 6899-689a #5 - 'A' // 689b #0 - 'C' // 689c #2 - 'A' // 689d #0 - 'D' // 689e #3 - 'dA' // 689f-68a3 #0 - 'B' // 68a4 #1 - 'F' // 68a5 #5 - 'C' // 68a6 #2 - 'aA' // 68a7-68a8 #0 - 'bC' // 68a9-68ab #2 - 'B' // 68ac #1 - 'A' // 68ad #0 - 'C' // 68ae #2 - 'bA' // 68af-68b1 #0 - 'C' // 68b2 #2 - 'A' // 68b3 #0 - 'C' // 68b4 #2 - 'aA' // 68b5-68b6 #0 - 'aD' // 68b7-68b8 #3 - 'C' // 68b9 #2 - 'bF' // 68ba-68bc #5 - 'B' // 68bd #1 - 'bD' // 68be-68c0 #3 - 'B' // 68c1 #1 - 'D' // 68c2 #3 - 'C' // 68c3 #2 - 'aA' // 68c4-68c5 #0 - 'C' // 68c6 #2 - 'B' // 68c7 #1 - 'C' // 68c8 #2 - 'dA' // 68c9-68cd #0 - 'B' // 68ce #1 - 'F' // 68cf #5 - 'A' // 68d0 #0 - 'C' // 68d1 #2 - 'A' // 68d2 #0 - 'aC' // 68d3-68d4 #2 - 'cA' // 68d5-68d8 #0 - 'F' // 68d9 #5 - 'A' // 68da #0 - 'D' // 68db #3 - 'aC' // 68dc-68dd #2 - 'B' // 68de #1 - 'aA' // 68df-68e0 #0 - 'C' // 68e1 #2 - 'D' // 68e2 #3 - 'A' // 68e3 #0 - 'C' // 68e4 #2 - 'F' // 68e5 #5 - 'B' // 68e6 #1 - 'aA' // 68e7-68e8 #0 - 'B' // 68e9 #1 - 'aC' // 68ea-68eb #2 - 'A' // 68ec #0 - 'F' // 68ed #5 - 'A' // 68ee #0 - 'bC' // 68ef-68f1 #2 - 'A' // 68f2 #0 - 'aB' // 68f3-68f4 #1 - 'bC' // 68f5-68f7 #2 - 'B' // 68f8 #1 - 'dA' // 68f9-68fd #0 - 'aD' // 68fe-68ff #3 - 'aA' // 6900-6901 #0 - 'B' // 6902 #1 - 'C' // 6903 #2 - 'bA' // 6904-6906 #0 - 'cC' // 6907-690a #2 - 'A' // 690b #0 - 'C' // 690c #2 - 'bA' // 690d-690f #0 - 'C' // 6910 #2 - 'aA' // 6911-6912 #0 - 'C' // 6913 #2 - 'aB' // 6914-6915 #1 - 'F' // 6916 #5 - 'C' // 6917 #2 - 'B' // 6918 #1 - 'A' // 6919 #0 - 'aC' // 691a-691b #2 - 'J' // 691c #9 - 'cD' // 691d-6920 #3 - 'bF' // 6921-6923 #5 - 'D' // 6924 #3 - 'C' // 6925 #2 - 'F' // 6926 #5 - 'K' // 6927 #10 - 'F' // 6928 #5 - 'D' // 6929 #3 - 'C' // 692a #2 - 'D' // 692b #3 - 'B' // 692c #1 - 'aD' // 692d-692e #3 - 'B' // 692f #1 - 'A' // 6930 #0 - 'F' // 6931 #5 - 'B' // 6932 #1 - 'C' // 6933 #2 - 'A' // 6934 #0 - 'C' // 6935 #2 - 'A' // 6936 #0 - 'B' // 6937 #1 - 'C' // 6938 #2 - 'A' // 6939 #0 - 'D' // 693a #3 - 'C' // 693b #2 - 'B' // 693c #1 - 'A' // 693d #0 - 'B' // 693e #1 - 'A' // 693f #0 - 'aB' // 6940-6941 #1 - 'A' // 6942 #0 - 'aB' // 6943-6944 #1 - 'aC' // 6945-6946 #2 - 'D' // 6947 #3 - 'B' // 6948 #1 - 'C' // 6949 #2 - 'A' // 694a #0 - 'aB' // 694b-694c #1 - 'D' // 694d #3 - 'C' // 694e #2 - 'I' // 694f #8 - 'D' // 6950 #3 - 'aB' // 6951-6952 #1 - 'bA' // 6953-6955 #0 - 'B' // 6956 #1 - 'A' // 6957 #0 - 'B' // 6958 #1 - 'aA' // 6959-695a #0 - 'aC' // 695b-695c #2 - 'aA' // 695d-695e #0 - 'B' // 695f #1 - 'cA' // 6960-6963 #0 - 'C' // 6964 #2 - 'A' // 6965 #0 - 'C' // 6966 #2 - 'B' // 6967 #1 - 'A' // 6968 #0 - 'C' // 6969 #2 - 'eA' // 696a-696f #0 - 'bC' // 6970-6972 #2 - 'A' // 6973 #0 - 'C' // 6974 #2 - 'A' // 6975 #0 - 'B' // 6976 #1 - 'bA' // 6977-6979 #0 - 'C' // 697a #2 - 'A' // 697b #0 - 'F' // 697c #5 - 'J' // 697d #9 - 'aF' // 697e-697f #5 - 'C' // 6980 #2 - 'F' // 6981 #5 - 'C' // 6982 #2 - 'B' // 6983 #1 - 'D' // 6984 #3 - 'B' // 6985 #1 - 'C' // 6986 #2 - 'bD' // 6987-6989 #3 - 'C' // 698a #2 - 'aD' // 698b-698c #3 - 'C' // 698d #2 - 'A' // 698e #0 - 'D' // 698f #3 - 'B' // 6990 #1 - 'A' // 6991 #0 - 'F' // 6992 #5 - 'B' // 6993 #1 - 'aA' // 6994-6995 #0 - 'C' // 6996 #2 - 'B' // 6997 #1 - 'A' // 6998 #0 - 'aB' // 6999-699a #1 - 'aA' // 699b-699c #0 - 'D' // 699d #3 - 'B' // 699e #1 - 'I' // 699f #8 - 'aC' // 69a0-69a1 #2 - 'aB' // 69a2-69a3 #1 - 'I' // 69a4 #8 - 'bA' // 69a5-69a7 #0 - 'C' // 69a8 #2 - 'aB' // 69a9-69aa #1 - 'C' // 69ab #2 - 'B' // 69ac #1 - 'aA' // 69ad-69ae #0 - 'C' // 69af #2 - 'bA' // 69b0-69b2 #0 - 'B' // 69b3 #1 - 'A' // 69b4 #0 - 'aB' // 69b5-69b6 #1 - 'A' // 69b7 #0 - 'F' // 69b8 #5 - 'B' // 69b9 #1 - 'J' // 69ba #9 - 'aA' // 69bb-69bc #0 - 'B' // 69bd #1 - 'cA' // 69be-69c1 #0 - 'B' // 69c2 #1 - 'A' // 69c3 #0 - 'B' // 69c4 #1 - 'F' // 69c5 #5 - 'B' // 69c6 #1 - 'J' // 69c7 #9 - 'F' // 69c8 #5 - 'B' // 69c9 #1 - 'fA' // 69ca-69d0 #0 - 'C' // 69d1 #2 - 'D' // 69d2 #3 - 'A' // 69d3 #0 - 'aB' // 69d4-69d5 #1 - 'A' // 69d6 #0 - 'aF' // 69d7-69d8 #5 - 'C' // 69d9 #2 - 'bD' // 69da-69dc #3 - 'aF' // 69dd-69de #5 - 'aD' // 69df-69e0 #3 - 'B' // 69e1 #1 - 'A' // 69e2 #0 - 'F' // 69e3 #5 - 'B' // 69e4 #1 - 'A' // 69e5 #0 - 'I' // 69e6 #8 - 'bA' // 69e7-69e9 #0 - 'J' // 69ea #9 - 'C' // 69eb #2 - 'B' // 69ec #1 - 'A' // 69ed #0 - 'C' // 69ee #2 - 'F' // 69ef #5 - 'D' // 69f0 #3 - 'C' // 69f1 #2 - 'A' // 69f2 #0 - 'aC' // 69f3-69f4 #2 - 'F' // 69f5 #5 - 'C' // 69f6 #2 - 'aB' // 69f7-69f8 #1 - 'A' // 69f9 #0 - 'B' // 69fa #1 - 'A' // 69fb #0 - 'B' // 69fc #1 - 'A' // 69fd #0 - 'C' // 69fe #2 - 'aA' // 69ff-6a00 #0 - 'C' // 6a01 #2 - 'A' // 6a02 #0 - 'C' // 6a03 #2 - 'B' // 6a04 #1 - 'A' // 6a05 #0 - 'cB' // 6a06-6a09 #1 - 'aA' // 6a0a-6a0b #0 - 'C' // 6a0c #2 - 'B' // 6a0d #1 - 'D' // 6a0e #3 - 'C' // 6a0f #2 - 'D' // 6a10 #3 - 'A' // 6a11 #0 - 'J' // 6a12 #9 - 'aA' // 6a13-6a14 #0 - 'C' // 6a15 #2 - 'B' // 6a16 #1 - 'A' // 6a17 #0 - 'B' // 6a18 #1 - 'A' // 6a19 #0 - 'C' // 6a1a #2 - 'A' // 6a1b #0 - 'B' // 6a1c #1 - 'C' // 6a1d #2 - 'aA' // 6a1e-6a1f #0 - 'C' // 6a20 #2 - 'A' // 6a21 #0 - 'F' // 6a22 #5 - 'A' // 6a23 #0 - 'F' // 6a24 #5 - 'bB' // 6a25-6a27 #1 - 'C' // 6a28 #2 - 'A' // 6a29 #0 - 'F' // 6a2a #5 - 'A' // 6a2b #0 - 'aB' // 6a2c-6a2d #1 - 'F' // 6a2e #5 - 'D' // 6a2f #3 - 'F' // 6a30 #5 - 'D' // 6a31 #3 - 'bC' // 6a32-6a34 #2 - 'A' // 6a35 #0 - 'aF' // 6a36-6a37 #5 - 'cA' // 6a38-6a3b #0 - 'B' // 6a3c #1 - 'A' // 6a3d #0 - 'aC' // 6a3e-6a3f #2 - 'aB' // 6a40-6a41 #1 - 'D' // 6a42 #3 - 'I' // 6a43 #8 - 'aA' // 6a44-6a45 #0 - 'C' // 6a46 #2 - 'bA' // 6a47-6a49 #0 - 'J' // 6a4a #9 - 'A' // 6a4b #0 - 'aI' // 6a4c-6a4d #8 - 'C' // 6a4e #2 - 'B' // 6a4f #1 - 'A' // 6a50 #0 - 'C' // 6a51 #2 - 'A' // 6a52 #0 - 'I' // 6a53 #8 - 'bC' // 6a54-6a56 #2 - 'B' // 6a57 #1 - 'aA' // 6a58-6a59 #0 - 'I' // 6a5a #8 - 'C' // 6a5b #2 - 'D' // 6a5c #3 - 'aB' // 6a5d-6a5e #1 - 'A' // 6a5f #0 - 'B' // 6a60 #1 - 'aA' // 6a61-6a62 #0 - 'B' // 6a63 #1 - 'A' // 6a64 #0 - 'B' // 6a65 #1 - 'A' // 6a66 #0 - 'C' // 6a67 #2 - 'aB' // 6a68-6a69 #1 - 'C' // 6a6a #2 - 'A' // 6a6b #0 - 'D' // 6a6c #3 - 'B' // 6a6d #1 - 'D' // 6a6e #3 - 'B' // 6a6f #1 - 'D' // 6a70 #3 - 'C' // 6a71 #2 - 'J' // 6a72 #9 - 'F' // 6a73 #5 - 'B' // 6a74 #1 - 'K' // 6a75 #10 - 'B' // 6a76 #1 - 'D' // 6a77 #3 - 'F' // 6a78 #5 - 'D' // 6a79 #3 - 'C' // 6a7a #2 - 'bD' // 6a7b-6a7d #3 - 'C' // 6a7e #2 - 'aA' // 6a7f-6a80 #0 - 'C' // 6a81 #2 - 'B' // 6a82 #1 - 'aA' // 6a83-6a84 #0 - 'B' // 6a85 #1 - 'F' // 6a86 #5 - 'C' // 6a87 #2 - 'D' // 6a88 #3 - 'A' // 6a89 #0 - 'B' // 6a8a #1 - 'F' // 6a8b #5 - 'B' // 6a8c #1 - 'aA' // 6a8d-6a8e #0 - 'B' // 6a8f #1 - 'A' // 6a90 #0 - 'C' // 6a91 #2 - 'aB' // 6a92-6a93 #1 - 'A' // 6a94 #0 - 'aB' // 6a95-6a96 #1 - 'A' // 6a97 #0 - 'D' // 6a98 #3 - 'aB' // 6a99-6a9a #1 - 'C' // 6a9b #2 - 'aA' // 6a9c-6a9d #0 - 'C' // 6a9e #2 - 'aA' // 6a9f-6aa0 #0 - 'C' // 6aa1 #2 - 'aA' // 6aa2-6aa3 #0 - 'B' // 6aa4 #1 - 'C' // 6aa5 #2 - 'bB' // 6aa6-6aa8 #1 - 'D' // 6aa9 #3 - 'F' // 6aaa #5 - 'aC' // 6aab-6aac #2 - 'B' // 6aad #1 - 'A' // 6aae #0 - 'C' // 6aaf #2 - 'F' // 6ab0 #5 - 'C' // 6ab1 #2 - 'B' // 6ab2 #1 - 'A' // 6ab3 #0 - 'C' // 6ab4 #2 - 'B' // 6ab5 #1 - 'I' // 6ab6 #8 - 'B' // 6ab7 #1 - 'C' // 6ab8 #2 - 'aB' // 6ab9-6aba #1 - 'A' // 6abb #0 - 'K' // 6abc #10 - 'aC' // 6abd-6abe #2 - 'J' // 6abf #9 - 'D' // 6ac0 #3 - 'F' // 6ac1 #5 - 'aA' // 6ac2-6ac3 #0 - 'D' // 6ac4 #3 - 'B' // 6ac5 #1 - 'C' // 6ac6 #2 - 'B' // 6ac7 #1 - 'aC' // 6ac8-6ac9 #2 - 'aB' // 6aca-6acb #1 - 'C' // 6acc #2 - 'B' // 6acd #1 - 'D' // 6ace #3 - 'B' // 6acf #1 - 'aC' // 6ad0-6ad1 #2 - 'D' // 6ad2 #3 - 'A' // 6ad3 #0 - 'C' // 6ad4 #2 - 'aF' // 6ad5-6ad6 #5 - 'D' // 6ad7 #3 - 'aB' // 6ad8-6ad9 #1 - 'eA' // 6ada-6adf #0 - 'aB' // 6ae0-6ae1 #1 - 'F' // 6ae2 #5 - 'D' // 6ae3 #3 - 'F' // 6ae4 #5 - 'B' // 6ae5 #1 - 'D' // 6ae6 #3 - 'C' // 6ae7 #2 - 'A' // 6ae8 #0 - 'D' // 6ae9 #3 - 'A' // 6aea #0 - 'B' // 6aeb #1 - 'A' // 6aec #0 - 'D' // 6aed #3 - 'aB' // 6aee-6aef #1 - 'aC' // 6af0-6af1 #2 - 'F' // 6af2 #5 - 'C' // 6af3 #2 - 'aD' // 6af4-6af5 #3 - 'I' // 6af6 #8 - 'D' // 6af7 #3 - 'C' // 6af8 #2 - 'B' // 6af9 #1 - 'C' // 6afa #2 - 'aA' // 6afb-6afc #0 - 'F' // 6afd #5 - 'aD' // 6afe-6aff #3 - 'B' // 6b00 #1 - 'D' // 6b01 #3 - 'bA' // 6b02-6b04 #0 - 'C' // 6b05 #2 - 'aF' // 6b06-6b07 #5 - 'B' // 6b08 #1 - 'C' // 6b09 #2 - 'A' // 6b0a #0 - 'C' // 6b0b #2 - 'K' // 6b0c #10 - 'aD' // 6b0d-6b0e #3 - 'aC' // 6b0f-6b10 #2 - 'aA' // 6b11-6b12 #0 - 'B' // 6b13 #1 - 'aD' // 6b14-6b15 #3 - 'A' // 6b16 #0 - 'C' // 6b17 #2 - 'bB' // 6b18-6b1a #1 - 'F' // 6b1b #5 - 'D' // 6b1c #3 - 'C' // 6b1d #2 - 'A' // 6b1e #0 - 'F' // 6b1f #5 - 'aA' // 6b20-6b21 #0 - 'D' // 6b22 #3 - 'A' // 6b23 #0 - 'F' // 6b24 #5 - 'B' // 6b25 #1 - 'D' // 6b26 #3 - 'F' // 6b27 #5 - 'C' // 6b28 #2 - 'aD' // 6b29-6b2a #3 - 'F' // 6b2b #5 - 'A' // 6b2c #0 - 'B' // 6b2d #1 - 'D' // 6b2e #3 - 'C' // 6b2f #2 - 'D' // 6b30 #3 - 'B' // 6b31 #1 - 'A' // 6b32 #0 - 'aB' // 6b33-6b34 #1 - 'aC' // 6b35-6b36 #2 - 'dA' // 6b37-6b3b #0 - 'B' // 6b3c #1 - 'bA' // 6b3d-6b3f #0 - 'D' // 6b40 #3 - 'aB' // 6b41-6b42 #1 - 'A' // 6b43 #0 - 'D' // 6b44 #3 - 'B' // 6b45 #1 - 'aA' // 6b46-6b47 #0 - 'B' // 6b48 #1 - 'aA' // 6b49-6b4a #0 - 'B' // 6b4b #1 - 'A' // 6b4c #0 - 'C' // 6b4d #2 - 'A' // 6b4e #0 - 'D' // 6b4f #3 - 'A' // 6b50 #0 - 'B' // 6b51 #1 - 'C' // 6b52 #2 - 'F' // 6b53 #5 - 'A' // 6b54 #0 - 'B' // 6b55 #1 - 'C' // 6b56 #2 - 'B' // 6b57 #1 - 'F' // 6b58 #5 - 'A' // 6b59 #0 - 'K' // 6b5a #10 - 'A' // 6b5b #0 - 'B' // 6b5c #1 - 'F' // 6b5d #5 - 'B' // 6b5e #1 - 'hA' // 6b5f-6b67 #0 - 'D' // 6b68 #3 - 'J' // 6b69 #9 - 'A' // 6b6a #0 - 'aF' // 6b6b-6b6c #5 - 'B' // 6b6d #1 - 'F' // 6b6e #5 - 'A' // 6b6f #0 - 'F' // 6b70 #5 - 'D' // 6b71 #3 - 'A' // 6b72 #0 - 'F' // 6b73 #5 - 'C' // 6b74 #2 - 'F' // 6b75 #5 - 'B' // 6b76 #1 - 'dA' // 6b77-6b7b #0 - 'D' // 6b7c #3 - 'F' // 6b7d #5 - 'C' // 6b7e #2 - 'aA' // 6b7f-6b80 #0 - 'C' // 6b81 #2 - 'bA' // 6b82-6b84 #0 - 'F' // 6b85 #5 - 'A' // 6b86 #0 - 'D' // 6b87 #3 - 'B' // 6b88 #1 - 'aA' // 6b89-6b8a #0 - 'F' // 6b8b #5 - 'B' // 6b8c #1 - 'A' // 6b8d #0 - 'aB' // 6b8e-6b8f #1 - 'D' // 6b90 #3 - 'I' // 6b91 #8 - 'aD' // 6b92-6b93 #3 - 'B' // 6b94 #1 - 'C' // 6b95 #2 - 'A' // 6b96 #0 - 'C' // 6b97 #2 - 'A' // 6b98 #0 - 'B' // 6b99 #1 - 'D' // 6b9a #3 - 'C' // 6b9b #2 - 'aD' // 6b9c-6b9d #3 - 'A' // 6b9e #0 - 'aC' // 6b9f-6ba0 #2 - 'D' // 6ba1 #3 - 'A' // 6ba2 #0 - 'C' // 6ba3 #2 - 'A' // 6ba4 #0 - 'bB' // 6ba5-6ba7 #1 - 'aF' // 6ba8-6ba9 #5 - 'C' // 6baa #2 - 'A' // 6bab #0 - 'F' // 6bac #5 - 'bA' // 6bad-6baf #0 - 'C' // 6bb0 #2 - 'F' // 6bb1 #5 - 'aA' // 6bb2-6bb3 #0 - 'F' // 6bb4 #5 - 'A' // 6bb5 #0 - 'B' // 6bb6 #1 - 'A' // 6bb7 #0 - 'aF' // 6bb8-6bb9 #5 - 'A' // 6bba #0 - 'F' // 6bbb #5 - 'aA' // 6bbc-6bbd #0 - 'F' // 6bbe #5 - 'aA' // 6bbf-6bc0 #0 - 'I' // 6bc1 #8 - 'D' // 6bc2 #3 - 'C' // 6bc3 #2 - 'bA' // 6bc4-6bc6 #0 - 'bC' // 6bc7-6bc9 #2 - 'B' // 6bca #1 - 'A' // 6bcb #0 - 'C' // 6bcc #2 - 'A' // 6bcd #0 - 'F' // 6bce #5 - 'A' // 6bcf #0 - 'B' // 6bd0 #1 - 'D' // 6bd1 #3 - 'bA' // 6bd2-6bd4 #0 - 'D' // 6bd5 #3 - 'bA' // 6bd6-6bd8 #0 - 'D' // 6bd9 #3 - 'aA' // 6bda-6bdb #0 - 'B' // 6bdc #1 - 'D' // 6bdd #3 - 'B' // 6bde #1 - 'F' // 6bdf #5 - 'B' // 6be0 #1 - 'C' // 6be1 #2 - 'B' // 6be2 #1 - 'C' // 6be3 #2 - 'B' // 6be4 #1 - 'D' // 6be5 #3 - 'aC' // 6be6-6be7 #2 - 'B' // 6be8 #1 - 'D' // 6be9 #3 - 'B' // 6bea #1 - 'aA' // 6beb-6bec #0 - 'D' // 6bed #3 - 'F' // 6bee #5 - 'A' // 6bef #0 - 'B' // 6bf0 #1 - 'F' // 6bf1 #5 - 'B' // 6bf2 #1 - 'A' // 6bf3 #0 - 'bD' // 6bf4-6bf6 #3 - 'C' // 6bf7 #2 - 'I' // 6bf8 #8 - 'C' // 6bf9 #2 - 'dB' // 6bfa-6bfe #1 - 'A' // 6bff #0 - 'aB' // 6c00-6c01 #1 - 'C' // 6c02 #2 - 'B' // 6c03 #1 - 'C' // 6c04 #2 - 'A' // 6c05 #0 - 'B' // 6c06 #1 - 'D' // 6c07 #3 - 'A' // 6c08 #0 - 'C' // 6c09 #2 - 'F' // 6c0a #5 - 'aB' // 6c0b-6c0c #1 - 'C' // 6c0d #2 - 'F' // 6c0e #5 - 'bA' // 6c0f-6c11 #0 - 'F' // 6c12 #5 - 'aA' // 6c13-6c14 #0 - 'aB' // 6c15-6c16 #1 - 'J' // 6c17 #9 - 'B' // 6c18 #1 - 'C' // 6c19 #2 - 'B' // 6c1a #1 - 'A' // 6c1b #0 - 'aB' // 6c1c-6c1d #1 - 'D' // 6c1e #3 - 'C' // 6c1f #2 - 'aB' // 6c20-6c21 #1 - 'D' // 6c22 #3 - 'aA' // 6c23-6c24 #0 - 'B' // 6c25 #1 - 'bC' // 6c26-6c28 #2 - 'D' // 6c29 #3 - 'aB' // 6c2a-6c2b #1 - 'C' // 6c2c #2 - 'D' // 6c2d #3 - 'C' // 6c2e #2 - 'cB' // 6c2f-6c32 #1 - 'eA' // 6c33-6c38 #0 - 'B' // 6c39 #1 - 'aC' // 6c3a-6c3b #2 - 'D' // 6c3c #3 - 'B' // 6c3d #1 - 'dA' // 6c3e-6c42 #0 - 'I' // 6c43 #8 - 'aD' // 6c44-6c45 #3 - 'B' // 6c46 #1 - 'aD' // 6c47-6c48 #3 - 'B' // 6c49 #1 - 'C' // 6c4a #2 - 'A' // 6c4b #0 - 'B' // 6c4c #1 - 'C' // 6c4d #2 - 'bA' // 6c4e-6c50 #0 - 'D' // 6c51 #3 - 'A' // 6c52 #0 - 'K' // 6c53 #10 - 'aA' // 6c54-6c55 #0 - 'D' // 6c56 #3 - 'A' // 6c57 #0 - 'B' // 6c58 #1 - 'gA' // 6c59-6c60 #0 - 'B' // 6c61 #1 - 'F' // 6c62 #5 - 'aD' // 6c63-6c64 #3 - 'B' // 6c65 #1 - 'I' // 6c66 #8 - 'C' // 6c67 #2 - 'A' // 6c68 #0 - 'I' // 6c69 #8 - 'A' // 6c6a #0 - 'C' // 6c6b #2 - 'D' // 6c6c #3 - 'A' // 6c6d #0 - 'B' // 6c6e #1 - 'C' // 6c6f #2 - 'A' // 6c70 #0 - 'B' // 6c71 #1 - 'A' // 6c72 #0 - 'C' // 6c73 #2 - 'A' // 6c74 #0 - 'B' // 6c75 #1 - 'A' // 6c76 #0 - 'D' // 6c77 #3 - 'aC' // 6c78-6c79 #2 - 'A' // 6c7a #0 - 'C' // 6c7b #2 - 'D' // 6c7c #3 - 'aA' // 6c7d-6c7e #0 - 'aB' // 6c7f-6c80 #1 - 'hA' // 6c81-6c89 #0 - 'aB' // 6c8a-6c8b #1 - 'aA' // 6c8c-6c8d #0 - 'aB' // 6c8e-6c8f #1 - 'A' // 6c90 #0 - 'D' // 6c91 #3 - 'dA' // 6c92-6c96 #0 - 'F' // 6c97 #5 - 'cA' // 6c98-6c9b #0 - 'C' // 6c9c #2 - 'B' // 6c9d #1 - 'D' // 6c9e #3 - 'C' // 6c9f #2 - 'D' // 6ca0 #3 - 'F' // 6ca1 #5 - 'A' // 6ca2 #0 - 'fD' // 6ca3-6ca9 #3 - 'C' // 6caa #2 - 'aA' // 6cab-6cac #0 - 'C' // 6cad #2 - 'A' // 6cae #0 - 'B' // 6caf #1 - 'aA' // 6cb0-6cb1 #0 - 'C' // 6cb2 #2 - 'A' // 6cb3 #0 - 'C' // 6cb4 #2 - 'D' // 6cb5 #3 - 'I' // 6cb6 #8 - 'B' // 6cb7 #1 - 'aA' // 6cb8-6cb9 #0 - 'C' // 6cba #2 - 'dA' // 6cbb-6cbf #0 - 'B' // 6cc0 #1 - 'aA' // 6cc1-6cc2 #0 - 'B' // 6cc3 #1 - 'bA' // 6cc4-6cc6 #0 - 'B' // 6cc7 #1 - 'D' // 6cc8 #3 - 'aA' // 6cc9-6cca #0 - 'B' // 6ccb #1 - 'A' // 6ccc #0 - 'C' // 6ccd #2 - 'B' // 6cce #1 - 'C' // 6ccf #2 - 'aA' // 6cd0-6cd1 #0 - 'C' // 6cd2 #2 - 'bA' // 6cd3-6cd5 #0 - 'C' // 6cd6 #2 - 'A' // 6cd7 #0 - 'D' // 6cd8 #3 - 'dA' // 6cd9-6cdd #0 - 'aB' // 6cde-6cdf #1 - 'cA' // 6ce0-6ce3 #0 - 'D' // 6ce4 #3 - 'A' // 6ce5 #0 - 'D' // 6ce6 #3 - 'C' // 6ce7 #2 - 'A' // 6ce8 #0 - 'aC' // 6ce9-6cea #2 - 'A' // 6ceb #0 - 'aC' // 6cec-6ced #2 - 'cA' // 6cee-6cf1 #0 - 'C' // 6cf2 #2 - 'A' // 6cf3 #0 - 'F' // 6cf4 #5 - 'B' // 6cf5 #1 - 'bD' // 6cf6-6cf8 #3 - 'B' // 6cf9 #1 - 'D' // 6cfa #3 - 'F' // 6cfb #5 - 'bD' // 6cfc-6cfe #3 - 'I' // 6cff #8 - 'aC' // 6d00-6d01 #2 - 'aB' // 6d02-6d03 #1 - 'A' // 6d04 #0 - 'aB' // 6d05-6d06 #1 - 'A' // 6d07 #0 - 'aB' // 6d08-6d09 #1 - 'bA' // 6d0a-6d0c #0 - 'B' // 6d0d #1 - 'aC' // 6d0e-6d0f #2 - 'B' // 6d10 #1 - 'aA' // 6d11-6d12 #0 - 'F' // 6d13 #5 - 'K' // 6d14 #10 - 'D' // 6d15 #3 - 'B' // 6d16 #1 - 'A' // 6d17 #0 - 'B' // 6d18 #1 - 'A' // 6d19 #0 - 'C' // 6d1a #2 - 'A' // 6d1b #0 - 'D' // 6d1c #3 - 'B' // 6d1d #1 - 'aA' // 6d1e-6d1f #0 - 'B' // 6d20 #1 - 'D' // 6d21 #3 - 'B' // 6d22 #1 - 'K' // 6d23 #10 - 'C' // 6d24 #2 - 'A' // 6d25 #0 - 'C' // 6d26 #2 - 'dA' // 6d27-6d2b #0 - 'I' // 6d2c #8 - 'B' // 6d2d #1 - 'A' // 6d2e #0 - 'C' // 6d2f #2 - 'B' // 6d30 #1 - 'C' // 6d31 #2 - 'A' // 6d32 #0 - 'aC' // 6d33-6d34 #2 - 'aA' // 6d35-6d36 #0 - 'B' // 6d37 #1 - 'aA' // 6d38-6d39 #0 - 'I' // 6d3a #8 - 'cA' // 6d3b-6d3e #0 - 'C' // 6d3f #2 - 'B' // 6d40 #1 - 'A' // 6d41 #0 - 'B' // 6d42 #1 - 'D' // 6d43 #3 - 'aF' // 6d44-6d45 #5 - 'gD' // 6d46-6d4d #3 - 'B' // 6d4e #1 - 'gD' // 6d4f-6d56 #3 - 'aC' // 6d57-6d58 #2 - 'aA' // 6d59-6d5a #0 - 'C' // 6d5b #2 - 'A' // 6d5c #0 - 'D' // 6d5d #3 - 'bC' // 6d5e-6d60 #2 - 'A' // 6d61 #0 - 'B' // 6d62 #1 - 'dA' // 6d63-6d67 #0 - 'B' // 6d68 #1 - 'aA' // 6d69-6d6a #0 - 'D' // 6d6b #3 - 'A' // 6d6c #0 - 'B' // 6d6d #1 - 'aA' // 6d6e-6d6f #0 - 'C' // 6d70 #2 - 'B' // 6d71 #1 - 'I' // 6d72 #8 - 'D' // 6d73 #3 - 'A' // 6d74 #0 - 'aB' // 6d75-6d76 #1 - 'bA' // 6d77-6d79 #0 - 'aB' // 6d7a-6d7b #1 - 'C' // 6d7c #2 - 'aB' // 6d7d-6d7e #1 - 'I' // 6d7f #8 - 'aC' // 6d80-6d81 #2 - 'A' // 6d82 #0 - 'aB' // 6d83-6d84 #1 - 'A' // 6d85 #0 - 'B' // 6d86 #1 - 'bA' // 6d87-6d89 #0 - 'C' // 6d8a #2 - 'B' // 6d8b #1 - 'bA' // 6d8c-6d8e #0 - 'I' // 6d8f #8 - 'B' // 6d90 #1 - 'A' // 6d91 #0 - 'C' // 6d92 #2 - 'dA' // 6d93-6d97 #0 - 'C' // 6d98 #2 - 'F' // 6d99 #5 - 'B' // 6d9a #1 - 'aF' // 6d9b-6d9c #5 - 'fD' // 6d9d-6da3 #3 - 'aB' // 6da4-6da5 #1 - 'cD' // 6da6-6da9 #3 - 'bA' // 6daa-6dac #0 - 'D' // 6dad #3 - 'C' // 6dae #2 - 'A' // 6daf #0 - 'D' // 6db0 #3 - 'B' // 6db1 #1 - 'A' // 6db2 #0 - 'B' // 6db3 #1 - 'aA' // 6db4-6db5 #0 - 'D' // 6db6 #3 - 'aA' // 6db7-6db8 #0 - 'C' // 6db9 #2 - 'aB' // 6dba-6dbb #1 - 'A' // 6dbc #0 - 'C' // 6dbd #2 - 'B' // 6dbe #1 - 'aA' // 6dbf-6dc0 #0 - 'D' // 6dc1 #3 - 'C' // 6dc2 #2 - 'K' // 6dc3 #10 - 'dA' // 6dc4-6dc8 #0 - 'B' // 6dc9 #1 - 'C' // 6dca #2 - 'aA' // 6dcb-6dcc #0 - 'B' // 6dcd #1 - 'F' // 6dce #5 - 'cA' // 6dcf-6dd2 #0 - 'aB' // 6dd3-6dd4 #1 - 'C' // 6dd5 #2 - 'A' // 6dd6 #0 - 'B' // 6dd7 #1 - 'bA' // 6dd8-6dda #0 - 'C' // 6ddb #2 - 'B' // 6ddc #1 - 'aA' // 6ddd-6dde #0 - 'C' // 6ddf #2 - 'bA' // 6de0-6de2 #0 - 'I' // 6de3 #8 - 'bA' // 6de4-6de6 #0 - 'D' // 6de7 #3 - 'A' // 6de8 #0 - 'C' // 6de9 #2 - 'bA' // 6dea-6dec #0 - 'B' // 6ded #1 - 'A' // 6dee #0 - 'aC' // 6def-6df0 #2 - 'A' // 6df1 #0 - 'C' // 6df2 #2 - 'A' // 6df3 #0 - 'C' // 6df4 #2 - 'bA' // 6df5-6df7 #0 - 'J' // 6df8 #9 - 'cA' // 6df9-6dfc #0 - 'aB' // 6dfd-6dfe #1 - 'D' // 6dff #3 - 'C' // 6e00 #2 - 'D' // 6e01 #3 - 'aB' // 6e02-6e03 #1 - 'C' // 6e04 #2 - 'A' // 6e05 #0 - 'D' // 6e06 #3 - 'F' // 6e07 #5 - 'J' // 6e08 #9 - 'F' // 6e09 #5 - 'A' // 6e0a #0 - 'F' // 6e0b #5 - 'bD' // 6e0c-6e0e #3 - 'B' // 6e0f #1 - 'bD' // 6e10-6e12 #3 - 'F' // 6e13 #5 - 'D' // 6e14 #3 - 'C' // 6e15 #2 - 'D' // 6e16 #3 - 'J' // 6e17 #9 - 'B' // 6e18 #1 - 'bA' // 6e19-6e1b #0 - 'B' // 6e1c #1 - 'A' // 6e1d #0 - 'F' // 6e1e #5 - 'gA' // 6e1f-6e26 #0 - 'C' // 6e27 #2 - 'I' // 6e28 #8 - 'C' // 6e29 #2 - 'B' // 6e2a #1 - 'bA' // 6e2b-6e2d #0 - 'C' // 6e2e #2 - 'A' // 6e2f #0 - 'aB' // 6e30-6e31 #1 - 'A' // 6e32 #0 - 'B' // 6e33 #1 - 'A' // 6e34 #0 - 'B' // 6e35 #1 - 'A' // 6e36 #0 - 'K' // 6e37 #10 - 'A' // 6e38 #0 - 'C' // 6e39 #2 - 'A' // 6e3a #0 - 'C' // 6e3b #2 - 'A' // 6e3c #0 - 'I' // 6e3d #8 - 'A' // 6e3e #0 - 'B' // 6e3f #1 - 'I' // 6e40 #8 - 'B' // 6e41 #1 - 'F' // 6e42 #5 - 'bA' // 6e43-6e45 #0 - 'aB' // 6e46-6e47 #1 - 'F' // 6e48 #5 - 'C' // 6e49 #2 - 'A' // 6e4a #0 - 'C' // 6e4b #2 - 'F' // 6e4c #5 - 'aA' // 6e4d-6e4e #0 - 'C' // 6e4f #2 - 'B' // 6e50 #1 - 'A' // 6e51 #0 - 'C' // 6e52 #2 - 'aA' // 6e53-6e54 #0 - 'I' // 6e55 #8 - 'A' // 6e56 #0 - 'C' // 6e57 #2 - 'A' // 6e58 #0 - 'aB' // 6e59-6e5a #1 - 'aA' // 6e5b-6e5c #0 - 'C' // 6e5d #2 - 'aA' // 6e5e-6e5f #0 - 'aB' // 6e60-6e61 #1 - 'C' // 6e62 #2 - 'A' // 6e63 #0 - 'bB' // 6e64-6e66 #1 - 'A' // 6e67 #0 - 'C' // 6e68 #2 - 'B' // 6e69 #1 - 'D' // 6e6a #3 - 'A' // 6e6b #0 - 'aD' // 6e6c-6e6d #3 - 'aA' // 6e6e-6e6f #0 - 'D' // 6e70 #3 - 'B' // 6e71 #1 - 'aA' // 6e72-6e73 #0 - 'B' // 6e74 #1 - 'K' // 6e75 #10 - 'C' // 6e76 #2 - 'bB' // 6e77-6e79 #1 - 'K' // 6e7a #10 - 'F' // 6e7b #5 - 'B' // 6e7c #1 - 'cF' // 6e7d-6e80 #5 - 'D' // 6e81 #3 - 'F' // 6e82 #5 - 'bD' // 6e83-6e85 #3 - 'B' // 6e86 #1 - 'D' // 6e87 #3 - 'B' // 6e88 #1 - 'C' // 6e89 #2 - 'D' // 6e8a #3 - 'B' // 6e8b #1 - 'F' // 6e8c #5 - 'C' // 6e8d #2 - 'B' // 6e8e #1 - 'aA' // 6e8f-6e90 #0 - 'D' // 6e91 #3 - 'B' // 6e92 #1 - 'C' // 6e93 #2 - 'B' // 6e94 #1 - 'K' // 6e95 #10 - 'A' // 6e96 #0 - 'B' // 6e97 #1 - 'A' // 6e98 #0 - 'C' // 6e99 #2 - 'aB' // 6e9a-6e9b #1 - 'aA' // 6e9c-6e9d #0 - 'B' // 6e9e #1 - 'A' // 6e9f #0 - 'C' // 6ea0 #2 - 'B' // 6ea1 #1 - 'A' // 6ea2 #0 - 'aB' // 6ea3-6ea4 #1 - 'A' // 6ea5 #0 - 'B' // 6ea6 #1 - 'A' // 6ea7 #0 - 'K' // 6ea8 #10 - 'D' // 6ea9 #3 - 'aA' // 6eaa-6eab #0 - 'D' // 6eac #3 - 'F' // 6ead #5 - 'C' // 6eae #2 - 'A' // 6eaf #0 - 'B' // 6eb0 #1 - 'aA' // 6eb1-6eb2 #0 - 'aC' // 6eb3-6eb4 #2 - 'I' // 6eb5 #8 - 'aA' // 6eb6-6eb7 #0 - 'aB' // 6eb8-6eb9 #1 - 'A' // 6eba #0 - 'aC' // 6ebb-6ebc #2 - 'A' // 6ebd #0 - 'B' // 6ebe #1 - 'bC' // 6ebf-6ec1 #2 - 'cA' // 6ec2-6ec5 #0 - 'B' // 6ec6 #1 - 'C' // 6ec7 #2 - 'aA' // 6ec8-6ec9 #0 - 'C' // 6eca #2 - 'aA' // 6ecb-6ecc #0 - 'C' // 6ecd #2 - 'A' // 6ece #0 - 'C' // 6ecf #2 - 'B' // 6ed0 #1 - 'A' // 6ed1 #0 - 'B' // 6ed2 #1 - 'bA' // 6ed3-6ed5 #0 - 'B' // 6ed6 #1 - 'D' // 6ed7 #3 - 'B' // 6ed8 #1 - 'A' // 6ed9 #0 - 'aC' // 6eda-6edb #2 - 'B' // 6edc #1 - 'C' // 6edd #2 - 'F' // 6ede #5 - 'bD' // 6edf-6ee1 #3 - 'B' // 6ee2 #1 - 'bD' // 6ee3-6ee5 #3 - 'F' // 6ee6 #5 - 'D' // 6ee7 #3 - 'aB' // 6ee8-6ee9 #1 - 'D' // 6eea #3 - 'C' // 6eeb #2 - 'aA' // 6eec-6eed #0 - 'C' // 6eee #2 - 'A' // 6eef #0 - 'D' // 6ef0 #3 - 'B' // 6ef1 #1 - 'A' // 6ef2 #0 - 'D' // 6ef3 #3 - 'A' // 6ef4 #0 - 'I' // 6ef5 #8 - 'B' // 6ef6 #1 - 'aA' // 6ef7-6ef8 #0 - 'C' // 6ef9 #2 - 'B' // 6efa #1 - 'C' // 6efb #2 - 'I' // 6efc #8 - 'C' // 6efd #2 - 'aA' // 6efe-6eff #0 - 'B' // 6f00 #1 - 'aA' // 6f01-6f02 #0 - 'B' // 6f03 #1 - 'A' // 6f04 #0 - 'B' // 6f05 #1 - 'A' // 6f06 #0 - 'B' // 6f07 #1 - 'C' // 6f08 #2 - 'A' // 6f09 #0 - 'C' // 6f0a #2 - 'B' // 6f0b #1 - 'A' // 6f0c #0 - 'C' // 6f0d #2 - 'B' // 6f0e #1 - 'A' // 6f0f #0 - 'F' // 6f10 #5 - 'J' // 6f11 #9 - 'B' // 6f12 #1 - 'bA' // 6f13-6f15 #0 - 'C' // 6f16 #2 - 'B' // 6f17 #1 - 'C' // 6f18 #2 - 'I' // 6f19 #8 - 'A' // 6f1a #0 - 'F' // 6f1b #5 - 'B' // 6f1c #1 - 'D' // 6f1d #3 - 'aB' // 6f1e-6f1f #1 - 'A' // 6f20 #0 - 'B' // 6f21 #1 - 'aA' // 6f22-6f23 #0 - 'I' // 6f24 #8 - 'C' // 6f25 #2 - 'A' // 6f26 #0 - 'I' // 6f27 #8 - 'K' // 6f28 #10 - 'C' // 6f29 #2 - 'cA' // 6f2a-6f2d #0 - 'B' // 6f2e #1 - 'C' // 6f2f #2 - 'cA' // 6f30-6f33 #0 - 'B' // 6f34 #1 - 'aC' // 6f35-6f36 #2 - 'B' // 6f37 #1 - 'A' // 6f38 #0 - 'aB' // 6f39-6f3a #1 - 'C' // 6f3b #2 - 'A' // 6f3c #0 - 'B' // 6f3d #1 - 'aA' // 6f3e-6f3f #0 - 'B' // 6f40 #1 - 'A' // 6f41 #0 - 'D' // 6f42 #3 - 'aB' // 6f43-6f44 #1 - 'F' // 6f45 #5 - 'gD' // 6f46-6f4d #3 - 'B' // 6f4e #1 - 'A' // 6f4f #0 - 'B' // 6f50 #1 - 'aA' // 6f51-6f52 #0 - 'C' // 6f53 #2 - 'A' // 6f54 #0 - 'aB' // 6f55-6f56 #1 - 'aA' // 6f57-6f58 #0 - 'J' // 6f59 #9 - 'eA' // 6f5a-6f5f #0 - 'C' // 6f60 #2 - 'aA' // 6f61-6f62 #0 - 'I' // 6f63 #8 - 'A' // 6f64 #0 - 'D' // 6f65 #3 - 'A' // 6f66 #0 - 'B' // 6f67 #1 - 'F' // 6f68 #5 - 'bB' // 6f69-6f6b #1 - 'C' // 6f6c #2 - 'cA' // 6f6d-6f70 #0 - 'K' // 6f71 #10 - 'aB' // 6f72-6f73 #1 - 'A' // 6f74 #0 - 'D' // 6f75 #3 - 'aB' // 6f76-6f77 #1 - 'A' // 6f78 #0 - 'B' // 6f79 #1 - 'A' // 6f7a #0 - 'B' // 6f7b #1 - 'bA' // 6f7c-6f7e #0 - 'B' // 6f7f #1 - 'C' // 6f80 #2 - 'aA' // 6f81-6f82 #0 - 'F' // 6f83 #5 - 'A' // 6f84 #0 - 'B' // 6f85 #1 - 'bA' // 6f86-6f88 #0 - 'I' // 6f89 #8 - 'B' // 6f8a #1 - 'cA' // 6f8b-6f8e #0 - 'D' // 6f8f #3 - 'A' // 6f90 #0 - 'F' // 6f91 #5 - 'A' // 6f92 #0 - 'C' // 6f93 #2 - 'A' // 6f94 #0 - 'I' // 6f95 #8 - 'aA' // 6f96-6f97 #0 - 'J' // 6f98 #9 - 'D' // 6f99 #3 - 'F' // 6f9a #5 - 'aD' // 6f9b-6f9c #3 - 'C' // 6f9d #2 - 'B' // 6f9e #1 - 'A' // 6f9f #0 - 'C' // 6fa0 #2 - 'A' // 6fa1 #0 - 'B' // 6fa2 #1 - 'bA' // 6fa3-6fa5 #0 - 'C' // 6fa6 #2 - 'aA' // 6fa7-6fa8 #0 - 'B' // 6fa9 #1 - 'A' // 6faa #0 - 'bB' // 6fab-6fad #1 - 'aA' // 6fae-6faf #0 - 'C' // 6fb0 #2 - 'A' // 6fb1 #0 - 'B' // 6fb2 #1 - 'A' // 6fb3 #0 - 'B' // 6fb4 #1 - 'C' // 6fb5 #2 - 'A' // 6fb6 #0 - 'F' // 6fb7 #5 - 'B' // 6fb8 #1 - 'A' // 6fb9 #0 - 'aB' // 6fba-6fbb #1 - 'C' // 6fbc #2 - 'B' // 6fbd #1 - 'A' // 6fbe #0 - 'B' // 6fbf #1 - 'cA' // 6fc0-6fc3 #0 - 'B' // 6fc4 #1 - 'F' // 6fc5 #5 - 'aA' // 6fc6-6fc7 #0 - 'C' // 6fc8 #2 - 'aA' // 6fc9-6fca #0 - 'dB' // 6fcb-6fcf #1 - 'bD' // 6fd0-6fd2 #3 - 'B' // 6fd3 #1 - 'aA' // 6fd4-6fd5 #0 - 'aD' // 6fd6-6fd7 #3 - 'A' // 6fd8 #0 - 'B' // 6fd9 #1 - 'aA' // 6fda-6fdb #0 - 'aB' // 6fdc-6fdd #1 - 'cA' // 6fde-6fe1 #0 - 'aB' // 6fe2-6fe3 #1 - 'A' // 6fe4 #0 - 'K' // 6fe5 #10 - 'I' // 6fe6 #8 - 'B' // 6fe7 #1 - 'C' // 6fe8 #2 - 'A' // 6fe9 #0 - 'D' // 6fea #3 - 'aA' // 6feb-6fec #0 - 'B' // 6fed #1 - 'aA' // 6fee-6fef #0 - 'C' // 6ff0 #2 - 'A' // 6ff1 #0 - 'B' // 6ff2 #1 - 'J' // 6ff3 #9 - 'I' // 6ff4 #8 - 'F' // 6ff5 #5 - 'A' // 6ff6 #0 - 'aB' // 6ff7-6ff8 #1 - 'F' // 6ff9 #5 - 'A' // 6ffa #0 - 'B' // 6ffb #1 - 'C' // 6ffc #2 - 'F' // 6ffd #5 - 'A' // 6ffe #0 - 'B' // 6fff #1 - 'C' // 7000 #2 - 'A' // 7001 #0 - 'D' // 7002 #3 - 'aB' // 7003-7004 #1 - 'bA' // 7005-7007 #0 - 'D' // 7008 #3 - 'A' // 7009 #0 - 'C' // 700a #2 - 'A' // 700b #0 - 'B' // 700c #1 - 'C' // 700d #2 - 'B' // 700e #1 - 'A' // 700f #0 - 'D' // 7010 #3 - 'A' // 7011 #0 - 'aK' // 7012-7013 #10 - 'B' // 7014 #1 - 'A' // 7015 #0 - 'B' // 7016 #1 - 'C' // 7017 #2 - 'A' // 7018 #0 - 'B' // 7019 #1 - 'aA' // 701a-701b #0 - 'I' // 701c #8 - 'bA' // 701d-701f #0 - 'C' // 7020 #2 - 'aB' // 7021-7022 #1 - 'A' // 7023 #0 - 'B' // 7024 #1 - 'D' // 7025 #3 - 'bA' // 7026-7028 #0 - 'bB' // 7029-702b #1 - 'C' // 702c #2 - 'aD' // 702d-702e #3 - 'aA' // 702f-7030 #0 - 'B' // 7031 #1 - 'A' // 7032 #0 - 'B' // 7033 #1 - 'C' // 7034 #2 - 'B' // 7035 #1 - 'D' // 7036 #3 - 'A' // 7037 #0 - 'I' // 7038 #8 - 'aC' // 7039-703a #2 - 'B' // 703b #1 - 'A' // 703c #0 - 'D' // 703d #3 - 'A' // 703e #0 - 'cB' // 703f-7042 #1 - 'C' // 7043 #2 - 'A' // 7044 #0 - 'B' // 7045 #1 - 'I' // 7046 #8 - 'F' // 7047 #5 - 'cC' // 7048-704b #2 - 'A' // 704c #0 - 'B' // 704d #1 - 'J' // 704e #9 - 'D' // 704f #3 - 'I' // 7050 #8 - 'A' // 7051 #0 - 'B' // 7052 #1 - 'K' // 7053 #10 - 'aC' // 7054-7055 #2 - 'aB' // 7056-7057 #1 - 'A' // 7058 #0 - 'D' // 7059 #3 - 'bB' // 705a-705c #1 - 'aA' // 705d-705e #0 - 'cB' // 705f-7062 #1 - 'A' // 7063 #0 - 'aC' // 7064-7065 #2 - 'I' // 7066 #8 - 'aB' // 7067-7068 #1 - 'A' // 7069 #0 - 'B' // 706a #1 - 'aA' // 706b-706c #0 - 'D' // 706d #3 - 'C' // 706e #2 - 'aA' // 706f-7070 #0 - 'B' // 7071 #1 - 'aD' // 7072-7073 #3 - 'B' // 7074 #1 - 'aC' // 7075-7076 #2 - 'B' // 7077 #1 - 'A' // 7078 #0 - 'aB' // 7079-707a #1 - 'D' // 707b #3 - 'bA' // 707c-707e #0 - 'B' // 707f #1 - 'D' // 7080 #3 - 'A' // 7081 #0 - 'bB' // 7082-7084 #1 - 'aA' // 7085-7086 #0 - 'aD' // 7087-7088 #3 - 'C' // 7089 #2 - 'A' // 708a #0 - 'B' // 708b #1 - 'aD' // 708c-708d #3 - 'A' // 708e #0 - 'B' // 708f #1 - 'D' // 7090 #3 - 'B' // 7091 #1 - 'A' // 7092 #0 - 'B' // 7093 #1 - 'C' // 7094 #2 - 'A' // 7095 #0 - 'C' // 7096 #2 - 'F' // 7097 #5 - 'aA' // 7098-7099 #0 - 'I' // 709a #8 - 'J' // 709b #9 - 'bD' // 709c-709e #3 - 'C' // 709f #2 - 'B' // 70a0 #1 - 'I' // 70a1 #8 - 'D' // 70a2 #3 - 'B' // 70a3 #1 - 'A' // 70a4 #0 - 'B' // 70a5 #1 - 'I' // 70a6 #8 - 'B' // 70a7 #1 - 'D' // 70a8 #3 - 'B' // 70a9 #1 - 'D' // 70aa #3 - 'eA' // 70ab-70b0 #0 - 'C' // 70b1 #2 - 'D' // 70b2 #3 - 'A' // 70b3 #0 - 'C' // 70b4 #2 - 'B' // 70b5 #1 - 'D' // 70b6 #3 - 'cA' // 70b7-70ba #0 - 'C' // 70bb #2 - 'bB' // 70bc-70be #1 - 'D' // 70bf #3 - 'B' // 70c0 #1 - 'bD' // 70c1-70c3 #3 - 'cB' // 70c4-70c7 #1 - 'A' // 70c8 #0 - 'D' // 70c9 #3 - 'aA' // 70ca-70cb #0 - 'bB' // 70cc-70ce #1 - 'A' // 70cf #0 - 'B' // 70d0 #1 - 'C' // 70d1 #2 - 'B' // 70d2 #1 - 'aA' // 70d3-70d4 #0 - 'aC' // 70d5-70d6 #2 - 'B' // 70d7 #1 - 'aA' // 70d8-70d9 #0 - 'B' // 70da #1 - 'D' // 70db #3 - 'aA' // 70dc-70dd #0 - 'B' // 70de #1 - 'A' // 70df #0 - 'bB' // 70e0-70e2 #1 - 'D' // 70e3 #3 - 'C' // 70e4 #2 - 'fD' // 70e5-70eb #3 - 'F' // 70ec #5 - 'aD' // 70ed-70ee #3 - 'I' // 70ef #8 - 'B' // 70f0 #1 - 'A' // 70f1 #0 - 'D' // 70f2 #3 - 'eB' // 70f3-70f8 #1 - 'aA' // 70f9-70fa #0 - 'aB' // 70fb-70fc #1 - 'A' // 70fd #0 - 'bB' // 70fe-7100 #1 - 'D' // 7101 #3 - 'B' // 7102 #1 - 'J' // 7103 #9 - 'A' // 7104 #0 - 'C' // 7105 #2 - 'A' // 7106 #0 - 'aF' // 7107-7108 #5 - 'A' // 7109 #0 - 'B' // 710a #1 - 'C' // 710b #2 - 'A' // 710c #0 - 'aB' // 710d-710e #1 - 'F' // 710f #5 - 'B' // 7110 #1 - 'aD' // 7111-7112 #3 - 'B' // 7113 #1 - 'F' // 7114 #5 - 'aD' // 7115-7116 #3 - 'B' // 7117 #1 - 'D' // 7118 #3 - 'aA' // 7119-711a #0 - 'B' // 711b #1 - 'A' // 711c #0 - 'B' // 711d #1 - 'A' // 711e #0 - 'B' // 711f #1 - 'aA' // 7120-7121 #0 - 'aB' // 7122-7123 #1 - 'D' // 7124 #3 - 'B' // 7125 #1 - 'A' // 7126 #0 - 'D' // 7127 #3 - 'aB' // 7128-7129 #1 - 'D' // 712a #3 - 'C' // 712b #2 - 'B' // 712c #1 - 'J' // 712d #9 - 'cA' // 712e-7131 #0 - 'cB' // 7132-7135 #1 - 'A' // 7136 #0 - 'D' // 7137 #3 - 'F' // 7138 #5 - 'D' // 7139 #3 - 'aB' // 713a-713b #1 - 'F' // 713c #5 - 'D' // 713d #3 - 'B' // 713e #1 - 'D' // 713f #3 - 'B' // 7140 #1 - 'C' // 7141 #2 - 'B' // 7142 #1 - 'I' // 7143 #8 - 'B' // 7144 #1 - 'C' // 7145 #2 - 'aA' // 7146-7147 #0 - 'D' // 7148 #3 - 'aA' // 7149-714a #0 - 'C' // 714b #2 - 'A' // 714c #0 - 'B' // 714d #1 - 'A' // 714e #0 - 'B' // 714f #1 - 'A' // 7150 #0 - 'C' // 7151 #2 - 'aA' // 7152-7153 #0 - 'B' // 7154 #1 - 'J' // 7155 #9 - 'aA' // 7156-7157 #0 - 'B' // 7158 #1 - 'A' // 7159 #0 - 'C' // 715a #2 - 'D' // 715b #3 - 'A' // 715c #0 - 'I' // 715d #8 - 'A' // 715e #0 - 'B' // 715f #1 - 'C' // 7160 #2 - 'B' // 7161 #1 - 'A' // 7162 #0 - 'B' // 7163 #1 - 'eA' // 7164-7169 #0 - 'aB' // 716a-716b #1 - 'A' // 716c #0 - 'D' // 716d #3 - 'A' // 716e #0 - 'D' // 716f #3 - 'hB' // 7170-7178 #1 - 'F' // 7179 #5 - 'bB' // 717a-717c #1 - 'A' // 717d #0 - 'B' // 717e #1 - 'D' // 717f #3 - 'A' // 7180 #0 - 'aB' // 7181-7182 #1 - 'D' // 7183 #3 - 'aA' // 7184-7185 #0 - 'B' // 7186 #1 - 'aA' // 7187-7188 #0 - 'I' // 7189 #8 - 'A' // 718a #0 - 'D' // 718b #3 - 'C' // 718c #2 - 'D' // 718d #3 - 'B' // 718e #1 - 'A' // 718f #0 - 'aB' // 7190-7191 #1 - 'A' // 7192 #0 - 'D' // 7193 #3 - 'A' // 7194 #0 - 'F' // 7195 #5 - 'C' // 7196 #2 - 'aB' // 7197-7198 #1 - 'A' // 7199 #0 - 'C' // 719a #2 - 'A' // 719b #0 - 'bB' // 719c-719e #1 - 'aA' // 719f-71a0 #0 - 'I' // 71a1 #8 - 'A' // 71a2 #0 - 'B' // 71a3 #1 - 'I' // 71a4 #8 - 'B' // 71a5 #1 - 'D' // 71a6 #3 - 'B' // 71a7 #1 - 'A' // 71a8 #0 - 'I' // 71a9 #8 - 'B' // 71aa #1 - 'D' // 71ab #3 - 'A' // 71ac #0 - 'B' // 71ad #1 - 'F' // 71ae #5 - 'A' // 71af #0 - 'C' // 71b0 #2 - 'aA' // 71b1-71b2 #0 - 'C' // 71b3 #2 - 'aB' // 71b4-71b5 #1 - 'D' // 71b6 #3 - 'aB' // 71b7-71b8 #1 - 'aA' // 71b9-71ba #0 - 'D' // 71bb #3 - 'aB' // 71bc-71bd #1 - 'A' // 71be #0 - 'aC' // 71bf-71c0 #2 - 'A' // 71c1 #0 - 'B' // 71c2 #1 - 'A' // 71c3 #0 - 'C' // 71c4 #2 - 'bB' // 71c5-71c7 #1 - 'aA' // 71c8-71c9 #0 - 'B' // 71ca #1 - 'A' // 71cb #0 - 'F' // 71cc #5 - 'D' // 71cd #3 - 'A' // 71ce #0 - 'I' // 71cf #8 - 'A' // 71d0 #0 - 'B' // 71d1 #1 - 'A' // 71d2 #0 - 'F' // 71d3 #5 - 'bA' // 71d4-71d6 #0 - 'F' // 71d7 #5 - 'B' // 71d8 #1 - 'A' // 71d9 #0 - 'C' // 71da #2 - 'I' // 71db #8 - 'C' // 71dc #2 - 'B' // 71dd #1 - 'D' // 71de #3 - 'aA' // 71df-71e0 #0 - 'aB' // 71e1-71e2 #1 - 'D' // 71e3 #3 - 'B' // 71e4 #1 - 'bA' // 71e5-71e7 #0 - 'B' // 71e8 #1 - 'aD' // 71e9-71ea #3 - 'B' // 71eb #1 - 'bA' // 71ec-71ee #0 - 'D' // 71ef #3 - 'bB' // 71f0-71f2 #1 - 'D' // 71f3 #3 - 'aC' // 71f4-71f5 #2 - 'B' // 71f6 #1 - 'D' // 71f7 #3 - 'C' // 71f8 #2 - 'A' // 71f9 #0 - 'D' // 71fa #3 - 'aA' // 71fb-71fc #0 - 'I' // 71fd #8 - 'bA' // 71fe-7200 #0 - 'I' // 7201 #8 - 'aB' // 7202-7203 #1 - 'D' // 7204 #3 - 'B' // 7205 #1 - 'aA' // 7206-7207 #0 - 'F' // 7208 #5 - 'C' // 7209 #2 - 'B' // 720a #1 - 'K' // 720b #10 - 'I' // 720c #8 - 'A' // 720d #0 - 'aB' // 720e-720f #1 - 'A' // 7210 #0 - 'aD' // 7211-7212 #3 - 'C' // 7213 #2 - 'I' // 7214 #8 - 'C' // 7215 #2 - 'B' // 7216 #1 - 'A' // 7217 #0 - 'D' // 7218 #3 - 'B' // 7219 #1 - 'aA' // 721a-721b #0 - 'D' // 721c #3 - 'C' // 721d #2 - 'B' // 721e #1 - 'A' // 721f #0 - 'aD' // 7220-7221 #3 - 'aB' // 7222-7223 #1 - 'C' // 7224 #2 - 'I' // 7225 #8 - 'aB' // 7226-7227 #1 - 'A' // 7228 #0 - 'B' // 7229 #1 - 'A' // 722a #0 - 'C' // 722b #2 - 'aA' // 722c-722d #0 - 'B' // 722e #1 - 'F' // 722f #5 - 'A' // 7230 #0 - 'D' // 7231 #3 - 'J' // 7232 #9 - 'D' // 7233 #3 - 'F' // 7234 #5 - 'aA' // 7235-7236 #0 - 'D' // 7237 #3 - 'cA' // 7238-723b #0 - 'F' // 723c #5 - 'cA' // 723d-7240 #0 - 'C' // 7241 #2 - 'A' // 7242 #0 - 'F' // 7243 #5 - 'B' // 7244 #1 - 'F' // 7245 #5 - 'bA' // 7246-7248 #0 - 'aB' // 7249-724a #1 - 'aA' // 724b-724c #0 - 'D' // 724d #3 - 'F' // 724e #5 - 'aC' // 724f-7250 #2 - 'D' // 7251 #3 - 'aA' // 7252-7253 #0 - 'K' // 7254 #10 - 'C' // 7255 #2 - 'A' // 7256 #0 - 'C' // 7257 #2 - 'cA' // 7258-725b #0 - 'C' // 725c #2 - 'A' // 725d #0 - 'C' // 725e #2 - 'A' // 725f #0 - 'C' // 7260 #2 - 'bA' // 7261-7263 #0 - 'aD' // 7264-7265 #3 - 'B' // 7266 #1 - 'A' // 7267 #0 - 'F' // 7268 #5 - 'A' // 7269 #0 - 'B' // 726a #1 - 'F' // 726b #5 - 'B' // 726c #1 - 'D' // 726d #3 - 'C' // 726e #2 - 'A' // 726f #0 - 'B' // 7270 #1 - 'F' // 7271 #5 - 'A' // 7272 #0 - 'B' // 7273 #1 - 'A' // 7274 #0 - 'D' // 7275 #3 - 'B' // 7276 #1 - 'C' // 7277 #2 - 'aA' // 7278-7279 #0 - 'D' // 727a #3 - 'aC' // 727b-727c #2 - 'A' // 727d #0 - 'aC' // 727e-727f #2 - 'bA' // 7280-7282 #0 - 'D' // 7283 #3 - 'C' // 7284 #2 - 'aB' // 7285-7286 #1 - 'A' // 7287 #0 - 'B' // 7288 #1 - 'C' // 7289 #2 - 'D' // 728a #3 - 'aB' // 728b-728c #1 - 'A' // 728d #0 - 'C' // 728e #2 - 'bB' // 728f-7291 #1 - 'A' // 7292 #0 - 'C' // 7293 #2 - 'aB' // 7294-7295 #1 - 'A' // 7296 #0 - 'aB' // 7297-7298 #1 - 'D' // 7299 #3 - 'B' // 729a #1 - 'C' // 729b #2 - 'D' // 729c #3 - 'bB' // 729d-729f #1 - 'F' // 72a0 #5 - 'B' // 72a1 #1 - 'A' // 72a2 #0 - 'cB' // 72a3-72a6 #1 - 'A' // 72a7 #0 - 'C' // 72a8 #2 - 'aB' // 72a9-72aa #1 - 'D' // 72ab #3 - 'aA' // 72ac-72ad #0 - 'C' // 72ae #2 - 'A' // 72af #0 - 'C' // 72b0 #2 - 'F' // 72b1 #5 - 'C' // 72b2 #2 - 'K' // 72b3 #10 - 'A' // 72b4 #0 - 'I' // 72b5 #8 - 'F' // 72b6 #5 - 'aD' // 72b7-72b8 #3 - 'F' // 72b9 #5 - 'B' // 72ba #1 - 'aD' // 72bb-72bc #3 - 'B' // 72bd #1 - 'F' // 72be #5 - 'B' // 72bf #1 - 'A' // 72c0 #0 - 'C' // 72c1 #2 - 'A' // 72c2 #0 - 'C' // 72c3 #2 - 'A' // 72c4 #0 - 'B' // 72c5 #1 - 'C' // 72c6 #2 - 'F' // 72c7 #5 - 'D' // 72c8 #3 - 'A' // 72c9 #0 - 'aB' // 72ca-72cb #1 - 'C' // 72cc #2 - 'B' // 72cd #1 - 'A' // 72ce #0 - 'D' // 72cf #3 - 'A' // 72d0 #0 - 'B' // 72d1 #1 - 'A' // 72d2 #0 - 'D' // 72d3 #3 - 'B' // 72d4 #1 - 'F' // 72d5 #5 - 'C' // 72d6 #2 - 'A' // 72d7 #0 - 'C' // 72d8 #2 - 'A' // 72d9 #0 - 'B' // 72da #1 - 'F' // 72db #5 - 'B' // 72dc #1 - 'aD' // 72dd-72de #3 - 'aC' // 72df-72e0 #2 - 'aA' // 72e1-72e2 #0 - 'aB' // 72e3-72e4 #1 - 'J' // 72e5 #9 - 'B' // 72e6 #1 - 'D' // 72e7 #3 - 'I' // 72e8 #8 - 'A' // 72e9 #0 - 'aB' // 72ea-72eb #1 - 'J' // 72ec #9 - 'F' // 72ed #5 - 'dD' // 72ee-72f2 #3 - 'C' // 72f3 #2 - 'A' // 72f4 #0 - 'D' // 72f5 #3 - 'B' // 72f6 #1 - 'fA' // 72f7-72fd #0 - 'C' // 72fe #2 - 'bB' // 72ff-7301 #1 - 'C' // 7302 #2 - 'D' // 7303 #3 - 'C' // 7304 #2 - 'F' // 7305 #5 - 'D' // 7306 #3 - 'C' // 7307 #2 - 'B' // 7308 #1 - 'K' // 7309 #10 - 'A' // 730a #0 - 'C' // 730b #2 - 'B' // 730c #1 - 'F' // 730d #5 - 'D' // 730e #3 - 'bB' // 730f-7311 #1 - 'C' // 7312 #2 - 'A' // 7313 #0 - 'aD' // 7314-7315 #3 - 'cA' // 7316-7319 #0 - 'D' // 731a #3 - 'bA' // 731b-731d #0 - 'C' // 731e #2 - 'F' // 731f #5 - 'aD' // 7320-7321 #3 - 'A' // 7322 #0 - 'B' // 7323 #1 - 'F' // 7324 #5 - 'A' // 7325 #0 - 'B' // 7326 #1 - 'dA' // 7327-732b #0 - 'C' // 732c #2 - 'B' // 732d #1 - 'C' // 732e #2 - 'F' // 732f #5 - 'B' // 7330 #1 - 'A' // 7331 #0 - 'aC' // 7332-7333 #2 - 'A' // 7334 #0 - 'C' // 7335 #2 - 'aA' // 7336-7337 #0 - 'B' // 7338 #1 - 'bC' // 7339-733b #2 - 'B' // 733c #1 - 'F' // 733d #5 - 'aA' // 733e-733f #0 - 'bB' // 7340-7342 #1 - 'bA' // 7343-7345 #0 - 'aD' // 7346-7347 #3 - 'bB' // 7348-734a #1 - 'D' // 734b #3 - 'B' // 734c #1 - 'C' // 734d #2 - 'A' // 734e #0 - 'C' // 734f #2 - 'A' // 7350 #0 - 'B' // 7351 #1 - 'A' // 7352 #0 - 'bD' // 7353-7355 #3 - 'F' // 7356 #5 - 'aA' // 7357-7358 #0 - 'bB' // 7359-735b #1 - 'K' // 735c #10 - 'bC' // 735d-735f #2 - 'A' // 7360 #0 - 'aB' // 7361-7362 #1 - 'F' // 7363 #5 - 'D' // 7364 #3 - 'B' // 7365 #1 - 'aC' // 7366-7367 #2 - 'dA' // 7368-736c #0 - 'D' // 736d #3 - 'C' // 736e #2 - 'aA' // 736f-7370 #0 - 'C' // 7371 #2 - 'A' // 7372 #0 - 'aB' // 7373-7374 #1 - 'A' // 7375 #0 - 'B' // 7376 #1 - 'aA' // 7377-7378 #0 - 'F' // 7379 #5 - 'bA' // 737a-737c #0 - 'bB' // 737d-737f #1 - 'C' // 7380 #2 - 'A' // 7381 #0 - 'B' // 7382 #1 - 'C' // 7383 #2 - 'A' // 7384 #0 - 'C' // 7385 #2 - 'aA' // 7386-7387 #0 - 'I' // 7388 #8 - 'A' // 7389 #0 - 'C' // 738a #2 - 'A' // 738b #0 - 'B' // 738c #1 - 'D' // 738d #3 - 'A' // 738e #0 - 'B' // 738f #1 - 'F' // 7390 #5 - 'D' // 7391 #3 - 'I' // 7392 #8 - 'C' // 7393 #2 - 'dA' // 7394-7398 #0 - 'bD' // 7399-739b #3 - 'C' // 739c #2 - 'B' // 739d #1 - 'bA' // 739e-73a0 #0 - 'B' // 73a1 #1 - 'C' // 73a2 #2 - 'D' // 73a3 #3 - 'B' // 73a4 #1 - 'C' // 73a5 #2 - 'A' // 73a6 #0 - 'I' // 73a7 #8 - 'C' // 73a8 #2 - 'bA' // 73a9-73ab #0 - 'B' // 73ac #1 - 'A' // 73ad #0 - 'cD' // 73ae-73b1 #3 - 'aA' // 73b2-73b3 #0 - 'I' // 73b4 #8 - 'C' // 73b5 #2 - 'B' // 73b6 #1 - 'A' // 73b7 #0 - 'B' // 73b8 #1 - 'A' // 73b9 #0 - 'C' // 73ba #2 - 'aA' // 73bb-73bc #0 - 'J' // 73bd #9 - 'B' // 73be #1 - 'aA' // 73bf-73c0 #0 - 'D' // 73c1 #3 - 'A' // 73c2 #0 - 'aB' // 73c3-73c4 #1 - 'C' // 73c5 #2 - 'A' // 73c6 #0 - 'B' // 73c7 #1 - 'bA' // 73c8-73ca #0 - 'C' // 73cb #2 - 'aA' // 73cc-73cd #0 - 'C' // 73ce #2 - 'A' // 73cf #0 - 'B' // 73d0 #1 - 'D' // 73d1 #3 - 'A' // 73d2 #0 - 'C' // 73d3 #2 - 'aB' // 73d4-73d5 #1 - 'A' // 73d6 #0 - 'aI' // 73d7-73d8 #8 - 'A' // 73d9 #0 - 'bB' // 73da-73dc #1 - 'aA' // 73dd-73de #0 - 'D' // 73df #3 - 'A' // 73e0 #0 - 'C' // 73e1 #2 - 'I' // 73e2 #8 - 'cA' // 73e3-73e6 #0 - 'C' // 73e7 #2 - 'B' // 73e8 #1 - 'aA' // 73e9-73ea #0 - 'I' // 73eb #8 - 'D' // 73ec #3 - 'aA' // 73ed-73ee #0 - 'B' // 73ef #1 - 'D' // 73f0 #3 - 'F' // 73f1 #5 - 'D' // 73f2 #3 - 'B' // 73f3 #1 - 'C' // 73f4 #2 - 'A' // 73f5 #0 - 'B' // 73f6 #1 - 'bA' // 73f7-73f9 #0 - 'aC' // 73fa-73fb #2 - 'B' // 73fc #1 - 'aA' // 73fd-73fe #0 - 'aC' // 73ff-7400 #2 - 'A' // 7401 #0 - 'B' // 7402 #1 - 'dA' // 7403-7407 #0 - 'B' // 7408 #1 - 'A' // 7409 #0 - 'C' // 740a #2 - 'bB' // 740b-740d #1 - 'bD' // 740e-7410 #3 - 'C' // 7411 #2 - 'B' // 7412 #1 - 'J' // 7413 #9 - 'bB' // 7414-7416 #1 - 'I' // 7417 #8 - 'K' // 7418 #10 - 'B' // 7419 #1 - 'C' // 741a #2 - 'A' // 741b #0 - 'B' // 741c #1 - 'I' // 741d #8 - 'B' // 741e #1 - 'aI' // 741f-7420 #8 - 'aA' // 7421-7422 #0 - 'B' // 7423 #1 - 'bA' // 7424-7426 #0 - 'D' // 7427 #3 - 'A' // 7428 #0 - 'C' // 7429 #2 - 'bA' // 742a-742c #0 - 'C' // 742d #2 - 'hA' // 742e-7436 #0 - 'B' // 7437 #1 - 'I' // 7438 #8 - 'C' // 7439 #2 - 'A' // 743a #0 - 'D' // 743b #3 - 'B' // 743c #1 - 'aD' // 743d-743e #3 - 'bA' // 743f-7441 #0 - 'I' // 7442 #8 - 'aA' // 7443-7444 #0 - 'I' // 7445 #8 - 'A' // 7446 #0 - 'C' // 7447 #2 - 'aI' // 7448-7449 #8 - 'B' // 744a #1 - 'A' // 744b #0 - 'I' // 744c #8 - 'C' // 744d #2 - 'I' // 744e #8 - 'aB' // 744f-7450 #1 - 'bC' // 7451-7453 #2 - 'B' // 7454 #1 - 'A' // 7455 #0 - 'B' // 7456 #1 - 'A' // 7457 #0 - 'D' // 7458 #3 - 'gA' // 7459-7460 #0 - 'B' // 7461 #1 - 'bA' // 7462-7464 #0 - 'I' // 7465 #8 - 'F' // 7466 #5 - 'C' // 7467 #2 - 'bA' // 7468-746a #0 - 'C' // 746b #2 - 'B' // 746c #1 - 'fA' // 746d-7473 #0 - 'aB' // 7474-7475 #1 - 'C' // 7476 #2 - 'aD' // 7477-7478 #3 - 'aB' // 7479-747a #1 - 'D' // 747b #3 - 'B' // 747c #1 - 'I' // 747d #8 - 'A' // 747e #0 - 'B' // 747f #1 - 'A' // 7480 #0 - 'C' // 7481 #2 - 'I' // 7482 #8 - 'A' // 7483 #0 - 'K' // 7484 #10 - 'bA' // 7485-7487 #0 - 'C' // 7488 #2 - 'A' // 7489 #0 - 'I' // 748a #8 - 'A' // 748b #0 - 'I' // 748c #8 - 'B' // 748d #1 - 'D' // 748e #3 - 'F' // 748f #5 - 'A' // 7490 #0 - 'F' // 7491 #5 - 'C' // 7492 #2 - 'D' // 7493 #3 - 'aB' // 7494-7495 #1 - 'D' // 7496 #3 - 'C' // 7497 #2 - 'A' // 7498 #0 - 'aC' // 7499-749a #2 - 'B' // 749b #1 - 'A' // 749c #0 - 'I' // 749d #8 - 'aA' // 749e-749f #0 - 'C' // 74a0 #2 - 'A' // 74a1 #0 - 'F' // 74a2 #5 - 'A' // 74a3 #0 - 'B' // 74a4 #1 - 'A' // 74a5 #0 - 'C' // 74a6 #2 - 'aA' // 74a7-74a8 #0 - 'C' // 74a9 #2 - 'aA' // 74aa-74ab #0 - 'D' // 74ac #3 - 'B' // 74ad #1 - 'F' // 74ae #5 - 'C' // 74af #2 - 'bA' // 74b0-74b2 #0 - 'D' // 74b3 #3 - 'B' // 74b4 #1 - 'A' // 74b5 #0 - 'I' // 74b6 #8 - 'B' // 74b7 #1 - 'I' // 74b8 #8 - 'A' // 74b9 #0 - 'aC' // 74ba-74bb #2 - 'K' // 74bc #10 - 'A' // 74bd #0 - 'B' // 74be #1 - 'A' // 74bf #0 - 'I' // 74c0 #8 - 'bB' // 74c1-74c3 #1 - 'D' // 74c4 #3 - 'B' // 74c5 #1 - 'I' // 74c6 #8 - 'D' // 74c7 #3 - 'C' // 74c8 #2 - 'F' // 74c9 #5 - 'A' // 74ca #0 - 'B' // 74cb #1 - 'C' // 74cc #2 - 'K' // 74cd #10 - 'D' // 74ce #3 - 'aA' // 74cf-74d0 #0 - 'aD' // 74d1-74d2 #3 - 'aA' // 74d3-74d4 #0 - 'B' // 74d5 #1 - 'C' // 74d6 #2 - 'B' // 74d7 #1 - 'A' // 74d8 #0 - 'B' // 74d9 #1 - 'bA' // 74da-74dc #0 - 'B' // 74dd #1 - 'aC' // 74de-74df #2 - 'A' // 74e0 #0 - 'B' // 74e1 #1 - 'aA' // 74e2-74e3 #0 - 'C' // 74e4 #2 - 'B' // 74e5 #1 - 'A' // 74e6 #0 - 'aC' // 74e7-74e8 #2 - 'A' // 74e9 #0 - 'aF' // 74ea-74eb #5 - 'B' // 74ec #1 - 'D' // 74ed #3 - 'A' // 74ee #0 - 'F' // 74ef #5 - 'aC' // 74f0-74f1 #2 - 'A' // 74f2 #0 - 'K' // 74f3 #10 - 'C' // 74f4 #2 - 'B' // 74f5 #1 - 'C' // 74f6 #2 - 'A' // 74f7 #0 - 'C' // 74f8 #2 - 'D' // 74f9 #3 - 'F' // 74fa #5 - 'C' // 74fb #2 - 'F' // 74fc #5 - 'aB' // 74fd-74fe #1 - 'C' // 74ff #2 - 'B' // 7500 #1 - 'J' // 7501 #9 - 'B' // 7502 #1 - 'aA' // 7503-7504 #0 - 'C' // 7505 #2 - 'F' // 7506 #5 - 'aB' // 7507-7508 #1 - 'aD' // 7509-750a #3 - 'B' // 750b #1 - 'bA' // 750c-750e #0 - 'aB' // 750f-7510 #1 - 'A' // 7511 #0 - 'C' // 7512 #2 - 'A' // 7513 #0 - 'B' // 7514 #1 - 'A' // 7515 #0 - 'aC' // 7516-7517 #2 - 'A' // 7518 #0 - 'B' // 7519 #1 - 'A' // 751a #0 - 'K' // 751b #10 - 'A' // 751c #0 - 'B' // 751d #1 - 'aA' // 751e-751f #0 - 'F' // 7520 #5 - 'C' // 7521 #2 - 'A' // 7522 #0 - 'aJ' // 7523-7524 #9 - 'aA' // 7525-7526 #0 - 'F' // 7527 #5 - 'A' // 7528 #0 - 'aC' // 7529-752a #2 - 'aA' // 752b-752c #0 - 'aB' // 752d-752e #1 - 'C' // 752f #2 - 'cA' // 7530-7533 #0 - 'aB' // 7534-7535 #1 - 'F' // 7536 #5 - 'aA' // 7537-7538 #0 - 'C' // 7539 #2 - 'aA' // 753a-753b #0 - 'F' // 753c #5 - 'aC' // 753d-753e #2 - 'A' // 753f #0 - 'C' // 7540 #2 - 'D' // 7541 #3 - 'B' // 7542 #1 - 'J' // 7543 #9 - 'F' // 7544 #5 - 'D' // 7545 #3 - 'C' // 7546 #2 - 'A' // 7547 #0 - 'C' // 7548 #2 - 'F' // 7549 #5 - 'bA' // 754a-754c #0 - 'C' // 754d #2 - 'aA' // 754e-754f #0 - 'F' // 7550 #5 - 'A' // 7551 #0 - 'F' // 7552 #5 - 'I' // 7553 #8 - 'A' // 7554 #0 - 'B' // 7555 #1 - 'D' // 7556 #3 - 'F' // 7557 #5 - 'D' // 7558 #3 - 'dA' // 7559-755d #0 - 'F' // 755e #5 - 'C' // 755f #2 - 'A' // 7560 #0 - 'F' // 7561 #5 - 'A' // 7562 #0 - 'B' // 7563 #1 - 'cA' // 7564-7567 #0 - 'D' // 7568 #3 - 'F' // 7569 #5 - 'aA' // 756a-756b #0 - 'aC' // 756c-756d #2 - 'B' // 756e #1 - 'aA' // 756f-7570 #0 - 'F' // 7571 #5 - 'C' // 7572 #2 - 'aF' // 7573-7574 #5 - 'J' // 7575 #9 - 'A' // 7576 #0 - 'C' // 7577 #2 - 'A' // 7578 #0 - 'C' // 7579 #2 - 'A' // 757a #0 - 'aF' // 757b-757c #5 - 'aC' // 757d-757e #2 - 'A' // 757f #0 - 'B' // 7580 #1 - 'aF' // 7581-7582 #5 - 'aB' // 7583-7584 #1 - 'F' // 7585 #5 - 'aA' // 7586-7587 #0 - 'K' // 7588 #10 - 'F' // 7589 #5 - 'aA' // 758a-758b #0 - 'C' // 758c #2 - 'B' // 758d #1 - 'aA' // 758e-758f #0 - 'C' // 7590 #2 - 'aA' // 7591-7592 #0 - 'F' // 7593 #5 - 'A' // 7594 #0 - 'C' // 7595 #2 - 'aD' // 7596-7597 #3 - 'B' // 7598 #1 - 'aA' // 7599-759a #0 - 'D' // 759b #3 - 'F' // 759c #5 - 'A' // 759d #0 - 'B' // 759e #1 - 'bD' // 759f-75a1 #3 - 'C' // 75a2 #2 - 'A' // 75a3 #0 - 'C' // 75a4 #2 - 'A' // 75a5 #0 - 'D' // 75a6 #3 - 'B' // 75a7 #1 - 'D' // 75a8 #3 - 'K' // 75a9 #10 - 'B' // 75aa #1 - 'A' // 75ab #0 - 'cD' // 75ac-75af #3 - 'C' // 75b0 #2 - 'dA' // 75b1-75b5 #0 - 'B' // 75b6 #1 - 'F' // 75b7 #5 - 'aA' // 75b8-75b9 #0 - 'C' // 75ba #2 - 'B' // 75bb #1 - 'bA' // 75bc-75be #0 - 'C' // 75bf #2 - 'A' // 75c0 #0 - 'C' // 75c1 #2 - 'aA' // 75c2-75c3 #0 - 'C' // 75c4 #2 - 'A' // 75c5 #0 - 'F' // 75c6 #5 - 'A' // 75c7 #0 - 'B' // 75c8 #1 - 'D' // 75c9 #3 - 'A' // 75ca #0 - 'B' // 75cb #1 - 'C' // 75cc #2 - 'aA' // 75cd-75ce #0 - 'C' // 75cf #2 - 'aB' // 75d0-75d1 #1 - 'A' // 75d2 #0 - 'J' // 75d3 #9 - 'aA' // 75d4-75d5 #0 - 'D' // 75d6 #3 - 'C' // 75d7 #2 - 'aA' // 75d8-75d9 #0 - 'B' // 75da #1 - 'A' // 75db #0 - 'aC' // 75dc-75dd #2 - 'A' // 75de #0 - 'bC' // 75df-75e1 #2 - 'bA' // 75e2-75e4 #0 - 'D' // 75e5 #3 - 'B' // 75e6 #1 - 'A' // 75e7 #0 - 'D' // 75e8 #3 - 'F' // 75e9 #5 - 'aD' // 75ea-75eb #3 - 'F' // 75ec #5 - 'B' // 75ed #1 - 'F' // 75ee #5 - 'C' // 75ef #2 - 'A' // 75f0 #0 - 'C' // 75f1 #2 - 'bA' // 75f2-75f4 #0 - 'cB' // 75f5-75f8 #1 - 'aA' // 75f9-75fa #0 - 'B' // 75fb #1 - 'A' // 75fc #0 - 'B' // 75fd #1 - 'C' // 75fe #2 - 'bA' // 75ff-7601 #0 - 'aC' // 7602-7603 #2 - 'F' // 7604 #5 - 'aD' // 7605-7606 #3 - 'bA' // 7607-7609 #0 - 'C' // 760a #2 - 'A' // 760b #0 - 'C' // 760c #2 - 'A' // 760d #0 - 'D' // 760e #3 - 'C' // 760f #2 - 'I' // 7610 #8 - 'B' // 7611 #1 - 'F' // 7612 #5 - 'C' // 7613 #2 - 'B' // 7614 #1 - 'A' // 7615 #0 - 'C' // 7616 #2 - 'D' // 7617 #3 - 'F' // 7618 #5 - 'A' // 7619 #0 - 'B' // 761a #1 - 'cC' // 761b-761e #2 - 'cA' // 761f-7622 #0 - 'C' // 7623 #2 - 'A' // 7624 #0 - 'C' // 7625 #2 - 'aA' // 7626-7627 #0 - 'aC' // 7628-7629 #2 - 'aD' // 762a-762b #3 - 'B' // 762c #1 - 'C' // 762d #2 - 'D' // 762e #3 - 'I' // 762f #8 - 'A' // 7630 #0 - 'I' // 7631 #8 - 'C' // 7632 #2 - 'aA' // 7633-7634 #0 - 'C' // 7635 #2 - 'aD' // 7636-7637 #3 - 'C' // 7638 #2 - 'F' // 7639 #5 - 'C' // 763a #2 - 'A' // 763b #0 - 'C' // 763c #2 - 'B' // 763d #1 - 'aD' // 763e-763f #3 - 'C' // 7640 #2 - 'F' // 7641 #5 - 'aA' // 7642-7643 #0 - 'aF' // 7644-7645 #5 - 'cA' // 7646-7649 #0 - 'aF' // 764a-764b #5 - 'A' // 764c #0 - 'B' // 764d #1 - 'A' // 764e #0 - 'bB' // 764f-7651 #1 - 'A' // 7652 #0 - 'aB' // 7653-7654 #1 - 'J' // 7655 #9 - 'A' // 7656 #0 - 'B' // 7657 #1 - 'A' // 7658 #0 - 'C' // 7659 #2 - 'B' // 765a #1 - 'D' // 765b #3 - 'A' // 765c #0 - 'aD' // 765d-765e #3 - 'C' // 765f #2 - 'B' // 7660 #1 - 'aA' // 7661-7662 #0 - 'D' // 7663 #3 - 'aA' // 7664-7665 #0 - 'B' // 7666 #1 - 'A' // 7667 #0 - 'J' // 7668 #9 - 'A' // 7669 #0 - 'C' // 766a #2 - 'D' // 766b #3 - 'fA' // 766c-7672 #0 - 'B' // 7673 #1 - 'C' // 7674 #2 - 'B' // 7675 #1 - 'A' // 7676 #0 - 'D' // 7677 #3 - 'A' // 7678 #0 - 'B' // 7679 #1 - 'dA' // 767a-767e #0 - 'B' // 767f #1 - 'J' // 7680 #9 - 'A' // 7681 #0 - 'C' // 7682 #2 - 'J' // 7683 #9 - 'A' // 7684 #0 - 'F' // 7685 #5 - 'aA' // 7686-7687 #0 - 'C' // 7688 #2 - 'aB' // 7689-768a #1 - 'A' // 768b #0 - 'aF' // 768c-768d #5 - 'A' // 768e #0 - 'B' // 768f #1 - 'A' // 7690 #0 - 'D' // 7691 #3 - 'I' // 7692 #8 - 'A' // 7693 #0 - 'D' // 7694 #3 - 'C' // 7695 #2 - 'A' // 7696 #0 - 'K' // 7697 #10 - 'D' // 7698 #3 - 'C' // 7699 #2 - 'bA' // 769a-769c #0 - 'C' // 769d #2 - 'A' // 769e #0 - 'aF' // 769f-76a0 #5 - 'C' // 76a1 #2 - 'aF' // 76a2-76a3 #5 - 'A' // 76a4 #0 - 'aC' // 76a5-76a6 #2 - 'aF' // 76a7-76a8 #5 - 'D' // 76a9 #3 - 'C' // 76aa #2 - 'B' // 76ab #1 - 'K' // 76ac #10 - 'C' // 76ad #2 - 'A' // 76ae #0 - 'aC' // 76af-76b0 #2 - 'bD' // 76b1-76b3 #3 - 'A' // 76b4 #0 - 'B' // 76b5 #1 - 'J' // 76b6 #9 - 'C' // 76b7 #2 - 'A' // 76b8 #0 - 'F' // 76b9 #5 - 'A' // 76ba #0 - 'B' // 76bb #1 - 'D' // 76bc #3 - 'C' // 76bd #2 - 'B' // 76be #1 - 'A' // 76bf #0 - 'D' // 76c0 #3 - 'F' // 76c1 #5 - 'aA' // 76c2-76c3 #0 - 'B' // 76c4 #1 - 'C' // 76c5 #2 - 'A' // 76c6 #0 - 'D' // 76c7 #3 - 'A' // 76c8 #0 - 'C' // 76c9 #2 - 'A' // 76ca #0 - 'F' // 76cb #5 - 'bA' // 76cc-76ce #0 - 'bD' // 76cf-76d1 #3 - 'A' // 76d2 #0 - 'I' // 76d3 #8 - 'A' // 76d4 #0 - 'D' // 76d5 #3 - 'A' // 76d6 #0 - 'F' // 76d7 #5 - 'D' // 76d8 #3 - 'A' // 76d9 #0 - 'B' // 76da #1 - 'aA' // 76db-76dc #0 - 'B' // 76dd #1 - 'aA' // 76de-76df #0 - 'F' // 76e0 #5 - 'A' // 76e1 #0 - 'D' // 76e2 #3 - 'bA' // 76e3-76e5 #0 - 'C' // 76e6 #2 - 'A' // 76e7 #0 - 'F' // 76e8 #5 - 'B' // 76e9 #1 - 'A' // 76ea #0 - 'F' // 76eb #5 - 'A' // 76ec #0 - 'B' // 76ed #1 - 'A' // 76ee #0 - 'B' // 76ef #1 - 'C' // 76f0 #2 - 'aA' // 76f1-76f2 #0 - 'B' // 76f3 #1 - 'A' // 76f4 #0 - 'B' // 76f5 #1 - 'F' // 76f6 #5 - 'B' // 76f7 #1 - 'aA' // 76f8-76f9 #0 - 'B' // 76fa #1 - 'aA' // 76fb-76fc #0 - 'D' // 76fd #3 - 'A' // 76fe #0 - 'K' // 76ff #10 - 'J' // 7700 #9 - 'A' // 7701 #0 - 'K' // 7702 #10 - 'B' // 7703 #1 - 'A' // 7704 #0 - 'B' // 7705 #1 - 'F' // 7706 #5 - 'eA' // 7707-770c #0 - 'D' // 770d #3 - 'C' // 770e #2 - 'B' // 770f #1 - 'I' // 7710 #8 - 'B' // 7711 #1 - 'C' // 7712 #2 - 'B' // 7713 #1 - 'F' // 7714 #5 - 'C' // 7715 #2 - 'D' // 7716 #3 - 'F' // 7717 #5 - 'D' // 7718 #3 - 'C' // 7719 #2 - 'aA' // 771a-771b #0 - 'F' // 771c #5 - 'B' // 771d #1 - 'bA' // 771e-7720 #0 - 'D' // 7721 #3 - 'C' // 7722 #2 - 'B' // 7723 #1 - 'C' // 7724 #2 - 'aA' // 7725-7726 #0 - 'B' // 7727 #1 - 'aA' // 7728-7729 #0 - 'D' // 772a #3 - 'B' // 772b #1 - 'D' // 772c #3 - 'C' // 772d #2 - 'F' // 772e #5 - 'C' // 772f #2 - 'D' // 7730 #3 - 'bB' // 7731-7733 #1 - 'A' // 7734 #0 - 'aC' // 7735-7736 #2 - 'cA' // 7737-773a #0 - 'I' // 773b #8 - 'A' // 773c #0 - 'C' // 773d #2 - 'A' // 773e #0 - 'D' // 773f #3 - 'A' // 7740 #0 - 'D' // 7741 #3 - 'F' // 7742 #5 - 'I' // 7743 #8 - 'B' // 7744 #1 - 'C' // 7745 #2 - 'aA' // 7746-7747 #0 - 'aD' // 7748-7749 #3 - 'C' // 774a #2 - 'aB' // 774b-774c #1 - 'A' // 774d #0 - 'aC' // 774e-774f #2 - 'aD' // 7750-7751 #3 - 'A' // 7752 #0 - 'D' // 7753 #3 - 'aB' // 7754-7755 #1 - 'C' // 7756 #2 - 'F' // 7757 #5 - 'C' // 7758 #2 - 'B' // 7759 #1 - 'aA' // 775a-775b #0 - 'C' // 775c #2 - 'D' // 775d #3 - 'C' // 775e #2 - 'dA' // 775f-7763 #0 - 'F' // 7764 #5 - 'aA' // 7765-7766 #0 - 'C' // 7767 #2 - 'A' // 7768 #0 - 'B' // 7769 #1 - 'C' // 776a #2 - 'aA' // 776b-776c #0 - 'bB' // 776d-776f #1 - 'F' // 7770 #5 - 'D' // 7771 #3 - 'C' // 7772 #2 - 'aF' // 7773-7774 #5 - 'aD' // 7775-7776 #3 - 'I' // 7777 #8 - 'B' // 7778 #1 - 'A' // 7779 #0 - 'C' // 777a #2 - 'B' // 777b #1 - 'C' // 777c #2 - 'bA' // 777d-777f #0 - 'C' // 7780 #2 - 'bB' // 7781-7783 #1 - 'C' // 7784 #2 - 'B' // 7785 #1 - 'D' // 7786 #3 - 'bB' // 7787-7789 #1 - 'D' // 778a #3 - 'A' // 778b #0 - 'C' // 778c #2 - 'aA' // 778d-778e #0 - 'B' // 778f #1 - 'D' // 7790 #3 - 'A' // 7791 #0 - 'D' // 7792 #3 - 'B' // 7793 #1 - 'F' // 7794 #5 - 'C' // 7795 #2 - 'J' // 7796 #9 - 'aB' // 7797-7798 #1 - 'I' // 7799 #8 - 'C' // 779a #2 - 'bB' // 779b-779d #1 - 'A' // 779e #0 - 'C' // 779f #2 - 'A' // 77a0 #0 - 'B' // 77a1 #1 - 'A' // 77a2 #0 - 'B' // 77a3 #1 - 'F' // 77a4 #5 - 'A' // 77a5 #0 - 'D' // 77a6 #3 - 'C' // 77a7 #2 - 'B' // 77a8 #1 - 'F' // 77a9 #5 - 'A' // 77aa #0 - 'B' // 77ab #1 - 'aA' // 77ac-77ad #0 - 'J' // 77ae #9 - 'C' // 77af #2 - 'A' // 77b0 #0 - 'C' // 77b1 #2 - 'B' // 77b2 #1 - 'A' // 77b3 #0 - 'B' // 77b4 #1 - 'bC' // 77b5-77b7 #2 - 'D' // 77b8 #3 - 'A' // 77b9 #0 - 'B' // 77ba #1 - 'bA' // 77bb-77bd #0 - 'C' // 77be #2 - 'A' // 77bf #0 - 'aD' // 77c0-77c1 #3 - 'B' // 77c2 #1 - 'C' // 77c3 #2 - 'aB' // 77c4-77c5 #1 - 'D' // 77c6 #3 - 'A' // 77c7 #0 - 'D' // 77c8 #3 - 'A' // 77c9 #0 - 'bB' // 77ca-77cc #1 - 'A' // 77cd #0 - 'bB' // 77ce-77d0 #1 - 'aF' // 77d1-77d2 #5 - 'aB' // 77d3-77d4 #1 - 'C' // 77d5 #2 - 'D' // 77d6 #3 - 'A' // 77d7 #0 - 'B' // 77d8 #1 - 'cA' // 77d9-77dc #0 - 'B' // 77dd #1 - 'A' // 77de #0 - 'F' // 77df #5 - 'C' // 77e0 #2 - 'K' // 77e1 #10 - 'aA' // 77e2-77e3 #0 - 'F' // 77e4 #5 - 'A' // 77e5 #0 - 'C' // 77e6 #2 - 'A' // 77e7 #0 - 'B' // 77e8 #1 - 'A' // 77e9 #0 - 'F' // 77ea #5 - 'D' // 77eb #3 - 'C' // 77ec #2 - 'cA' // 77ed-77f0 #0 - 'C' // 77f1 #2 - 'B' // 77f2 #1 - 'A' // 77f3 #0 - 'C' // 77f4 #2 - 'aD' // 77f5-77f6 #3 - 'B' // 77f7 #1 - 'A' // 77f8 #0 - 'B' // 77f9 #1 - 'I' // 77fa #8 - 'aA' // 77fb-77fc #0 - 'I' // 77fd #8 - 'B' // 77fe #1 - 'bD' // 77ff-7801 #3 - 'A' // 7802 #0 - 'B' // 7803 #1 - 'D' // 7804 #3 - 'aC' // 7805-7806 #2 - 'K' // 7807 #10 - 'B' // 7808 #1 - 'C' // 7809 #2 - 'aD' // 780a-780b #3 - 'A' // 780c #0 - 'aC' // 780d-780e #2 - 'I' // 780f #8 - 'B' // 7810 #1 - 'aA' // 7811-7812 #0 - 'B' // 7813 #1 - 'A' // 7814 #0 - 'F' // 7815 #5 - 'aD' // 7816-7817 #3 - 'B' // 7818 #1 - 'F' // 7819 #5 - 'aD' // 781a-781b #3 - 'B' // 781c #1 - 'C' // 781d #2 - 'aB' // 781e-781f #1 - 'aC' // 7820-7821 #2 - 'A' // 7822 #0 - 'C' // 7823 #2 - 'D' // 7824 #3 - 'bA' // 7825-7827 #0 - 'cB' // 7828-782b #1 - 'aA' // 782c-782d #0 - 'C' // 782e #2 - 'B' // 782f #1 - 'A' // 7830 #0 - 'B' // 7831 #1 - 'A' // 7832 #0 - 'B' // 7833 #1 - 'A' // 7834 #0 - 'C' // 7835 #2 - 'D' // 7836 #3 - 'C' // 7837 #2 - 'aB' // 7838-7839 #1 - 'F' // 783a #5 - 'D' // 783b #3 - 'aB' // 783c-783d #1 - 'D' // 783e #3 - 'F' // 783f #5 - 'aD' // 7840-7841 #3 - 'B' // 7842 #1 - 'A' // 7843 #0 - 'C' // 7844 #2 - 'A' // 7845 #0 - 'D' // 7846 #3 - 'aC' // 7847-7848 #2 - 'bB' // 7849-784b #1 - 'C' // 784c #2 - 'B' // 784d #1 - 'C' // 784e #2 - 'J' // 784f #9 - 'B' // 7850 #1 - 'aC' // 7851-7852 #2 - 'aB' // 7853-7854 #1 - 'fD' // 7855-785b #3 - 'aA' // 785c-785d #0 - 'C' // 785e #2 - 'D' // 785f #3 - 'A' // 7860 #0 - 'F' // 7861 #5 - 'B' // 7862 #1 - 'F' // 7863 #5 - 'C' // 7864 #2 - 'aB' // 7865-7866 #1 - 'K' // 7867 #10 - 'A' // 7868 #0 - 'B' // 7869 #1 - 'bA' // 786a-786c #0 - 'B' // 786d #1 - 'aA' // 786e-786f #0 - 'aB' // 7870-7871 #1 - 'F' // 7872 #5 - 'D' // 7873 #3 - 'F' // 7874 #5 - 'cD' // 7875-7878 #3 - 'B' // 7879 #1 - 'C' // 787a #2 - 'B' // 787b #1 - 'A' // 787c #0 - 'D' // 787d #3 - 'C' // 787e #2 - 'aB' // 787f-7880 #1 - 'A' // 7881 #0 - 'D' // 7882 #3 - 'B' // 7883 #1 - 'I' // 7884 #8 - 'B' // 7885 #1 - 'C' // 7886 #2 - 'A' // 7887 #0 - 'aB' // 7888-7889 #1 - 'F' // 788a #5 - 'D' // 788b #3 - 'cA' // 788c-788f #0 - 'D' // 7890 #3 - 'A' // 7891 #0 - 'D' // 7892 #3 - 'A' // 7893 #0 - 'aC' // 7894-7895 #2 - 'B' // 7896 #1 - 'A' // 7897 #0 - 'C' // 7898 #2 - 'B' // 7899 #1 - 'C' // 789a #2 - 'aD' // 789b-789c #3 - 'F' // 789d #5 - 'C' // 789e #2 - 'A' // 789f #0 - 'B' // 78a0 #1 - 'C' // 78a1 #2 - 'B' // 78a2 #1 - 'aA' // 78a3-78a4 #0 - 'B' // 78a5 #1 - 'D' // 78a6 #3 - 'bA' // 78a7-78a9 #0 - 'C' // 78aa #2 - 'B' // 78ab #1 - 'aA' // 78ac-78ad #0 - 'D' // 78ae #3 - 'dC' // 78af-78b3 #2 - 'B' // 78b4 #1 - 'F' // 78b5 #5 - 'B' // 78b6 #1 - 'D' // 78b7 #3 - 'aB' // 78b8-78b9 #1 - 'bA' // 78ba-78bc #0 - 'F' // 78bd #5 - 'A' // 78be #0 - 'F' // 78bf #5 - 'D' // 78c0 #3 - 'A' // 78c1 #0 - 'D' // 78c2 #3 - 'aB' // 78c3-78c4 #1 - 'A' // 78c5 #0 - 'F' // 78c6 #5 - 'C' // 78c7 #2 - 'A' // 78c8 #0 - 'C' // 78c9 #2 - 'aA' // 78ca-78cb #0 - 'C' // 78cc #2 - 'B' // 78cd #1 - 'A' // 78ce #0 - 'I' // 78cf #8 - 'aA' // 78d0-78d1 #0 - 'aC' // 78d2-78d3 #2 - 'aA' // 78d4-78d5 #0 - 'F' // 78d6 #5 - 'aB' // 78d7-78d8 #1 - 'D' // 78d9 #3 - 'A' // 78da #0 - 'C' // 78db #2 - 'D' // 78dc #3 - 'aB' // 78dd-78de #1 - 'C' // 78df #2 - 'A' // 78e0 #0 - 'C' // 78e1 #2 - 'aB' // 78e2-78e3 #1 - 'C' // 78e4 #2 - 'B' // 78e5 #1 - 'F' // 78e6 #5 - 'aA' // 78e7-78e8 #0 - 'B' // 78e9 #1 - 'A' // 78ea #0 - 'D' // 78eb #3 - 'A' // 78ec #0 - 'aB' // 78ed-78ee #1 - 'A' // 78ef #0 - 'aB' // 78f0-78f1 #1 - 'aC' // 78f2-78f3 #2 - 'A' // 78f4 #0 - 'I' // 78f5 #8 - 'F' // 78f6 #5 - 'A' // 78f7 #0 - 'D' // 78f8 #3 - 'C' // 78f9 #2 - 'aA' // 78fa-78fb #0 - 'I' // 78fc #8 - 'A' // 78fd #0 - 'aC' // 78fe-78ff #2 - 'F' // 7900 #5 - 'A' // 7901 #0 - 'B' // 7902 #1 - 'D' // 7903 #3 - 'aB' // 7904-7905 #1 - 'C' // 7906 #2 - 'F' // 7907 #5 - 'D' // 7908 #3 - 'B' // 7909 #1 - 'aD' // 790a-790b #3 - 'A' // 790c #0 - 'D' // 790d #3 - 'A' // 790e #0 - 'K' // 790f #10 - 'C' // 7910 #2 - 'aA' // 7911-7912 #0 - 'aB' // 7913-7914 #1 - 'D' // 7915 #3 - 'K' // 7916 #10 - 'B' // 7917 #1 - 'D' // 7918 #3 - 'A' // 7919 #0 - 'F' // 791a #5 - 'aC' // 791b-791c #2 - 'B' // 791d #1 - 'C' // 791e #2 - 'aF' // 791f-7920 #5 - 'B' // 7921 #1 - 'D' // 7922 #3 - 'aB' // 7923-7924 #1 - 'aC' // 7925-7926 #2 - 'A' // 7927 #0 - 'aC' // 7928-7929 #2 - 'cA' // 792a-792d #0 - 'C' // 792e #2 - 'B' // 792f #1 - 'F' // 7930 #5 - 'A' // 7931 #0 - 'aB' // 7932-7933 #1 - 'aC' // 7934-7935 #2 - 'B' // 7936 #1 - 'D' // 7937 #3 - 'aB' // 7938-7939 #1 - 'bA' // 793a-793c #0 - 'C' // 793d #2 - 'A' // 793e #0 - 'C' // 793f #2 - 'aA' // 7940-7941 #0 - 'C' // 7942 #2 - 'D' // 7943 #3 - 'fA' // 7944-794a #0 - 'C' // 794b #2 - 'B' // 794c #1 - 'aD' // 794d-794e #3 - 'C' // 794f #2 - 'A' // 7950 #0 - 'C' // 7951 #2 - 'B' // 7952 #1 - 'eA' // 7953-7958 #0 - 'B' // 7959 #1 - 'fA' // 795a-7960 #0 - 'B' // 7961 #1 - 'A' // 7962 #0 - 'aB' // 7963-7964 #1 - 'A' // 7965 #0 - 'D' // 7966 #3 - 'aA' // 7967-7968 #0 - 'C' // 7969 #2 - 'B' // 796a #1 - 'C' // 796b #2 - 'D' // 796c #3 - 'A' // 796d #0 - 'aD' // 796e-796f #3 - 'aB' // 7970-7971 #1 - 'C' // 7972 #2 - 'aB' // 7973-7974 #1 - 'aD' // 7975-7976 #3 - 'F' // 7977 #5 - 'D' // 7978 #3 - 'aA' // 7979-797a #0 - 'F' // 797b #5 - 'A' // 797c #0 - 'B' // 797d #1 - 'C' // 797e #2 - 'bA' // 797f-7981 #0 - 'aB' // 7982-7983 #1 - 'aF' // 7984-7985 #5 - 'bB' // 7986-7988 #1 - 'D' // 7989 #3 - 'aA' // 798a-798b #0 - 'F' // 798c #5 - 'bA' // 798d-798f #0 - 'B' // 7990 #1 - 'A' // 7991 #0 - 'B' // 7992 #1 - 'C' // 7993 #2 - 'A' // 7994 #0 - 'aC' // 7995-7996 #2 - 'B' // 7997 #1 - 'C' // 7998 #2 - 'aB' // 7999-799a #1 - 'A' // 799b #0 - 'C' // 799c #2 - 'A' // 799d #0 - 'D' // 799e #3 - 'aB' // 799f-79a0 #1 - 'C' // 79a1 #2 - 'B' // 79a2 #1 - 'D' // 79a3 #3 - 'aB' // 79a4-79a5 #1 - 'bA' // 79a6-79a8 #0 - 'C' // 79a9 #2 - 'aA' // 79aa-79ab #0 - 'aB' // 79ac-79ad #1 - 'A' // 79ae #0 - 'F' // 79af #5 - 'aA' // 79b0-79b1 #0 - 'B' // 79b2 #1 - 'aA' // 79b3-79b4 #0 - 'D' // 79b5 #3 - 'aB' // 79b6-79b7 #1 - 'cA' // 79b8-79bb #0 - 'K' // 79bc #10 - 'dA' // 79bd-79c1 #0 - 'F' // 79c2 #5 - 'D' // 79c3 #3 - 'A' // 79c4 #0 - 'B' // 79c5 #1 - 'I' // 79c6 #8 - 'F' // 79c7 #5 - 'C' // 79c8 #2 - 'bA' // 79c9-79cb #0 - 'aC' // 79cc-79cd #2 - 'B' // 79ce #1 - 'C' // 79cf #2 - 'B' // 79d0 #1 - 'aA' // 79d1-79d2 #0 - 'D' // 79d3 #3 - 'C' // 79d4 #2 - 'A' // 79d5 #0 - 'C' // 79d6 #2 - 'D' // 79d7 #3 - 'A' // 79d8 #0 - 'D' // 79d9 #3 - 'F' // 79da #5 - 'D' // 79db #3 - 'B' // 79dc #1 - 'C' // 79dd #2 - 'aA' // 79de-79df #0 - 'C' // 79e0 #2 - 'F' // 79e1 #5 - 'bA' // 79e2-79e4 #0 - 'F' // 79e5 #5 - 'aA' // 79e6-79e7 #0 - 'D' // 79e8 #3 - 'cA' // 79e9-79ec #0 - 'C' // 79ed #2 - 'B' // 79ee #1 - 'D' // 79ef #3 - 'F' // 79f0 #5 - 'C' // 79f1 #2 - 'aD' // 79f2-79f3 #3 - 'B' // 79f4 #1 - 'K' // 79f5 #10 - 'aB' // 79f6-79f7 #1 - 'A' // 79f8 #0 - 'D' // 79f9 #3 - 'B' // 79fa #1 - 'A' // 79fb #0 - 'F' // 79fc #5 - 'bD' // 79fd-79ff #3 - 'A' // 7a00 #0 - 'K' // 7a01 #10 - 'A' // 7a02 #0 - 'C' // 7a03 #2 - 'B' // 7a04 #1 - 'A' // 7a05 #0 - 'B' // 7a06 #1 - 'F' // 7a07 #5 - 'A' // 7a08 #0 - 'F' // 7a09 #5 - 'cA' // 7a0a-7a0d #0 - 'C' // 7a0e #2 - 'D' // 7a0f #3 - 'B' // 7a10 #1 - 'C' // 7a11 #2 - 'aB' // 7a12-7a13 #1 - 'A' // 7a14 #0 - 'C' // 7a15 #2 - 'D' // 7a16 #3 - 'cA' // 7a17-7a1a #0 - 'C' // 7a1b #2 - 'A' // 7a1c #0 - 'D' // 7a1d #3 - 'bA' // 7a1e-7a20 #0 - 'F' // 7a21 #5 - 'I' // 7a22 #8 - 'bD' // 7a23-7a25 #3 - 'B' // 7a26 #1 - 'J' // 7a27 #9 - 'B' // 7a28 #1 - 'D' // 7a29 #3 - 'B' // 7a2a #1 - 'C' // 7a2b #2 - 'B' // 7a2c #1 - 'C' // 7a2d #2 - 'A' // 7a2e #0 - 'C' // 7a2f #2 - 'aA' // 7a30-7a31 #0 - 'C' // 7a32 #2 - 'K' // 7a33 #10 - 'aF' // 7a34-7a35 #5 - 'K' // 7a36 #10 - 'A' // 7a37 #0 - 'F' // 7a38 #5 - 'A' // 7a39 #0 - 'C' // 7a3a #2 - 'bA' // 7a3b-7a3d #0 - 'C' // 7a3e #2 - 'aA' // 7a3f-7a40 #0 - 'D' // 7a41 #3 - 'J' // 7a42 #9 - 'aC' // 7a43-7a44 #2 - 'aA' // 7a45-7a46 #0 - 'aC' // 7a47-7a48 #2 - 'A' // 7a49 #0 - 'aB' // 7a4a-7a4b #1 - 'bA' // 7a4c-7a4e #0 - 'aF' // 7a4f-7a50 #5 - 'bD' // 7a51-7a53 #3 - 'B' // 7a54 #1 - 'F' // 7a55 #5 - 'C' // 7a56 #2 - 'A' // 7a57 #0 - 'B' // 7a58 #1 - 'F' // 7a59 #5 - 'aB' // 7a5a-7a5b #1 - 'C' // 7a5c #2 - 'F' // 7a5d #5 - 'D' // 7a5e #3 - 'C' // 7a5f #2 - 'bA' // 7a60-7a62 #0 - 'F' // 7a63 #5 - 'D' // 7a64 #3 - 'C' // 7a65 #2 - 'K' // 7a66 #10 - 'C' // 7a67 #2 - 'B' // 7a68 #1 - 'A' // 7a69 #0 - 'F' // 7a6a #5 - 'A' // 7a6b #0 - 'B' // 7a6c #1 - 'C' // 7a6d #2 - 'B' // 7a6e #1 - 'D' // 7a6f #3 - 'A' // 7a70 #0 - 'aB' // 7a71-7a72 #1 - 'D' // 7a73 #3 - 'bA' // 7a74-7a76 #0 - 'D' // 7a77 #3 - 'C' // 7a78 #2 - 'aA' // 7a79-7a7a #0 - 'B' // 7a7b #1 - 'D' // 7a7c #3 - 'dA' // 7a7d-7a81 #0 - 'J' // 7a82 #9 - 'cA' // 7a83-7a86 #0 - 'B' // 7a87 #1 - 'A' // 7a88 #0 - 'B' // 7a89 #1 - 'A' // 7a8a #0 - 'C' // 7a8b #2 - 'B' // 7a8c #1 - 'aD' // 7a8d-7a8e #3 - 'B' // 7a8f #1 - 'aC' // 7a90-7a91 #2 - 'aA' // 7a92-7a93 #0 - 'C' // 7a94 #2 - 'cA' // 7a95-7a98 #0 - 'I' // 7a99 #8 - 'D' // 7a9a #3 - 'K' // 7a9b #10 - 'aD' // 7a9c-7a9d #3 - 'C' // 7a9e #2 - 'aA' // 7a9f-7aa0 #0 - 'D' // 7aa1 #3 - 'B' // 7aa2 #1 - 'A' // 7aa3 #0 - 'cD' // 7aa4-7aa7 #3 - 'B' // 7aa8 #1 - 'aA' // 7aa9-7aaa #0 - 'B' // 7aab #1 - 'A' // 7aac #0 - 'D' // 7aad #3 - 'aA' // 7aae-7aaf #0 - 'C' // 7ab0 #2 - 'aB' // 7ab1-7ab2 #1 - 'A' // 7ab3 #0 - 'B' // 7ab4 #1 - 'C' // 7ab5 #2 - 'A' // 7ab6 #0 - 'aB' // 7ab7-7ab8 #1 - 'J' // 7ab9 #9 - 'aA' // 7aba-7abb #0 - 'C' // 7abc #2 - 'F' // 7abd #5 - 'aA' // 7abe-7abf #0 - 'bB' // 7ac0-7ac2 #1 - 'C' // 7ac3 #2 - 'aA' // 7ac4-7ac5 #0 - 'F' // 7ac6 #5 - 'aA' // 7ac7-7ac8 #0 - 'C' // 7ac9 #2 - 'aA' // 7aca-7acb #0 - 'bF' // 7acc-7ace #5 - 'C' // 7acf #2 - 'D' // 7ad0 #3 - 'C' // 7ad1 #2 - 'F' // 7ad2 #5 - 'C' // 7ad3 #2 - 'D' // 7ad4 #3 - 'F' // 7ad5 #5 - 'D' // 7ad6 #3 - 'K' // 7ad7 #10 - 'B' // 7ad8 #1 - 'A' // 7ad9 #0 - 'aC' // 7ada-7adb #2 - 'aA' // 7adc-7add #0 - 'D' // 7ade #3 - 'aA' // 7adf-7ae0 #0 - 'F' // 7ae1 #5 - 'aA' // 7ae2-7ae3 #0 - 'B' // 7ae4 #1 - 'aA' // 7ae5-7ae6 #0 - 'C' // 7ae7 #2 - 'F' // 7ae8 #5 - 'C' // 7ae9 #2 - 'A' // 7aea #0 - 'C' // 7aeb #2 - 'F' // 7aec #5 - 'A' // 7aed #0 - 'B' // 7aee #1 - 'A' // 7aef #0 - 'aF' // 7af0-7af1 #5 - 'aD' // 7af2-7af3 #3 - 'J' // 7af4 #9 - 'D' // 7af5 #3 - 'A' // 7af6 #0 - 'B' // 7af7 #1 - 'J' // 7af8 #9 - 'aA' // 7af9-7afa #0 - 'C' // 7afb #2 - 'B' // 7afc #1 - 'A' // 7afd #0 - 'C' // 7afe #2 - 'A' // 7aff #0 - 'aB' // 7b00-7b01 #1 - 'F' // 7b02 #5 - 'D' // 7b03 #3 - 'C' // 7b04 #2 - 'B' // 7b05 #1 - 'A' // 7b06 #0 - 'F' // 7b07 #5 - 'A' // 7b08 #0 - 'B' // 7b09 #1 - 'A' // 7b0a #0 - 'C' // 7b0b #2 - 'I' // 7b0c #8 - 'D' // 7b0d #3 - 'I' // 7b0e #8 - 'A' // 7b0f #0 - 'B' // 7b10 #1 - 'aA' // 7b11-7b12 #0 - 'B' // 7b13 #1 - 'C' // 7b14 #2 - 'bD' // 7b15-7b17 #3 - 'aA' // 7b18-7b19 #0 - 'B' // 7b1a #1 - 'A' // 7b1b #0 - 'D' // 7b1c #3 - 'B' // 7b1d #1 - 'A' // 7b1e #0 - 'C' // 7b1f #2 - 'A' // 7b20 #0 - 'D' // 7b21 #3 - 'B' // 7b22 #1 - 'C' // 7b23 #2 - 'B' // 7b24 #1 - 'cA' // 7b25-7b28 #0 - 'bC' // 7b29-7b2b #2 - 'aA' // 7b2c-7b2d #0 - 'C' // 7b2e #2 - 'A' // 7b2f #0 - 'aC' // 7b30-7b31 #2 - 'B' // 7b32 #1 - 'A' // 7b33 #0 - 'C' // 7b34 #2 - 'A' // 7b35 #0 - 'F' // 7b36 #5 - 'D' // 7b37 #3 - 'B' // 7b38 #1 - 'A' // 7b39 #0 - 'D' // 7b3a #3 - 'C' // 7b3b #2 - 'D' // 7b3c #3 - 'F' // 7b3d #5 - 'D' // 7b3e #3 - 'F' // 7b3f #5 - 'C' // 7b40 #2 - 'F' // 7b41 #5 - 'bB' // 7b42-7b44 #1 - 'aA' // 7b45-7b46 #0 - 'C' // 7b47 #2 - 'aA' // 7b48-7b49 #0 - 'B' // 7b4a #1 - 'bA' // 7b4b-7b4d #0 - 'C' // 7b4e #2 - 'cA' // 7b4f-7b52 #0 - 'J' // 7b53 #9 - 'A' // 7b54 #0 - 'C' // 7b55 #2 - 'A' // 7b56 #0 - 'D' // 7b57 #3 - 'B' // 7b58 #1 - 'cD' // 7b59-7b5c #3 - 'F' // 7b5d #5 - 'D' // 7b5e #3 - 'K' // 7b5f #10 - 'A' // 7b60 #0 - 'bB' // 7b61-7b63 #1 - 'C' // 7b64 #2 - 'bA' // 7b65-7b67 #0 - 'D' // 7b68 #3 - 'A' // 7b69 #0 - 'F' // 7b6a #5 - 'D' // 7b6b #3 - 'A' // 7b6c #0 - 'C' // 7b6d #2 - 'A' // 7b6e #0 - 'aC' // 7b6f-7b70 #2 - 'A' // 7b71 #0 - 'C' // 7b72 #2 - 'A' // 7b73 #0 - 'C' // 7b74 #2 - 'A' // 7b75 #0 - 'B' // 7b76 #1 - 'C' // 7b77 #2 - 'B' // 7b78 #1 - 'aF' // 7b79-7b7a #5 - 'B' // 7b7b #1 - 'D' // 7b7c #3 - 'K' // 7b7d #10 - 'D' // 7b7e #3 - 'F' // 7b7f #5 - 'aD' // 7b80-7b81 #3 - 'B' // 7b82 #1 - 'D' // 7b83 #3 - 'C' // 7b84 #2 - 'B' // 7b85 #1 - 'F' // 7b86 #5 - 'A' // 7b87 #0 - 'B' // 7b88 #1 - 'F' // 7b89 #5 - 'B' // 7b8a #1 - 'A' // 7b8b #0 - 'B' // 7b8c #1 - 'bA' // 7b8d-7b8f #0 - 'aC' // 7b90-7b91 #2 - 'A' // 7b92 #0 - 'D' // 7b93 #3 - 'aA' // 7b94-7b95 #0 - 'C' // 7b96 #2 - 'A' // 7b97 #0 - 'C' // 7b98 #2 - 'aA' // 7b99-7b9a #0 - 'C' // 7b9b #2 - 'aA' // 7b9c-7b9d #0 - 'aF' // 7b9e-7b9f #5 - 'aA' // 7ba0-7ba1 #0 - 'bB' // 7ba2-7ba4 #1 - 'F' // 7ba5 #5 - 'cD' // 7ba6-7ba9 #3 - 'F' // 7baa #5 - 'D' // 7bab #3 - 'C' // 7bac #2 - 'A' // 7bad #0 - 'B' // 7bae #1 - 'C' // 7baf #2 - 'F' // 7bb0 #5 - 'A' // 7bb1 #0 - 'C' // 7bb2 #2 - 'D' // 7bb3 #3 - 'A' // 7bb4 #0 - 'C' // 7bb5 #2 - 'F' // 7bb6 #5 - 'B' // 7bb7 #1 - 'A' // 7bb8 #0 - 'B' // 7bb9 #1 - 'cF' // 7bba-7bbd #5 - 'I' // 7bbe #8 - 'D' // 7bbf #3 - 'aA' // 7bc0-7bc1 #0 - 'F' // 7bc2 #5 - 'D' // 7bc3 #3 - 'A' // 7bc4 #0 - 'C' // 7bc5 #2 - 'aA' // 7bc6-7bc7 #0 - 'F' // 7bc8 #5 - 'cA' // 7bc9-7bcc #0 - 'D' // 7bcd #3 - 'B' // 7bce #1 - 'C' // 7bcf #2 - 'B' // 7bd0 #1 - 'D' // 7bd1 #3 - 'K' // 7bd2 #10 - 'D' // 7bd3 #3 - 'A' // 7bd4 #0 - 'B' // 7bd5 #1 - 'aF' // 7bd6-7bd7 #5 - 'B' // 7bd8 #1 - 'A' // 7bd9 #0 - 'C' // 7bda #2 - 'A' // 7bdb #0 - 'B' // 7bdc #1 - 'A' // 7bdd #0 - 'aB' // 7bde-7bdf #1 - 'A' // 7be0 #0 - 'I' // 7be1 #8 - 'aB' // 7be2-7be3 #1 - 'A' // 7be4 #0 - 'C' // 7be5 #2 - 'A' // 7be6 #0 - 'B' // 7be7 #1 - 'C' // 7be8 #2 - 'aA' // 7be9-7bea #0 - 'aB' // 7beb-7bec #1 - 'F' // 7bed #5 - 'aD' // 7bee-7bef #3 - 'C' // 7bf0 #2 - 'B' // 7bf1 #1 - 'C' // 7bf2 #2 - 'A' // 7bf3 #0 - 'C' // 7bf4 #2 - 'aF' // 7bf5-7bf6 #5 - 'A' // 7bf7 #0 - 'bC' // 7bf8-7bfa #2 - 'B' // 7bfb #1 - 'C' // 7bfc #2 - 'B' // 7bfd #1 - 'A' // 7bfe #0 - 'B' // 7bff #1 - 'A' // 7c00 #0 - 'bC' // 7c01-7c03 #2 - 'F' // 7c04 #5 - 'B' // 7c05 #1 - 'C' // 7c06 #2 - 'A' // 7c07 #0 - 'D' // 7c08 #3 - 'A' // 7c09 #0 - 'B' // 7c0a #1 - 'A' // 7c0b #0 - 'bC' // 7c0c-7c0e #2 - 'A' // 7c0f #0 - 'B' // 7c10 #1 - 'C' // 7c11 #2 - 'A' // 7c12 #0 - 'aF' // 7c13-7c14 #5 - 'B' // 7c15 #1 - 'D' // 7c16 #3 - 'F' // 7c17 #5 - 'D' // 7c18 #3 - 'C' // 7c19 #2 - 'D' // 7c1a #3 - 'C' // 7c1b #2 - 'aB' // 7c1c-7c1d #1 - 'cA' // 7c1e-7c21 #0 - 'B' // 7c22 #1 - 'C' // 7c23 #2 - 'D' // 7c24 #3 - 'aC' // 7c25-7c26 #2 - 'A' // 7c27 #0 - 'C' // 7c28 #2 - 'B' // 7c29 #1 - 'aA' // 7c2a-7c2b #0 - 'C' // 7c2c #2 - 'B' // 7c2d #1 - 'D' // 7c2e #3 - 'F' // 7c2f #5 - 'B' // 7c30 #1 - 'F' // 7c31 #5 - 'D' // 7c32 #3 - 'C' // 7c33 #2 - 'F' // 7c34 #5 - 'B' // 7c35 #1 - 'F' // 7c36 #5 - 'aA' // 7c37-7c38 #0 - 'C' // 7c39 #2 - 'F' // 7c3a #5 - 'aB' // 7c3b-7c3c #1 - 'bA' // 7c3d-7c3f #0 - 'C' // 7c40 #2 - 'D' // 7c41 #3 - 'C' // 7c42 #2 - 'A' // 7c43 #0 - 'B' // 7c44 #1 - 'C' // 7c45 #2 - 'F' // 7c46 #5 - 'bB' // 7c47-7c49 #1 - 'C' // 7c4a #2 - 'D' // 7c4b #3 - 'aA' // 7c4c-7c4d #0 - 'D' // 7c4e #3 - 'F' // 7c4f #5 - 'A' // 7c50 #0 - 'C' // 7c51 #2 - 'J' // 7c52 #9 - 'C' // 7c53 #2 - 'A' // 7c54 #0 - 'F' // 7c55 #5 - 'aC' // 7c56-7c57 #2 - 'F' // 7c58 #5 - 'aC' // 7c59-7c5a #2 - 'aA' // 7c5b-7c5c #0 - 'C' // 7c5d #2 - 'F' // 7c5e #5 - 'aA' // 7c5f-7c60 #0 - 'F' // 7c61 #5 - 'D' // 7c62 #3 - 'C' // 7c63 #2 - 'aA' // 7c64-7c65 #0 - 'B' // 7c66 #1 - 'A' // 7c67 #0 - 'D' // 7c68 #3 - 'A' // 7c69 #0 - 'aB' // 7c6a-7c6b #1 - 'A' // 7c6c #0 - 'cC' // 7c6d-7c70 #2 - 'D' // 7c71 #3 - 'aA' // 7c72-7c73 #0 - 'B' // 7c74 #1 - 'C' // 7c75 #2 - 'aD' // 7c76-7c77 #3 - 'B' // 7c78 #1 - 'C' // 7c79 #2 - 'B' // 7c7a #1 - 'bC' // 7c7b-7c7d #2 - 'A' // 7c7e #0 - 'aB' // 7c7f-7c80 #1 - 'A' // 7c81 #0 - 'F' // 7c82 #5 - 'A' // 7c83 #0 - 'aB' // 7c84-7c85 #1 - 'C' // 7c86 #2 - 'F' // 7c87 #5 - 'B' // 7c88 #1 - 'A' // 7c89 #0 - 'B' // 7c8a #1 - 'F' // 7c8b #5 - 'B' // 7c8c #1 - 'A' // 7c8d #0 - 'B' // 7c8e #1 - 'aF' // 7c8f-7c90 #5 - 'B' // 7c91 #1 - 'A' // 7c92 #0 - 'D' // 7c93 #3 - 'C' // 7c94 #2 - 'A' // 7c95 #0 - 'B' // 7c96 #1 - 'aA' // 7c97-7c98 #0 - 'aD' // 7c99-7c9a #3 - 'F' // 7c9b #5 - 'B' // 7c9c #1 - 'D' // 7c9d #3 - 'C' // 7c9e #2 - 'A' // 7c9f #0 - 'F' // 7ca0 #5 - 'C' // 7ca1 #2 - 'A' // 7ca2 #0 - 'B' // 7ca3 #1 - 'J' // 7ca4 #9 - 'cA' // 7ca5-7ca8 #0 - 'aD' // 7ca9-7caa #3 - 'F' // 7cab #5 - 'B' // 7cac #1 - 'F' // 7cad #5 - 'A' // 7cae #0 - 'B' // 7caf #1 - 'F' // 7cb0 #5 - 'bA' // 7cb1-7cb3 #0 - 'aB' // 7cb4-7cb5 #1 - 'aF' // 7cb6-7cb7 #5 - 'B' // 7cb8 #1 - 'A' // 7cb9 #0 - 'aC' // 7cba-7cbb #2 - 'bA' // 7cbc-7cbe #0 - 'C' // 7cbf #2 - 'F' // 7cc0 #5 - 'D' // 7cc1 #3 - 'C' // 7cc2 #2 - 'B' // 7cc3 #1 - 'F' // 7cc4 #5 - 'A' // 7cc5 #0 - 'K' // 7cc6 #10 - 'bC' // 7cc7-7cc9 #2 - 'A' // 7cca #0 - 'aB' // 7ccb-7ccc #1 - 'aC' // 7ccd-7cce #2 - 'F' // 7ccf #5 - 'aB' // 7cd0-7cd1 #1 - 'bC' // 7cd2-7cd4 #2 - 'bA' // 7cd5-7cd7 #0 - 'F' // 7cd8 #5 - 'aA' // 7cd9-7cda #0 - 'D' // 7cdb #3 - 'dA' // 7cdc-7ce0 #0 - 'D' // 7ce1 #3 - 'A' // 7ce2 #0 - 'aD' // 7ce3-7ce4 #3 - 'K' // 7ce5 #10 - 'C' // 7ce6 #2 - 'A' // 7ce7 #0 - 'B' // 7ce8 #1 - 'F' // 7ce9 #5 - 'B' // 7cea #1 - 'F' // 7ceb #5 - 'bB' // 7cec-7cee #1 - 'A' // 7cef #0 - 'B' // 7cf0 #1 - 'I' // 7cf1 #8 - 'A' // 7cf2 #0 - 'B' // 7cf3 #1 - 'bA' // 7cf4-7cf6 #0 - 'B' // 7cf7 #1 - 'aA' // 7cf8-7cf9 #0 - 'J' // 7cfa #9 - 'A' // 7cfb #0 - 'aB' // 7cfc-7cfd #1 - 'A' // 7cfe #0 - 'D' // 7cff #3 - 'A' // 7d00 #0 - 'B' // 7d01 #1 - 'fA' // 7d02-7d08 #0 - 'C' // 7d09 #2 - 'aA' // 7d0a-7d0b #0 - 'B' // 7d0c #1 - 'A' // 7d0d #0 - 'B' // 7d0e #1 - 'C' // 7d0f #2 - 'A' // 7d10 #0 - 'aC' // 7d11-7d12 #2 - 'bA' // 7d13-7d15 #0 - 'C' // 7d16 #2 - 'eA' // 7d17-7d1c #0 - 'aC' // 7d1d-7d1e #2 - 'B' // 7d1f #1 - 'bA' // 7d20-7d22 #0 - 'F' // 7d23 #5 - 'D' // 7d24 #3 - 'B' // 7d25 #1 - 'F' // 7d26 #5 - 'D' // 7d27 #3 - 'aB' // 7d28-7d29 #1 - 'F' // 7d2a #5 - 'aA' // 7d2b-7d2c #0 - 'F' // 7d2d #5 - 'eA' // 7d2e-7d33 #0 - 'D' // 7d34 #3 - 'A' // 7d35 #0 - 'B' // 7d36 #1 - 'D' // 7d37 #3 - 'I' // 7d38 #8 - 'aA' // 7d39-7d3a #0 - 'B' // 7d3b #1 - 'dC' // 7d3c-7d40 #2 - 'eA' // 7d41-7d46 #0 - 'C' // 7d47 #2 - 'F' // 7d48 #5 - 'K' // 7d49 #10 - 'B' // 7d4a #1 - 'F' // 7d4b #5 - 'J' // 7d4c #9 - 'A' // 7d4d #0 - 'aC' // 7d4e-7d4f #2 - 'aA' // 7d50-7d51 #0 - 'B' // 7d52 #1 - 'C' // 7d53 #2 - 'B' // 7d54 #1 - 'aA' // 7d55-7d56 #0 - 'F' // 7d57 #5 - 'B' // 7d58 #1 - 'J' // 7d59 #9 - 'C' // 7d5a #2 - 'aA' // 7d5b-7d5c #0 - 'C' // 7d5d #2 - 'A' // 7d5e #0 - 'B' // 7d5f #1 - 'D' // 7d60 #3 - 'bA' // 7d61-7d63 #0 - 'D' // 7d64 #3 - 'F' // 7d65 #5 - 'A' // 7d66 #0 - 'C' // 7d67 #2 - 'A' // 7d68 #0 - 'I' // 7d69 #8 - 'A' // 7d6a #0 - 'B' // 7d6b #1 - 'D' // 7d6c #3 - 'B' // 7d6d #1 - 'A' // 7d6e #0 - 'B' // 7d6f #1 - 'cA' // 7d70-7d73 #0 - 'D' // 7d74 #3 - 'aJ' // 7d75-7d76 #9 - 'D' // 7d77 #3 - 'F' // 7d78 #5 - 'aA' // 7d79-7d7a #0 - 'C' // 7d7b #2 - 'B' // 7d7c #1 - 'C' // 7d7d #2 - 'D' // 7d7e #3 - 'A' // 7d7f #0 - 'B' // 7d80 #1 - 'C' // 7d81 #2 - 'F' // 7d82 #5 - 'A' // 7d83 #0 - 'B' // 7d84 #1 - 'C' // 7d85 #2 - 'A' // 7d86 #0 - 'D' // 7d87 #3 - 'aC' // 7d88-7d89 #2 - 'D' // 7d8a #3 - 'bC' // 7d8b-7d8d #2 - 'I' // 7d8e #8 - 'A' // 7d8f #0 - 'D' // 7d90 #3 - 'C' // 7d91 #2 - 'B' // 7d92 #1 - 'A' // 7d93 #0 - 'aB' // 7d94-7d95 #1 - 'aC' // 7d96-7d97 #2 - 'K' // 7d98 #10 - 'F' // 7d99 #5 - 'J' // 7d9a #9 - 'F' // 7d9b #5 - 'A' // 7d9c #0 - 'bC' // 7d9d-7d9f #2 - 'A' // 7da0 #0 - 'B' // 7da1 #1 - 'aA' // 7da2-7da3 #0 - 'B' // 7da4 #1 - 'K' // 7da5 #10 - 'aA' // 7da6-7da7 #0 - 'B' // 7da8 #1 - 'I' // 7da9 #8 - 'C' // 7daa #2 - 'cA' // 7dab-7dae #0 - 'C' // 7daf #2 - 'bA' // 7db0-7db2 #0 - 'C' // 7db3 #2 - 'aA' // 7db4-7db5 #0 - 'F' // 7db6 #5 - 'C' // 7db7 #2 - 'A' // 7db8 #0 - 'C' // 7db9 #2 - 'aA' // 7dba-7dbb #0 - 'B' // 7dbc #1 - 'bA' // 7dbd-7dbf #0 - 'C' // 7dc0 #2 - 'B' // 7dc1 #1 - 'C' // 7dc2 #2 - 'F' // 7dc3 #5 - 'A' // 7dc4 #0 - 'aC' // 7dc5-7dc6 #2 - 'A' // 7dc7 #0 - 'D' // 7dc8 #3 - 'B' // 7dc9 #1 - 'cA' // 7dca-7dcd #0 - 'C' // 7dce #2 - 'A' // 7dcf #0 - 'C' // 7dd0 #2 - 'F' // 7dd1 #5 - 'C' // 7dd2 #2 - 'aB' // 7dd3-7dd4 #1 - 'F' // 7dd5 #5 - 'J' // 7dd6 #9 - 'aA' // 7dd7-7dd8 #0 - 'C' // 7dd9 #2 - 'A' // 7dda #0 - 'B' // 7ddb #1 - 'bA' // 7ddc-7dde #0 - 'B' // 7ddf #1 - 'aA' // 7de0-7de1 #0 - 'F' // 7de2 #5 - 'A' // 7de3 #0 - 'aC' // 7de4-7de5 #2 - 'A' // 7de6 #0 - 'B' // 7de7 #1 - 'aA' // 7de8-7de9 #0 - 'C' // 7dea #2 - 'F' // 7deb #5 - 'A' // 7dec #0 - 'F' // 7ded #5 - 'B' // 7dee #1 - 'A' // 7def #0 - 'B' // 7df0 #1 - 'aC' // 7df1-7df2 #2 - 'B' // 7df3 #1 - 'A' // 7df4 #0 - 'C' // 7df5 #2 - 'A' // 7df6 #0 - 'B' // 7df7 #1 - 'D' // 7df8 #3 - 'A' // 7df9 #0 - 'C' // 7dfa #2 - 'A' // 7dfb #0 - 'bB' // 7dfc-7dfe #1 - 'D' // 7dff #3 - 'aF' // 7e00-7e01 #5 - 'D' // 7e02 #3 - 'I' // 7e03 #8 - 'aF' // 7e04-7e05 #5 - 'D' // 7e06 #3 - 'B' // 7e07 #1 - 'cA' // 7e08-7e0b #0 - 'cB' // 7e0c-7e0f #1 - 'aA' // 7e10-7e11 #0 - 'C' // 7e12 #2 - 'aB' // 7e13-7e14 #1 - 'A' // 7e15 #0 - 'B' // 7e16 #1 - 'A' // 7e17 #0 - 'K' // 7e18 #10 - 'D' // 7e19 #3 - 'B' // 7e1a #1 - 'A' // 7e1b #0 - 'C' // 7e1c #2 - 'fA' // 7e1d-7e23 #0 - 'aB' // 7e24-7e25 #1 - 'F' // 7e26 #5 - 'C' // 7e27 #2 - 'F' // 7e28 #5 - 'aB' // 7e29-7e2a #1 - 'A' // 7e2b #0 - 'F' // 7e2c #5 - 'C' // 7e2d #2 - 'aA' // 7e2e-7e2f #0 - 'B' // 7e30 #1 - 'bA' // 7e31-7e33 #0 - 'B' // 7e34 #1 - 'A' // 7e35 #0 - 'C' // 7e36 #2 - 'A' // 7e37 #0 - 'B' // 7e38 #1 - 'A' // 7e39 #0 - 'C' // 7e3a #2 - 'A' // 7e3b #0 - 'B' // 7e3c #1 - 'aA' // 7e3d-7e3e #0 - 'C' // 7e3f #2 - 'B' // 7e40 #1 - 'A' // 7e41 #0 - 'B' // 7e42 #1 - 'eA' // 7e43-7e48 #0 - 'B' // 7e49 #1 - 'aF' // 7e4a-7e4b #5 - 'B' // 7e4c #1 - 'aF' // 7e4d-7e4e #5 - 'D' // 7e4f #3 - 'A' // 7e50 #0 - 'B' // 7e51 #1 - 'A' // 7e52 #0 - 'B' // 7e53 #1 - 'bA' // 7e54-7e56 #0 - 'I' // 7e57 #8 - 'C' // 7e58 #2 - 'aA' // 7e59-7e5a #0 - 'aB' // 7e5b-7e5c #1 - 'F' // 7e5d #5 - 'A' // 7e5e #0 - 'C' // 7e5f #2 - 'B' // 7e60 #1 - 'aA' // 7e61-7e62 #0 - 'B' // 7e63 #1 - 'D' // 7e64 #3 - 'C' // 7e65 #2 - 'F' // 7e66 #5 - 'C' // 7e67 #2 - 'B' // 7e68 #1 - 'bA' // 7e69-7e6b #0 - 'B' // 7e6c #1 - 'A' // 7e6d #0 - 'C' // 7e6e #2 - 'aA' // 7e6f-7e70 #0 - 'D' // 7e71 #3 - 'B' // 7e72 #1 - 'C' // 7e73 #2 - 'B' // 7e74 #1 - 'C' // 7e75 #2 - 'I' // 7e76 #8 - 'B' // 7e77 #1 - 'C' // 7e78 #2 - 'A' // 7e79 #0 - 'B' // 7e7a #1 - 'C' // 7e7b #2 - 'bA' // 7e7c-7e7e #0 - 'C' // 7e7f #2 - 'B' // 7e80 #1 - 'aA' // 7e81-7e82 #0 - 'F' // 7e83 #5 - 'aD' // 7e84-7e85 #3 - 'C' // 7e86 #2 - 'aA' // 7e87-7e88 #0 - 'F' // 7e89 #5 - 'A' // 7e8a #0 - 'B' // 7e8b #1 - 'aA' // 7e8c-7e8d #0 - 'C' // 7e8e #2 - 'A' // 7e8f #0 - 'F' // 7e90 #5 - 'aC' // 7e91-7e92 #2 - 'aA' // 7e93-7e94 #0 - 'C' // 7e95 #2 - 'A' // 7e96 #0 - 'B' // 7e97 #1 - 'A' // 7e98 #0 - 'B' // 7e99 #1 - 'C' // 7e9a #2 - 'aA' // 7e9b-7e9c #0 - 'aF' // 7e9d-7e9e #5 - 'A' // 7e9f #0 - 'cD' // 7ea0-7ea3 #3 - 'B' // 7ea4 #1 - 'fD' // 7ea5-7eab #3 - 'B' // 7eac #1 - 'lD' // 7ead-7eb9 #3 - 'B' // 7eba #1 - 'kD' // 7ebb-7ec6 #3 - 'B' // 7ec7 #1 - 'fD' // 7ec8-7ece #3 - 'B' // 7ecf #1 - 'nD' // 7ed0-7ede #3 - 'B' // 7edf #1 - '1kD' // 7ee0-7f05 #3 - 'B' // 7f06 #1 - '1tD' // 7f07-7f35 #3 - 'A' // 7f36 #0 - 'I' // 7f37 #8 - 'A' // 7f38 #0 - 'B' // 7f39 #1 - 'A' // 7f3a #0 - 'aF' // 7f3b-7f3c #5 - 'C' // 7f3d #2 - 'aA' // 7f3e-7f3f #0 - 'aB' // 7f40-7f41 #1 - 'D' // 7f42 #3 - 'bA' // 7f43-7f45 #0 - 'D' // 7f46 #3 - 'A' // 7f47 #0 - 'bB' // 7f48-7f4a #1 - 'I' // 7f4b #8 - 'bA' // 7f4c-7f4e #0 - 'C' // 7f4f #2 - 'eA' // 7f50-7f55 #0 - 'aD' // 7f56-7f57 #3 - 'A' // 7f58 #0 - 'aD' // 7f59-7f5a #3 - 'aC' // 7f5b-7f5c #2 - 'A' // 7f5d #0 - 'B' // 7f5e #1 - 'bA' // 7f5f-7f61 #0 - 'D' // 7f62 #3 - 'A' // 7f63 #0 - 'F' // 7f64 #5 - 'C' // 7f65 #2 - 'A' // 7f66 #0 - 'C' // 7f67 #2 - 'A' // 7f68 #0 - 'C' // 7f69 #2 - 'aA' // 7f6a-7f6b #0 - 'B' // 7f6c #1 - 'C' // 7f6d #2 - 'A' // 7f6e #0 - 'D' // 7f6f #3 - 'A' // 7f70 #0 - 'C' // 7f71 #2 - 'A' // 7f72 #0 - 'B' // 7f73 #1 - 'D' // 7f74 #3 - 'A' // 7f75 #0 - 'B' // 7f76 #1 - 'bA' // 7f77-7f79 #0 - 'aB' // 7f7a-7f7b #1 - 'I' // 7f7c #8 - 'aA' // 7f7d-7f7e #0 - 'C' // 7f7f #2 - 'F' // 7f80 #5 - 'D' // 7f81 #3 - 'J' // 7f82 #9 - 'C' // 7f83 #2 - 'D' // 7f84 #3 - 'cA' // 7f85-7f88 #0 - 'B' // 7f89 #1 - 'A' // 7f8a #0 - 'C' // 7f8b #2 - 'A' // 7f8c #0 - 'C' // 7f8d #2 - 'A' // 7f8e #0 - 'C' // 7f8f #2 - 'F' // 7f90 #5 - 'C' // 7f91 #2 - 'aB' // 7f92-7f93 #1 - 'A' // 7f94 #0 - 'B' // 7f95 #1 - 'aA' // 7f96-7f97 #0 - 'K' // 7f98 #10 - 'D' // 7f99 #3 - 'A' // 7f9a #0 - 'B' // 7f9b #1 - 'C' // 7f9c #2 - 'aA' // 7f9d-7f9e #0 - 'D' // 7f9f #3 - 'B' // 7fa0 #1 - 'A' // 7fa1 #0 - 'aC' // 7fa2-7fa3 #2 - 'A' // 7fa4 #0 - 'B' // 7fa5 #1 - 'C' // 7fa6 #2 - 'B' // 7fa7 #1 - 'aA' // 7fa8-7fa9 #0 - 'F' // 7faa #5 - 'K' // 7fab #10 - 'B' // 7fac #1 - 'aC' // 7fad-7fae #2 - 'A' // 7faf #0 - 'aB' // 7fb0-7fb1 #1 - 'A' // 7fb2 #0 - 'B' // 7fb3 #1 - 'C' // 7fb4 #2 - 'B' // 7fb5 #1 - 'A' // 7fb6 #0 - 'B' // 7fb7 #1 - 'aA' // 7fb8-7fb9 #0 - 'aB' // 7fba-7fbb #1 - 'C' // 7fbc #2 - 'A' // 7fbd #0 - 'B' // 7fbe #1 - 'A' // 7fbf #0 - 'C' // 7fc0 #2 - 'A' // 7fc1 #0 - 'B' // 7fc2 #1 - 'C' // 7fc3 #2 - 'D' // 7fc4 #3 - 'A' // 7fc5 #0 - 'F' // 7fc6 #5 - 'B' // 7fc7 #1 - 'F' // 7fc8 #5 - 'B' // 7fc9 #1 - 'A' // 7fca #0 - 'B' // 7fcb #1 - 'A' // 7fcc #0 - 'B' // 7fcd #1 - 'A' // 7fce #0 - 'C' // 7fcf #2 - 'aB' // 7fd0-7fd1 #1 - 'A' // 7fd2 #0 - 'D' // 7fd3 #3 - 'aA' // 7fd4-7fd5 #0 - 'K' // 7fd6 #10 - 'B' // 7fd7 #1 - 'bD' // 7fd8-7fda #3 - 'A' // 7fdb #0 - 'bB' // 7fdc-7fde #1 - 'bA' // 7fdf-7fe1 #0 - 'B' // 7fe2 #1 - 'A' // 7fe3 #0 - 'K' // 7fe4 #10 - 'C' // 7fe5 #2 - 'A' // 7fe6 #0 - 'B' // 7fe7 #1 - 'C' // 7fe8 #2 - 'A' // 7fe9 #0 - 'B' // 7fea #1 - 'aA' // 7feb-7fec #0 - 'B' // 7fed #1 - 'A' // 7fee #0 - 'C' // 7fef #2 - 'A' // 7ff0 #0 - 'B' // 7ff1 #1 - 'C' // 7ff2 #2 - 'A' // 7ff3 #0 - 'aB' // 7ff4-7ff5 #1 - 'D' // 7ff6 #3 - 'aB' // 7ff7-7ff8 #1 - 'cA' // 7ff9-7ffc #0 - 'C' // 7ffd #2 - 'A' // 7ffe #0 - 'C' // 7fff #2 - 'hA' // 8000-8008 #0 - 'K' // 8009 #10 - 'F' // 800a #5 - 'C' // 800b #2 - 'A' // 800c #0 - 'bC' // 800d-800f #2 - 'bA' // 8010-8012 #0 - 'F' // 8013 #5 - 'eA' // 8014-8019 #0 - 'D' // 801a #3 - 'B' // 801b #1 - 'aC' // 801c-801d #2 - 'A' // 801e #0 - 'aC' // 801f-8020 #2 - 'A' // 8021 #0 - 'aD' // 8022-8023 #3 - 'C' // 8024 #2 - 'B' // 8025 #1 - 'A' // 8026 #0 - 'D' // 8027 #3 - 'A' // 8028 #0 - 'aB' // 8029-802a #1 - 'D' // 802b #3 - 'A' // 802c #0 - 'K' // 802d #10 - 'C' // 802e #2 - 'B' // 802f #1 - 'A' // 8030 #0 - 'B' // 8031 #1 - 'D' // 8032 #3 - 'A' // 8033 #0 - 'aC' // 8034-8035 #2 - 'A' // 8036 #0 - 'C' // 8037 #2 - 'D' // 8038 #3 - 'C' // 8039 #2 - 'F' // 803a #5 - 'aC' // 803b-803c #2 - 'A' // 803d #0 - 'C' // 803e #2 - 'A' // 803f #0 - 'F' // 8040 #5 - 'aD' // 8041-8042 #3 - 'A' // 8043 #0 - 'F' // 8044 #5 - 'D' // 8045 #3 - 'A' // 8046 #0 - 'B' // 8047 #1 - 'I' // 8048 #8 - 'D' // 8049 #3 - 'A' // 804a #0 - 'cD' // 804b-804e #3 - 'bB' // 804f-8051 #1 - 'A' // 8052 #0 - 'D' // 8053 #3 - 'B' // 8054 #1 - 'K' // 8055 #10 - 'A' // 8056 #0 - 'D' // 8057 #3 - 'A' // 8058 #0 - 'D' // 8059 #3 - 'A' // 805a #0 - 'bB' // 805b-805d #1 - 'A' // 805e #0 - 'aF' // 805f-8060 #5 - 'A' // 8061 #0 - 'C' // 8062 #2 - 'B' // 8063 #1 - 'C' // 8064 #2 - 'D' // 8065 #3 - 'C' // 8066 #2 - 'B' // 8067 #1 - 'F' // 8068 #5 - 'bD' // 8069-806b #3 - 'B' // 806c #1 - 'F' // 806d #5 - 'D' // 806e #3 - 'dA' // 806f-8073 #0 - 'F' // 8074 #5 - 'bA' // 8075-8077 #0 - 'B' // 8078 #1 - 'C' // 8079 #2 - 'D' // 807a #3 - 'F' // 807b #5 - 'D' // 807c #3 - 'cA' // 807d-8080 #0 - 'F' // 8081 #5 - 'B' // 8082 #1 - 'D' // 8083 #3 - 'cA' // 8084-8087 #0 - 'F' // 8088 #5 - 'A' // 8089 #0 - 'B' // 808a #1 - 'aA' // 808b-808c #0 - 'D' // 808d #3 - 'F' // 808e #5 - 'aB' // 808f-8090 #1 - 'D' // 8091 #3 - 'B' // 8092 #1 - 'A' // 8093 #0 - 'D' // 8094 #3 - 'B' // 8095 #1 - 'A' // 8096 #0 - 'D' // 8097 #3 - 'A' // 8098 #0 - 'C' // 8099 #2 - 'aA' // 809a-809b #0 - 'C' // 809c #2 - 'A' // 809d #0 - 'F' // 809e #5 - 'B' // 809f #1 - 'D' // 80a0 #3 - 'aA' // 80a1-80a2 #0 - 'B' // 80a3 #1 - 'F' // 80a4 #5 - 'A' // 80a5 #0 - 'J' // 80a6 #9 - 'C' // 80a7 #2 - 'D' // 80a8 #3 - 'bA' // 80a9-80ab #0 - 'F' // 80ac #5 - 'A' // 80ad #0 - 'B' // 80ae #1 - 'A' // 80af #0 - 'D' // 80b0 #3 - 'aA' // 80b1-80b2 #0 - 'D' // 80b3 #3 - 'A' // 80b4 #0 - 'I' // 80b5 #8 - 'aB' // 80b6-80b7 #1 - 'C' // 80b8 #2 - 'F' // 80b9 #5 - 'A' // 80ba #0 - 'D' // 80bb #3 - 'aB' // 80bc-80bd #1 - 'cD' // 80be-80c1 #3 - 'B' // 80c2 #1 - 'aA' // 80c3-80c4 #0 - 'C' // 80c5 #2 - 'A' // 80c6 #0 - 'B' // 80c7 #1 - 'C' // 80c8 #2 - 'B' // 80c9 #1 - 'A' // 80ca #0 - 'D' // 80cb #3 - 'A' // 80cc #0 - 'C' // 80cd #2 - 'A' // 80ce #0 - 'C' // 80cf #2 - 'aB' // 80d0-80d1 #1 - 'F' // 80d2 #5 - 'D' // 80d3 #3 - 'C' // 80d4 #2 - 'aA' // 80d5-80d6 #0 - 'aC' // 80d7-80d8 #2 - 'bA' // 80d9-80db #0 - 'I' // 80dc #8 - 'C' // 80dd #2 - 'A' // 80de #0 - 'D' // 80df #3 - 'aA' // 80e0-80e1 #0 - 'D' // 80e2 #3 - 'B' // 80e3 #1 - 'aA' // 80e4-80e5 #0 - 'C' // 80e6 #2 - 'aD' // 80e7-80e8 #3 - 'B' // 80e9 #1 - 'aD' // 80ea-80eb #3 - 'B' // 80ec #1 - 'C' // 80ed #2 - 'F' // 80ee #5 - 'A' // 80ef #0 - 'C' // 80f0 #2 - 'A' // 80f1 #0 - 'aC' // 80f2-80f3 #2 - 'A' // 80f4 #0 - 'aC' // 80f5-80f6 #2 - 'J' // 80f7 #9 - 'A' // 80f8 #0 - 'cC' // 80f9-80fc #2 - 'aA' // 80fd-80fe #0 - 'D' // 80ff #3 - 'B' // 8100 #1 - 'C' // 8101 #2 - 'A' // 8102 #0 - 'C' // 8103 #2 - 'D' // 8104 #3 - 'eA' // 8105-810a #0 - 'F' // 810b #5 - 'B' // 810c #1 - 'F' // 810d #5 - 'B' // 810e #1 - 'bD' // 810f-8111 #3 - 'B' // 8112 #1 - 'D' // 8113 #3 - 'aB' // 8114-8115 #1 - 'bA' // 8116-8118 #0 - 'B' // 8119 #1 - 'aA' // 811a-811b #0 - 'F' // 811c #5 - 'B' // 811d #1 - 'C' // 811e #2 - 'B' // 811f #1 - 'F' // 8120 #5 - 'aB' // 8121-8122 #1 - 'aA' // 8123-8124 #0 - 'B' // 8125 #1 - 'D' // 8126 #3 - 'A' // 8127 #0 - 'D' // 8128 #3 - 'A' // 8129 #0 - 'B' // 812a #1 - 'A' // 812b #0 - 'C' // 812c #2 - 'B' // 812d #1 - 'D' // 812e #3 - 'aA' // 812f-8130 #0 - 'C' // 8131 #2 - 'B' // 8132 #1 - 'F' // 8133 #5 - 'B' // 8134 #1 - 'F' // 8135 #5 - 'D' // 8136 #3 - 'B' // 8137 #1 - 'D' // 8138 #3 - 'aA' // 8139-813a #0 - 'D' // 813b #3 - 'F' // 813c #5 - 'C' // 813d #2 - 'A' // 813e #0 - 'aD' // 813f-8140 #3 - 'J' // 8141 #9 - 'bB' // 8142-8144 #1 - 'F' // 8145 #5 - 'A' // 8146 #0 - 'C' // 8147 #2 - 'B' // 8148 #1 - 'D' // 8149 #3 - 'aA' // 814a-814b #0 - 'C' // 814c #2 - 'B' // 814d #1 - 'A' // 814e #0 - 'B' // 814f #1 - 'eA' // 8150-8155 #0 - 'B' // 8156 #1 - 'F' // 8157 #5 - 'D' // 8158 #3 - 'cB' // 8159-815c #1 - 'D' // 815d #3 - 'B' // 815e #1 - 'F' // 815f #5 - 'A' // 8160 #0 - 'C' // 8161 #2 - 'B' // 8162 #1 - 'D' // 8163 #3 - 'I' // 8164 #8 - 'aA' // 8165-8166 #0 - 'C' // 8167 #2 - 'F' // 8168 #5 - 'C' // 8169 #2 - 'D' // 816a #3 - 'A' // 816b #0 - 'B' // 816c #1 - 'A' // 816d #0 - 'aC' // 816e-816f #2 - 'aA' // 8170-8171 #0 - 'B' // 8172 #1 - 'C' // 8173 #2 - 'A' // 8174 #0 - 'D' // 8175 #3 - 'I' // 8176 #8 - 'cA' // 8177-817a #0 - 'D' // 817b #3 - 'aB' // 817c-817d #1 - 'D' // 817e #3 - 'aA' // 817f-8180 #0 - 'F' // 8181 #5 - 'bA' // 8182-8184 #0 - 'F' // 8185 #5 - 'A' // 8186 #0 - 'B' // 8187 #1 - 'A' // 8188 #0 - 'B' // 8189 #1 - 'aA' // 818a-818b #0 - 'aB' // 818c-818d #1 - 'F' // 818e #5 - 'A' // 818f #0 - 'F' // 8190 #5 - 'aD' // 8191-8192 #3 - 'C' // 8193 #2 - 'D' // 8194 #3 - 'C' // 8195 #2 - 'F' // 8196 #5 - 'B' // 8197 #1 - 'C' // 8198 #2 - 'B' // 8199 #1 - 'A' // 819a #0 - 'C' // 819b #2 - 'bA' // 819c-819e #0 - 'B' // 819f #1 - 'A' // 81a0 #0 - 'D' // 81a1 #3 - 'C' // 81a2 #2 - 'A' // 81a3 #0 - 'F' // 81a4 #5 - 'bB' // 81a5-81a7 #1 - 'aA' // 81a8-81a9 #0 - 'bB' // 81aa-81ac #1 - 'D' // 81ad #3 - 'C' // 81ae #2 - 'D' // 81af #3 - 'A' // 81b0 #0 - 'B' // 81b1 #1 - 'C' // 81b2 #2 - 'bA' // 81b3-81b5 #0 - 'aB' // 81b6-81b7 #1 - 'J' // 81b8 #9 - 'I' // 81b9 #8 - 'A' // 81ba #0 - 'C' // 81bb #2 - 'B' // 81bc #1 - 'cA' // 81bd-81c0 #0 - 'C' // 81c1 #2 - 'A' // 81c2 #0 - 'C' // 81c3 #2 - 'B' // 81c4 #1 - 'C' // 81c5 #2 - 'A' // 81c6 #0 - 'B' // 81c7 #1 - 'aC' // 81c8-81c9 #2 - 'A' // 81ca #0 - 'F' // 81cb #5 - 'B' // 81cc #1 - 'A' // 81cd #0 - 'F' // 81ce #5 - 'A' // 81cf #0 - 'B' // 81d0 #1 - 'A' // 81d1 #0 - 'B' // 81d2 #1 - 'F' // 81d3 #5 - 'D' // 81d4 #3 - 'C' // 81d5 #2 - 'F' // 81d6 #5 - 'C' // 81d7 #2 - 'bA' // 81d8-81da #0 - 'C' // 81db #2 - 'D' // 81dc #3 - 'A' // 81dd #0 - 'C' // 81de #2 - 'aA' // 81df-81e0 #0 - 'C' // 81e1 #2 - 'B' // 81e2 #1 - 'A' // 81e3 #0 - 'C' // 81e4 #2 - 'A' // 81e5 #0 - 'B' // 81e6 #1 - 'aA' // 81e7-81e8 #0 - 'B' // 81e9 #1 - 'A' // 81ea #0 - 'F' // 81eb #5 - 'aA' // 81ec-81ed #0 - 'B' // 81ee #1 - 'C' // 81ef #2 - 'aF' // 81f0-81f1 #5 - 'C' // 81f2 #2 - 'aA' // 81f3-81f4 #0 - 'F' // 81f5 #5 - 'A' // 81f6 #0 - 'B' // 81f7 #1 - 'aC' // 81f8-81f9 #2 - 'bA' // 81fa-81fc #0 - 'F' // 81fd #5 - 'A' // 81fe #0 - 'aC' // 81ff-8200 #2 - 'aA' // 8201-8202 #0 - 'J' // 8203 #9 - 'C' // 8204 #2 - 'A' // 8205 #0 - 'D' // 8206 #3 - 'aA' // 8207-8208 #0 - 'C' // 8209 #2 - 'A' // 820a #0 - 'C' // 820b #2 - 'aA' // 820c-820d #0 - 'aF' // 820e-820f #5 - 'A' // 8210 #0 - 'B' // 8211 #1 - 'A' // 8212 #0 - 'F' // 8213 #5 - 'C' // 8214 #2 - 'B' // 8215 #1 - 'A' // 8216 #0 - 'F' // 8217 #5 - 'A' // 8218 #0 - 'F' // 8219 #5 - 'C' // 821a #2 - 'aA' // 821b-821c #0 - 'C' // 821d #2 - 'aA' // 821e-821f #0 - 'B' // 8220 #1 - 'A' // 8221 #0 - 'C' // 8222 #2 - 'aD' // 8223-8224 #3 - 'aB' // 8225-8226 #1 - 'D' // 8227 #3 - 'aC' // 8228-8229 #2 - 'bA' // 822a-822c #0 - 'B' // 822d #1 - 'F' // 822e #5 - 'B' // 822f #1 - 'aD' // 8230-8231 #3 - 'C' // 8232 #2 - 'A' // 8233 #0 - 'C' // 8234 #2 - 'dA' // 8235-8239 #0 - 'C' // 823a #2 - 'D' // 823b #3 - 'C' // 823c #2 - 'I' // 823d #8 - 'aB' // 823e-823f #1 - 'A' // 8240 #0 - 'D' // 8241 #3 - 'B' // 8242 #1 - 'F' // 8243 #5 - 'C' // 8244 #2 - 'A' // 8245 #0 - 'F' // 8246 #5 - 'A' // 8247 #0 - 'D' // 8248 #3 - 'C' // 8249 #2 - 'D' // 824a #3 - 'C' // 824b #2 - 'aD' // 824c-824d #3 - 'aC' // 824e-824f #2 - 'B' // 8250 #1 - 'A' // 8251 #0 - 'cB' // 8252-8255 #1 - 'aC' // 8256-8257 #2 - 'bA' // 8258-825a #0 - 'B' // 825b #1 - 'C' // 825c #2 - 'F' // 825d #5 - 'B' // 825e #1 - 'A' // 825f #0 - 'F' // 8260 #5 - 'B' // 8261 #1 - 'aC' // 8262-8263 #2 - 'A' // 8264 #0 - 'B' // 8265 #1 - 'A' // 8266 #0 - 'F' // 8267 #5 - 'A' // 8268 #0 - 'B' // 8269 #1 - 'J' // 826a #9 - 'A' // 826b #0 - 'B' // 826c #1 - 'C' // 826d #2 - 'aA' // 826e-826f #0 - 'D' // 8270 #3 - 'aA' // 8271-8272 #0 - 'D' // 8273 #3 - 'A' // 8274 #0 - 'B' // 8275 #1 - 'cA' // 8276-8279 #0 - 'B' // 827a #1 - 'C' // 827b #2 - 'B' // 827c #1 - 'aA' // 827d-827e #0 - 'aC' // 827f-8280 #2 - 'F' // 8281 #5 - 'D' // 8282 #3 - 'A' // 8283 #0 - 'C' // 8284 #2 - 'B' // 8285 #1 - 'D' // 8286 #3 - 'C' // 8287 #2 - 'D' // 8288 #3 - 'F' // 8289 #5 - 'aA' // 828a-828b #0 - 'D' // 828c #3 - 'aA' // 828d-828e #0 - 'B' // 828f #1 - 'I' // 8290 #8 - 'C' // 8291 #2 - 'A' // 8292 #0 - 'C' // 8293 #2 - 'A' // 8294 #0 - 'D' // 8295 #3 - 'F' // 8296 #5 - 'D' // 8297 #3 - 'bA' // 8298-829a #0 - 'C' // 829b #2 - 'D' // 829c #3 - 'A' // 829d #0 - 'B' // 829e #1 - 'A' // 829f #0 - 'C' // 82a0 #2 - 'A' // 82a1 #0 - 'I' // 82a2 #8 - 'A' // 82a3 #0 - 'C' // 82a4 #2 - 'lA' // 82a5-82b1 #0 - 'F' // 82b2 #5 - 'A' // 82b3 #0 - 'C' // 82b4 #2 - 'aB' // 82b5-82b6 #1 - 'bA' // 82b7-82b9 #0 - 'C' // 82ba #2 - 'dA' // 82bb-82bf #0 - 'B' // 82c0 #1 - 'D' // 82c1 #3 - 'bB' // 82c2-82c4 #1 - 'J' // 82c5 #9 - 'F' // 82c6 #5 - 'bD' // 82c7-82c9 #3 - 'B' // 82ca #1 - 'cD' // 82cb-82ce #3 - 'B' // 82cf #1 - 'C' // 82d0 #2 - 'dA' // 82d1-82d5 #0 - 'B' // 82d6 #1 - 'A' // 82d7 #0 - 'B' // 82d8 #1 - 'C' // 82d9 #2 - 'F' // 82da #5 - 'aA' // 82db-82dc #0 - 'D' // 82dd #3 - 'cA' // 82de-82e1 #0 - 'C' // 82e2 #2 - 'A' // 82e3 #0 - 'C' // 82e4 #2 - 'bA' // 82e5-82e7 #0 - 'C' // 82e8 #2 - 'K' // 82e9 #10 - 'C' // 82ea #2 - 'A' // 82eb #0 - 'B' // 82ec #1 - 'C' // 82ed #2 - 'B' // 82ee #1 - 'C' // 82ef #2 - 'B' // 82f0 #1 - 'A' // 82f1 #0 - 'B' // 82f2 #1 - 'aA' // 82f3-82f4 #0 - 'B' // 82f5 #1 - 'aC' // 82f6-82f7 #2 - 'B' // 82f8 #1 - 'bA' // 82f9-82fb #0 - 'B' // 82fc #1 - 'aA' // 82fd-82fe #0 - 'I' // 82ff #8 - 'eA' // 8300-8305 #0 - 'aC' // 8306-8307 #2 - 'aA' // 8308-8309 #0 - 'F' // 830a #5 - 'aC' // 830b-830c #2 - 'B' // 830d #1 - 'F' // 830e #5 - 'fD' // 830f-8315 #3 - 'C' // 8316 #2 - 'A' // 8317 #0 - 'C' // 8318 #2 - 'aB' // 8319-831a #1 - 'bA' // 831b-831d #0 - 'C' // 831e #2 - 'F' // 831f #5 - 'B' // 8320 #1 - 'F' // 8321 #5 - 'C' // 8322 #2 - 'J' // 8323 #9 - 'aI' // 8324-8325 #8 - 'aB' // 8326-8327 #1 - 'A' // 8328 #0 - 'B' // 8329 #1 - 'I' // 832a #8 - 'A' // 832b #0 - 'aC' // 832c-832d #2 - 'F' // 832e #5 - 'A' // 832f #0 - 'F' // 8330 #5 - 'eA' // 8331-8336 #0 - 'C' // 8337 #2 - 'aA' // 8338-8339 #0 - 'C' // 833a #2 - 'B' // 833b #1 - 'A' // 833c #0 - 'C' // 833d #2 - 'D' // 833e #3 - 'B' // 833f #1 - 'A' // 8340 #0 - 'B' // 8341 #1 - 'C' // 8342 #2 - 'A' // 8343 #0 - 'aC' // 8344-8345 #2 - 'F' // 8346 #5 - 'A' // 8347 #0 - 'B' // 8348 #1 - 'aA' // 8349-834a #0 - 'aB' // 834b-834c #1 - 'aC' // 834d-834e #2 - 'cA' // 834f-8352 #0 - 'aC' // 8353-8354 #2 - 'F' // 8355 #5 - 'aC' // 8356-8357 #2 - 'F' // 8358 #5 - 'D' // 8359 #3 - 'F' // 835a #5 - 'fD' // 835b-8361 #3 - 'C' // 8362 #2 - 'A' // 8363 #0 - 'aD' // 8364-8365 #3 - 'B' // 8366 #1 - 'gD' // 8367-836e #3 - 'B' // 836f #1 - 'F' // 8370 #5 - 'aD' // 8371-8372 #3 - 'A' // 8373 #0 - 'B' // 8374 #1 - 'C' // 8375 #2 - 'B' // 8376 #1 - 'A' // 8377 #0 - 'C' // 8378 #2 - 'D' // 8379 #3 - 'I' // 837a #8 - 'A' // 837b #0 - 'aC' // 837c-837d #2 - 'B' // 837e #1 - 'C' // 837f #2 - 'F' // 8380 #5 - 'B' // 8381 #1 - 'J' // 8382 #9 - 'B' // 8383 #1 - 'F' // 8384 #5 - 'A' // 8385 #0 - 'aC' // 8386-8387 #2 - 'B' // 8388 #1 - 'aA' // 8389-838a #0 - 'aB' // 838b-838c #1 - 'C' // 838d #2 - 'A' // 838e #0 - 'bB' // 838f-8391 #1 - 'aA' // 8392-8393 #0 - 'aC' // 8394-8395 #2 - 'A' // 8396 #0 - 'B' // 8397 #1 - 'A' // 8398 #0 - 'C' // 8399 #2 - 'aA' // 839a-839b #0 - 'C' // 839c #2 - 'aA' // 839d-839e #0 - 'J' // 839f #9 - 'A' // 83a0 #0 - 'D' // 83a1 #3 - 'A' // 83a2 #0 - 'bB' // 83a3-83a5 #1 - 'aC' // 83a6-83a7 #2 - 'cA' // 83a8-83ab #0 - 'C' // 83ac #2 - 'F' // 83ad #5 - 'bB' // 83ae-83b0 #1 - 'F' // 83b1 #5 - 'bD' // 83b2-83b4 #3 - 'F' // 83b5 #5 - 'bD' // 83b6-83b8 #3 - 'B' // 83b9 #1 - 'bD' // 83ba-83bc #3 - 'dA' // 83bd-83c1 #0 - 'I' // 83c2 #8 - 'aB' // 83c3-83c4 #1 - 'A' // 83c5 #0 - 'B' // 83c6 #1 - 'C' // 83c7 #2 - 'B' // 83c8 #1 - 'aA' // 83c9-83ca #0 - 'B' // 83cb #1 - 'A' // 83cc #0 - 'B' // 83cd #1 - 'aC' // 83ce-83cf #2 - 'F' // 83d0 #5 - 'A' // 83d1 #0 - 'D' // 83d2 #3 - 'aA' // 83d3-83d4 #0 - 'B' // 83d5 #1 - 'A' // 83d6 #0 - 'B' // 83d7 #1 - 'A' // 83d8 #0 - 'B' // 83d9 #1 - 'D' // 83da #3 - 'B' // 83db #1 - 'A' // 83dc #0 - 'C' // 83dd #2 - 'B' // 83de #1 - 'bA' // 83df-83e1 #0 - 'bB' // 83e2-83e4 #1 - 'C' // 83e5 #2 - 'D' // 83e6 #3 - 'B' // 83e7 #1 - 'C' // 83e8 #2 - 'A' // 83e9 #0 - 'C' // 83ea #2 - 'A' // 83eb #0 - 'bB' // 83ec-83ee #1 - 'cA' // 83ef-83f2 #0 - 'B' // 83f3 #1 - 'A' // 83f4 #0 - 'B' // 83f5 #1 - 'A' // 83f6 #0 - 'F' // 83f7 #5 - 'C' // 83f8 #2 - 'A' // 83f9 #0 - 'B' // 83fa #1 - 'A' // 83fb #0 - 'C' // 83fc #2 - 'A' // 83fd #0 - 'aB' // 83fe-83ff #1 - 'D' // 8400 #3 - 'C' // 8401 #2 - 'D' // 8402 #3 - 'aA' // 8403-8404 #0 - 'B' // 8405 #1 - 'aA' // 8406-8407 #0 - 'D' // 8408 #3 - 'B' // 8409 #1 - 'dA' // 840a-840e #0 - 'C' // 840f #2 - 'B' // 8410 #1 - 'C' // 8411 #2 - 'B' // 8412 #1 - 'C' // 8413 #2 - 'B' // 8414 #1 - 'F' // 8415 #5 - 'B' // 8416 #1 - 'F' // 8417 #5 - 'B' // 8418 #1 - 'F' // 8419 #5 - 'D' // 841a #3 - 'aB' // 841b-841c #1 - 'bD' // 841d-841f #3 - 'C' // 8420 #2 - 'B' // 8421 #1 - 'F' // 8422 #5 - 'aB' // 8423-8424 #1 - 'D' // 8425 #3 - 'B' // 8426 #1 - 'aD' // 8427-8428 #3 - 'A' // 8429 #0 - 'F' // 842a #5 - 'B' // 842b #1 - 'A' // 842c #0 - 'aB' // 842d-842e #1 - 'C' // 842f #2 - 'B' // 8430 #1 - 'A' // 8431 #0 - 'bB' // 8432-8434 #1 - 'A' // 8435 #0 - 'aB' // 8436-8437 #1 - 'aA' // 8438-8439 #0 - 'aB' // 843a-843b #1 - 'aA' // 843c-843d #0 - 'bB' // 843e-8440 #1 - 'D' // 8441 #3 - 'bB' // 8442-8444 #1 - 'C' // 8445 #2 - 'A' // 8446 #0 - 'aC' // 8447-8448 #2 - 'aA' // 8449-844a #0 - 'aB' // 844b-844c #1 - 'aC' // 844d-844e #2 - 'F' // 844f #5 - 'B' // 8450 #1 - 'A' // 8451 #0 - 'C' // 8452 #2 - 'bB' // 8453-8455 #1 - 'C' // 8456 #2 - 'A' // 8457 #0 - 'aC' // 8458-8459 #2 - 'aA' // 845a-845b #0 - 'C' // 845c #2 - 'aB' // 845d-845e #1 - 'aC' // 845f-8460 #2 - 'A' // 8461 #0 - 'C' // 8462 #2 - 'A' // 8463 #0 - 'aC' // 8464-8465 #2 - 'A' // 8466 #0 - 'C' // 8467 #2 - 'B' // 8468 #1 - 'A' // 8469 #0 - 'J' // 846a #9 - 'bA' // 846b-846d #0 - 'C' // 846e #2 - 'bA' // 846f-8471 #0 - 'B' // 8472 #1 - 'A' // 8473 #0 - 'C' // 8474 #2 - 'A' // 8475 #0 - 'C' // 8476 #2 - 'A' // 8477 #0 - 'aC' // 8478-8479 #2 - 'A' // 847a #0 - 'D' // 847b #3 - 'F' // 847c #5 - 'C' // 847d #2 - 'bB' // 847e-8480 #1 - 'F' // 8481 #5 - 'A' // 8482 #0 - 'D' // 8483 #3 - 'C' // 8484 #2 - 'F' // 8485 #5 - 'B' // 8486 #1 - 'D' // 8487 #3 - 'B' // 8488 #1 - 'aD' // 8489-848a #3 - 'F' // 848b #5 - 'D' // 848c #3 - 'bB' // 848d-848f #1 - 'A' // 8490 #0 - 'I' // 8491 #8 - 'aC' // 8492-8493 #2 - 'A' // 8494 #0 - 'F' // 8495 #5 - 'B' // 8496 #1 - 'C' // 8497 #2 - 'B' // 8498 #1 - 'A' // 8499 #0 - 'aB' // 849a-849b #1 - 'A' // 849c #0 - 'B' // 849d #1 - 'C' // 849e #2 - 'A' // 849f #0 - 'B' // 84a0 #1 - 'A' // 84a1 #0 - 'bB' // 84a2-84a4 #1 - 'D' // 84a5 #3 - 'F' // 84a6 #5 - 'B' // 84a7 #1 - 'A' // 84a8 #0 - 'aC' // 84a9-84aa #2 - 'aB' // 84ab-84ac #1 - 'A' // 84ad #0 - 'B' // 84ae #1 - 'A' // 84af #0 - 'B' // 84b0 #1 - 'C' // 84b1 #2 - 'A' // 84b2 #0 - 'D' // 84b3 #3 - 'A' // 84b4 #0 - 'D' // 84b5 #3 - 'B' // 84b6 #1 - 'D' // 84b7 #3 - 'eA' // 84b8-84bd #0 - 'C' // 84be #2 - 'cA' // 84bf-84c2 #0 - 'D' // 84c3 #3 - 'A' // 84c4 #0 - 'B' // 84c5 #1 - 'A' // 84c6 #0 - 'C' // 84c7 #2 - 'F' // 84c8 #5 - 'bA' // 84c9-84cb #0 - 'C' // 84cc #2 - 'A' // 84cd #0 - 'C' // 84ce #2 - 'bA' // 84cf-84d1 #0 - 'B' // 84d2 #1 - 'A' // 84d3 #0 - 'B' // 84d4 #1 - 'D' // 84d5 #3 - 'A' // 84d6 #0 - 'B' // 84d7 #1 - 'D' // 84d8 #3 - 'F' // 84d9 #5 - 'A' // 84da #0 - 'B' // 84db #1 - 'F' // 84dc #5 - 'D' // 84dd #3 - 'B' // 84de #1 - 'aD' // 84df-84e0 #3 - 'aB' // 84e1-84e2 #1 - 'D' // 84e3 #3 - 'aB' // 84e4-84e5 #1 - 'D' // 84e6 #3 - 'C' // 84e7 #2 - 'aB' // 84e8-84e9 #1 - 'C' // 84ea #2 - 'B' // 84eb #1 - 'A' // 84ec #0 - 'K' // 84ed #10 - 'aA' // 84ee-84ef #0 - 'C' // 84f0 #2 - 'A' // 84f1 #0 - 'C' // 84f2 #2 - 'B' // 84f3 #1 - 'A' // 84f4 #0 - 'D' // 84f5 #3 - 'B' // 84f6 #1 - 'C' // 84f7 #2 - 'aB' // 84f8-84f9 #1 - 'A' // 84fa #0 - 'C' // 84fb #2 - 'aA' // 84fc-84fd #0 - 'B' // 84fe #1 - 'C' // 84ff #2 - 'A' // 8500 #0 - 'D' // 8501 #3 - 'aC' // 8502-8503 #2 - 'aB' // 8504-8505 #1 - 'A' // 8506 #0 - 'C' // 8507 #2 - 'cB' // 8508-850b #1 - 'C' // 850c #2 - 'B' // 850d #1 - 'A' // 850e #0 - 'B' // 850f #1 - 'C' // 8510 #2 - 'A' // 8511 #0 - 'B' // 8512 #1 - 'bA' // 8513-8515 #0 - 'B' // 8516 #1 - 'aA' // 8517-8518 #0 - 'B' // 8519 #1 - 'A' // 851a #0 - 'F' // 851b #5 - 'C' // 851c #2 - 'B' // 851d #1 - 'aA' // 851e-851f #0 - 'B' // 8520 #1 - 'A' // 8521 #0 - 'F' // 8522 #5 - 'A' // 8523 #0 - 'C' // 8524 #2 - 'aA' // 8525-8526 #0 - 'C' // 8527 #2 - 'aB' // 8528-8529 #1 - 'A' // 852a #0 - 'C' // 852b #2 - 'aA' // 852c-852d #0 - 'B' // 852e #1 - 'A' // 852f #0 - 'I' // 8530 #8 - 'B' // 8531 #1 - 'F' // 8532 #5 - 'aC' // 8533-8534 #2 - 'aF' // 8535-8536 #5 - 'D' // 8537 #3 - 'B' // 8538 #1 - 'aD' // 8539-853a #3 - 'B' // 853b #1 - 'D' // 853c #3 - 'A' // 853d #0 - 'C' // 853e #2 - 'J' // 853f #9 - 'C' // 8540 #2 - 'A' // 8541 #0 - 'B' // 8542 #1 - 'A' // 8543 #0 - 'aB' // 8544-8545 #1 - 'A' // 8546 #0 - 'B' // 8547 #1 - 'C' // 8548 #2 - 'bA' // 8549-854b #0 - 'aB' // 854c-854d #1 - 'A' // 854e #0 - 'aF' // 854f-8550 #5 - 'aC' // 8551-8552 #2 - 'A' // 8553 #0 - 'B' // 8554 #1 - 'dA' // 8555-8559 #0 - 'C' // 855a #2 - 'B' // 855b #1 - 'F' // 855c #5 - 'C' // 855d #2 - 'A' // 855e #0 - 'aC' // 855f-8560 #2 - 'A' // 8561 #0 - 'C' // 8562 #2 - 'aA' // 8563-8564 #0 - 'bB' // 8565-8567 #1 - 'cA' // 8568-856b #0 - 'B' // 856c #1 - 'A' // 856d #0 - 'B' // 856e #1 - 'C' // 856f #2 - 'aB' // 8570-8571 #1 - 'D' // 8572 #3 - 'B' // 8573 #1 - 'D' // 8574 #3 - 'aB' // 8575-8576 #1 - 'C' // 8577 #2 - 'I' // 8578 #8 - 'C' // 8579 #2 - 'A' // 857a #0 - 'C' // 857b #2 - 'B' // 857c #1 - 'F' // 857d #5 - 'A' // 857e #0 - 'F' // 857f #5 - 'A' // 8580 #0 - 'C' // 8581 #2 - 'aB' // 8582-8583 #1 - 'A' // 8584 #0 - 'C' // 8585 #2 - 'aA' // 8586-8587 #0 - 'C' // 8588 #2 - 'aA' // 8589-858a #0 - 'C' // 858b #2 - 'A' // 858c #0 - 'aB' // 858d-858e #1 - 'A' // 858f #0 - 'C' // 8590 #2 - 'A' // 8591 #0 - 'D' // 8592 #3 - 'C' // 8593 #2 - 'A' // 8594 #0 - 'aB' // 8595-8596 #1 - 'A' // 8597 #0 - 'C' // 8598 #2 - 'A' // 8599 #0 - 'B' // 859a #1 - 'A' // 859b #0 - 'C' // 859c #2 - 'A' // 859d #0 - 'B' // 859e #1 - 'aC' // 859f-85a0 #2 - 'B' // 85a1 #1 - 'C' // 85a2 #2 - 'B' // 85a3 #1 - 'A' // 85a4 #0 - 'J' // 85a5 #9 - 'A' // 85a6 #0 - 'C' // 85a7 #2 - 'bA' // 85a8-85aa #0 - 'J' // 85ab #9 - 'bF' // 85ac-85ae #5 - 'aA' // 85af-85b0 #0 - 'B' // 85b1 #1 - 'D' // 85b2 #3 - 'B' // 85b3 #1 - 'C' // 85b4 #2 - 'B' // 85b5 #1 - 'cC' // 85b6-85b9 #2 - 'A' // 85ba #0 - 'D' // 85bb #3 - 'F' // 85bc #5 - 'bC' // 85bd-85bf #2 - 'B' // 85c0 #1 - 'A' // 85c1 #0 - 'C' // 85c2 #2 - 'cB' // 85c3-85c6 #1 - 'A' // 85c7 #0 - 'B' // 85c8 #1 - 'A' // 85c9 #0 - 'F' // 85ca #5 - 'C' // 85cb #2 - 'D' // 85cc #3 - 'cA' // 85cd-85d0 #0 - 'aB' // 85d1-85d2 #1 - 'aD' // 85d3-85d4 #3 - 'A' // 85d5 #0 - 'aB' // 85d6-85d7 #1 - 'bC' // 85d8-85da #2 - 'D' // 85db #3 - 'aA' // 85dc-85dd #0 - 'B' // 85de #1 - 'bC' // 85df-85e1 #2 - 'aB' // 85e2-85e3 #1 - 'aA' // 85e4-85e5 #0 - 'C' // 85e6 #2 - 'D' // 85e7 #3 - 'C' // 85e8 #2 - 'aA' // 85e9-85ea #0 - 'aB' // 85eb-85ec #1 - 'C' // 85ed #2 - 'dB' // 85ee-85f2 #1 - 'F' // 85f3 #5 - 'C' // 85f4 #2 - 'D' // 85f5 #3 - 'C' // 85f6 #2 - 'A' // 85f7 #0 - 'B' // 85f8 #1 - 'bA' // 85f9-85fb #0 - 'C' // 85fc #2 - 'I' // 85fd #8 - 'C' // 85fe #2 - 'aA' // 85ff-8600 #0 - 'B' // 8601 #1 - 'A' // 8602 #0 - 'D' // 8603 #3 - 'A' // 8604 #0 - 'C' // 8605 #2 - 'aA' // 8606-8607 #0 - 'D' // 8608 #3 - 'B' // 8609 #1 - 'aA' // 860a-860b #0 - 'B' // 860c #1 - 'C' // 860d #2 - 'F' // 860e #5 - 'B' // 860f #1 - 'aC' // 8610-8611 #2 - 'F' // 8612 #5 - 'C' // 8613 #2 - 'B' // 8614 #1 - 'D' // 8615 #3 - 'bA' // 8616-8618 #0 - 'C' // 8619 #2 - 'A' // 861a #0 - 'C' // 861b #2 - 'B' // 861c #1 - 'D' // 861d #3 - 'C' // 861e #2 - 'I' // 861f #8 - 'B' // 8620 #1 - 'C' // 8621 #2 - 'A' // 8622 #0 - 'B' // 8623 #1 - 'C' // 8624 #2 - 'aB' // 8625-8626 #1 - 'A' // 8627 #0 - 'B' // 8628 #1 - 'A' // 8629 #0 - 'I' // 862a #8 - 'D' // 862b #3 - 'B' // 862c #1 - 'A' // 862d #0 - 'B' // 862e #1 - 'A' // 862f #0 - 'F' // 8630 #5 - 'dB' // 8631-8635 #1 - 'C' // 8636 #2 - 'D' // 8637 #3 - 'bC' // 8638-863a #2 - 'B' // 863b #1 - 'A' // 863c #0 - 'F' // 863d #5 - 'B' // 863e #1 - 'A' // 863f #0 - 'C' // 8640 #2 - 'J' // 8641 #9 - 'C' // 8642 #2 - 'B' // 8643 #1 - 'D' // 8644 #3 - 'B' // 8645 #1 - 'C' // 8646 #2 - 'aB' // 8647-8648 #1 - 'aD' // 8649-864a #3 - 'aB' // 864b-864c #1 - 'aA' // 864d-864e #0 - 'D' // 864f #3 - 'A' // 8650 #0 - 'D' // 8651 #3 - 'C' // 8652 #2 - 'bA' // 8653-8655 #0 - 'C' // 8656 #2 - 'aF' // 8657-8658 #5 - 'C' // 8659 #2 - 'F' // 865a #5 - 'aA' // 865b-865c #0 - 'F' // 865d #5 - 'aA' // 865e-865f #0 - 'F' // 8660 #5 - 'cC' // 8661-8664 #2 - 'B' // 8665 #1 - 'D' // 8666 #3 - 'A' // 8667 #0 - 'B' // 8668 #1 - 'C' // 8669 #2 - 'B' // 866a #1 - 'aA' // 866b-866c #0 - 'aB' // 866d-866e #1 - 'A' // 866f #0 - 'B' // 8670 #1 - 'A' // 8671 #0 - 'bB' // 8672-8674 #1 - 'aF' // 8675-8676 #5 - 'C' // 8677 #2 - 'K' // 8678 #10 - 'bA' // 8679-867b #0 - 'B' // 867c #1 - 'F' // 867d #5 - 'B' // 867e #1 - 'eD' // 867f-8684 #3 - 'aB' // 8685-8686 #1 - 'C' // 8687 #2 - 'aF' // 8688-8689 #5 - 'cA' // 868a-868d #0 - 'B' // 868e #1 - 'D' // 868f #3 - 'B' // 8690 #1 - 'C' // 8691 #2 - 'B' // 8692 #1 - 'A' // 8693 #0 - 'B' // 8694 #1 - 'A' // 8695 #0 - 'C' // 8696 #2 - 'B' // 8697 #1 - 'C' // 8698 #2 - 'B' // 8699 #1 - 'C' // 869a #2 - 'D' // 869b #3 - 'aC' // 869c-869d #2 - 'B' // 869e #1 - 'D' // 869f #3 - 'B' // 86a0 #1 - 'C' // 86a1 #2 - 'B' // 86a2 #1 - 'aA' // 86a3-86a4 #0 - 'B' // 86a5 #1 - 'F' // 86a6 #5 - 'C' // 86a7 #2 - 'bA' // 86a8-86aa #0 - 'F' // 86ab #5 - 'D' // 86ac #3 - 'C' // 86ad #2 - 'D' // 86ae #3 - 'bA' // 86af-86b1 #0 - 'B' // 86b2 #1 - 'C' // 86b3 #2 - 'A' // 86b4 #0 - 'dC' // 86b5-86b9 #2 - 'dB' // 86ba-86be #1 - 'C' // 86bf #2 - 'A' // 86c0 #0 - 'C' // 86c1 #2 - 'B' // 86c2 #1 - 'aC' // 86c3-86c4 #2 - 'bA' // 86c5-86c7 #0 - 'B' // 86c8 #1 - 'A' // 86c9 #0 - 'D' // 86ca #3 - 'A' // 86cb #0 - 'B' // 86cc #1 - 'aF' // 86cd-86ce #5 - 'D' // 86cf #3 - 'B' // 86d0 #1 - 'C' // 86d1 #2 - 'F' // 86d2 #5 - 'B' // 86d3 #1 - 'A' // 86d4 #0 - 'F' // 86d5 #5 - 'B' // 86d6 #1 - 'C' // 86d7 #2 - 'B' // 86d8 #1 - 'A' // 86d9 #0 - 'C' // 86da #2 - 'A' // 86db #0 - 'C' // 86dc #2 - 'B' // 86dd #1 - 'aA' // 86de-86df #0 - 'F' // 86e0 #5 - 'D' // 86e1 #3 - 'B' // 86e2 #1 - 'aA' // 86e3-86e4 #0 - 'F' // 86e5 #5 - 'C' // 86e6 #2 - 'F' // 86e7 #5 - 'B' // 86e8 #1 - 'A' // 86e9 #0 - 'aB' // 86ea-86eb #1 - 'aA' // 86ec-86ed #0 - 'F' // 86ee #5 - 'C' // 86ef #2 - 'dD' // 86f0-86f4 #3 - 'bB' // 86f5-86f7 #1 - 'aA' // 86f8-86f9 #0 - 'C' // 86fa #2 - 'A' // 86fb #0 - 'aF' // 86fc-86fd #5 - 'A' // 86fe #0 - 'D' // 86ff #3 - 'A' // 8700 #0 - 'B' // 8701 #1 - 'aA' // 8702-8703 #0 - 'aC' // 8704-8705 #2 - 'A' // 8706 #0 - 'C' // 8707 #2 - 'cA' // 8708-870b #0 - 'B' // 870c #1 - 'aC' // 870d-870e #2 - 'aF' // 870f-8710 #5 - 'A' // 8711 #0 - 'aC' // 8712-8713 #2 - 'F' // 8714 #5 - 'B' // 8715 #1 - 'aD' // 8716-8717 #3 - 'A' // 8718 #0 - 'C' // 8719 #2 - 'A' // 871a #0 - 'B' // 871b #1 - 'A' // 871c #0 - 'K' // 871d #10 - 'C' // 871e #2 - 'F' // 871f #5 - 'B' // 8720 #1 - 'A' // 8721 #0 - 'aC' // 8722-8723 #2 - 'B' // 8724 #1 - 'A' // 8725 #0 - 'aB' // 8726-8727 #1 - 'aA' // 8728-8729 #0 - 'B' // 872a #1 - 'D' // 872b #3 - 'aB' // 872c-872d #1 - 'C' // 872e #2 - 'F' // 872f #5 - 'B' // 8730 #1 - 'aC' // 8731-8732 #2 - 'B' // 8733 #1 - 'A' // 8734 #0 - 'I' // 8735 #8 - 'D' // 8736 #3 - 'A' // 8737 #0 - 'B' // 8738 #1 - 'F' // 8739 #5 - 'aA' // 873a-873b #0 - 'C' // 873c #2 - 'F' // 873d #5 - 'C' // 873e #2 - 'aA' // 873f-8740 #0 - 'aB' // 8741-8742 #1 - 'C' // 8743 #2 - 'D' // 8744 #3 - 'F' // 8745 #5 - 'B' // 8746 #1 - 'aD' // 8747-8748 #3 - 'F' // 8749 #5 - 'D' // 874a #3 - 'F' // 874b #5 - 'A' // 874c #0 - 'C' // 874d #2 - 'A' // 874e #0 - 'aB' // 874f-8750 #1 - 'C' // 8751 #2 - 'B' // 8752 #1 - 'C' // 8753 #2 - 'B' // 8754 #1 - 'A' // 8755 #0 - 'B' // 8756 #1 - 'A' // 8757 #0 - 'C' // 8758 #2 - 'A' // 8759 #0 - 'bB' // 875a-875c #1 - 'C' // 875d #2 - 'B' // 875e #1 - 'aA' // 875f-8760 #0 - 'C' // 8761 #2 - 'B' // 8762 #1 - 'C' // 8763 #2 - 'bA' // 8764-8766 #0 - 'B' // 8767 #1 - 'A' // 8768 #0 - 'B' // 8769 #1 - 'C' // 876a #2 - 'bB' // 876b-876d #1 - 'A' // 876e #0 - 'C' // 876f #2 - 'B' // 8770 #1 - 'C' // 8771 #2 - 'F' // 8772 #5 - 'B' // 8773 #1 - 'A' // 8774 #0 - 'B' // 8775 #1 - 'A' // 8776 #0 - 'B' // 8777 #1 - 'A' // 8778 #0 - 'aB' // 8779-877a #1 - 'C' // 877b #2 - 'F' // 877c #5 - 'B' // 877d #1 - 'D' // 877e #3 - 'F' // 877f #5 - 'D' // 8780 #3 - 'B' // 8781 #1 - 'aA' // 8782-8783 #0 - 'eC' // 8784-8789 #2 - 'D' // 878a #3 - 'C' // 878b #2 - 'aA' // 878c-878d #0 - 'F' // 878e #5 - 'B' // 878f #1 - 'C' // 8790 #2 - 'aB' // 8791-8792 #1 - 'C' // 8793 #2 - 'B' // 8794 #1 - 'F' // 8795 #5 - 'B' // 8796 #1 - 'C' // 8797 #2 - 'A' // 8798 #0 - 'F' // 8799 #5 - 'cB' // 879a-879d #1 - 'aA' // 879e-879f #0 - 'F' // 87a0 #5 - 'D' // 87a1 #3 - 'aA' // 87a2-87a3 #0 - 'aB' // 87a4-87a5 #1 - 'D' // 87a6 #3 - 'F' // 87a7 #5 - 'D' // 87a8 #3 - 'aB' // 87a9-87aa #1 - 'aC' // 87ab-87ac #2 - 'A' // 87ad #0 - 'aC' // 87ae-87af #2 - 'B' // 87b0 #1 - 'C' // 87b1 #2 - 'B' // 87b2 #1 - 'A' // 87b3 #0 - 'I' // 87b4 #8 - 'C' // 87b5 #2 - 'cB' // 87b6-87b9 #1 - 'aA' // 87ba-87bb #0 - 'B' // 87bc #1 - 'A' // 87bd #0 - 'aC' // 87be-87bf #2 - 'A' // 87c0 #0 - 'C' // 87c1 #2 - 'aB' // 87c2-87c3 #1 - 'A' // 87c4 #0 - 'B' // 87c5 #1 - 'C' // 87c6 #2 - 'J' // 87c7 #9 - 'aC' // 87c8-87c9 #2 - 'aA' // 87ca-87cb #0 - 'B' // 87cc #1 - 'D' // 87cd #3 - 'C' // 87ce #2 - 'D' // 87cf #3 - 'F' // 87d0 #5 - 'B' // 87d1 #1 - 'A' // 87d2 #0 - 'aB' // 87d3-87d4 #1 - 'F' // 87d5 #5 - 'C' // 87d6 #2 - 'aB' // 87d7-87d8 #1 - 'C' // 87d9 #2 - 'A' // 87da #0 - 'I' // 87db #8 - 'C' // 87dc #2 - 'aB' // 87dd-87de #1 - 'C' // 87df #2 - 'A' // 87e0 #0 - 'B' // 87e1 #1 - 'C' // 87e2 #2 - 'A' // 87e3 #0 - 'bC' // 87e4-87e6 #2 - 'aB' // 87e7-87e8 #1 - 'D' // 87e9 #3 - 'aC' // 87ea-87eb #2 - 'A' // 87ec #0 - 'C' // 87ed #2 - 'B' // 87ee #1 - 'A' // 87ef #0 - 'D' // 87f0 #3 - 'F' // 87f1 #5 - 'A' // 87f2 #0 - 'C' // 87f3 #2 - 'B' // 87f4 #1 - 'aC' // 87f5-87f6 #2 - 'A' // 87f7 #0 - 'F' // 87f8 #5 - 'A' // 87f9 #0 - 'C' // 87fa #2 - 'A' // 87fb #0 - 'B' // 87fc #1 - 'D' // 87fd #3 - 'A' // 87fe #0 - 'C' // 87ff #2 - 'B' // 8800 #1 - 'C' // 8801 #2 - 'B' // 8802 #1 - 'C' // 8803 #2 - 'B' // 8804 #1 - 'A' // 8805 #0 - 'C' // 8806 #2 - 'F' // 8807 #5 - 'B' // 8808 #1 - 'bC' // 8809-880b #2 - 'B' // 880c #1 - 'A' // 880d #0 - 'F' // 880e #5 - 'aC' // 880f-8810 #2 - 'A' // 8811 #0 - 'F' // 8812 #5 - 'aC' // 8813-8814 #2 - 'A' // 8815 #0 - 'C' // 8816 #2 - 'B' // 8817 #1 - 'aC' // 8818-8819 #2 - 'F' // 881a #5 - 'aC' // 881b-881c #2 - 'B' // 881d #1 - 'F' // 881e #5 - 'A' // 881f #0 - 'B' // 8820 #1 - 'bA' // 8821-8823 #0 - 'bB' // 8824-8826 #1 - 'aC' // 8827-8828 #2 - 'cB' // 8829-882c #1 - 'aC' // 882d-882e #2 - 'B' // 882f #1 - 'C' // 8830 #2 - 'aA' // 8831-8832 #0 - 'B' // 8833 #1 - 'D' // 8834 #3 - 'C' // 8835 #2 - 'A' // 8836 #0 - 'aB' // 8837-8838 #1 - 'A' // 8839 #0 - 'F' // 883a #5 - 'A' // 883b #0 - 'C' // 883c #2 - 'bB' // 883d-883f #1 - 'A' // 8840 #0 - 'bC' // 8841-8843 #2 - 'A' // 8844 #0 - 'C' // 8845 #2 - 'A' // 8846 #0 - 'D' // 8847 #3 - 'C' // 8848 #2 - 'F' // 8849 #5 - 'A' // 884a #0 - 'C' // 884b #2 - 'bA' // 884c-884e #0 - 'B' // 884f #1 - 'D' // 8850 #3 - 'F' // 8851 #5 - 'aA' // 8852-8853 #0 - 'D' // 8854 #3 - 'aC' // 8855-8856 #2 - 'A' // 8857 #0 - 'F' // 8858 #5 - 'A' // 8859 #0 - 'C' // 885a #2 - 'A' // 885b #0 - 'F' // 885c #5 - 'aA' // 885d-885e #0 - 'F' // 885f #5 - 'C' // 8860 #2 - 'cA' // 8861-8864 #0 - 'B' // 8865 #1 - 'D' // 8866 #3 - 'B' // 8867 #1 - 'A' // 8868 #0 - 'C' // 8869 #2 - 'B' // 886a #1 - 'A' // 886b #0 - 'D' // 886c #3 - 'B' // 886d #1 - 'A' // 886e #0 - 'C' // 886f #2 - 'A' // 8870 #0 - 'C' // 8871 #2 - 'A' // 8872 #0 - 'D' // 8873 #3 - 'B' // 8874 #1 - 'C' // 8875 #2 - 'B' // 8876 #1 - 'A' // 8877 #0 - 'D' // 8878 #3 - 'C' // 8879 #2 - 'D' // 887a #3 - 'F' // 887b #5 - 'B' // 887c #1 - 'bA' // 887d-887f #0 - 'C' // 8880 #2 - 'aA' // 8881-8882 #0 - 'aB' // 8883-8884 #1 - 'aD' // 8885-8886 #3 - 'B' // 8887 #1 - 'A' // 8888 #0 - 'B' // 8889 #1 - 'D' // 888a #3 - 'A' // 888b #0 - 'B' // 888c #1 - 'A' // 888d #0 - 'cB' // 888e-8891 #1 - 'A' // 8892 #0 - 'B' // 8893 #1 - 'D' // 8894 #3 - 'B' // 8895 #1 - 'aA' // 8896-8897 #0 - 'bC' // 8898-889a #2 - 'A' // 889b #0 - 'C' // 889c #2 - 'I' // 889d #8 - 'A' // 889e #0 - 'aC' // 889f-88a0 #2 - 'B' // 88a1 #1 - 'A' // 88a2 #0 - 'D' // 88a3 #3 - 'C' // 88a4 #2 - 'aD' // 88a5-88a6 #3 - 'B' // 88a7 #1 - 'C' // 88a8 #2 - 'D' // 88a9 #3 - 'aA' // 88aa-88ab #0 - 'B' // 88ac #1 - 'D' // 88ad #3 - 'C' // 88ae #2 - 'D' // 88af #3 - 'F' // 88b0 #5 - 'C' // 88b1 #2 - 'B' // 88b2 #1 - 'D' // 88b3 #3 - 'A' // 88b4 #0 - 'C' // 88b5 #2 - 'B' // 88b6 #1 - 'C' // 88b7 #2 - 'aB' // 88b8-88b9 #1 - 'C' // 88ba #2 - 'D' // 88bb #3 - 'cC' // 88bc-88bf #2 - 'bA' // 88c0-88c2 #0 - 'aF' // 88c3-88c4 #5 - 'A' // 88c5 #0 - 'F' // 88c6 #5 - 'B' // 88c7 #1 - 'D' // 88c8 #3 - 'B' // 88c9 #1 - 'A' // 88ca #0 - 'aC' // 88cb-88cc #2 - 'A' // 88cd #0 - 'C' // 88ce #2 - 'A' // 88cf #0 - 'B' // 88d0 #1 - 'F' // 88d1 #5 - 'A' // 88d2 #0 - 'F' // 88d3 #5 - 'aA' // 88d4-88d5 #0 - 'aB' // 88d6-88d7 #1 - 'aA' // 88d8-88d9 #0 - 'B' // 88da #1 - 'C' // 88db #2 - 'aA' // 88dc-88dd #0 - 'C' // 88de #2 - 'A' // 88df #0 - 'F' // 88e0 #5 - 'A' // 88e1 #0 - 'cD' // 88e2-88e5 #3 - 'B' // 88e6 #1 - 'C' // 88e7 #2 - 'A' // 88e8 #0 - 'aD' // 88e9-88ea #3 - 'aB' // 88eb-88ec #1 - 'D' // 88ed #3 - 'B' // 88ee #1 - 'A' // 88ef #0 - 'C' // 88f0 #2 - 'A' // 88f1 #0 - 'C' // 88f2 #2 - 'bA' // 88f3-88f5 #0 - 'B' // 88f6 #1 - 'C' // 88f7 #2 - 'aA' // 88f8-88f9 #0 - 'aB' // 88fa-88fb #1 - 'C' // 88fc #2 - 'aA' // 88fd-88fe #0 - 'aB' // 88ff-8900 #1 - 'aC' // 8901-8902 #2 - 'D' // 8903 #3 - 'J' // 8904 #9 - 'B' // 8905 #1 - 'C' // 8906 #2 - 'A' // 8907 #0 - 'D' // 8908 #3 - 'B' // 8909 #1 - 'A' // 890a #0 - 'B' // 890b #1 - 'A' // 890c #0 - 'F' // 890d #5 - 'C' // 890e #2 - 'F' // 890f #5 - 'A' // 8910 #0 - 'I' // 8911 #8 - 'aA' // 8912-8913 #0 - 'B' // 8914 #1 - 'A' // 8915 #0 - 'C' // 8916 #2 - 'B' // 8917 #1 - 'bA' // 8918-891a #0 - 'D' // 891b #3 - 'aF' // 891c-891d #5 - 'C' // 891e #2 - 'B' // 891f #1 - 'F' // 8920 #5 - 'cB' // 8921-8924 #1 - 'A' // 8925 #0 - 'C' // 8926 #2 - 'A' // 8927 #0 - 'F' // 8928 #5 - 'B' // 8929 #1 - 'aA' // 892a-892b #0 - 'bB' // 892c-892e #1 - 'I' // 892f #8 - 'A' // 8930 #0 - 'aC' // 8931-8932 #2 - 'B' // 8933 #1 - 'D' // 8934 #3 - 'C' // 8935 #2 - 'A' // 8936 #0 - 'C' // 8937 #2 - 'A' // 8938 #0 - 'F' // 8939 #5 - 'J' // 893a #9 - 'A' // 893b #0 - 'aB' // 893c-893d #1 - 'C' // 893e #2 - 'D' // 893f #3 - 'F' // 8940 #5 - 'A' // 8941 #0 - 'aC' // 8942-8943 #2 - 'A' // 8944 #0 - 'F' // 8945 #5 - 'C' // 8946 #2 - 'B' // 8947 #1 - 'D' // 8948 #3 - 'C' // 8949 #2 - 'D' // 894a #3 - 'B' // 894b #1 - 'C' // 894c #2 - 'A' // 894d #0 - 'D' // 894e #3 - 'C' // 894f #2 - 'aB' // 8950-8951 #1 - 'A' // 8952 #0 - 'aB' // 8953-8954 #1 - 'D' // 8955 #3 - 'A' // 8956 #0 - 'C' // 8957 #2 - 'I' // 8958 #8 - 'B' // 8959 #1 - 'aC' // 895a-895b #2 - 'A' // 895c #0 - 'B' // 895d #1 - 'bA' // 895e-8960 #0 - 'bC' // 8961-8963 #2 - 'A' // 8964 #0 - 'B' // 8965 #1 - 'C' // 8966 #2 - 'aD' // 8967-8968 #3 - 'B' // 8969 #1 - 'A' // 896a #0 - 'C' // 896b #2 - 'B' // 896c #1 - 'A' // 896d #0 - 'C' // 896e #2 - 'A' // 896f #0 - 'F' // 8970 #5 - 'B' // 8971 #1 - 'A' // 8972 #0 - 'C' // 8973 #2 - 'A' // 8974 #0 - 'F' // 8975 #5 - 'B' // 8976 #1 - 'C' // 8977 #2 - 'D' // 8978 #3 - 'B' // 8979 #1 - 'bC' // 897a-897c #2 - 'F' // 897d #5 - 'cA' // 897e-8981 #0 - 'B' // 8982 #1 - 'A' // 8983 #0 - 'D' // 8984 #3 - 'B' // 8985 #1 - 'cA' // 8986-8989 #0 - 'C' // 898a #2 - 'A' // 898b #0 - 'D' // 898c #3 - 'F' // 898d #5 - 'D' // 898e #3 - 'A' // 898f #0 - 'F' // 8990 #5 - 'B' // 8991 #1 - 'D' // 8992 #3 - 'A' // 8993 #0 - 'aC' // 8994-8995 #2 - 'bA' // 8996-8998 #0 - 'D' // 8999 #3 - 'J' // 899a #9 - 'aC' // 899b-899c #2 - 'aB' // 899d-899e #1 - 'C' // 899f #2 - 'J' // 89a0 #9 - 'A' // 89a1 #0 - 'bB' // 89a2-89a4 #1 - 'bC' // 89a5-89a7 #2 - 'K' // 89a8 #10 - 'aA' // 89a9-89aa #0 - 'D' // 89ab #3 - 'A' // 89ac #0 - 'aB' // 89ad-89ae #1 - 'A' // 89af #0 - 'F' // 89b0 #5 - 'D' // 89b1 #3 - 'A' // 89b2 #0 - 'J' // 89b3 #9 - 'aF' // 89b4-89b5 #5 - 'aA' // 89b6-89b7 #0 - 'D' // 89b8 #3 - 'B' // 89b9 #1 - 'A' // 89ba #0 - 'D' // 89bb #3 - 'C' // 89bc #2 - 'A' // 89bd #0 - 'B' // 89be #1 - 'bA' // 89bf-89c1 #0 - 'cD' // 89c2-89c5 #3 - 'B' // 89c6 #1 - 'jD' // 89c7-89d1 #3 - 'A' // 89d2 #0 - 'B' // 89d3 #1 - 'A' // 89d4 #0 - 'C' // 89d5 #2 - 'A' // 89d6 #0 - 'J' // 89d7 #9 - 'F' // 89d8 #5 - 'B' // 89d9 #1 - 'A' // 89da #0 - 'B' // 89db #1 - 'aA' // 89dc-89dd #0 - 'D' // 89de #3 - 'cB' // 89df-89e2 #1 - 'A' // 89e3 #0 - 'B' // 89e4 #1 - 'bA' // 89e5-89e7 #0 - 'B' // 89e8 #1 - 'C' // 89e9 #2 - 'D' // 89ea #3 - 'C' // 89eb #2 - 'B' // 89ec #1 - 'C' // 89ed #2 - 'aD' // 89ee-89ef #3 - 'I' // 89f0 #8 - 'A' // 89f1 #0 - 'B' // 89f2 #1 - 'aA' // 89f3-89f4 #0 - 'D' // 89f5 #3 - 'A' // 89f6 #0 - 'B' // 89f7 #1 - 'A' // 89f8 #0 - 'F' // 89f9 #5 - 'bB' // 89fa-89fc #1 - 'F' // 89fd #5 - 'B' // 89fe #1 - 'C' // 89ff #2 - 'A' // 8a00 #0 - 'F' // 8a01 #5 - 'aA' // 8a02-8a03 #0 - 'C' // 8a04 #2 - 'F' // 8a05 #5 - 'D' // 8a06 #3 - 'aA' // 8a07-8a08 #0 - 'K' // 8a09 #10 - 'A' // 8a0a #0 - 'D' // 8a0b #3 - 'A' // 8a0c #0 - 'D' // 8a0d #3 - 'A' // 8a0e #0 - 'C' // 8a0f #2 - 'cA' // 8a10-8a13 #0 - 'J' // 8a14 #9 - 'cA' // 8a15-8a18 #0 - 'aD' // 8a19-8a1a #3 - 'A' // 8a1b #0 - 'B' // 8a1c #1 - 'A' // 8a1d #0 - 'C' // 8a1e #2 - 'A' // 8a1f #0 - 'aF' // 8a20-8a21 #5 - 'aA' // 8a22-8a23 #0 - 'F' // 8a24 #5 - 'A' // 8a25 #0 - 'F' // 8a26 #5 - 'B' // 8a27 #1 - 'D' // 8a28 #3 - 'B' // 8a29 #1 - 'A' // 8a2a #0 - 'aC' // 8a2b-8a2c #2 - 'A' // 8a2d #0 - 'D' // 8a2e #3 - 'F' // 8a2f #5 - 'B' // 8a30 #1 - 'A' // 8a31 #0 - 'D' // 8a32 #3 - 'J' // 8a33 #9 - 'A' // 8a34 #0 - 'F' // 8a35 #5 - 'A' // 8a36 #0 - 'F' // 8a37 #5 - 'aB' // 8a38-8a39 #1 - 'bA' // 8a3a-8a3c #0 - 'C' // 8a3d #2 - 'A' // 8a3e #0 - 'B' // 8a3f #1 - 'C' // 8a40 #2 - 'A' // 8a41 #0 - 'D' // 8a42 #3 - 'F' // 8a43 #5 - 'B' // 8a44 #1 - 'C' // 8a45 #2 - 'A' // 8a46 #0 - 'F' // 8a47 #5 - 'aC' // 8a48-8a49 #2 - 'B' // 8a4a #1 - 'K' // 8a4b #10 - 'B' // 8a4c #1 - 'aC' // 8a4d-8a4e #2 - 'B' // 8a4f #1 - 'aA' // 8a50-8a51 #0 - 'C' // 8a52 #2 - 'F' // 8a53 #5 - 'dA' // 8a54-8a58 #0 - 'B' // 8a59 #1 - 'D' // 8a5a #3 - 'A' // 8a5b #0 - 'aF' // 8a5c-8a5d #5 - 'A' // 8a5e #0 - 'D' // 8a5f #3 - 'cA' // 8a60-8a63 #0 - 'D' // 8a64 #3 - 'F' // 8a65 #5 - 'A' // 8a66 #0 - 'C' // 8a67 #2 - 'B' // 8a68 #1 - 'A' // 8a69 #0 - 'D' // 8a6a #3 - 'cA' // 8a6b-8a6e #0 - 'D' // 8a6f #3 - 'cA' // 8a70-8a73 #0 - 'B' // 8a74 #1 - 'A' // 8a75 #0 - 'aC' // 8a76-8a77 #2 - 'D' // 8a78 #3 - 'A' // 8a79 #0 - 'aC' // 8a7a-8a7b #2 - 'A' // 8a7c #0 - 'D' // 8a7d #3 - 'C' // 8a7e #2 - 'A' // 8a7f #0 - 'F' // 8a80 #5 - 'B' // 8a81 #1 - 'A' // 8a82 #0 - 'C' // 8a83 #2 - 'cA' // 8a84-8a87 #0 - 'D' // 8a88 #3 - 'F' // 8a89 #5 - 'D' // 8a8a #3 - 'C' // 8a8b #2 - 'aA' // 8a8c-8a8d #0 - 'D' // 8a8e #3 - 'aC' // 8a8f-8a90 #2 - 'A' // 8a91 #0 - 'C' // 8a92 #2 - 'A' // 8a93 #0 - 'B' // 8a94 #1 - 'A' // 8a95 #0 - 'C' // 8a96 #2 - 'F' // 8a97 #5 - 'A' // 8a98 #0 - 'C' // 8a99 #2 - 'A' // 8a9a #0 - 'D' // 8a9b #3 - 'B' // 8a9c #1 - 'D' // 8a9d #3 - 'A' // 8a9e #0 - 'F' // 8a9f #5 - 'aA' // 8aa0-8aa1 #0 - 'K' // 8aa2 #10 - 'eA' // 8aa3-8aa8 #0 - 'C' // 8aa9 #2 - 'A' // 8aaa #0 - 'B' // 8aab #1 - 'C' // 8aac #2 - 'aF' // 8aad-8aae #5 - 'C' // 8aaf #2 - 'A' // 8ab0 #0 - 'D' // 8ab1 #3 - 'A' // 8ab2 #0 - 'F' // 8ab3 #5 - 'B' // 8ab4 #1 - 'D' // 8ab5 #3 - 'C' // 8ab6 #2 - 'F' // 8ab7 #5 - 'B' // 8ab8 #1 - 'A' // 8ab9 #0 - 'B' // 8aba #1 - 'C' // 8abb #2 - 'A' // 8abc #0 - 'B' // 8abd #1 - 'aA' // 8abe-8abf #0 - 'B' // 8ac0 #1 - 'D' // 8ac1 #3 - 'A' // 8ac2 #0 - 'C' // 8ac3 #2 - 'A' // 8ac4 #0 - 'B' // 8ac5 #1 - 'C' // 8ac6 #2 - 'A' // 8ac7 #0 - 'C' // 8ac8 #2 - 'A' // 8ac9 #0 - 'F' // 8aca #5 - 'A' // 8acb #0 - 'C' // 8acc #2 - 'A' // 8acd #0 - 'D' // 8ace #3 - 'A' // 8acf #0 - 'F' // 8ad0 #5 - 'C' // 8ad1 #2 - 'A' // 8ad2 #0 - 'bC' // 8ad3-8ad5 #2 - 'aA' // 8ad6-8ad7 #0 - 'aB' // 8ad8-8ad9 #1 - 'C' // 8ada #2 - 'fA' // 8adb-8ae1 #0 - 'C' // 8ae2 #2 - 'D' // 8ae3 #3 - 'A' // 8ae4 #0 - 'D' // 8ae5 #3 - 'aA' // 8ae6-8ae7 #0 - 'B' // 8ae8 #1 - 'D' // 8ae9 #3 - 'I' // 8aea #8 - 'A' // 8aeb #0 - 'F' // 8aec #5 - 'aA' // 8aed-8aee #0 - 'B' // 8aef #1 - 'aA' // 8af0-8af1 #0 - 'I' // 8af2 #8 - 'aA' // 8af3-8af4 #0 - 'C' // 8af5 #2 - 'bA' // 8af6-8af8 #0 - 'B' // 8af9 #1 - 'A' // 8afa #0 - 'B' // 8afb #1 - 'A' // 8afc #0 - 'D' // 8afd #3 - 'A' // 8afe #0 - 'C' // 8aff #2 - 'bA' // 8b00-8b02 #0 - 'D' // 8b03 #3 - 'A' // 8b04 #0 - 'aC' // 8b05-8b06 #2 - 'A' // 8b07 #0 - 'B' // 8b08 #1 - 'D' // 8b09 #3 - 'aC' // 8b0a-8b0b #2 - 'A' // 8b0c #0 - 'C' // 8b0d #2 - 'A' // 8b0e #0 - 'C' // 8b0f #2 - 'aA' // 8b10-8b11 #0 - 'aB' // 8b12-8b13 #1 - 'A' // 8b14 #0 - 'B' // 8b15 #1 - 'aA' // 8b16-8b17 #0 - 'B' // 8b18 #1 - 'dA' // 8b19-8b1d #0 - 'aC' // 8b1e-8b1f #2 - 'A' // 8b20 #0 - 'F' // 8b21 #5 - 'B' // 8b22 #1 - 'I' // 8b23 #8 - 'aB' // 8b24-8b25 #1 - 'A' // 8b26 #0 - 'B' // 8b27 #1 - 'A' // 8b28 #0 - 'D' // 8b29 #3 - 'B' // 8b2a #1 - 'aA' // 8b2b-8b2c #0 - 'C' // 8b2d #2 - 'aB' // 8b2e-8b2f #1 - 'C' // 8b30 #2 - 'B' // 8b31 #1 - 'D' // 8b32 #3 - 'A' // 8b33 #0 - 'D' // 8b34 #3 - 'aB' // 8b35-8b36 #1 - 'A' // 8b37 #0 - 'D' // 8b38 #3 - 'A' // 8b39 #0 - 'aB' // 8b3a-8b3b #1 - 'A' // 8b3c #0 - 'B' // 8b3d #1 - 'A' // 8b3e #0 - 'aB' // 8b3f-8b40 #1 - 'A' // 8b41 #0 - 'C' // 8b42 #2 - 'A' // 8b43 #0 - 'F' // 8b44 #5 - 'C' // 8b45 #2 - 'A' // 8b46 #0 - 'B' // 8b47 #1 - 'C' // 8b48 #2 - 'A' // 8b49 #0 - 'aB' // 8b4a-8b4b #1 - 'A' // 8b4c #0 - 'C' // 8b4d #2 - 'aA' // 8b4e-8b4f #0 - 'B' // 8b50 #1 - 'aC' // 8b51-8b52 #2 - 'aA' // 8b53-8b54 #0 - 'B' // 8b55 #1 - 'A' // 8b56 #0 - 'B' // 8b57 #1 - 'bA' // 8b58-8b5a #0 - 'F' // 8b5b #5 - 'A' // 8b5c #0 - 'B' // 8b5d #1 - 'aA' // 8b5e-8b5f #0 - 'B' // 8b60 #1 - 'D' // 8b61 #3 - 'B' // 8b62 #1 - 'C' // 8b63 #2 - 'D' // 8b64 #3 - 'B' // 8b65 #1 - 'A' // 8b66 #0 - 'aB' // 8b67-8b68 #1 - 'C' // 8b69 #2 - 'B' // 8b6a #1 - 'aA' // 8b6b-8b6c #0 - 'C' // 8b6d #2 - 'D' // 8b6e #3 - 'aA' // 8b6f-8b70 #0 - 'J' // 8b71 #9 - 'F' // 8b72 #5 - 'D' // 8b73 #3 - 'A' // 8b74 #0 - 'D' // 8b75 #3 - 'F' // 8b76 #5 - 'A' // 8b77 #0 - 'aC' // 8b78-8b79 #2 - 'aB' // 8b7a-8b7b #1 - 'F' // 8b7c #5 - 'A' // 8b7d #0 - 'C' // 8b7e #2 - 'aA' // 8b7f-8b80 #0 - 'C' // 8b81 #2 - 'B' // 8b82 #1 - 'A' // 8b83 #0 - 'aC' // 8b84-8b85 #2 - 'B' // 8b86 #1 - 'D' // 8b87 #3 - 'B' // 8b88 #1 - 'K' // 8b89 #10 - 'A' // 8b8a #0 - 'C' // 8b8b #2 - 'A' // 8b8c #0 - 'F' // 8b8d #5 - 'A' // 8b8e #0 - 'C' // 8b8f #2 - 'A' // 8b90 #0 - 'D' // 8b91 #3 - 'aA' // 8b92-8b93 #0 - 'aC' // 8b94-8b95 #2 - 'A' // 8b96 #0 - 'D' // 8b97 #3 - 'B' // 8b98 #1 - 'C' // 8b99 #2 - 'A' // 8b9a #0 - 'B' // 8b9b #1 - 'A' // 8b9c #0 - 'F' // 8b9d #5 - 'A' // 8b9e #0 - 'C' // 8b9f #2 - 'A' // 8ba0 #0 - '1bD' // 8ba1-8bbd #3 - 'B' // 8bbe #1 - '1hD' // 8bbf-8be1 #3 - 'B' // 8be2 #1 - '3eD' // 8be3-8c36 #3 - 'A' // 8c37 #0 - 'F' // 8c38 #5 - 'C' // 8c39 #2 - 'F' // 8c3a #5 - 'aB' // 8c3b-8c3c #1 - 'aC' // 8c3d-8c3e #2 - 'A' // 8c3f #0 - 'D' // 8c40 #3 - 'A' // 8c41 #0 - 'aB' // 8c42-8c43 #1 - 'D' // 8c44 #3 - 'C' // 8c45 #2 - 'dA' // 8c46-8c4a #0 - 'C' // 8c4b #2 - 'A' // 8c4c #0 - 'B' // 8c4d #1 - 'A' // 8c4e #0 - 'C' // 8c4f #2 - 'A' // 8c50 #0 - 'C' // 8c51 #2 - 'D' // 8c52 #3 - 'F' // 8c53 #5 - 'C' // 8c54 #2 - 'A' // 8c55 #0 - 'I' // 8c56 #8 - 'C' // 8c57 #2 - 'aF' // 8c58-8c59 #5 - 'A' // 8c5a #0 - 'F' // 8c5b #5 - 'B' // 8c5c #1 - 'C' // 8c5d #2 - 'D' // 8c5e #3 - 'B' // 8c5f #1 - 'D' // 8c60 #3 - 'aA' // 8c61-8c62 #0 - 'F' // 8c63 #5 - 'C' // 8c64 #2 - 'B' // 8c65 #1 - 'C' // 8c66 #2 - 'D' // 8c67 #3 - 'A' // 8c68 #0 - 'C' // 8c69 #2 - 'bA' // 8c6a-8c6c #0 - 'C' // 8c6d #2 - 'D' // 8c6e #3 - 'cB' // 8c6f-8c72 #1 - 'A' // 8c73 #0 - 'D' // 8c74 #3 - 'aC' // 8c75-8c76 #2 - 'B' // 8c77 #1 - 'bA' // 8c78-8c7a #0 - 'C' // 8c7b #2 - 'F' // 8c7c #5 - 'B' // 8c7d #1 - 'F' // 8c7e #5 - 'D' // 8c7f #3 - 'aB' // 8c80-8c81 #1 - 'A' // 8c82 #0 - 'K' // 8c83 #10 - 'B' // 8c84 #1 - 'aC' // 8c85-8c86 #2 - 'F' // 8c87 #5 - 'D' // 8c88 #3 - 'C' // 8c89 #2 - 'A' // 8c8a #0 - 'F' // 8c8b #5 - 'aA' // 8c8c-8c8d #0 - 'F' // 8c8e #5 - 'B' // 8c8f #1 - 'C' // 8c90 #2 - 'B' // 8c91 #1 - 'C' // 8c92 #2 - 'aA' // 8c93-8c94 #0 - 'B' // 8c95 #1 - 'D' // 8c96 #3 - 'B' // 8c97 #1 - 'A' // 8c98 #0 - 'C' // 8c99 #2 - 'B' // 8c9a #1 - 'aC' // 8c9b-8c9c #2 - 'eA' // 8c9d-8ca2 #0 - 'B' // 8ca3 #1 - 'C' // 8ca4 #2 - 'B' // 8ca5 #1 - 'D' // 8ca6 #3 - 'eA' // 8ca7-8cac #0 - 'C' // 8cad #2 - 'F' // 8cae #5 - 'aA' // 8caf-8cb0 #0 - 'D' // 8cb1 #3 - 'bA' // 8cb2-8cb4 #0 - 'B' // 8cb5 #1 - 'gA' // 8cb6-8cbd #0 - 'B' // 8cbe #1 - 'eA' // 8cbf-8cc4 #0 - 'C' // 8cc5 #2 - 'J' // 8cc6 #9 - 'aA' // 8cc7-8cc8 #0 - 'F' // 8cc9 #5 - 'A' // 8cca #0 - 'F' // 8ccb #5 - 'B' // 8ccc #1 - 'C' // 8ccd #2 - 'F' // 8cce #5 - 'C' // 8ccf #2 - 'D' // 8cd0 #3 - 'A' // 8cd1 #0 - 'C' // 8cd2 #2 - 'A' // 8cd3 #0 - 'B' // 8cd4 #1 - 'aC' // 8cd5-8cd6 #2 - 'B' // 8cd7 #1 - 'D' // 8cd8 #3 - 'cA' // 8cd9-8cdc #0 - 'C' // 8cdd #2 - 'A' // 8cde #0 - 'B' // 8cdf #1 - 'dA' // 8ce0-8ce4 #0 - 'I' // 8ce5 #8 - 'A' // 8ce6 #0 - 'B' // 8ce7 #1 - 'C' // 8ce8 #2 - 'B' // 8ce9 #1 - 'A' // 8cea #0 - 'B' // 8ceb #1 - 'aA' // 8cec-8ced #0 - 'B' // 8cee #1 - 'F' // 8cef #5 - 'aA' // 8cf0-8cf1 #0 - 'C' // 8cf2 #2 - 'I' // 8cf3 #8 - 'A' // 8cf4 #0 - 'C' // 8cf5 #2 - 'D' // 8cf6 #3 - 'aC' // 8cf7-8cf8 #2 - 'B' // 8cf9 #1 - 'C' // 8cfa #2 - 'bA' // 8cfb-8cfd #0 - 'C' // 8cfe #2 - 'F' // 8cff #5 - 'B' // 8d00 #1 - 'F' // 8d01 #5 - 'B' // 8d02 #1 - 'C' // 8d03 #2 - 'aA' // 8d04-8d05 #0 - 'B' // 8d06 #1 - 'aA' // 8d07-8d08 #0 - 'C' // 8d09 #2 - 'aA' // 8d0a-8d0b #0 - 'B' // 8d0c #1 - 'A' // 8d0d #0 - 'F' // 8d0e #5 - 'aA' // 8d0f-8d10 #0 - 'B' // 8d11 #1 - 'C' // 8d12 #2 - 'aA' // 8d13-8d14 #0 - 'B' // 8d15 #1 - 'A' // 8d16 #0 - 'C' // 8d17 #2 - 'aB' // 8d18-8d19 #1 - 'D' // 8d1a #3 - 'A' // 8d1b #0 - 'C' // 8d1c #2 - 'A' // 8d1d #0 - '2qD' // 8d1e-8d63 #3 - 'A' // 8d64 #0 - 'F' // 8d65 #5 - 'aA' // 8d66-8d67 #0 - 'B' // 8d68 #1 - 'C' // 8d69 #2 - 'D' // 8d6a #3 - 'A' // 8d6b #0 - 'C' // 8d6c #2 - 'aA' // 8d6d-8d6e #0 - 'B' // 8d6f #1 - 'A' // 8d70 #0 - 'F' // 8d71 #5 - 'B' // 8d72 #1 - 'aA' // 8d73-8d74 #0 - 'D' // 8d75 #3 - 'aA' // 8d76-8d77 #0 - 'cB' // 8d78-8d7b #1 - 'D' // 8d7c #3 - 'B' // 8d7d #1 - 'D' // 8d7e #3 - 'F' // 8d7f #5 - 'B' // 8d80 #1 - 'A' // 8d81 #0 - 'C' // 8d82 #2 - 'D' // 8d83 #3 - 'C' // 8d84 #2 - 'A' // 8d85 #0 - 'aD' // 8d86-8d87 #3 - 'F' // 8d88 #5 - 'B' // 8d89 #1 - 'A' // 8d8a #0 - 'D' // 8d8b #3 - 'B' // 8d8c #1 - 'C' // 8d8d #2 - 'I' // 8d8e #8 - 'B' // 8d8f #1 - 'A' // 8d90 #0 - 'C' // 8d91 #2 - 'bB' // 8d92-8d94 #1 - 'C' // 8d95 #2 - 'B' // 8d96 #1 - 'aD' // 8d97-8d98 #3 - 'A' // 8d99 #0 - 'D' // 8d9a #3 - 'aB' // 8d9b-8d9c #1 - 'D' // 8d9d #3 - 'F' // 8d9e #5 - 'C' // 8d9f #2 - 'A' // 8da0 #0 - 'B' // 8da1 #1 - 'D' // 8da2 #3 - 'A' // 8da3 #0 - 'D' // 8da4 #3 - 'B' // 8da5 #1 - 'C' // 8da6 #2 - 'B' // 8da7 #1 - 'A' // 8da8 #0 - 'aB' // 8da9-8daa #1 - 'A' // 8dab #0 - 'C' // 8dac #2 - 'aB' // 8dad-8dae #1 - 'C' // 8daf #2 - 'aD' // 8db0-8db1 #3 - 'aA' // 8db2-8db3 #0 - 'B' // 8db4 #1 - 'C' // 8db5 #2 - 'B' // 8db6 #1 - 'C' // 8db7 #2 - 'D' // 8db8 #3 - 'C' // 8db9 #2 - 'A' // 8dba #0 - 'F' // 8dbb #5 - 'C' // 8dbc #2 - 'D' // 8dbd #3 - 'A' // 8dbe #0 - 'B' // 8dbf #1 - 'C' // 8dc0 #2 - 'B' // 8dc1 #1 - 'A' // 8dc2 #0 - 'B' // 8dc3 #1 - 'D' // 8dc4 #3 - 'C' // 8dc5 #2 - 'A' // 8dc6 #0 - 'aC' // 8dc7-8dc8 #2 - 'D' // 8dc9 #3 - 'F' // 8dca #5 - 'aA' // 8dcb-8dcc #0 - 'B' // 8dcd #1 - 'aA' // 8dce-8dcf #0 - 'B' // 8dd0 #1 - 'C' // 8dd1 #2 - 'D' // 8dd2 #3 - 'B' // 8dd3 #1 - 'C' // 8dd4 #2 - 'bA' // 8dd5-8dd7 #0 - 'B' // 8dd8 #1 - 'aC' // 8dd9-8dda #2 - 'A' // 8ddb #0 - 'B' // 8ddc #1 - 'A' // 8ddd #0 - 'D' // 8dde #3 - 'A' // 8ddf #0 - 'B' // 8de0 #1 - 'A' // 8de1 #0 - 'B' // 8de2 #1 - 'A' // 8de3 #0 - 'C' // 8de4 #2 - 'F' // 8de5 #5 - 'B' // 8de6 #1 - 'C' // 8de7 #2 - 'A' // 8de8 #0 - 'B' // 8de9 #1 - 'bA' // 8dea-8dec #0 - 'K' // 8ded #10 - 'B' // 8dee #1 - 'A' // 8def #0 - 'C' // 8df0 #2 - 'A' // 8df1 #0 - 'C' // 8df2 #2 - 'A' // 8df3 #0 - 'C' // 8df4 #2 - 'F' // 8df5 #5 - 'cD' // 8df6-8df9 #3 - 'B' // 8dfa #1 - 'D' // 8dfb #3 - 'A' // 8dfc #0 - 'C' // 8dfd #2 - 'B' // 8dfe #1 - 'C' // 8dff #2 - 'B' // 8e00 #1 - 'C' // 8e01 #2 - 'aB' // 8e02-8e03 #1 - 'aC' // 8e04-8e05 #2 - 'A' // 8e06 #0 - 'B' // 8e07 #1 - 'J' // 8e08 #9 - 'aA' // 8e09-8e0a #0 - 'aF' // 8e0b-8e0c #5 - 'aB' // 8e0d-8e0e #1 - 'aA' // 8e0f-8e10 #0 - 'C' // 8e11 #2 - 'aB' // 8e12-8e13 #1 - 'A' // 8e14 #0 - 'B' // 8e15 #1 - 'C' // 8e16 #2 - 'eB' // 8e17-8e1c #1 - 'bA' // 8e1d-8e1f #0 - 'cC' // 8e20-8e23 #2 - 'aB' // 8e24-8e25 #1 - 'aC' // 8e26-8e27 #2 - 'aB' // 8e28-8e29 #1 - 'A' // 8e2a #0 - 'B' // 8e2b #1 - 'D' // 8e2c #3 - 'aB' // 8e2d-8e2e #1 - 'D' // 8e2f #3 - 'A' // 8e30 #0 - 'C' // 8e31 #2 - 'D' // 8e32 #3 - 'C' // 8e33 #2 - 'bA' // 8e34-8e36 #0 - 'F' // 8e37 #5 - 'aC' // 8e38-8e39 #2 - 'I' // 8e3a #8 - 'D' // 8e3b #3 - 'B' // 8e3c #1 - 'A' // 8e3d #0 - 'aB' // 8e3e-8e3f #1 - 'A' // 8e40 #0 - 'C' // 8e41 #2 - 'A' // 8e42 #0 - 'D' // 8e43 #3 - 'A' // 8e44 #0 - 'aB' // 8e45-8e46 #1 - 'cA' // 8e47-8e4a #0 - 'C' // 8e4b #2 - 'A' // 8e4c #0 - 'aC' // 8e4d-8e4e #2 - 'A' // 8e4f #0 - 'C' // 8e50 #2 - 'aD' // 8e51-8e52 #3 - 'B' // 8e53 #1 - 'C' // 8e54 #2 - 'A' // 8e55 #0 - 'aB' // 8e56-8e57 #1 - 'D' // 8e58 #3 - 'A' // 8e59 #0 - 'B' // 8e5a #1 - 'C' // 8e5b #2 - 'A' // 8e5c #0 - 'aC' // 8e5d-8e5e #2 - 'aA' // 8e5f-8e60 #0 - 'aC' // 8e61-8e62 #2 - 'aA' // 8e63-8e64 #0 - 'cB' // 8e65-8e68 #1 - 'C' // 8e69 #2 - 'B' // 8e6a #1 - 'D' // 8e6b #3 - 'aC' // 8e6c-8e6d #2 - 'D' // 8e6e #3 - 'C' // 8e6f #2 - 'F' // 8e70 #5 - 'C' // 8e71 #2 - 'A' // 8e72 #0 - 'B' // 8e73 #1 - 'A' // 8e74 #0 - 'C' // 8e75 #2 - 'A' // 8e76 #0 - 'C' // 8e77 #2 - 'B' // 8e78 #1 - 'F' // 8e79 #5 - 'C' // 8e7a #2 - 'A' // 8e7b #0 - 'C' // 8e7c #2 - 'D' // 8e7d #3 - 'B' // 8e7e #1 - 'D' // 8e7f #3 - 'B' // 8e80 #1 - 'A' // 8e81 #0 - 'C' // 8e82 #2 - 'F' // 8e83 #5 - 'C' // 8e84 #2 - 'A' // 8e85 #0 - 'B' // 8e86 #1 - 'A' // 8e87 #0 - 'B' // 8e88 #1 - 'bA' // 8e89-8e8b #0 - 'B' // 8e8c #1 - 'A' // 8e8d #0 - 'B' // 8e8e #1 - 'D' // 8e8f #3 - 'aA' // 8e90-8e91 #0 - 'C' // 8e92 #2 - 'aA' // 8e93-8e94 #0 - 'C' // 8e95 #2 - 'aB' // 8e96-8e97 #1 - 'C' // 8e98 #2 - 'J' // 8e99 #9 - 'C' // 8e9a #2 - 'F' // 8e9b #5 - 'D' // 8e9c #3 - 'C' // 8e9d #2 - 'A' // 8e9e #0 - 'aB' // 8e9f-8ea0 #1 - 'A' // 8ea1 #0 - 'F' // 8ea2 #5 - 'cB' // 8ea3-8ea6 #1 - 'C' // 8ea7 #2 - 'B' // 8ea8 #1 - 'cA' // 8ea9-8eac #0 - 'C' // 8ead #2 - 'aF' // 8eae-8eaf #5 - 'C' // 8eb0 #2 - 'J' // 8eb1 #9 - 'B' // 8eb2 #1 - 'J' // 8eb3 #9 - 'D' // 8eb4 #3 - 'F' // 8eb5 #5 - 'C' // 8eb6 #2 - 'aD' // 8eb7-8eb8 #3 - 'B' // 8eb9 #1 - 'C' // 8eba #2 - 'F' // 8ebb #5 - 'aB' // 8ebc-8ebd #1 - 'J' // 8ebe #9 - 'D' // 8ebf #3 - 'A' // 8ec0 #0 - 'F' // 8ec1 #5 - 'B' // 8ec2 #1 - 'C' // 8ec3 #2 - 'aF' // 8ec4-8ec5 #5 - 'J' // 8ec6 #9 - 'aF' // 8ec7-8ec8 #5 - 'B' // 8ec9 #1 - 'cA' // 8eca-8ecd #0 - 'B' // 8ece #1 - 'C' // 8ecf #2 - 'D' // 8ed0 #3 - 'C' // 8ed1 #2 - 'A' // 8ed2 #0 - 'B' // 8ed3 #1 - 'C' // 8ed4 #2 - 'aD' // 8ed5-8ed6 #3 - 'aB' // 8ed7-8ed8 #1 - 'D' // 8ed9 #3 - 'B' // 8eda #1 - 'aC' // 8edb-8edc #2 - 'B' // 8edd #1 - 'I' // 8ede #8 - 'A' // 8edf #0 - 'aB' // 8ee0-8ee1 #1 - 'A' // 8ee2 #0 - 'F' // 8ee3 #5 - 'cB' // 8ee4-8ee7 #1 - 'A' // 8ee8 #0 - 'B' // 8ee9 #1 - 'D' // 8eea #3 - 'A' // 8eeb #0 - 'B' // 8eec #1 - 'aC' // 8eed-8eee #2 - 'B' // 8eef #1 - 'F' // 8ef0 #5 - 'C' // 8ef1 #2 - 'B' // 8ef2 #1 - 'D' // 8ef3 #3 - 'bB' // 8ef4-8ef6 #1 - 'C' // 8ef7 #2 - 'dA' // 8ef8-8efc #0 - 'F' // 8efd #5 - 'A' // 8efe #0 - 'B' // 8eff #1 - 'C' // 8f00 #2 - 'B' // 8f01 #1 - 'C' // 8f02 #2 - 'A' // 8f03 #0 - 'D' // 8f04 #3 - 'A' // 8f05 #0 - 'B' // 8f06 #1 - 'bA' // 8f07-8f09 #0 - 'C' // 8f0a #2 - 'B' // 8f0b #1 - 'F' // 8f0c #5 - 'aB' // 8f0d-8f0e #1 - 'F' // 8f0f #5 - 'C' // 8f10 #2 - 'B' // 8f11 #1 - 'cA' // 8f12-8f15 #0 - 'cC' // 8f16-8f19 #2 - 'B' // 8f1a #1 - 'dA' // 8f1b-8f1f #0 - 'C' // 8f20 #2 - 'F' // 8f21 #5 - 'D' // 8f22 #3 - 'C' // 8f23 #2 - 'B' // 8f24 #1 - 'C' // 8f25 #2 - 'A' // 8f26 #0 - 'aJ' // 8f27-8f28 #9 - 'aA' // 8f29-8f2a #0 - 'F' // 8f2b #5 - 'C' // 8f2c #2 - 'A' // 8f2d #0 - 'C' // 8f2e #2 - 'A' // 8f2f #0 - 'I' // 8f30 #8 - 'D' // 8f31 #3 - 'B' // 8f32 #1 - 'A' // 8f33 #0 - 'cC' // 8f34-8f37 #2 - 'aA' // 8f38-8f39 #0 - 'F' // 8f3a #5 - 'A' // 8f3b #0 - 'B' // 8f3c #1 - 'D' // 8f3d #3 - 'bA' // 8f3e-8f40 #0 - 'C' // 8f41 #2 - 'A' // 8f42 #0 - 'C' // 8f43 #2 - 'bA' // 8f44-8f46 #0 - 'C' // 8f47 #2 - 'B' // 8f48 #1 - 'A' // 8f49 #0 - 'C' // 8f4a #2 - 'B' // 8f4b #1 - 'F' // 8f4c #5 - 'aA' // 8f4d-8f4e #0 - 'C' // 8f4f #2 - 'B' // 8f50 #1 - 'C' // 8f51 #2 - 'A' // 8f52 #0 - 'C' // 8f53 #2 - 'A' // 8f54 #0 - 'C' // 8f55 #2 - 'B' // 8f56 #1 - 'aA' // 8f57-8f58 #0 - 'bB' // 8f59-8f5b #1 - 'C' // 8f5c #2 - 'bA' // 8f5d-8f5f #0 - 'B' // 8f60 #1 - 'cA' // 8f61-8f64 #0 - 'F' // 8f65 #5 - 'A' // 8f66 #0 - 'B' // 8f67 #1 - 'eD' // 8f68-8f6d #3 - 'B' // 8f6e #1 - '1iD' // 8f6f-8f92 #3 - 'B' // 8f93 #1 - 'fD' // 8f94-8f9a #3 - 'aA' // 8f9b-8f9c #0 - 'aF' // 8f9d-8f9e #5 - 'A' // 8f9f #0 - 'C' // 8fa0 #2 - 'F' // 8fa1 #5 - 'J' // 8fa2 #9 - 'A' // 8fa3 #0 - 'F' // 8fa4 #5 - 'C' // 8fa5 #2 - 'A' // 8fa6 #0 - 'C' // 8fa7 #2 - 'A' // 8fa8 #0 - 'cD' // 8fa9-8fac #3 - 'eA' // 8fad-8fb2 #0 - 'B' // 8fb3 #1 - 'C' // 8fb4 #2 - 'aA' // 8fb5-8fb6 #0 - 'aC' // 8fb7-8fb8 #2 - 'B' // 8fb9 #1 - 'C' // 8fba #2 - 'A' // 8fbb #0 - 'C' // 8fbc #2 - 'D' // 8fbd #3 - 'C' // 8fbe #2 - 'A' // 8fbf #0 - 'J' // 8fc0 #9 - 'C' // 8fc1 #2 - 'A' // 8fc2 #0 - 'K' // 8fc3 #10 - 'aA' // 8fc4-8fc5 #0 - 'C' // 8fc6 #2 - 'D' // 8fc7 #3 - 'F' // 8fc8 #5 - 'B' // 8fc9 #1 - 'aC' // 8fca-8fcb #2 - 'B' // 8fcc #1 - 'aA' // 8fcd-8fce #0 - 'B' // 8fcf #1 - 'aA' // 8fd0-8fd1 #0 - 'C' // 8fd2 #2 - 'bA' // 8fd3-8fd5 #0 - 'aB' // 8fd6-8fd7 #1 - 'aD' // 8fd8-8fd9 #3 - 'C' // 8fda #2 - 'dD' // 8fdb-8fdf #3 - 'C' // 8fe0 #2 - 'B' // 8fe1 #1 - 'A' // 8fe2 #0 - 'C' // 8fe3 #2 - 'bA' // 8fe4-8fe6 #0 - 'D' // 8fe7 #3 - 'A' // 8fe8 #0 - 'F' // 8fe9 #5 - 'aA' // 8fea-8feb #0 - 'K' // 8fec #10 - 'A' // 8fed #0 - 'C' // 8fee #2 - 'F' // 8fef #5 - 'A' // 8ff0 #0 - 'F' // 8ff1 #5 - 'K' // 8ff2 #10 - 'D' // 8ff3 #3 - 'A' // 8ff4 #0 - 'aC' // 8ff5-8ff6 #2 - 'cA' // 8ff7-8ffa #0 - 'C' // 8ffb #2 - 'I' // 8ffc #8 - 'A' // 8ffd #0 - 'C' // 8ffe #2 - 'I' // 8fff #8 - 'cA' // 9000-9003 #0 - 'C' // 9004 #2 - 'aA' // 9005-9006 #0 - 'D' // 9007 #3 - 'A' // 9008 #0 - 'aD' // 9009-900a #3 - 'bA' // 900b-900d #0 - 'J' // 900e #9 - 'bA' // 900f-9011 #0 - 'B' // 9012 #1 - 'F' // 9013 #5 - 'cA' // 9014-9017 #0 - 'F' // 9018 #5 - 'aA' // 9019-901a #0 - 'C' // 901b #2 - 'B' // 901c #1 - 'fA' // 901d-9023 #0 - 'B' // 9024 #1 - 'aD' // 9025-9026 #3 - 'cF' // 9027-902a #5 - 'D' // 902b #3 - 'F' // 902c #5 - 'C' // 902d #2 - 'A' // 902e #0 - 'C' // 902f #2 - 'D' // 9030 #3 - 'aA' // 9031-9032 #0 - 'C' // 9033 #2 - 'bA' // 9034-9036 #0 - 'C' // 9037 #2 - 'A' // 9038 #0 - 'F' // 9039 #5 - 'aD' // 903a-903b #3 - 'A' // 903c #0 - 'B' // 903d #1 - 'A' // 903e #0 - 'C' // 903f #2 - 'D' // 9040 #3 - 'aA' // 9041-9042 #0 - 'F' // 9043 #5 - 'C' // 9044 #2 - 'F' // 9045 #5 - 'B' // 9046 #1 - 'A' // 9047 #0 - 'D' // 9048 #3 - 'bA' // 9049-904b #0 - 'C' // 904c #2 - 'hA' // 904d-9055 #0 - 'C' // 9056 #2 - 'D' // 9057 #3 - 'aA' // 9058-9059 #0 - 'D' // 905a #3 - 'cA' // 905b-905e #0 - 'D' // 905f #3 - 'aA' // 9060-9061 #0 - 'C' // 9062 #2 - 'A' // 9063 #0 - 'B' // 9064 #1 - 'aF' // 9065-9066 #5 - 'C' // 9067 #2 - 'aA' // 9068-9069 #0 - 'D' // 906a #3 - 'B' // 906b #1 - 'cA' // 906c-906f #0 - 'C' // 9070 #2 - 'D' // 9071 #3 - 'A' // 9072 #0 - 'B' // 9073 #1 - 'C' // 9074 #2 - 'cA' // 9075-9078 #0 - 'C' // 9079 #2 - 'A' // 907a #0 - 'B' // 907b #1 - 'aA' // 907c-907d #0 - 'I' // 907e #8 - 'fA' // 907f-9085 #0 - 'B' // 9086 #1 - 'aA' // 9087-9088 #0 - 'F' // 9089 #5 - 'A' // 908a #0 - 'C' // 908b #2 - 'J' // 908c #9 - 'I' // 908d #8 - 'F' // 908e #5 - 'bA' // 908f-9091 #0 - 'aD' // 9092-9093 #3 - 'B' // 9094 #1 - 'A' // 9095 #0 - 'D' // 9096 #3 - 'bA' // 9097-9099 #0 - 'D' // 909a #3 - 'C' // 909b #2 - 'aD' // 909c-909d #3 - 'aB' // 909e-909f #1 - 'A' // 90a0 #0 - 'C' // 90a1 #2 - 'aA' // 90a2-90a3 #0 - 'D' // 90a4 #3 - 'C' // 90a5 #2 - 'A' // 90a6 #0 - 'B' // 90a7 #1 - 'A' // 90a8 #0 - 'D' // 90a9 #3 - 'A' // 90aa #0 - 'bD' // 90ab-90ad #3 - 'B' // 90ae #1 - 'bA' // 90af-90b1 #0 - 'C' // 90b2 #2 - 'A' // 90b3 #0 - 'C' // 90b4 #2 - 'A' // 90b5 #0 - 'C' // 90b6 #2 - 'D' // 90b7 #3 - 'A' // 90b8 #0 - 'aD' // 90b9-90ba #3 - 'B' // 90bb #1 - 'D' // 90bc #3 - 'aA' // 90bd-90be #0 - 'B' // 90bf #1 - 'D' // 90c0 #3 - 'A' // 90c1 #0 - 'D' // 90c2 #3 - 'bA' // 90c3-90c5 #0 - 'D' // 90c6 #3 - 'aC' // 90c7-90c8 #2 - 'F' // 90c9 #5 - 'A' // 90ca #0 - 'B' // 90cb #1 - 'F' // 90cc #5 - 'D' // 90cd #3 - 'A' // 90ce #0 - 'bD' // 90cf-90d1 #3 - 'F' // 90d2 #5 - 'D' // 90d3 #3 - 'B' // 90d4 #1 - 'C' // 90d5 #2 - 'B' // 90d6 #1 - 'bC' // 90d7-90d9 #2 - 'B' // 90da #1 - 'C' // 90db #2 - 'aA' // 90dc-90dd #0 - 'J' // 90de #9 - 'C' // 90df #2 - 'B' // 90e0 #1 - 'aA' // 90e1-90e2 #0 - 'B' // 90e3 #1 - 'aC' // 90e4-90e5 #2 - 'aD' // 90e6-90e7 #3 - 'A' // 90e8 #0 - 'B' // 90e9 #1 - 'I' // 90ea #8 - 'A' // 90eb #0 - 'B' // 90ec #1 - 'A' // 90ed #0 - 'D' // 90ee #3 - 'A' // 90ef #0 - 'C' // 90f0 #2 - 'B' // 90f1 #1 - 'C' // 90f2 #2 - 'I' // 90f3 #8 - 'aA' // 90f4-90f5 #0 - 'aF' // 90f6-90f7 #5 - 'D' // 90f8 #3 - 'cB' // 90f9-90fc #1 - 'A' // 90fd #0 - 'bC' // 90fe-9100 #2 - 'B' // 9101 #1 - 'A' // 9102 #0 - 'B' // 9103 #1 - 'bC' // 9104-9106 #2 - 'B' // 9107 #1 - 'C' // 9108 #2 - 'B' // 9109 #1 - 'D' // 910a #3 - 'B' // 910b #1 - 'D' // 910c #3 - 'C' // 910d #2 - 'aB' // 910e-910f #1 - 'C' // 9110 #2 - 'B' // 9111 #1 - 'A' // 9112 #0 - 'D' // 9113 #3 - 'C' // 9114 #2 - 'J' // 9115 #9 - 'aA' // 9116-9117 #0 - 'C' // 9118 #2 - 'A' // 9119 #0 - 'C' // 911a #2 - 'B' // 911b #1 - 'C' // 911c #2 - 'B' // 911d #1 - 'A' // 911e #0 - 'B' // 911f #1 - 'C' // 9120 #2 - 'B' // 9121 #1 - 'aA' // 9122-9123 #0 - 'B' // 9124 #1 - 'F' // 9125 #5 - 'B' // 9126 #1 - 'A' // 9127 #0 - 'B' // 9128 #1 - 'C' // 9129 #2 - 'bB' // 912a-912c #1 - 'A' // 912d #0 - 'aC' // 912e-912f #2 - 'bA' // 9130-9132 #0 - 'B' // 9133 #1 - 'A' // 9134 #0 - 'B' // 9135 #1 - 'C' // 9136 #2 - 'F' // 9137 #5 - 'B' // 9138 #1 - 'aC' // 9139-913a #2 - 'B' // 913b #1 - 'F' // 913c #5 - 'J' // 913d #9 - 'cB' // 913e-9141 #1 - 'D' // 9142 #3 - 'C' // 9143 #2 - 'aB' // 9144-9145 #1 - 'aC' // 9146-9147 #2 - 'fA' // 9148-914e #0 - 'C' // 914f #2 - 'aB' // 9150-9151 #1 - 'A' // 9152 #0 - 'C' // 9153 #2 - 'F' // 9154 #5 - 'B' // 9155 #1 - 'aA' // 9156-9157 #0 - 'bC' // 9158-915a #2 - 'F' // 915b #5 - 'B' // 915c #1 - 'D' // 915d #3 - 'bB' // 915e-9160 #1 - 'C' // 9161 #2 - 'cA' // 9162-9165 #0 - 'D' // 9166 #3 - 'C' // 9167 #2 - 'B' // 9168 #1 - 'aA' // 9169-916a #0 - 'D' // 916b #3 - 'A' // 916c #0 - 'F' // 916d #5 - 'bB' // 916e-9170 #1 - 'D' // 9171 #3 - 'A' // 9172 #0 - 'C' // 9173 #2 - 'aA' // 9174-9175 #0 - 'I' // 9176 #8 - 'bA' // 9177-9179 #0 - 'C' // 917a #2 - 'F' // 917b #5 - 'B' // 917c #1 - 'bD' // 917d-917f #3 - 'B' // 9180 #1 - 'aC' // 9181-9182 #2 - 'A' // 9183 #0 - 'B' // 9184 #1 - 'aC' // 9185-9186 #2 - 'A' // 9187 #0 - 'D' // 9188 #3 - 'A' // 9189 #0 - 'C' // 918a #2 - 'A' // 918b #0 - 'B' // 918c #1 - 'A' // 918d #0 - 'C' // 918e #2 - 'B' // 918f #1 - 'A' // 9190 #0 - 'C' // 9191 #2 - 'A' // 9192 #0 - 'C' // 9193 #2 - 'aF' // 9194-9195 #5 - 'B' // 9196 #1 - 'aF' // 9197-9198 #5 - 'bB' // 9199-919b #1 - 'A' // 919c #0 - 'B' // 919d #1 - 'A' // 919e #0 - 'aB' // 919f-91a0 #1 - 'C' // 91a1 #2 - 'A' // 91a2 #0 - 'B' // 91a3 #1 - 'F' // 91a4 #5 - 'B' // 91a5 #1 - 'F' // 91a6 #5 - 'B' // 91a7 #1 - 'C' // 91a8 #2 - 'B' // 91a9 #1 - 'bA' // 91aa-91ac #0 - 'C' // 91ad #2 - 'aA' // 91ae-91af #0 - 'C' // 91b0 #2 - 'aA' // 91b1-91b2 #0 - 'C' // 91b3 #2 - 'aA' // 91b4-91b5 #0 - 'C' // 91b6 #2 - 'B' // 91b7 #1 - 'F' // 91b8 #5 - 'B' // 91b9 #1 - 'aC' // 91ba-91bb #2 - 'A' // 91bc #0 - 'C' // 91bd #2 - 'B' // 91be #1 - 'F' // 91bf #5 - 'aA' // 91c0-91c1 #0 - 'C' // 91c2 #2 - 'A' // 91c3 #0 - 'C' // 91c4 #2 - 'bA' // 91c5-91c7 #0 - 'F' // 91c8 #5 - 'A' // 91c9 #0 - 'D' // 91ca #3 - 'fA' // 91cb-91d1 #0 - 'D' // 91d2 #3 - 'aC' // 91d3-91d4 #2 - 'B' // 91d5 #1 - 'C' // 91d6 #2 - 'aA' // 91d7-91d8 #0 - 'aC' // 91d9-91da #2 - 'F' // 91db #5 - 'aA' // 91dc-91dd #0 - 'F' // 91de #5 - 'C' // 91df #2 - 'D' // 91e0 #3 - 'F' // 91e1 #5 - 'B' // 91e2 #1 - 'dA' // 91e3-91e7 #0 - 'B' // 91e8 #1 - 'aA' // 91e9-91ea #0 - 'B' // 91eb #1 - 'C' // 91ec #2 - 'A' // 91ed #0 - 'C' // 91ee #2 - 'aF' // 91ef-91f0 #5 - 'C' // 91f1 #2 - 'D' // 91f2 #3 - 'aB' // 91f3-91f4 #1 - 'A' // 91f5 #0 - 'aC' // 91f6-91f7 #2 - 'B' // 91f8 #1 - 'C' // 91f9 #2 - 'B' // 91fa #1 - 'aF' // 91fb-91fc #5 - 'C' // 91fd #2 - 'B' // 91fe #1 - 'A' // 91ff #0 - 'aC' // 9200-9201 #2 - 'aB' // 9202-9203 #1 - 'bC' // 9204-9206 #2 - 'A' // 9207 #0 - 'B' // 9208 #1 - 'aC' // 9209-920a #2 - 'D' // 920b #3 - 'C' // 920c #2 - 'A' // 920d #0 - 'C' // 920e #2 - 'B' // 920f #1 - 'bA' // 9210-9212 #0 - 'C' // 9213 #2 - 'aA' // 9214-9215 #0 - 'C' // 9216 #2 - 'A' // 9217 #0 - 'C' // 9218 #2 - 'aB' // 9219-921a #1 - 'D' // 921b #3 - 'A' // 921c #0 - 'F' // 921d #5 - 'A' // 921e #0 - 'K' // 921f #10 - 'D' // 9220 #3 - 'B' // 9221 #1 - 'D' // 9222 #3 - 'bC' // 9223-9225 #2 - 'A' // 9226 #0 - 'B' // 9227 #1 - 'C' // 9228 #2 - 'F' // 9229 #5 - 'aB' // 922a-922b #1 - 'F' // 922c #5 - 'B' // 922d #1 - 'C' // 922e #2 - 'F' // 922f #5 - 'C' // 9230 #2 - 'I' // 9231 #8 - 'B' // 9232 #1 - 'C' // 9233 #2 - 'aA' // 9234-9235 #0 - 'C' // 9236 #2 - 'aA' // 9237-9238 #0 - 'C' // 9239 #2 - 'A' // 923a #0 - 'D' // 923b #3 - 'C' // 923c #2 - 'B' // 923d #1 - 'C' // 923e #2 - 'aA' // 923f-9240 #0 - 'I' // 9241 #8 - 'aF' // 9242-9243 #5 - 'aA' // 9244-9245 #0 - 'C' // 9246 #2 - 'F' // 9247 #5 - 'C' // 9248 #2 - 'A' // 9249 #0 - 'C' // 924a #2 - 'A' // 924b #0 - 'B' // 924c #1 - 'dA' // 924d-9251 #0 - 'I' // 9252 #8 - 'bB' // 9253-9255 #1 - 'C' // 9256 #2 - 'A' // 9257 #0 - 'C' // 9258 #2 - 'F' // 9259 #5 - 'C' // 925a #2 - 'A' // 925b #0 - 'F' // 925c #5 - 'C' // 925d #2 - 'A' // 925e #0 - 'B' // 925f #1 - 'aC' // 9260-9261 #2 - 'A' // 9262 #0 - 'B' // 9263 #1 - 'bA' // 9264-9266 #0 - 'C' // 9267 #2 - 'aF' // 9268-9269 #5 - 'D' // 926a #3 - 'bB' // 926b-926d #1 - 'bC' // 926e-9270 #2 - 'F' // 9271 #5 - 'B' // 9272 #1 - 'aD' // 9273-9274 #3 - 'F' // 9275 #5 - 'C' // 9276 #2 - 'aA' // 9277-9278 #0 - 'C' // 9279 #2 - 'B' // 927a #1 - 'C' // 927b #2 - 'A' // 927c #0 - 'C' // 927d #2 - 'A' // 927e #0 - 'C' // 927f #2 - 'A' // 9280 #0 - 'aB' // 9281-9282 #1 - 'A' // 9283 #0 - 'B' // 9284 #1 - 'A' // 9285 #0 - 'aB' // 9286-9287 #1 - 'bC' // 9288-928a #2 - 'I' // 928b #8 - 'B' // 928c #1 - 'aC' // 928d-928e #2 - 'B' // 928f #1 - 'D' // 9290 #3 - 'A' // 9291 #0 - 'F' // 9292 #5 - 'A' // 9293 #0 - 'B' // 9294 #1 - 'aA' // 9295-9296 #0 - 'C' // 9297 #2 - 'dA' // 9298-929c #0 - 'B' // 929d #1 - 'D' // 929e #3 - 'F' // 929f #5 - 'C' // 92a0 #2 - 'bB' // 92a1-92a3 #1 - 'aC' // 92a4-92a5 #2 - 'B' // 92a6 #1 - 'aC' // 92a7-92a8 #2 - 'aB' // 92a9-92aa #1 - 'C' // 92ab #2 - 'B' // 92ac #1 - 'F' // 92ad #5 - 'B' // 92ae #1 - 'F' // 92af #5 - 'D' // 92b0 #3 - 'B' // 92b1 #1 - 'C' // 92b2 #2 - 'A' // 92b3 #0 - 'I' // 92b4 #8 - 'B' // 92b5 #1 - 'aA' // 92b6-92b7 #0 - 'F' // 92b8 #5 - 'A' // 92b9 #0 - 'bC' // 92ba-92bc #2 - 'F' // 92bd #5 - 'B' // 92be #1 - 'dC' // 92bf-92c3 #2 - 'B' // 92c4 #1 - 'C' // 92c5 #2 - 'A' // 92c6 #0 - 'aC' // 92c7-92c8 #2 - 'aB' // 92c9-92ca #1 - 'C' // 92cb #2 - 'A' // 92cc #0 - 'aC' // 92cd-92ce #2 - 'A' // 92cf #0 - 'C' // 92d0 #2 - 'I' // 92d1 #8 - 'A' // 92d2 #0 - 'C' // 92d3 #2 - 'B' // 92d4 #1 - 'A' // 92d5 #0 - 'D' // 92d6 #3 - 'A' // 92d7 #0 - 'aC' // 92d8-92d9 #2 - 'D' // 92da #3 - 'B' // 92db #1 - 'F' // 92dc #5 - 'C' // 92dd #2 - 'B' // 92de #1 - 'A' // 92df #0 - 'aC' // 92e0-92e1 #2 - 'D' // 92e2 #3 - 'C' // 92e3 #2 - 'aA' // 92e4-92e5 #0 - 'B' // 92e6 #1 - 'bC' // 92e7-92e9 #2 - 'A' // 92ea #0 - 'B' // 92eb #1 - 'bC' // 92ec-92ee #2 - 'B' // 92ef #1 - 'C' // 92f0 #2 - 'B' // 92f1 #1 - 'A' // 92f2 #0 - 'C' // 92f3 #2 - 'B' // 92f4 #1 - 'D' // 92f5 #3 - 'B' // 92f6 #1 - 'C' // 92f7 #2 - 'bA' // 92f8-92fa #0 - 'C' // 92fb #2 - 'A' // 92fc #0 - 'aI' // 92fd-92fe #8 - 'C' // 92ff #2 - 'A' // 9300 #0 - 'B' // 9301 #1 - 'C' // 9302 #2 - 'B' // 9303 #1 - 'A' // 9304 #0 - 'D' // 9305 #3 - 'A' // 9306 #0 - 'B' // 9307 #1 - 'C' // 9308 #2 - 'B' // 9309 #1 - 'D' // 930a #3 - 'aB' // 930b-930c #1 - 'C' // 930d #2 - 'B' // 930e #1 - 'aA' // 930f-9310 #0 - 'F' // 9311 #5 - 'aB' // 9312-9313 #1 - 'C' // 9314 #2 - 'A' // 9315 #0 - 'B' // 9316 #1 - 'D' // 9317 #3 - 'bA' // 9318-931a #0 - 'B' // 931b #1 - 'F' // 931c #5 - 'C' // 931d #2 - 'fA' // 931e-9324 #0 - 'C' // 9325 #2 - 'bA' // 9326-9328 #0 - 'C' // 9329 #2 - 'bA' // 932a-932c #0 - 'B' // 932d #1 - 'aA' // 932e-932f #0 - 'aB' // 9330-9331 #1 - 'F' // 9332 #5 - 'cC' // 9333-9336 #2 - 'F' // 9337 #5 - 'aB' // 9338-9339 #1 - 'aF' // 933a-933b #5 - 'B' // 933c #1 - 'bD' // 933d-933f #3 - 'cB' // 9340-9343 #1 - 'C' // 9344 #2 - 'aB' // 9345-9346 #1 - 'C' // 9347 #2 - 'cA' // 9348-934b #0 - 'B' // 934c #1 - 'A' // 934d #0 - 'aB' // 934e-934f #1 - 'C' // 9350 #2 - 'A' // 9351 #0 - 'C' // 9352 #2 - 'D' // 9353 #3 - 'A' // 9354 #0 - 'aC' // 9355-9356 #2 - 'A' // 9357 #0 - 'C' // 9358 #2 - 'B' // 9359 #1 - 'C' // 935a #2 - 'aA' // 935b-935c #0 - 'K' // 935d #10 - 'C' // 935e #2 - 'B' // 935f #1 - 'C' // 9360 #2 - 'bB' // 9361-9363 #1 - 'aA' // 9364-9365 #0 - 'B' // 9366 #1 - 'C' // 9367 #2 - 'B' // 9368 #1 - 'aC' // 9369-936a #2 - 'aA' // 936b-936c #0 - 'C' // 936d #2 - 'A' // 936e #0 - 'F' // 936f #5 - 'A' // 9370 #0 - 'C' // 9371 #2 - 'K' // 9372 #10 - 'aC' // 9373-9374 #2 - 'A' // 9375 #0 - 'C' // 9376 #2 - 'bB' // 9377-9379 #1 - 'C' // 937a #2 - 'B' // 937b #1 - 'A' // 937c #0 - 'C' // 937d #2 - 'A' // 937e #0 - 'F' // 937f #5 - 'bC' // 9380-9382 #2 - 'dB' // 9383-9387 #1 - 'C' // 9388 #2 - 'B' // 9389 #1 - 'A' // 938a #0 - 'F' // 938b #5 - 'A' // 938c #0 - 'C' // 938d #2 - 'B' // 938e #1 - 'C' // 938f #2 - 'aB' // 9390-9391 #1 - 'C' // 9392 #2 - 'D' // 9393 #3 - 'A' // 9394 #0 - 'C' // 9395 #2 - 'aA' // 9396-9397 #0 - 'C' // 9398 #2 - 'B' // 9399 #1 - 'aA' // 939a-939b #0 - 'aB' // 939c-939d #1 - 'C' // 939e #2 - 'aI' // 939f-93a0 #8 - 'A' // 93a1 #0 - 'B' // 93a2 #1 - 'aA' // 93a3-93a4 #0 - 'B' // 93a5 #1 - 'C' // 93a6 #2 - 'A' // 93a7 #0 - 'aC' // 93a8-93a9 #2 - 'B' // 93aa #1 - 'F' // 93ab #5 - 'aA' // 93ac-93ad #0 - 'C' // 93ae #2 - 'B' // 93af #1 - 'A' // 93b0 #0 - 'bB' // 93b1-93b3 #1 - 'aC' // 93b4-93b5 #2 - 'F' // 93b6 #5 - 'aB' // 93b7-93b8 #1 - 'aF' // 93b9-93ba #5 - 'A' // 93bb #0 - 'D' // 93bc #3 - 'B' // 93bd #1 - 'D' // 93be #3 - 'aB' // 93bf-93c0 #1 - 'F' // 93c1 #5 - 'B' // 93c2 #1 - 'A' // 93c3 #0 - 'C' // 93c4 #2 - 'F' // 93c5 #5 - 'C' // 93c6 #2 - 'aA' // 93c7-93c8 #0 - 'F' // 93c9 #5 - 'bA' // 93ca-93cc #0 - 'C' // 93cd #2 - 'aB' // 93ce-93cf #1 - 'C' // 93d0 #2 - 'A' // 93d1 #0 - 'B' // 93d2 #1 - 'C' // 93d3 #2 - 'aB' // 93d4-93d5 #1 - 'bA' // 93d6-93d8 #0 - 'C' // 93d9 #2 - 'aB' // 93da-93db #1 - 'cA' // 93dc-93df #0 - 'B' // 93e0 #1 - 'aA' // 93e1-93e2 #0 - 'B' // 93e3 #1 - 'A' // 93e4 #0 - 'F' // 93e5 #5 - 'A' // 93e6 #0 - 'C' // 93e7 #2 - 'A' // 93e8 #0 - 'bD' // 93e9-93eb #3 - 'B' // 93ec #1 - 'D' // 93ed #3 - 'B' // 93ee #1 - 'D' // 93ef #3 - 'B' // 93f0 #1 - 'C' // 93f1 #2 - 'D' // 93f2 #3 - 'aB' // 93f3-93f4 #1 - 'C' // 93f5 #2 - 'I' // 93f6 #8 - 'C' // 93f7 #2 - 'aA' // 93f8-93f9 #0 - 'C' // 93fa #2 - 'A' // 93fb #0 - 'B' // 93fc #1 - 'C' // 93fd #2 - 'bB' // 93fe-9400 #1 - 'C' // 9401 #2 - 'F' // 9402 #5 - 'aA' // 9403-9404 #0 - 'D' // 9405 #3 - 'B' // 9406 #1 - 'bC' // 9407-9409 #2 - 'bB' // 940a-940c #1 - 'aC' // 940d-940e #2 - 'aA' // 940f-9410 #0 - 'aB' // 9411-9412 #1 - 'aA' // 9413-9414 #0 - 'bC' // 9415-9417 #2 - 'aA' // 9418-9419 #0 - 'F' // 941a #5 - 'B' // 941b #1 - 'D' // 941c #3 - 'B' // 941d #1 - 'D' // 941e #3 - 'F' // 941f #5 - 'B' // 9420 #1 - 'F' // 9421 #5 - 'aD' // 9422-9423 #3 - 'B' // 9424 #1 - 'I' // 9425 #8 - 'cB' // 9426-9429 #1 - 'I' // 942a #8 - 'A' // 942b #0 - 'aB' // 942c-942d #1 - 'aC' // 942e-942f #2 - 'B' // 9430 #1 - 'bC' // 9431-9433 #2 - 'F' // 9434 #5 - 'aA' // 9435-9436 #0 - 'B' // 9437 #1 - 'A' // 9438 #0 - 'B' // 9439 #1 - 'A' // 943a #0 - 'C' // 943b #2 - 'B' // 943c #1 - 'C' // 943d #2 - 'B' // 943e #1 - 'C' // 943f #2 - 'B' // 9440 #1 - 'F' // 9441 #5 - 'I' // 9442 #8 - 'C' // 9443 #2 - 'A' // 9444 #0 - 'C' // 9445 #2 - 'aB' // 9446-9447 #1 - 'C' // 9448 #2 - 'B' // 9449 #1 - 'A' // 944a #0 - 'B' // 944b #1 - 'A' // 944c #0 - 'B' // 944d #1 - 'D' // 944e #3 - 'aB' // 944f-9450 #1 - 'aA' // 9451-9452 #0 - 'F' // 9453 #5 - 'B' // 9454 #1 - 'A' // 9455 #0 - 'D' // 9456 #3 - 'aB' // 9457-9458 #1 - 'aF' // 9459-945a #5 - 'A' // 945b #0 - 'F' // 945c #5 - 'B' // 945d #1 - 'A' // 945e #0 - 'F' // 945f #5 - 'A' // 9460 #0 - 'F' // 9461 #5 - 'aA' // 9462-9463 #0 - 'aB' // 9464-9465 #1 - 'D' // 9466 #3 - 'B' // 9467 #1 - 'C' // 9468 #2 - 'B' // 9469 #1 - 'aA' // 946a-946b #0 - 'B' // 946c #1 - 'bC' // 946d-946f #2 - 'bA' // 9470-9472 #0 - 'aB' // 9473-9474 #1 - 'A' // 9475 #0 - 'B' // 9476 #1 - 'A' // 9477 #0 - 'aB' // 9478-9479 #1 - 'D' // 947a #3 - 'B' // 947b #1 - 'cA' // 947c-947f #0 - 'B' // 9480 #1 - 'C' // 9481 #2 - 'B' // 9482 #1 - 'C' // 9483 #2 - 'F' // 9484 #5 - 'A' // 9485 #0 - 'xD' // 9486-949e #3 - 'B' // 949f #1 - 'aD' // 94a0-94a1 #3 - 'B' // 94a2 #1 - '1cD' // 94a3-94c0 #3 - 'B' // 94c1 #1 - 'D' // 94c2 #3 - 'B' // 94c3 #1 - 'wD' // 94c4-94db #3 - 'B' // 94dc #1 - 'xD' // 94dd-94f5 #3 - 'B' // 94f6 #1 - '2aD' // 94f7-952c #3 - 'B' // 952d #1 - 'xD' // 952e-9546 #3 - 'B' // 9547 #1 - '1tD' // 9548-9576 #3 - 'aA' // 9577-9578 #0 - 'F' // 9579 #5 - 'cB' // 957a-957d #1 - 'F' // 957e #5 - 'aA' // 957f-9580 #0 - 'D' // 9581 #3 - 'C' // 9582 #2 - 'A' // 9583 #0 - 'F' // 9584 #5 - 'B' // 9585 #1 - 'C' // 9586 #2 - 'F' // 9587 #5 - 'aA' // 9588-9589 #0 - 'J' // 958a #9 - 'A' // 958b #0 - 'aC' // 958c-958d #2 - 'aA' // 958e-958f #0 - 'B' // 9590 #1 - 'cA' // 9591-9594 #0 - 'D' // 9595 #3 - 'C' // 9596 #2 - 'B' // 9597 #1 - 'A' // 9598 #0 - 'C' // 9599 #2 - 'D' // 959a #3 - 'B' // 959b #1 - 'I' // 959c #8 - 'F' // 959d #5 - 'C' // 959e #2 - 'aA' // 959f-95a0 #0 - 'C' // 95a1 #2 - 'cA' // 95a2-95a5 #0 - 'aC' // 95a6-95a7 #2 - 'aA' // 95a8-95a9 #0 - 'B' // 95aa #1 - 'bA' // 95ab-95ad #0 - 'B' // 95ae #1 - 'D' // 95af #3 - 'B' // 95b0 #1 - 'A' // 95b1 #0 - 'C' // 95b2 #2 - 'D' // 95b3 #3 - 'F' // 95b4 #5 - 'B' // 95b5 #1 - 'A' // 95b6 #0 - 'B' // 95b7 #1 - 'D' // 95b8 #3 - 'A' // 95b9 #0 - 'C' // 95ba #2 - 'cA' // 95bb-95be #0 - 'C' // 95bf #2 - 'B' // 95c0 #1 - 'aD' // 95c1-95c2 #3 - 'A' // 95c3 #0 - 'D' // 95c4 #3 - 'B' // 95c5 #1 - 'C' // 95c6 #2 - 'aA' // 95c7-95c8 #0 - 'C' // 95c9 #2 - 'cA' // 95ca-95cd #0 - 'aD' // 95ce-95cf #3 - 'bC' // 95d0-95d2 #2 - 'cA' // 95d3-95d6 #0 - 'D' // 95d7 #3 - 'aF' // 95d8-95d9 #5 - 'A' // 95da #0 - 'B' // 95db #1 - 'A' // 95dc #0 - 'F' // 95dd #5 - 'A' // 95de #0 - 'C' // 95df #2 - 'bA' // 95e0-95e2 #0 - 'B' // 95e3 #1 - 'C' // 95e4 #2 - 'A' // 95e5 #0 - 'F' // 95e6 #5 - 'D' // 95e7 #3 - 'A' // 95e8 #0 - 'jD' // 95e9-95f3 #3 - 'B' // 95f4 #1 - '1lD' // 95f5-961b #3 - 'aA' // 961c-961d #0 - 'C' // 961e #2 - 'D' // 961f #3 - 'B' // 9620 #1 - 'A' // 9621 #0 - 'C' // 9622 #2 - 'B' // 9623 #1 - 'A' // 9624 #0 - 'aF' // 9625-9626 #5 - 'K' // 9627 #10 - 'A' // 9628 #0 - 'K' // 9629 #10 - 'A' // 962a #0 - 'D' // 962b #3 - 'C' // 962c #2 - 'I' // 962d #8 - 'aA' // 962e-962f #0 - 'B' // 9630 #1 - 'C' // 9631 #2 - 'A' // 9632 #0 - 'C' // 9633 #2 - 'F' // 9634 #5 - 'aD' // 9635-9636 #3 - 'F' // 9637 #5 - 'bC' // 9638-963a #2 - 'A' // 963b #0 - 'aC' // 963c-963d #2 - 'D' // 963e #3 - 'aA' // 963f-9640 #0 - 'C' // 9641 #2 - 'A' // 9642 #0 - 'B' // 9643 #1 - 'A' // 9644 #0 - 'B' // 9645 #1 - 'cD' // 9646-9649 #3 - 'B' // 964a #1 - 'bA' // 964b-964d #0 - 'B' // 964e #1 - 'C' // 964f #2 - 'A' // 9650 #0 - 'B' // 9651 #1 - 'F' // 9652 #5 - 'B' // 9653 #1 - 'A' // 9654 #0 - 'D' // 9655 #3 - 'A' // 9656 #0 - 'F' // 9657 #5 - 'A' // 9658 #0 - 'aD' // 9659-965a #3 - 'dA' // 965b-965f #0 - 'D' // 9660 #3 - 'cA' // 9661-9664 #0 - 'aF' // 9665-9666 #5 - 'aD' // 9667-9668 #3 - 'B' // 9669 #1 - 'A' // 966a #0 - 'B' // 966b #1 - 'A' // 966c #0 - 'B' // 966d #1 - 'F' // 966e #5 - 'B' // 966f #1 - 'A' // 9670 #0 - 'B' // 9671 #1 - 'fA' // 9672-9678 #0 - 'K' // 9679 #10 - 'F' // 967a #5 - 'C' // 967b #2 - 'aA' // 967c-967d #0 - 'C' // 967e #2 - 'F' // 967f #5 - 'B' // 9680 #1 - 'C' // 9681 #2 - 'F' // 9682 #5 - 'C' // 9683 #2 - 'bA' // 9684-9686 #0 - 'B' // 9687 #1 - 'aC' // 9688-9689 #2 - 'aA' // 968a-968b #0 - 'D' // 968c #3 - 'bA' // 968d-968f #0 - 'D' // 9690 #3 - 'A' // 9691 #0 - 'aB' // 9692-9693 #1 - 'aA' // 9694-9695 #0 - 'C' // 9696 #2 - 'aA' // 9697-9698 #0 - 'C' // 9699 #2 - 'F' // 969a #5 - 'aA' // 969b-969c #0 - 'F' // 969d #5 - 'B' // 969e #1 - 'aF' // 969f-96a0 #5 - 'aB' // 96a1-96a2 #1 - 'aA' // 96a3-96a4 #0 - 'C' // 96a5 #2 - 'F' // 96a6 #5 - 'cA' // 96a7-96aa #0 - 'D' // 96ab #3 - 'B' // 96ac #1 - 'D' // 96ad #3 - 'C' // 96ae #2 - 'F' // 96af #5 - 'aA' // 96b0-96b1 #0 - 'F' // 96b2 #5 - 'aA' // 96b3-96b4 #0 - 'D' // 96b5 #3 - 'A' // 96b6 #0 - 'J' // 96b7 #9 - 'aA' // 96b8-96b9 #0 - 'F' // 96ba #5 - 'aA' // 96bb-96bc #0 - 'C' // 96bd #2 - 'D' // 96be #3 - 'B' // 96bf #1 - 'aA' // 96c0-96c1 #0 - 'aB' // 96c2-96c3 #1 - 'cA' // 96c4-96c7 #0 - 'B' // 96c8 #1 - 'A' // 96c9 #0 - 'C' // 96ca #2 - 'cA' // 96cb-96ce #0 - 'aD' // 96cf-96d0 #3 - 'F' // 96d1 #5 - 'C' // 96d2 #2 - 'aB' // 96d3-96d4 #1 - 'aA' // 96d5-96d6 #0 - 'B' // 96d7 #1 - 'C' // 96d8 #2 - 'eA' // 96d9-96de #0 - 'C' // 96df #2 - 'D' // 96e0 #3 - 'B' // 96e1 #1 - 'aA' // 96e2-96e3 #0 - 'D' // 96e4 #3 - 'B' // 96e5 #1 - 'aD' // 96e6-96e7 #3 - 'bA' // 96e8-96ea #0 - 'F' // 96eb #5 - 'bD' // 96ec-96ee #3 - 'aA' // 96ef-96f0 #0 - 'C' // 96f1 #2 - 'A' // 96f2 #0 - 'D' // 96f3 #3 - 'aB' // 96f4-96f5 #1 - 'aA' // 96f6-96f7 #0 - 'B' // 96f8 #1 - 'bA' // 96f9-96fb #0 - 'D' // 96fc #3 - 'B' // 96fd #1 - 'D' // 96fe #3 - 'B' // 96ff #1 - 'A' // 9700 #0 - 'D' // 9701 #3 - 'aC' // 9702-9703 #2 - 'eA' // 9704-9709 #0 - 'F' // 970a #5 - 'B' // 970b #1 - 'K' // 970c #10 - 'bA' // 970d-970f #0 - 'B' // 9710 #1 - 'A' // 9711 #0 - 'B' // 9712 #1 - 'A' // 9713 #0 - 'J' // 9714 #9 - 'D' // 9715 #3 - 'A' // 9716 #0 - 'D' // 9717 #3 - 'B' // 9718 #1 - 'A' // 9719 #0 - 'F' // 971a #5 - 'C' // 971b #2 - 'A' // 971c #0 - 'C' // 971d #2 - 'A' // 971e #0 - 'aB' // 971f-9720 #1 - 'aC' // 9721-9722 #2 - 'A' // 9723 #0 - 'C' // 9724 #2 - 'B' // 9725 #1 - 'I' // 9726 #8 - 'A' // 9727 #0 - 'C' // 9728 #2 - 'B' // 9729 #1 - 'A' // 972a #0 - 'aB' // 972b-972c #1 - 'D' // 972d #3 - 'aB' // 972e-972f #1 - 'A' // 9730 #0 - 'C' // 9731 #2 - 'A' // 9732 #0 - 'F' // 9733 #5 - 'aB' // 9734-9735 #1 - 'C' // 9736 #2 - 'D' // 9737 #3 - 'aA' // 9738-9739 #0 - 'B' // 973a #1 - 'F' // 973b #5 - 'D' // 973c #3 - 'A' // 973d #0 - 'C' // 973e #2 - 'aB' // 973f-9740 #1 - 'C' // 9741 #2 - 'A' // 9742 #0 - 'C' // 9743 #2 - 'A' // 9744 #0 - 'D' // 9745 #3 - 'A' // 9746 #0 - 'C' // 9747 #2 - 'aA' // 9748-9749 #0 - 'C' // 974a #2 - 'B' // 974b #1 - 'K' // 974c #10 - 'bF' // 974d-974f #5 - 'D' // 9750 #3 - 'aA' // 9751-9752 #0 - 'aD' // 9753-9754 #3 - 'aA' // 9755-9756 #0 - 'C' // 9757 #2 - 'A' // 9758 #0 - 'J' // 9759 #9 - 'bA' // 975a-975c #0 - 'I' // 975d #8 - 'A' // 975e #0 - 'B' // 975f #1 - 'bA' // 9760-9762 #0 - 'aF' // 9763-9764 #5 - 'D' // 9765 #3 - 'A' // 9766 #0 - 'F' // 9767 #5 - 'aA' // 9768-9769 #0 - 'C' // 976a #2 - 'F' // 976b #5 - 'B' // 976c #1 - 'A' // 976d #0 - 'C' // 976e #2 - 'D' // 976f #3 - 'B' // 9770 #1 - 'C' // 9771 #2 - 'B' // 9772 #1 - 'aA' // 9773-9774 #0 - 'K' // 9775 #10 - 'C' // 9776 #2 - 'A' // 9777 #0 - 'C' // 9778 #2 - 'F' // 9779 #5 - 'A' // 977a #0 - 'C' // 977b #2 - 'A' // 977c #0 - 'C' // 977d #2 - 'B' // 977e #1 - 'C' // 977f #2 - 'aA' // 9780-9781 #0 - 'aB' // 9782-9783 #1 - 'aA' // 9784-9785 #0 - 'F' // 9786 #5 - 'aB' // 9787-9788 #1 - 'C' // 9789 #2 - 'B' // 978a #1 - 'A' // 978b #0 - 'D' // 978c #3 - 'A' // 978d #0 - 'B' // 978e #1 - 'A' // 978f #0 - 'F' // 9790 #5 - 'bD' // 9791-9793 #3 - 'B' // 9794 #1 - 'aF' // 9795-9796 #5 - 'C' // 9797 #2 - 'A' // 9798 #0 - 'aC' // 9799-979a #2 - 'B' // 979b #1 - 'C' // 979c #2 - 'B' // 979d #1 - 'aC' // 979e-979f #2 - 'A' // 97a0 #0 - 'B' // 97a1 #1 - 'C' // 97a2 #2 - 'A' // 97a3 #0 - 'aB' // 97a4-97a5 #1 - 'A' // 97a6 #0 - 'D' // 97a7 #3 - 'A' // 97a8 #0 - 'D' // 97a9 #3 - 'B' // 97aa #1 - 'bA' // 97ab-97ad #0 - 'C' // 97ae #2 - 'aD' // 97af-97b0 #3 - 'A' // 97b1 #0 - 'aC' // 97b2-97b3 #2 - 'A' // 97b4 #0 - 'F' // 97b5 #5 - 'C' // 97b6 #2 - 'B' // 97b7 #1 - 'aA' // 97b8-97b9 #0 - 'C' // 97ba #2 - 'B' // 97bb #1 - 'F' // 97bc #5 - 'B' // 97bd #1 - 'aC' // 97be-97bf #2 - 'B' // 97c0 #1 - 'A' // 97c1 #0 - 'B' // 97c2 #1 - 'A' // 97c3 #0 - 'aC' // 97c4-97c5 #2 - 'A' // 97c6 #0 - 'bC' // 97c7-97c9 #2 - 'F' // 97ca #5 - 'bA' // 97cb-97cd #0 - 'C' // 97ce #2 - 'B' // 97cf #1 - 'A' // 97d0 #0 - 'F' // 97d1 #5 - 'B' // 97d2 #1 - 'A' // 97d3 #0 - 'C' // 97d4 #2 - 'aB' // 97d5-97d6 #1 - 'aC' // 97d7-97d8 #2 - 'A' // 97d9 #0 - 'D' // 97da #3 - 'F' // 97db #5 - 'bA' // 97dc-97de #0 - 'B' // 97df #1 - 'aA' // 97e0-97e1 #0 - 'D' // 97e2 #3 - 'B' // 97e3 #1 - 'F' // 97e4 #5 - 'B' // 97e5 #1 - 'A' // 97e6 #0 - 'eD' // 97e7-97ec #3 - 'aA' // 97ed-97ee #0 - 'F' // 97ef #5 - 'B' // 97f0 #1 - 'bA' // 97f1-97f3 #0 - 'F' // 97f4 #5 - 'aA' // 97f5-97f6 #0 - 'F' // 97f7 #5 - 'C' // 97f8 #2 - 'B' // 97f9 #1 - 'aA' // 97fa-97fb #0 - 'D' // 97fc #3 - 'B' // 97fd #1 - 'I' // 97fe #8 - 'A' // 97ff #0 - 'I' // 9800 #8 - 'bA' // 9801-9803 #0 - 'C' // 9804 #2 - 'aA' // 9805-9806 #0 - 'C' // 9807 #2 - 'A' // 9808 #0 - 'D' // 9809 #3 - 'A' // 980a #0 - 'D' // 980b #3 - 'gA' // 980c-9813 #0 - 'C' // 9814 #2 - 'B' // 9815 #1 - 'bA' // 9816-9818 #0 - 'aF' // 9819-981a #5 - 'B' // 981b #1 - 'C' // 981c #2 - 'B' // 981d #1 - 'A' // 981e #0 - 'B' // 981f #1 - 'C' // 9820 #2 - 'A' // 9821 #0 - 'D' // 9822 #3 - 'aA' // 9823-9824 #0 - 'F' // 9825 #5 - 'A' // 9826 #0 - 'bB' // 9827-9829 #1 - 'D' // 982a #3 - 'A' // 982b #0 - 'F' // 982c #5 - 'aA' // 982d-982e #0 - 'C' // 982f #2 - 'A' // 9830 #0 - 'D' // 9831 #3 - 'A' // 9832 #0 - 'bC' // 9833-9835 #2 - 'D' // 9836 #3 - 'bA' // 9837-9839 #0 - 'D' // 983a #3 - 'A' // 983b #0 - 'J' // 983c #9 - 'aF' // 983d-983e #5 - 'K' // 983f #10 - 'D' // 9840 #3 - 'B' // 9841 #1 - 'K' // 9842 #10 - 'B' // 9843 #1 - 'C' // 9844 #2 - 'B' // 9845 #1 - 'aA' // 9846-9847 #0 - 'I' // 9848 #8 - 'B' // 9849 #1 - 'C' // 984a #2 - 'cA' // 984b-984e #0 - 'C' // 984f #2 - 'B' // 9850 #1 - 'C' // 9851 #2 - 'aA' // 9852-9853 #0 - 'aJ' // 9854-9855 #9 - 'aC' // 9856-9857 #2 - 'aA' // 9858-9859 #0 - 'J' // 985a #9 - 'C' // 985b #2 - 'I' // 985c #8 - 'B' // 985d #1 - 'A' // 985e #0 - 'aB' // 985f-9860 #1 - 'D' // 9861 #3 - 'aC' // 9862-9863 #2 - 'B' // 9864 #1 - 'bA' // 9865-9867 #0 - 'aB' // 9868-9869 #1 - 'C' // 986a #2 - 'A' // 986b #0 - 'C' // 986c #2 - 'aD' // 986d-986e #3 - 'bA' // 986f-9871 #0 - 'B' // 9872 #1 - 'bA' // 9873-9875 #0 - '1wD' // 9876-98a7 #3 - 'A' // 98a8 #0 - 'B' // 98a9 #1 - 'aF' // 98aa-98ab #5 - 'B' // 98ac #1 - 'A' // 98ad #0 - 'C' // 98ae #2 - 'A' // 98af #0 - 'F' // 98b0 #5 - 'A' // 98b1 #0 - 'I' // 98b2 #8 - 'B' // 98b3 #1 - 'C' // 98b4 #2 - 'D' // 98b5 #3 - 'A' // 98b6 #0 - 'aC' // 98b7-98b8 #2 - 'B' // 98b9 #1 - 'A' // 98ba #0 - 'C' // 98bb #2 - 'A' // 98bc #0 - 'aB' // 98bd-98be #1 - 'A' // 98bf #0 - 'aB' // 98c0-98c1 #1 - 'A' // 98c2 #0 - 'C' // 98c3 #2 - 'A' // 98c4 #0 - 'F' // 98c5 #5 - 'aA' // 98c6-98c7 #0 - 'C' // 98c8 #2 - 'I' // 98c9 #8 - 'B' // 98ca #1 - 'A' // 98cb #0 - 'C' // 98cc #2 - 'D' // 98cd #3 - 'A' // 98ce #0 - 'kD' // 98cf-98da #3 - 'aA' // 98db-98dc #0 - 'D' // 98dd #3 - 'dA' // 98de-98e2 #0 - 'C' // 98e3 #2 - 'D' // 98e4 #3 - 'C' // 98e5 #2 - 'aA' // 98e6-98e7 #0 - 'D' // 98e8 #3 - 'C' // 98e9 #2 - 'aA' // 98ea-98eb #0 - 'B' // 98ec #1 - 'A' // 98ed #0 - 'J' // 98ee #9 - 'A' // 98ef #0 - 'F' // 98f0 #5 - 'A' // 98f1 #0 - 'C' // 98f2 #2 - 'F' // 98f3 #5 - 'A' // 98f4 #0 - 'B' // 98f5 #1 - 'C' // 98f6 #2 - 'aD' // 98f7-98f8 #3 - 'aB' // 98f9-98fa #1 - 'K' // 98fb #10 - 'bA' // 98fc-98fe #0 - 'D' // 98ff #3 - 'B' // 9900 #1 - 'D' // 9901 #3 - 'C' // 9902 #2 - 'A' // 9903 #0 - 'D' // 9904 #3 - 'C' // 9905 #2 - 'D' // 9906 #3 - 'aC' // 9907-9908 #2 - 'aA' // 9909-990a #0 - 'D' // 990b #3 - 'A' // 990c #0 - 'D' // 990d #3 - 'B' // 990e #1 - 'D' // 990f #3 - 'A' // 9910 #0 - 'C' // 9911 #2 - 'cA' // 9912-9915 #0 - 'aC' // 9916-9917 #2 - 'A' // 9918 #0 - 'B' // 9919 #1 - 'A' // 991a #0 - 'aC' // 991b-991c #2 - 'F' // 991d #5 - 'A' // 991e #0 - 'C' // 991f #2 - 'J' // 9920 #9 - 'C' // 9921 #2 - 'F' // 9922 #5 - 'D' // 9923 #3 - 'C' // 9924 #2 - 'B' // 9925 #1 - 'J' // 9926 #9 - 'aA' // 9927-9928 #0 - 'B' // 9929 #1 - 'I' // 992a #8 - 'C' // 992b #2 - 'A' // 992c #0 - 'B' // 992d #1 - 'A' // 992e #0 - 'B' // 992f #1 - 'I' // 9930 #8 - 'A' // 9931 #0 - 'C' // 9932 #2 - 'A' // 9933 #0 - 'F' // 9934 #5 - 'C' // 9935 #2 - 'D' // 9936 #3 - 'aB' // 9937-9938 #1 - 'A' // 9939 #0 - 'aC' // 993a-993b #2 - 'aA' // 993c-993d #0 - 'C' // 993e #2 - 'B' // 993f #1 - 'aC' // 9940-9941 #2 - 'A' // 9942 #0 - 'B' // 9943 #1 - 'D' // 9944 #3 - 'A' // 9945 #0 - 'F' // 9946 #5 - 'C' // 9947 #2 - 'aA' // 9948-9949 #0 - 'B' // 994a #1 - 'bA' // 994b-994d #0 - 'C' // 994e #2 - 'D' // 994f #3 - 'bA' // 9950-9952 #0 - 'B' // 9953 #1 - 'aA' // 9954-9955 #0 - 'B' // 9956 #1 - 'A' // 9957 #0 - 'aC' // 9958-9959 #2 - 'D' // 995a #3 - 'C' // 995b #2 - 'A' // 995c #0 - 'B' // 995d #1 - 'A' // 995e #0 - 'C' // 995f #2 - 'F' // 9960 #5 - 'aB' // 9961-9962 #1 - 'A' // 9963 #0 - '1wD' // 9964-9995 #3 - 'cA' // 9996-9999 #0 - 'D' // 999a #3 - 'C' // 999b #2 - 'I' // 999c #8 - 'A' // 999d #0 - 'C' // 999e #2 - 'J' // 999f #9 - 'D' // 99a0 #3 - 'I' // 99a1 #8 - 'D' // 99a2 #3 - 'A' // 99a3 #0 - 'B' // 99a4 #1 - 'A' // 99a5 #0 - 'C' // 99a6 #2 - 'I' // 99a7 #8 - 'A' // 99a8 #0 - 'D' // 99a9 #3 - 'I' // 99aa #8 - 'B' // 99ab #1 - 'bA' // 99ac-99ae #0 - 'B' // 99af #1 - 'aA' // 99b0-99b1 #0 - 'C' // 99b2 #2 - 'aA' // 99b3-99b4 #0 - 'C' // 99b5 #2 - 'K' // 99b6 #10 - 'D' // 99b7 #3 - 'B' // 99b8 #1 - 'A' // 99b9 #0 - 'C' // 99ba #2 - 'B' // 99bb #1 - 'aC' // 99bc-99bd #2 - 'D' // 99be #3 - 'F' // 99bf #5 - 'D' // 99c0 #3 - 'A' // 99c1 #0 - 'B' // 99c2 #1 - 'C' // 99c3 #2 - 'aA' // 99c4-99c5 #0 - 'F' // 99c6 #5 - 'B' // 99c7 #1 - 'J' // 99c8 #9 - 'A' // 99c9 #0 - 'D' // 99ca #3 - 'cB' // 99cb-99ce #1 - 'I' // 99cf #8 - 'bA' // 99d0-99d2 #0 - 'aC' // 99d3-99d4 #2 - 'A' // 99d5 #0 - 'aB' // 99d6-99d7 #1 - 'aA' // 99d8-99d9 #0 - 'C' // 99da #2 - 'bA' // 99db-99dd #0 - 'J' // 99de #9 - 'A' // 99df #0 - 'B' // 99e0 #1 - 'C' // 99e1 #2 - 'A' // 99e2 #0 - 'cB' // 99e3-99e6 #1 - 'C' // 99e7 #2 - 'K' // 99e8 #10 - 'B' // 99e9 #1 - 'A' // 99ea #0 - 'F' // 99eb #5 - 'C' // 99ec #2 - 'aA' // 99ed-99ee #0 - 'D' // 99ef #3 - 'C' // 99f0 #2 - 'A' // 99f1 #0 - 'F' // 99f2 #5 - 'D' // 99f3 #3 - 'aC' // 99f4-99f5 #2 - 'aB' // 99f6-99f7 #1 - 'A' // 99f8 #0 - 'C' // 99f9 #2 - 'I' // 99fa #8 - 'A' // 99fb #0 - 'C' // 99fc #2 - 'A' // 99fd #0 - 'C' // 99fe #2 - 'A' // 99ff #0 - 'D' // 9a00 #3 - 'dA' // 9a01-9a05 #0 - 'aB' // 9a06-9a07 #1 - 'J' // 9a08 #9 - 'B' // 9a09 #1 - 'C' // 9a0a #2 - 'A' // 9a0b #0 - 'C' // 9a0c #2 - 'I' // 9a0d #8 - 'aA' // 9a0e-9a0f #0 - 'C' // 9a10 #2 - 'A' // 9a11 #0 - 'aF' // 9a12-9a13 #5 - 'aB' // 9a14-9a15 #1 - 'A' // 9a16 #0 - 'D' // 9a17 #3 - 'K' // 9a18 #10 - 'A' // 9a19 #0 - 'C' // 9a1a #2 - 'I' // 9a1b #8 - 'aB' // 9a1c-9a1d #1 - 'C' // 9a1e #2 - 'B' // 9a1f #1 - 'C' // 9a20 #2 - 'B' // 9a21 #1 - 'bC' // 9a22-9a24 #2 - 'aB' // 9a25-9a26 #1 - 'C' // 9a27 #2 - 'F' // 9a28 #5 - 'aB' // 9a29-9a2a #1 - 'A' // 9a2b #0 - 'B' // 9a2c #1 - 'A' // 9a2d #0 - 'C' // 9a2e #2 - 'B' // 9a2f #1 - 'A' // 9a30 #0 - 'C' // 9a31 #2 - 'B' // 9a32 #1 - 'F' // 9a33 #5 - 'B' // 9a34 #1 - 'cA' // 9a35-9a38 #0 - 'dB' // 9a39-9a3d #1 - 'A' // 9a3e #0 - 'B' // 9a3f #1 - 'eA' // 9a40-9a45 #0 - 'B' // 9a46 #1 - 'F' // 9a47 #5 - 'aB' // 9a48-9a49 #1 - 'A' // 9a4a #0 - 'F' // 9a4b #5 - 'bA' // 9a4c-9a4e #0 - 'I' // 9a4f #8 - 'B' // 9a50 #1 - 'F' // 9a51 #5 - 'A' // 9a52 #0 - 'B' // 9a53 #1 - 'C' // 9a54 #2 - 'A' // 9a55 #0 - 'C' // 9a56 #2 - 'aA' // 9a57-9a58 #0 - 'B' // 9a59 #1 - 'aA' // 9a5a-9a5b #0 - 'B' // 9a5c #1 - 'F' // 9a5d #5 - 'B' // 9a5e #1 - 'A' // 9a5f #0 - 'B' // 9a60 #1 - 'D' // 9a61 #3 - 'A' // 9a62 #0 - 'B' // 9a63 #1 - 'aA' // 9a64-9a65 #0 - 'bB' // 9a66-9a68 #1 - 'aA' // 9a69-9a6a #0 - 'C' // 9a6b #2 - 'A' // 9a6c #0 - '1gD' // 9a6d-9a8e #3 - 'B' // 9a8f #1 - 'wD' // 9a90-9aa7 #3 - 'A' // 9aa8 #0 - 'D' // 9aa9 #3 - 'J' // 9aaa #9 - 'B' // 9aab #1 - 'F' // 9aac #5 - 'C' // 9aad #2 - 'F' // 9aae #5 - 'C' // 9aaf #2 - 'A' // 9ab0 #0 - 'B' // 9ab1 #1 - 'C' // 9ab2 #2 - 'B' // 9ab3 #1 - 'C' // 9ab4 #2 - 'F' // 9ab5 #5 - 'aC' // 9ab6-9ab7 #2 - 'aA' // 9ab8-9ab9 #0 - 'B' // 9aba #1 - 'C' // 9abb #2 - 'A' // 9abc #0 - 'aC' // 9abd-9abe #2 - 'aA' // 9abf-9ac0 #0 - 'C' // 9ac1 #2 - 'B' // 9ac2 #1 - 'aF' // 9ac3-9ac4 #5 - 'D' // 9ac5 #3 - 'A' // 9ac6 #0 - 'B' // 9ac7 #1 - 'F' // 9ac8 #5 - 'D' // 9ac9 #3 - 'B' // 9aca #1 - 'aD' // 9acb-9acc #3 - 'B' // 9acd #1 - 'F' // 9ace #5 - 'A' // 9acf #0 - 'C' // 9ad0 #2 - 'A' // 9ad1 #0 - 'C' // 9ad2 #2 - 'aA' // 9ad3-9ad4 #0 - 'C' // 9ad5 #2 - 'bA' // 9ad6-9ad8 #0 - 'F' // 9ad9 #5 - 'D' // 9ada #3 - 'F' // 9adb #5 - 'C' // 9adc #2 - 'D' // 9add #3 - 'F' // 9ade #5 - 'A' // 9adf #0 - 'C' // 9ae0 #2 - 'I' // 9ae1 #8 - 'C' // 9ae2 #2 - 'A' // 9ae3 #0 - 'F' // 9ae4 #5 - 'J' // 9ae5 #9 - 'A' // 9ae6 #0 - 'C' // 9ae7 #2 - 'D' // 9ae8 #3 - 'aF' // 9ae9-9aea #5 - 'A' // 9aeb #0 - 'C' // 9aec #2 - 'aA' // 9aed-9aee #0 - 'C' // 9aef #2 - 'K' // 9af0 #10 - 'C' // 9af1 #2 - 'A' // 9af2 #0 - 'C' // 9af3 #2 - 'A' // 9af4 #0 - 'F' // 9af5 #5 - 'B' // 9af6 #1 - 'C' // 9af7 #2 - 'D' // 9af8 #3 - 'bA' // 9af9-9afb #0 - 'B' // 9afc #1 - 'A' // 9afd #0 - 'B' // 9afe #1 - 'C' // 9aff #2 - 'F' // 9b00 #5 - 'C' // 9b01 #2 - 'A' // 9b02 #0 - 'aC' // 9b03-9b04 #2 - 'aA' // 9b05-9b06 #0 - 'D' // 9b07 #3 - 'aC' // 9b08-9b09 #2 - 'I' // 9b0a #8 - 'A' // 9b0b #0 - 'C' // 9b0c #2 - 'A' // 9b0d #0 - 'C' // 9b0e #2 - 'B' // 9b0f #1 - 'A' // 9b10 #0 - 'B' // 9b11 #1 - 'A' // 9b12 #0 - 'D' // 9b13 #3 - 'aB' // 9b14-9b15 #1 - 'A' // 9b16 #0 - 'B' // 9b17 #1 - 'bA' // 9b18-9b1a #0 - 'bF' // 9b1b-9b1d #5 - 'B' // 9b1e #1 - 'A' // 9b1f #0 - 'C' // 9b20 #2 - 'D' // 9b21 #3 - 'aA' // 9b22-9b23 #0 - 'B' // 9b24 #1 - 'A' // 9b25 #0 - 'F' // 9b26 #5 - 'cA' // 9b27-9b2a #0 - 'C' // 9b2b #2 - 'F' // 9b2c #5 - 'C' // 9b2d #2 - 'aA' // 9b2e-9b2f #0 - 'D' // 9b30 #3 - 'aA' // 9b31-9b32 #0 - 'bC' // 9b33-9b35 #2 - 'D' // 9b36 #3 - 'C' // 9b37 #2 - 'D' // 9b38 #3 - 'C' // 9b39 #2 - 'A' // 9b3a #0 - 'C' // 9b3b #2 - 'A' // 9b3c #0 - 'F' // 9b3d #5 - 'bB' // 9b3e-9b40 #1 - 'dA' // 9b41-9b45 #0 - 'B' // 9b46 #1 - 'D' // 9b47 #3 - 'A' // 9b48 #0 - 'D' // 9b49 #3 - 'B' // 9b4a #1 - 'A' // 9b4b #0 - 'C' // 9b4c #2 - 'bA' // 9b4d-9b4f #0 - 'B' // 9b50 #1 - 'A' // 9b51 #0 - 'B' // 9b52 #1 - 'D' // 9b53 #3 - 'A' // 9b54 #0 - 'aC' // 9b55-9b56 #2 - 'F' // 9b57 #5 - 'A' // 9b58 #0 - 'B' // 9b59 #1 - 'A' // 9b5a #0 - 'C' // 9b5b #2 - 'aD' // 9b5c-9b5d #3 - 'F' // 9b5e #5 - 'aB' // 9b5f-9b60 #1 - 'C' // 9b61 #2 - 'D' // 9b62 #3 - 'F' // 9b63 #5 - 'B' // 9b64 #1 - 'F' // 9b65 #5 - 'A' // 9b66 #0 - 'B' // 9b67 #1 - 'C' // 9b68 #2 - 'B' // 9b69 #1 - 'aF' // 9b6a-9b6b #5 - 'C' // 9b6c #2 - 'aF' // 9b6d-9b6e #5 - 'A' // 9b6f #0 - 'aB' // 9b70-9b71 #1 - 'aF' // 9b72-9b73 #5 - 'A' // 9b74 #0 - 'bC' // 9b75-9b77 #2 - 'aF' // 9b78-9b79 #5 - 'dB' // 9b7a-9b7e #1 - 'C' // 9b7f #2 - 'A' // 9b80 #0 - 'aB' // 9b81-9b82 #1 - 'A' // 9b83 #0 - 'F' // 9b84 #5 - 'bC' // 9b85-9b87 #2 - 'B' // 9b88 #1 - 'aF' // 9b89-9b8a #5 - 'C' // 9b8b #2 - 'D' // 9b8c #3 - 'C' // 9b8d #2 - 'A' // 9b8e #0 - 'C' // 9b8f #2 - 'cA' // 9b90-9b93 #0 - 'F' // 9b94 #5 - 'B' // 9b95 #1 - 'F' // 9b96 #5 - 'A' // 9b97 #0 - 'aD' // 9b98-9b99 #3 - 'C' // 9b9a #2 - 'B' // 9b9b #1 - 'D' // 9b9c #3 - 'aC' // 9b9d-9b9e #2 - 'A' // 9b9f #0 - 'C' // 9ba0 #2 - 'aB' // 9ba1-9ba2 #1 - 'D' // 9ba3 #3 - 'aB' // 9ba4-9ba5 #1 - 'C' // 9ba6 #2 - 'J' // 9ba7 #9 - 'A' // 9ba8 #0 - 'F' // 9ba9 #5 - 'aA' // 9baa-9bab #0 - 'F' // 9bac #5 - 'aA' // 9bad-9bae #0 - 'B' // 9baf #1 - 'C' // 9bb0 #2 - 'aF' // 9bb1-9bb2 #5 - 'D' // 9bb3 #3 - 'F' // 9bb4 #5 - 'aB' // 9bb5-9bb6 #1 - 'F' // 9bb7 #5 - 'C' // 9bb8 #2 - 'A' // 9bb9 #0 - 'D' // 9bba #3 - 'aF' // 9bbb-9bbc #5 - 'B' // 9bbd #1 - 'F' // 9bbe #5 - 'aC' // 9bbf-9bc0 #2 - 'A' // 9bc1 #0 - 'D' // 9bc2 #3 - 'aB' // 9bc3-9bc4 #1 - 'D' // 9bc5 #3 - 'A' // 9bc6 #0 - 'aC' // 9bc7-9bc8 #2 - 'aA' // 9bc9-9bca #0 - 'bD' // 9bcb-9bcd #3 - 'F' // 9bce #5 - 'C' // 9bcf #2 - 'bF' // 9bd0-9bd2 #5 - 'B' // 9bd3 #1 - 'A' // 9bd4 #0 - 'B' // 9bd5 #1 - 'A' // 9bd6 #0 - 'C' // 9bd7 #2 - 'F' // 9bd8 #5 - 'aB' // 9bd9-9bda #1 - 'A' // 9bdb #0 - 'B' // 9bdc #1 - 'C' // 9bdd #2 - 'B' // 9bde #1 - 'F' // 9bdf #5 - 'B' // 9be0 #1 - 'C' // 9be1 #2 - 'A' // 9be2 #0 - 'F' // 9be3 #5 - 'A' // 9be4 #0 - 'C' // 9be5 #2 - 'B' // 9be6 #1 - 'C' // 9be7 #2 - 'A' // 9be8 #0 - 'B' // 9be9 #1 - 'aC' // 9bea-9beb #2 - 'aB' // 9bec-9bed #1 - 'aF' // 9bee-9bef #5 - 'aC' // 9bf0-9bf1 #2 - 'aF' // 9bf2-9bf3 #5 - 'B' // 9bf4 #1 - 'F' // 9bf5 #5 - 'D' // 9bf6 #3 - 'A' // 9bf7 #0 - 'C' // 9bf8 #2 - 'aF' // 9bf9-9bfa #5 - 'aD' // 9bfb-9bfc #3 - 'C' // 9bfd #2 - 'D' // 9bfe #3 - 'C' // 9bff #2 - 'F' // 9c00 #5 - 'D' // 9c01 #3 - 'C' // 9c02 #2 - 'D' // 9c03 #3 - 'F' // 9c04 #5 - 'B' // 9c05 #1 - 'C' // 9c06 #2 - 'B' // 9c07 #1 - 'A' // 9c08 #0 - 'C' // 9c09 #2 - 'A' // 9c0a #0 - 'C' // 9c0b #2 - 'aA' // 9c0c-9c0d #0 - 'B' // 9c0e #1 - 'F' // 9c0f #5 - 'A' // 9c10 #0 - 'F' // 9c11 #5 - 'aA' // 9c12-9c13 #0 - 'C' // 9c14 #2 - 'A' // 9c15 #0 - 'F' // 9c16 #5 - 'B' // 9c17 #1 - 'bF' // 9c18-9c1a #5 - 'bC' // 9c1b-9c1d #2 - 'F' // 9c1e #5 - 'aB' // 9c1f-9c20 #1 - 'C' // 9c21 #2 - 'F' // 9c22 #5 - 'C' // 9c23 #2 - 'aA' // 9c24-9c25 #0 - 'C' // 9c26 #2 - 'F' // 9c27 #5 - 'aC' // 9c28-9c29 #2 - 'F' // 9c2a #5 - 'aB' // 9c2b-9c2c #1 - 'A' // 9c2d #0 - 'J' // 9c2e #9 - 'A' // 9c2f #0 - 'F' // 9c30 #5 - 'aA' // 9c31-9c32 #0 - 'aB' // 9c33-9c34 #1 - 'A' // 9c35 #0 - 'aC' // 9c36-9c37 #2 - 'D' // 9c38 #3 - 'bA' // 9c39-9c3b #0 - 'B' // 9c3c #1 - 'C' // 9c3d #2 - 'A' // 9c3e #0 - 'aB' // 9c3f-9c40 #1 - 'C' // 9c41 #2 - 'D' // 9c42 #3 - 'F' // 9c43 #5 - 'bC' // 9c44-9c46 #2 - 'A' // 9c47 #0 - 'C' // 9c48 #2 - 'A' // 9c49 #0 - 'C' // 9c4a #2 - 'bB' // 9c4b-9c4d #1 - 'C' // 9c4e #2 - 'A' // 9c4f #0 - 'C' // 9c50 #2 - 'D' // 9c51 #3 - 'aA' // 9c52-9c53 #0 - 'C' // 9c54 #2 - 'B' // 9c55 #1 - 'C' // 9c56 #2 - 'A' // 9c57 #0 - 'C' // 9c58 #2 - 'B' // 9c59 #1 - 'bF' // 9c5a-9c5c #5 - 'bC' // 9c5d-9c5f #2 - 'A' // 9c60 #0 - 'F' // 9c61 #5 - 'B' // 9c62 #1 - 'A' // 9c63 #0 - 'D' // 9c64 #3 - 'F' // 9c65 #5 - 'B' // 9c66 #1 - 'A' // 9c67 #0 - 'C' // 9c68 #2 - 'bF' // 9c69-9c6b #5 - 'D' // 9c6c #3 - 'aC' // 9c6d-9c6e #2 - 'D' // 9c6f #3 - 'F' // 9c70 #5 - 'B' // 9c71 #1 - 'C' // 9c72 #2 - 'aB' // 9c73-9c74 #1 - 'C' // 9c75 #2 - 'F' // 9c76 #5 - 'C' // 9c77 #2 - 'A' // 9c78 #0 - 'B' // 9c79 #1 - 'C' // 9c7a #2 - 'aA' // 9c7b-9c7c #0 - '3yD' // 9c7d-9ce4 #3 - 'bA' // 9ce5-9ce7 #0 - 'D' // 9ce8 #3 - 'A' // 9ce9 #0 - 'B' // 9cea #1 - 'aF' // 9ceb-9cec #5 - 'B' // 9ced #1 - 'aD' // 9cee-9cef #3 - 'F' // 9cf0 #5 - 'B' // 9cf1 #1 - 'C' // 9cf2 #2 - 'aA' // 9cf3-9cf4 #0 - 'B' // 9cf5 #1 - 'A' // 9cf6 #0 - 'C' // 9cf7 #2 - 'D' // 9cf8 #3 - 'C' // 9cf9 #2 - 'cB' // 9cfa-9cfd #1 - 'D' // 9cfe #3 - 'aB' // 9cff-9d00 #1 - 'D' // 9d01 #3 - 'C' // 9d02 #2 - 'A' // 9d03 #0 - 'aB' // 9d04-9d05 #1 - 'cA' // 9d06-9d09 #0 - 'D' // 9d0a #3 - 'F' // 9d0b #5 - 'I' // 9d0c #8 - 'D' // 9d0d #3 - 'F' // 9d0e #5 - 'D' // 9d0f #3 - 'B' // 9d10 #1 - 'F' // 9d11 #5 - 'A' // 9d12 #0 - 'D' // 9d13 #3 - 'B' // 9d14 #1 - 'A' // 9d15 #0 - 'B' // 9d16 #1 - 'C' // 9d17 #2 - 'A' // 9d18 #0 - 'I' // 9d19 #8 - 'D' // 9d1a #3 - 'A' // 9d1b #0 - 'F' // 9d1c #5 - 'C' // 9d1d #2 - 'aA' // 9d1e-9d1f #0 - 'bB' // 9d20-9d22 #1 - 'A' // 9d23 #0 - 'D' // 9d24 #3 - 'I' // 9d25 #8 - 'A' // 9d26 #0 - 'D' // 9d27 #3 - 'A' // 9d28 #0 - 'B' // 9d29 #1 - 'bF' // 9d2a-9d2c #5 - 'aB' // 9d2d-9d2e #1 - 'aA' // 9d2f-9d30 #0 - 'B' // 9d31 #1 - 'F' // 9d32 #5 - 'aC' // 9d33-9d34 #2 - 'D' // 9d35 #3 - 'I' // 9d36 #8 - 'bB' // 9d37-9d39 #1 - 'F' // 9d3a #5 - 'A' // 9d3b #0 - 'F' // 9d3c #5 - 'bC' // 9d3d-9d3f #2 - 'B' // 9d40 #1 - 'aA' // 9d41-9d42 #0 - 'C' // 9d43 #2 - 'A' // 9d44 #0 - 'C' // 9d45 #2 - 'bF' // 9d46-9d48 #5 - 'B' // 9d49 #1 - 'C' // 9d4a #2 - 'aB' // 9d4b-9d4c #1 - 'D' // 9d4d #3 - 'aB' // 9d4e-9d4f #1 - 'C' // 9d50 #2 - 'A' // 9d51 #0 - 'C' // 9d52 #2 - 'aA' // 9d53-9d54 #0 - 'D' // 9d55 #3 - 'bB' // 9d56-9d58 #1 - 'C' // 9d59 #2 - 'aB' // 9d5a-9d5b #1 - 'C' // 9d5c #2 - 'aA' // 9d5d-9d5e #0 - 'C' // 9d5f #2 - 'aA' // 9d60-9d61 #0 - 'cF' // 9d62-9d65 #5 - 'K' // 9d66 #10 - 'aB' // 9d67-9d68 #1 - 'A' // 9d69 #0 - 'aC' // 9d6a-9d6b #2 - 'A' // 9d6c #0 - 'aB' // 9d6d-9d6e #1 - 'aA' // 9d6f-9d70 #0 - 'B' // 9d71 #1 - 'A' // 9d72 #0 - 'C' // 9d73 #2 - 'aB' // 9d74-9d75 #1 - 'F' // 9d76 #5 - 'A' // 9d77 #0 - 'aB' // 9d78-9d79 #1 - 'F' // 9d7a #5 - 'A' // 9d7b #0 - 'C' // 9d7c #2 - 'B' // 9d7d #1 - 'A' // 9d7e #0 - 'cB' // 9d7f-9d82 #1 - 'C' // 9d83 #2 - 'A' // 9d84 #0 - 'B' // 9d85 #1 - 'aC' // 9d86-9d87 #2 - 'B' // 9d88 #1 - 'aA' // 9d89-9d8a #0 - 'aB' // 9d8b-9d8c #1 - 'bF' // 9d8d-9d8f #5 - 'B' // 9d90 #1 - 'D' // 9d91 #3 - 'aC' // 9d92-9d93 #2 - 'B' // 9d94 #1 - 'F' // 9d95 #5 - 'A' // 9d96 #0 - 'bC' // 9d97-9d99 #2 - 'A' // 9d9a #0 - 'eB' // 9d9b-9da0 #1 - 'A' // 9da1 #0 - 'aB' // 9da2-9da3 #1 - 'A' // 9da4 #0 - 'cB' // 9da5-9da8 #1 - 'A' // 9da9 #0 - 'aC' // 9daa-9dab #2 - 'A' // 9dac #0 - 'B' // 9dad #1 - 'F' // 9dae #5 - 'A' // 9daf #0 - 'D' // 9db0 #3 - 'aC' // 9db1-9db2 #2 - 'B' // 9db3 #1 - 'aA' // 9db4-9db5 #0 - 'aB' // 9db6-9db7 #1 - 'aA' // 9db8-9db9 #0 - 'C' // 9dba #2 - 'A' // 9dbb #0 - 'aC' // 9dbc-9dbd #2 - 'B' // 9dbe #1 - 'A' // 9dbf #0 - 'C' // 9dc0 #2 - 'aA' // 9dc1-9dc2 #0 - 'C' // 9dc3 #2 - 'A' // 9dc4 #0 - 'B' // 9dc5 #1 - 'F' // 9dc6 #5 - 'A' // 9dc7 #0 - 'B' // 9dc8 #1 - 'aC' // 9dc9-9dca #2 - 'cB' // 9dcb-9dce #1 - 'C' // 9dcf #2 - 'bB' // 9dd0-9dd2 #1 - 'A' // 9dd3 #0 - 'aC' // 9dd4-9dd5 #2 - 'aA' // 9dd6-9dd7 #0 - 'B' // 9dd8 #1 - 'A' // 9dd9 #0 - 'C' // 9dda #2 - 'bB' // 9ddb-9ddd #1 - 'aC' // 9dde-9ddf #2 - 'F' // 9de0 #5 - 'aB' // 9de1-9de2 #1 - 'C' // 9de3 #2 - 'B' // 9de4 #1 - 'C' // 9de5 #2 - 'A' // 9de6 #0 - 'F' // 9de7 #5 - 'B' // 9de8 #1 - 'A' // 9de9 #0 - 'K' // 9dea #10 - 'A' // 9deb #0 - 'B' // 9dec #1 - 'bC' // 9ded-9def #2 - 'A' // 9df0 #0 - 'K' // 9df1 #10 - 'aA' // 9df2-9df3 #0 - 'C' // 9df4 #2 - 'bB' // 9df5-9df7 #1 - 'bA' // 9df8-9dfa #0 - 'aB' // 9dfb-9dfc #1 - 'A' // 9dfd #0 - 'C' // 9dfe #2 - 'I' // 9dff #8 - 'aB' // 9e00-9e01 #1 - 'C' // 9e02 #2 - 'cB' // 9e03-9e06 #1 - 'A' // 9e07 #0 - 'D' // 9e08 #3 - 'B' // 9e09 #1 - 'C' // 9e0a #2 - 'aB' // 9e0b-9e0c #1 - 'aC' // 9e0d-9e0e #2 - 'I' // 9e0f #8 - 'bC' // 9e10-9e12 #2 - 'aB' // 9e13-9e14 #1 - 'A' // 9e15 #0 - 'F' // 9e16 #5 - 'aB' // 9e17-9e18 #1 - 'C' // 9e19 #2 - 'eA' // 9e1a-9e1f #0 - '3fD' // 9e20-9e74 #3 - 'A' // 9e75 #0 - 'D' // 9e76 #3 - 'K' // 9e77 #10 - 'F' // 9e78 #5 - 'bA' // 9e79-9e7b #0 - 'C' // 9e7c #2 - 'A' // 9e7d #0 - 'D' // 9e7e #3 - 'aA' // 9e7f-9e80 #0 - 'C' // 9e81 #2 - 'A' // 9e82 #0 - 'C' // 9e83 #2 - 'A' // 9e84 #0 - 'C' // 9e85 #2 - 'B' // 9e86 #1 - 'aC' // 9e87-9e88 #2 - 'aB' // 9e89-9e8a #1 - 'aA' // 9e8b-9e8c #0 - 'B' // 9e8d #1 - 'C' // 9e8e #2 - 'J' // 9e8f #9 - 'B' // 9e90 #1 - 'bA' // 9e91-9e93 #0 - 'B' // 9e94 #1 - 'aC' // 9e95-9e96 #2 - 'aA' // 9e97-9e98 #0 - 'aB' // 9e99-9e9a #1 - 'C' // 9e9b #2 - 'B' // 9e9c #1 - 'bA' // 9e9d-9e9f #0 - 'bB' // 9ea0-9ea2 #1 - 'D' // 9ea3 #3 - 'bA' // 9ea4-9ea6 #0 - 'B' // 9ea7 #1 - 'C' // 9ea8 #2 - 'aA' // 9ea9-9eaa #0 - 'B' // 9eab #1 - 'bC' // 9eac-9eae #2 - 'A' // 9eaf #0 - 'C' // 9eb0 #2 - 'B' // 9eb1 #1 - 'D' // 9eb2 #3 - 'F' // 9eb3 #5 - 'aA' // 9eb4-9eb5 #0 - 'aB' // 9eb6-9eb7 #1 - 'bF' // 9eb8-9eba #5 - 'A' // 9ebb #0 - 'C' // 9ebc #2 - 'bA' // 9ebd-9ebf #0 - 'bB' // 9ec0-9ec2 #1 - 'aA' // 9ec3-9ec4 #0 - 'K' // 9ec5 #10 - 'C' // 9ec6 #2 - 'B' // 9ec7 #1 - 'C' // 9ec8 #2 - 'aD' // 9ec9-9eca #3 - 'F' // 9ecb #5 - 'eA' // 9ecc-9ed1 #0 - 'F' // 9ed2 #5 - 'B' // 9ed3 #1 - 'A' // 9ed4 #0 - 'C' // 9ed5 #2 - 'I' // 9ed6 #8 - 'D' // 9ed7 #3 - 'A' // 9ed8 #0 - 'F' // 9ed9 #5 - 'I' // 9eda #8 - 'cA' // 9edb-9ede #0 - 'C' // 9edf #2 - 'A' // 9ee0 #0 - 'D' // 9ee1 #3 - 'B' // 9ee2 #1 - 'D' // 9ee3 #3 - 'C' // 9ee4 #2 - 'A' // 9ee5 #0 - 'B' // 9ee6 #1 - 'C' // 9ee7 #2 - 'A' // 9ee8 #0 - 'aD' // 9ee9-9eea #3 - 'B' // 9eeb #1 - 'F' // 9eec #5 - 'C' // 9eed #2 - 'aA' // 9eee-9eef #0 - 'aC' // 9ef0-9ef1 #2 - 'A' // 9ef2 #0 - 'B' // 9ef3 #1 - 'cA' // 9ef4-9ef7 #0 - 'C' // 9ef8 #2 - 'A' // 9ef9 #0 - 'I' // 9efa #8 - 'dA' // 9efb-9eff #0 - 'I' // 9f00 #8 - 'B' // 9f01 #1 - 'A' // 9f02 #0 - 'F' // 9f03 #5 - 'K' // 9f04 #10 - 'D' // 9f05 #3 - 'B' // 9f06 #1 - 'bA' // 9f07-9f09 #0 - 'I' // 9f0a #8 - 'bD' // 9f0b-9f0d #3 - 'A' // 9f0e #0 - 'C' // 9f0f #2 - 'A' // 9f10 #0 - 'F' // 9f11 #5 - 'C' // 9f12 #2 - 'A' // 9f13 #0 - 'J' // 9f14 #9 - 'aC' // 9f15-9f16 #2 - 'A' // 9f17 #0 - 'B' // 9f18 #1 - 'A' // 9f19 #0 - 'aC' // 9f1a-9f1b #2 - 'B' // 9f1c #1 - 'D' // 9f1d #3 - 'B' // 9f1e #1 - 'F' // 9f1f #5 - 'A' // 9f20 #0 - 'F' // 9f21 #5 - 'A' // 9f22 #0 - 'bB' // 9f23-9f25 #1 - 'C' // 9f26 #2 - 'bB' // 9f27-9f29 #1 - 'C' // 9f2a #2 - 'aA' // 9f2b-9f2c #0 - 'aB' // 9f2d-9f2e #1 - 'A' // 9f2f #0 - 'B' // 9f30 #1 - 'aC' // 9f31-9f32 #2 - 'B' // 9f33 #1 - 'A' // 9f34 #0 - 'aB' // 9f35-9f36 #1 - 'C' // 9f37 #2 - 'I' // 9f38 #8 - 'A' // 9f39 #0 - 'F' // 9f3a #5 - 'A' // 9f3b #0 - 'F' // 9f3c #5 - 'C' // 9f3d #2 - 'A' // 9f3e #0 - 'F' // 9f3f #5 - 'B' // 9f40 #1 - 'C' // 9f41 #2 - 'B' // 9f42 #1 - 'dC' // 9f43-9f47 #2 - 'aB' // 9f48-9f49 #1 - 'aA' // 9f4a-9f4b #0 - 'aB' // 9f4c-9f4d #1 - 'A' // 9f4e #0 - 'C' // 9f4f #2 - 'A' // 9f50 #0 - 'D' // 9f51 #3 - 'A' // 9f52 #0 - 'C' // 9f53 #2 - 'aA' // 9f54-9f55 #0 - 'C' // 9f56 #2 - 'A' // 9f57 #0 - 'C' // 9f58 #2 - 'B' // 9f59 #1 - 'C' // 9f5a #2 - 'aB' // 9f5b-9f5c #1 - 'aC' // 9f5d-9f5e #2 - 'bA' // 9f5f-9f61 #0 - 'aC' // 9f62-9f63 #2 - 'aB' // 9f64-9f65 #1 - 'aA' // 9f66-9f67 #0 - 'F' // 9f68 #5 - 'aA' // 9f69-9f6a #0 - 'I' // 9f6b #8 - 'A' // 9f6c #0 - 'F' // 9f6d #5 - 'cC' // 9f6e-9f71 #2 - 'A' // 9f72 #0 - 'F' // 9f73 #5 - 'B' // 9f74 #1 - 'C' // 9f75 #2 - 'aA' // 9f76-9f77 #0 - 'aB' // 9f78-9f79 #1 - 'C' // 9f7a #2 - 'B' // 9f7b #1 - 'D' // 9f7c #3 - 'F' // 9f7d #5 - 'B' // 9f7e #1 - 'A' // 9f7f #0 - 'lD' // 9f80-9f8c #3 - 'A' // 9f8d #0 - 'I' // 9f8e #8 - 'F' // 9f8f #5 - 'bA' // 9f90-9f92 #0 - 'D' // 9f93 #3 - 'aA' // 9f94-9f95 #0 - 'aC' // 9f96-9f97 #2 - 'B' // 9f98 #1 - 'A' // 9f99 #0 - 'aD' // 9f9a-9f9b #3 - 'A' // 9f9c #0 - 'J' // 9f9d #9 - 'F' // 9f9e #5 - 'aA' // 9f9f-9fa0 #0 - 'F' // 9fa1 #5 - 'A' // 9fa2 #0 - 'F' // 9fa3 #5 - 'B' // 9fa4 #1 - 'A' // 9fa5 #0 - 'mB' // 9fa6-9fb3 #1 - 'F' // 9fb4 #5 - 'fD' // 9fb5-9fbb #3 - 'fF' // 9fbc-9fc2 #5 - 'D' // 9fc3 #3 - 'F' // 9fc4 #5 - 'D' // 9fc5 #3 - 'F' // 9fc6 #5 - 'dB' // 9fc7-9fcb #1 - 'F' // 9fcc #5 - 'bD' // 9fcd-9fcf #3 - 'B' // 9fd0 #1 - '1dD' // 9fd1-9fef #3 - 'oE' // 9ff0-9fff - '44t8P' // a000-a48c #223 - 'bE' // a48d-a48f - '2b8P' // a490-a4c6 #223 - 'hE' // a4c7-a4cf - '1u7C' // a4d0-a4ff #184 - '11m14T' // a500-a62b #383 - 'sE' // a62c-a63f - '1tL' // a640-a66e #11 - '3V' // a66f #99 - '1uL' // a670-a69f #11 - '3i6F' // a6a0-a6f7 #161 - 'gE' // a6f8-a6ff - '7tL' // a700-a7ca #11 - 'dE' // a7cb-a7cf - 'aL' // a7d0-a7d1 #11 - 'E' // a7d2 - 'L' // a7d3 #11 - 'E' // a7d4 - 'dL' // a7d5-a7d9 #11 - 'wE' // a7da-a7f1 - 'mL' // a7f2-a7ff #11 - '1r14C' // a800-a82c #366 - 'bE' // a82d-a82f - 'b12B' // a830-a832 #313 - 'b12C' // a833-a835 #314 - 'c12D' // a836-a839 #315 - 'eE' // a83a-a83f - '2c13W' // a840-a877 #360 - 'gE' // a878-a87f - '2q7T' // a880-a8c5 #201 - 'gE' // a8c6-a8cd - 'k7T' // a8ce-a8d9 #201 - 'eE' // a8da-a8df - 'p2A' // a8e0-a8f0 #52 - '2N' // a8f1 #65 - '2A' // a8f2 #52 - '12F' // a8f3 #317 - 'j2A' // a8f4-a8fe #52 - '3E' // a8ff #82 - '1s6Z' // a900-a92d #181 - '10Q' // a92e #276 - '6Z' // a92f #181 - '1i7R' // a930-a953 #199 - 'jE' // a954-a95e - '7R' // a95f #199 - '1bP' // a960-a97c #15 - 'bE' // a97d-a97f - '2y4Y' // a980-a9cd #128 - 'E' // a9ce - '11T' // a9cf #305 - 'i4Y' // a9d0-a9d9 #128 - 'cE' // a9da-a9dd - 'a4Y' // a9de-a9df #128 - '1d4E' // a9e0-a9fe #108 - 'E' // a9ff - '2b3Y' // aa00-aa36 #102 - 'hE' // aa37-aa3f - 'm3Y' // aa40-aa4d #102 - 'aE' // aa4e-aa4f - 'i3Y' // aa50-aa59 #102 - 'aE' // aa5a-aa5b - 'c3Y' // aa5c-aa5f #102 - '1e4E' // aa60-aa7f #108 - '2n8H' // aa80-aac2 #215 - 'wE' // aac3-aada - 'd8H' // aadb-aadf #215 - 'v5C' // aae0-aaf6 #132 - 'iE' // aaf7-ab00 - 'eV' // ab01-ab06 #21 - 'aE' // ab07-ab08 - 'eV' // ab09-ab0e #21 - 'aE' // ab0f-ab10 - 'eV' // ab11-ab16 #21 - 'hE' // ab17-ab1f - 'fV' // ab20-ab26 #21 - 'E' // ab27 - 'fV' // ab28-ab2e #21 - 'E' // ab2f - '2gL' // ab30-ab6b #11 - 'cE' // ab6c-ab6f - '3a4Q' // ab70-abbf #120 - '1s5C' // abc0-abed #132 - 'aE' // abee-abef - 'i5C' // abf0-abf9 #132 - 'eE' // abfa-abff - '429qP' // ac00-d7a3 #15 - 'kE' // d7a4-d7af - 'vP' // d7b0-d7c6 #15 - 'cE' // d7c7-d7ca - '1vP' // d7cb-d7fb #15 - '325aE' // d7fc-f8ff - 'cA' // f900-f903 #0 - '4B' // f904 #105 - 'aA' // f905-f906 #0 - '2G' // f907 #58 - '4B' // f908 #105 - 'aO' // f909-f90a #14 - 'A' // f90b #0 - '2J' // f90c #61 - '2G' // f90d #58 - 'fO' // f90e-f914 #14 - 'A' // f915 #0 - 'O' // f916 #14 - 'A' // f917 #0 - 'aJ' // f918-f919 #9 - 'A' // f91a #0 - 'eO' // f91b-f920 #14 - 'J' // f921 #9 - '2G' // f922 #58 - 'hO' // f923-f92b #14 - 'J' // f92c #9 - 'A' // f92d #0 - 'O' // f92e #14 - 'J' // f92f #9 - 'O' // f930 #14 - '6X' // f931 #179 - 'aJ' // f932-f933 #9 - '2J' // f934 #61 - 'J' // f935 #9 - 'O' // f936 #14 - 'A' // f937 #0 - 'J' // f938 #9 - '6X' // f939 #179 - 'A' // f93a #0 - 'gO' // f93b-f942 #14 - 'A' // f943 #0 - 'bO' // f944-f946 #14 - 'aA' // f947-f948 #0 - 'J' // f949 #9 - 'A' // f94a #0 - 'bO' // f94b-f94d #14 - 'J' // f94e #9 - 'bO' // f94f-f951 #14 - 'A' // f952 #0 - 'J' // f953 #9 - 'eO' // f954-f959 #14 - 'P' // f95a #15 - 'bO' // f95b-f95d #14 - 'A' // f95e #0 - 'H' // f95f #7 - 'aO' // f960-f961 #14 - 'A' // f962 #0 - 'aJ' // f963-f964 #9 - 'A' // f965 #0 - 'J' // f966 #9 - 'A' // f967 #0 - 'dO' // f968-f96c #14 - '4B' // f96d #105 - 'J' // f96e #9 - 'bO' // f96f-f971 #14 - 'A' // f972 #0 - 'O' // f973 #14 - 'J' // f974 #9 - 'O' // f975 #14 - 'A' // f976 #0 - 'O' // f977 #14 - '2G' // f978 #58 - 'A' // f979 #0 - 'J' // f97a #9 - '2J' // f97b #61 - 'aO' // f97c-f97d #14 - 'A' // f97e #0 - 'J' // f97f #9 - 'A' // f980 #0 - 'bO' // f981-f983 #14 - 'aJ' // f984-f985 #9 - '2G' // f986 #58 - 'bO' // f987-f989 #14 - 'A' // f98a #0 - 'aJ' // f98b-f98c #9 - 'O' // f98d #14 - 'A' // f98e #0 - 'cO' // f98f-f992 #14 - 'J' // f993 #9 - 'O' // f994 #14 - 'A' // f995 #0 - 'aO' // f996-f997 #14 - 'J' // f998 #9 - 'bO' // f999-f99b #14 - '2G' // f99c #58 - '4B' // f99d #105 - 'O' // f99e #14 - '2G' // f99f #58 - 'iO' // f9a0-f9a9 #14 - 'H' // f9aa #7 - 'bO' // f9ab-f9ad #14 - 'J' // f9ae #9 - 'cO' // f9af-f9b2 #14 - 'J' // f9b3 #9 - 'O' // f9b4 #14 - '2G' // f9b5 #58 - 'O' // f9b6 #14 - 'J' // f9b7 #9 - 'bO' // f9b8-f9ba #14 - 'A' // f9bb #0 - 'O' // f9bc #14 - 'A' // f9bd #0 - '2J' // f9be #61 - 'O' // f9bf #14 - 'J' // f9c0 #9 - 'cO' // f9c1-f9c4 #14 - 'aA' // f9c5-f9c6 #0 - 'O' // f9c7 #14 - '2G' // f9c8 #58 - 'fO' // f9c9-f9cf #14 - '2J' // f9d0 #61 - 'fO' // f9d1-f9d7 #14 - 'A' // f9d8 #0 - '2J' // f9d9 #61 - 'aO' // f9da-f9db #14 - 'bA' // f9dc-f9de #0 - 'J' // f9df #9 - 'A' // f9e0 #0 - 'O' // f9e1 #14 - 'a2J' // f9e2-f9e3 #61 - 'A' // f9e4 #0 - 'aO' // f9e5-f9e6 #14 - 'A' // f9e7 #0 - 'O' // f9e8 #14 - 'A' // f9e9 #0 - 'fO' // f9ea-f9f0 #14 - 'J' // f9f1 #9 - 'O' // f9f2 #14 - 'P' // f9f3 #15 - 'aA' // f9f4-f9f5 #0 - 'cO' // f9f6-f9f9 #14 - 'A' // f9fa #0 - 'aO' // f9fb-f9fc #14 - 'A' // f9fd #0 - 'O' // f9fe #14 - 'A' // f9ff #0 - 'aO' // fa00-fa01 #14 - 'A' // fa02 #0 - 'aO' // fa03-fa04 #14 - 'cA' // fa05-fa08 #0 - 'O' // fa09 #14 - 'A' // fa0a #0 - 'O' // fa0b #14 - 'aB' // fa0c-fa0d #1 - 'aF' // fa0e-fa0f #5 - 'H' // fa10 #7 - 'F' // fa11 #5 - 'O' // fa12 #14 - 'aF' // fa13-fa14 #5 - 'bO' // fa15-fa17 #14 - 'F' // fa18 #5 - 'eO' // fa19-fa1e #14 - 'bF' // fa1f-fa21 #5 - 'O' // fa22 #14 - 'aF' // fa23-fa24 #5 - 'H' // fa25 #7 - 'O' // fa26 #14 - 'bF' // fa27-fa29 #5 - 'bO' // fa2a-fa2c #14 - 'H' // fa2d #7 - 'O' // fa2e #14 - 'J' // fa2f #9 - 'aO' // fa30-fa31 #14 - 'H' // fa32 #7 - 'bA' // fa33-fa35 #0 - 'O' // fa36 #14 - 'aJ' // fa37-fa38 #9 - 'O' // fa39 #14 - 'A' // fa3a #0 - 'bO' // fa3b-fa3d #14 - 'H' // fa3e #7 - 'O' // fa3f #14 - 'H' // fa40 #7 - 'O' // fa41 #14 - 'H' // fa42 #7 - 'cO' // fa43-fa46 #14 - 'J' // fa47 #9 - 'O' // fa48 #14 - 'A' // fa49 #0 - 'O' // fa4a #14 - 'A' // fa4b #0 - 'iO' // fa4c-fa55 #14 - 'H' // fa56 #7 - 'O' // fa57 #14 - 'H' // fa58 #7 - 'cO' // fa59-fa5c #14 - 'aA' // fa5d-fa5e #0 - 'iO' // fa5f-fa68 #14 - 'H' // fa69 #7 - 'O' // fa6a #14 - 'bH' // fa6b-fa6d #7 - '5oE' // fa6e-faff - 'dQ' // fb00-fb04 #16 - 'aL' // fb05-fb06 #11 - 'kE' // fb07-fb12 - 'd3G' // fb13-fb17 #84 - 'dE' // fb18-fb1c - 'y2H' // fb1d-fb36 #59 - 'E' // fb37 - 'd2H' // fb38-fb3c #59 - 'E' // fb3d - '2H' // fb3e #59 - 'E' // fb3f - 'a2H' // fb40-fb41 #59 - 'E' // fb42 - 'a2H' // fb43-fb44 #59 - 'E' // fb45 - 'i2H' // fb46-fb4f #59 - '4i1F' // fb50-fbc1 #31 - 'pE' // fbc2-fbd2 - '13x1F' // fbd3-fd3d #31 - 'a11M' // fd3e-fd3f #298 - 'oE' // fd40-fd4f - '2k1F' // fd50-fd8f #31 - 'aE' // fd90-fd91 - '2a1F' // fd92-fdc7 #31 - '1mE' // fdc8-fdef - 'a1F' // fdf0-fdf1 #31 - '3W' // fdf2 #100 - 'i1F' // fdf3-fdfc #31 - '3W' // fdfd #100 - 'aE' // fdfe-fdff - '10T' // fe00 #279 - 'nE' // fe01-fe0f - 'iA' // fe10-fe19 #0 - 'eE' // fe1a-fe1f - 'cL' // fe20-fe23 #11 - 'b9Z' // fe24-fe26 #259 - 'cL' // fe27-fe2a #11 - 'b9W' // fe2b-fe2d #256 - 'a3V' // fe2e-fe2f #99 - '1hA' // fe30-fe52 #0 - 'E' // fe53 - 'rA' // fe54-fe66 #0 - 'E' // fe67 - 'cA' // fe68-fe6b #0 - 'cE' // fe6c-fe6f - 'd1F' // fe70-fe74 #31 - 'E' // fe75 - '5d1F' // fe76-fefc #31 - 'aE' // fefd-fefe - 'L' // feff #11 - 'E' // ff00 - '4C' // ff01 #106 - 'eA' // ff02-ff07 #0 - 'a12V' // ff08-ff09 #333 - 'aA' // ff0a-ff0b #0 - '4C' // ff0c #106 - '4T' // ff0d #123 - '6Q' // ff0e #172 - 'jA' // ff0f-ff19 #0 - '4U' // ff1a #124 - '4C' // ff1b #106 - 'bA' // ff1c-ff1e #0 - '4C' // ff1f #106 - 'zA' // ff20-ff3a #0 - '4T' // ff3b #123 - 'A' // ff3c #0 - '4T' // ff3d #123 - '1bA' // ff3e-ff5a #0 - 'T' // ff5b #19 - 'A' // ff5c #0 - 'T' // ff5d #19 - 'bA' // ff5e-ff60 #0 - '4U' // ff61 #124 - 'a4W' // ff62-ff63 #126 - '4U' // ff64 #124 - '4W' // ff65 #126 - '2eA' // ff66-ff9f #0 - 'P' // ffa0 #15 - '1cA' // ffa1-ffbe #0 - 'bE' // ffbf-ffc1 - 'eA' // ffc2-ffc7 #0 - 'aE' // ffc8-ffc9 - 'eA' // ffca-ffcf #0 - 'aE' // ffd0-ffd1 - 'eA' // ffd2-ffd7 #0 - 'aE' // ffd8-ffd9 - 'bA' // ffda-ffdc #0 - 'bE' // ffdd-ffdf - 'fA' // ffe0-ffe6 #0 - 'E' // ffe7 - 'fA' // ffe8-ffee #0 - 'iE' // ffef-fff8 - 'bM' // fff9-fffb #12 - 'aL' // fffc-fffd #11 - 'aE' // fffe-ffff - 'k2L' // 10000-1000b #63 - 'E' // 1000c - 'y2L' // 1000d-10026 #63 - 'E' // 10027 - 'r2L' // 10028-1003a #63 - 'E' // 1003b - 'a2L' // 1003c-1003d #63 - 'E' // 1003e - 'n2L' // 1003f-1004d #63 - 'aE' // 1004e-1004f - 'm2L' // 10050-1005d #63 - '1gE' // 1005e-1007f - '4r2L' // 10080-100fa #63 - 'dE' // 100fb-100ff - 'b6M' // 10100-10102 #168 - 'cE' // 10103-10106 - '1r6M' // 10107-10133 #168 - 'bE' // 10134-10136 - 'h2L' // 10137-1013f #63 - '2zM' // 10140-1018e #12 - 'E' // 1018f - 'lM' // 10190-1019c #12 - 'bE' // 1019d-1019f - 'M' // 101a0 #12 - '1tE' // 101a1-101cf - '1sM' // 101d0-101fd #12 - '4yE' // 101fe-1027f - '1b13F' // 10280-1029c #343 - 'bE' // 1029d-1029f - '1v11W' // 102a0-102d0 #308 - 'nE' // 102d1-102df - '1a11Y' // 102e0-102fb #310 - 'cE' // 102fc-102ff - '1i7M' // 10300-10323 #194 - 'hE' // 10324-1032c - 'b7M' // 1032d-1032f #194 - 'z12N' // 10330-1034a #325 - 'dE' // 1034b-1034f - '1p13Q' // 10350-1037a #354 - 'dE' // 1037b-1037f - '1c8M' // 10380-1039d #220 - 'E' // 1039e - '8M' // 1039f #220 - '1i7N' // 103a0-103c3 #195 - 'cE' // 103c4-103c7 - 'm7N' // 103c8-103d5 #195 - '1oE' // 103d6-103ff - '3a11Z' // 10400-1044f #311 - '1u13Z' // 10450-1047f #363 - '1c7P' // 10480-1049d #197 - 'aE' // 1049e-1049f - 'i7P' // 104a0-104a9 #197 - 'eE' // 104aa-104af - '1i7O' // 104b0-104d3 #196 - 'cE' // 104d4-104d7 - '1i7O' // 104d8-104fb #196 - 'cE' // 104fc-104ff - '1m12K' // 10500-10527 #322 - 'gE' // 10528-1052f - '1y6K' // 10530-10563 #166 - 'jE' // 10564-1056e - '6K' // 1056f #166 - '5mE' // 10570-105ff - '11x5A' // 10600-10736 #130 - 'hE' // 10737-1073f - 'u5A' // 10740-10755 #130 - 'iE' // 10756-1075f - 'g5A' // 10760-10767 #130 - 'wE' // 10768-1077f - 'eL' // 10780-10785 #11 - 'E' // 10786 - '1oL' // 10787-107b0 #11 - 'E' // 107b1 - 'hL' // 107b2-107ba #11 - '2pE' // 107bb-107ff - 'e2U' // 10800-10805 #72 - 'aE' // 10806-10807 - '2U' // 10808 #72 - 'E' // 10809 - '1q2U' // 1080a-10835 #72 - 'E' // 10836 - 'a2U' // 10837-10838 #72 - 'bE' // 10839-1083b - '2U' // 1083c #72 - 'aE' // 1083d-1083e - '2U' // 1083f #72 - 'u6U' // 10840-10855 #176 - 'E' // 10856 - 'h6U' // 10857-1085f #176 - '1e13U' // 10860-1087f #358 - '1d7J' // 10880-1089e #191 - 'gE' // 1089f-108a6 - 'h7J' // 108a7-108af #191 - '1uE' // 108b0-108df - 'r4X' // 108e0-108f2 #127 - 'E' // 108f3 - 'a4X' // 108f4-108f5 #127 - 'dE' // 108f6-108fa - 'd4X' // 108fb-108ff #127 - '1a7Q' // 10900-1091b #198 - 'bE' // 1091c-1091e - '7Q' // 1091f #198 - 'y7D' // 10920-10939 #185 - 'dE' // 1093a-1093e - '7D' // 1093f #185 - '2kE' // 10940-1097f - '2c5D' // 10980-109b7 #133 - 'cE' // 109b8-109bb - 's5D' // 109bc-109cf #133 - 'aE' // 109d0-109d1 - '1s5D' // 109d2-109ff #133 - 'c2K' // 10a00-10a03 #62 - 'E' // 10a04 - 'a2K' // 10a05-10a06 #62 - 'dE' // 10a07-10a0b - 'g2K' // 10a0c-10a13 #62 - 'E' // 10a14 - 'b2K' // 10a15-10a17 #62 - 'E' // 10a18 - '1b2K' // 10a19-10a35 #62 - 'aE' // 10a36-10a37 - 'b2K' // 10a38-10a3a #62 - 'cE' // 10a3b-10a3e - 'i2K' // 10a3f-10a48 #62 - 'fE' // 10a49-10a4f - 'h2K' // 10a50-10a58 #62 - 'fE' // 10a59-10a5f - '1e13S' // 10a60-10a7f #356 - '1e13P' // 10a80-10a9f #353 - '1eE' // 10aa0-10abf - '1l7F' // 10ac0-10ae6 #187 - 'cE' // 10ae7-10aea - 'k7F' // 10aeb-10af6 #187 - 'hE' // 10af7-10aff - '2a6D' // 10b00-10b35 #159 - 'bE' // 10b36-10b38 - 'f6D' // 10b39-10b3f #159 - 'u6W' // 10b40-10b55 #178 - 'aE' // 10b56-10b57 - 'g6W' // 10b58-10b5f #178 - 'r6V' // 10b60-10b72 #177 - 'dE' // 10b73-10b77 - 'g6V' // 10b78-10b7f #177 - 'q5H' // 10b80-10b91 #137 - 'fE' // 10b92-10b98 - 'c5H' // 10b99-10b9c #137 - 'kE' // 10b9d-10ba8 - 'f5H' // 10ba9-10baf #137 - '3aE' // 10bb0-10bff - '2t13T' // 10c00-10c48 #357 - '2bE' // 10c49-10c7f - '1x5G' // 10c80-10cb2 #136 - 'lE' // 10cb3-10cbf - '1x5G' // 10cc0-10cf2 #136 - 'fE' // 10cf3-10cf9 - 'e5G' // 10cfa-10cff #136 - '13mE' // 10d00-10e5f - '1dM' // 10e60-10e7e #12 - '4xE' // 10e7f-10eff - '1m13R' // 10f00-10f27 #355 - 'gE' // 10f28-10f2f - '1o14A' // 10f30-10f59 #364 - '5cE' // 10f5a-10fdf - 'v12L' // 10fe0-10ff6 #323 - 'hE' // 10ff7-10fff - '2y4O' // 11000-1104d #118 - 'cE' // 1104e-11051 - '1i4O' // 11052-11075 #118 - 'hE' // 11076-1107e - '4O' // 1107f #118 - '2n6Y' // 11080-110c2 #180 - 'iE' // 110c3-110cc - '6Y' // 110cd #180 - 'aE' // 110ce-110cf - 'x7V' // 110d0-110e8 #203 - 'fE' // 110e9-110ef - 'i7V' // 110f0-110f9 #203 - 'eE' // 110fa-110ff - '1z6L' // 11100-11134 #167 - 'E' // 11135 - 'q6L' // 11136-11147 #167 - 'gE' // 11148-1114f - '1l13G' // 11150-11176 #344 - 'hE' // 11177-1117f - '3q13Y' // 11180-111df #362 - 'E' // 111e0 - 's1V' // 111e1-111f4 #47 - 'jE' // 111f5-111ff - 'q7A' // 11200-11211 #182 - 'E' // 11212 - '1s7A' // 11213-11240 #182 - '2jE' // 11241-1127f - 'f3L' // 11280-11286 #89 - 'E' // 11287 - '3L' // 11288 #89 - 'E' // 11289 - 'c3L' // 1128a-1128d #89 - 'E' // 1128e - 'n3L' // 1128f-1129d #89 - 'E' // 1129e - 'j3L' // 1129f-112a9 #89 - 'eE' // 112aa-112af - '2f7B' // 112b0-112ea #183 - 'dE' // 112eb-112ef - 'i7B' // 112f0-112f9 #183 - 'eE' // 112fa-112ff - '1P' // 11300 #41 - '2V' // 11301 #73 - '1P' // 11302 #41 - '2V' // 11303 #73 - 'E' // 11304 - 'g1P' // 11305-1130c #41 - 'aE' // 1130d-1130e - 'a1P' // 1130f-11310 #41 - 'aE' // 11311-11312 - 'u1P' // 11313-11328 #41 - 'E' // 11329 - 'f1P' // 1132a-11330 #41 - 'E' // 11331 - 'a1P' // 11332-11333 #41 - 'E' // 11334 - 'd1P' // 11335-11339 #41 - 'E' // 1133a - 'a2V' // 1133b-1133c #73 - 'g1P' // 1133d-11344 #41 - 'aE' // 11345-11346 - 'a1P' // 11347-11348 #41 - 'aE' // 11349-1134a - 'b1P' // 1134b-1134d #41 - 'aE' // 1134e-1134f - '1P' // 11350 #41 - 'eE' // 11351-11356 - '1P' // 11357 #41 - 'dE' // 11358-1135c - 'f1P' // 1135d-11363 #41 - 'aE' // 11364-11365 - 'f1P' // 11366-1136c #41 - 'bE' // 1136d-1136f - 'd1P' // 11370-11374 #41 - '5hE' // 11375-113ff - '3m7K' // 11400-1145b #192 - 'E' // 1145c - 'd7K' // 1145d-11461 #192 - '1cE' // 11462-1147f - '2s8L' // 11480-114c7 #219 - 'gE' // 114c8-114cf - 'i8L' // 114d0-114d9 #219 - '6iE' // 114da-1157f - '2a7U' // 11580-115b5 #202 - 'aE' // 115b6-115b7 - '1k7U' // 115b8-115dd #202 - '1gE' // 115de-115ff - '2p7G' // 11600-11644 #188 - 'jE' // 11645-1164f - 'i7G' // 11650-11659 #188 - 'eE' // 1165a-1165f - 'l2Y' // 11660-1166c #76 - 'rE' // 1166d-1167f - '2e8I' // 11680-116b9 #216 - 'eE' // 116ba-116bf - 'i8I' // 116c0-116c9 #216 - '18aE' // 116ca-1189f - '3d8O' // 118a0-118f2 #222 - 'kE' // 118f3-118fe - '8O' // 118ff #222 - '9uE' // 11900-119ff - '2s14U' // 11a00-11a47 #384 - 'gE' // 11a48-11a4f - '3d14B' // 11a50-11aa2 #365 - 'lE' // 11aa3-11aaf - 'o4P' // 11ab0-11abf #119 - '2d13V' // 11ac0-11af8 #359 - 'fE' // 11af9-11aff - 'i2A' // 11b00-11b09 #52 - '9kE' // 11b0a-11bff - 'h3X' // 11c00-11c08 #101 - 'E' // 11c09 - '1r3X' // 11c0a-11c36 #101 - 'E' // 11c37 - 'm3X' // 11c38-11c45 #101 - 'iE' // 11c46-11c4f - '1b3X' // 11c50-11c6c #101 - 'bE' // 11c6d-11c6f - '1e5B' // 11c70-11c8f #131 - 'aE' // 11c90-11c91 - 'u5B' // 11c92-11ca7 #131 - 'E' // 11ca8 - 'm5B' // 11ca9-11cb6 #131 - '2tE' // 11cb7-11cff - 'f2S' // 11d00-11d06 #70 - 'E' // 11d07 - 'a2S' // 11d08-11d09 #70 - 'E' // 11d0a - '1q2S' // 11d0b-11d36 #70 - 'bE' // 11d37-11d39 - '2S' // 11d3a #70 - 'E' // 11d3b - 'a2S' // 11d3c-11d3d #70 - 'E' // 11d3e - 'h2S' // 11d3f-11d47 #70 - 'gE' // 11d48-11d4f - 'i2S' // 11d50-11d59 #70 - 'eE' // 11d5a-11d5f - 'e2W' // 11d60-11d65 #74 - 'E' // 11d66 - 'a2W' // 11d67-11d68 #74 - 'E' // 11d69 - '1j2W' // 11d6a-11d8e #74 - 'E' // 11d8f - 'a2W' // 11d90-11d91 #74 - 'E' // 11d92 - 'e2W' // 11d93-11d98 #74 - 'fE' // 11d99-11d9f - 'i2W' // 11da0-11da9 #74 - '19wE' // 11daa-11faf - '7C' // 11fb0 #184 - 'nE' // 11fb1-11fbf - '1w8J' // 11fc0-11ff1 #217 - 'lE' // 11ff2-11ffe - '8J' // 11fff #217 - '35k3Z' // 12000-12399 #103 - '3wE' // 1239a-123ff - '4f3Z' // 12400-1246e #103 - 'E' // 1246f - 'd3Z' // 12470-12474 #103 - 'jE' // 12475-1247f - '7m3Z' // 12480-12543 #103 - '105qE' // 12544-12fff - '41e6N' // 13000-1342f #169 - 'pE' // 13430-13440 - 'e6N' // 13441-13446 #169 - '154tE' // 13447-143ff - '22j11J' // 14400-14646 #295 - '331zE' // 14647-167ff - '21v6F' // 16800-16a38 #161 - 'fE' // 16a39-16a3f - '1d5F' // 16a40-16a5e #135 - 'E' // 16a5f - 'i5F' // 16a60-16a69 #135 - 'cE' // 16a6a-16a6d - 'a5F' // 16a6e-16a6f #135 - '3qE' // 16a70-16acf - '1c6G' // 16ad0-16aed #162 - 'aE' // 16aee-16aef - 'e6G' // 16af0-16af5 #162 - 'iE' // 16af6-16aff - '2q3M' // 16b00-16b45 #90 - 'iE' // 16b46-16b4f - 'i3M' // 16b50-16b59 #90 - 'E' // 16b5a - 'f3M' // 16b5b-16b61 #90 - 'E' // 16b62 - 't3M' // 16b63-16b77 #90 - 'dE' // 16b78-16b7c - 'r3M' // 16b7d-16b8f #90 - '26kE' // 16b90-16e3f - '3l13M' // 16e40-16e9a #350 - '3vE' // 16e9b-16eff - '2v5E' // 16f00-16f4a #134 - 'cE' // 16f4b-16f4e - '2d5E' // 16f4f-16f87 #134 - 'fE' // 16f88-16f8e - 'p5E' // 16f8f-16f9f #134 - '2lE' // 16fa0-16fe0 - '7L' // 16fe1 #193 - '645kE' // 16fe2-1b16f - '15e7L' // 1b170-1b2fb #193 - '88sE' // 1b2fc-1bbff - '4b3H' // 1bc00-1bc6a #85 - 'dE' // 1bc6b-1bc6f - 'l3H' // 1bc70-1bc7c #85 - 'bE' // 1bc7d-1bc7f - 'h3H' // 1bc80-1bc88 #85 - 'fE' // 1bc89-1bc8f - 'i3H' // 1bc90-1bc99 #85 - 'aE' // 1bc9a-1bc9b - 'g3H' // 1bc9c-1bca3 #85 - '190oE' // 1bca4-1cfff - '9k3R' // 1d000-1d0f5 #95 - 'iE' // 1d0f6-1d0ff - '1l3R' // 1d100-1d126 #95 - 'aE' // 1d127-1d128 - '7k3R' // 1d129-1d1ea #95 - 'tE' // 1d1eb-1d1ff - '2q3R' // 1d200-1d245 #95 - '4qE' // 1d246-1d2bf - 'sM' // 1d2c0-1d2d3 #12 - 'kE' // 1d2d4-1d2df - 's13L' // 1d2e0-1d2f3 #349 - 'kE' // 1d2f4-1d2ff - '3hM' // 1d300-1d356 #12 - 'hE' // 1d357-1d35f - 'xM' // 1d360-1d378 #12 - '5dE' // 1d379-1d3ff - '3fN' // 1d400-1d454 #13 - 'E' // 1d455 - '2rN' // 1d456-1d49c #13 - 'E' // 1d49d - 'aN' // 1d49e-1d49f #13 - 'aE' // 1d4a0-1d4a1 - 'N' // 1d4a2 #13 - 'aE' // 1d4a3-1d4a4 - 'aN' // 1d4a5-1d4a6 #13 - 'aE' // 1d4a7-1d4a8 - 'cN' // 1d4a9-1d4ac #13 - 'E' // 1d4ad - 'kN' // 1d4ae-1d4b9 #13 - 'E' // 1d4ba - 'N' // 1d4bb #13 - 'E' // 1d4bc - 'fN' // 1d4bd-1d4c3 #13 - 'E' // 1d4c4 - '2lN' // 1d4c5-1d505 #13 - 'E' // 1d506 - 'cN' // 1d507-1d50a #13 - 'aE' // 1d50b-1d50c - 'gN' // 1d50d-1d514 #13 - 'E' // 1d515 - 'fN' // 1d516-1d51c #13 - 'E' // 1d51d - '1aN' // 1d51e-1d539 #13 - 'E' // 1d53a - 'cN' // 1d53b-1d53e #13 - 'E' // 1d53f - 'dN' // 1d540-1d544 #13 - 'E' // 1d545 - 'N' // 1d546 #13 - 'bE' // 1d547-1d549 - 'fN' // 1d54a-1d550 #13 - 'E' // 1d551 - '13aN' // 1d552-1d6a5 #13 - 'aE' // 1d6a6-1d6a7 - '11eN' // 1d6a8-1d7cb #13 - 'aE' // 1d7cc-1d7cd - '1wN' // 1d7ce-1d7ff #13 - '68wE' // 1d800-1deff - '1dL' // 1df00-1df1e #11 - '8pE' // 1df1f-1dfff - 'f2O' // 1e000-1e006 #66 - 'E' // 1e007 - 'p2O' // 1e008-1e018 #66 - 'aE' // 1e019-1e01a - 'f2O' // 1e01b-1e021 #66 - 'E' // 1e022 - 'a2O' // 1e023-1e024 #66 - 'E' // 1e025 - 'd2O' // 1e026-1e02a #66 - '25jE' // 1e02b-1e2bf - '2e8N' // 1e2c0-1e2f9 #221 - 'dE' // 1e2fa-1e2fe - '8N' // 1e2ff #221 - '47yE' // 1e300-1e7df - 'fV' // 1e7e0-1e7e6 #21 - 'E' // 1e7e7 - 'cV' // 1e7e8-1e7eb #21 - 'E' // 1e7ec - 'aV' // 1e7ed-1e7ee #21 - 'E' // 1e7ef - 'nV' // 1e7f0-1e7fe #21 - '9vE' // 1e7ff-1e8ff - '2w4L' // 1e900-1e94b #115 - 'cE' // 1e94c-1e94f - 'i4L' // 1e950-1e959 #115 - 'cE' // 1e95a-1e95d - 'a4L' // 1e95e-1e95f #115 - '30dE' // 1e960-1ec70 - '2o13E' // 1ec71-1ecb4 #342 - '12rE' // 1ecb5-1edff - 'cN' // 1ee00-1ee03 #13 - 'E' // 1ee04 - 'zN' // 1ee05-1ee1f #13 - 'E' // 1ee20 - 'aN' // 1ee21-1ee22 #13 - 'E' // 1ee23 - 'N' // 1ee24 #13 - 'aE' // 1ee25-1ee26 - 'N' // 1ee27 #13 - 'E' // 1ee28 - 'iN' // 1ee29-1ee32 #13 - 'E' // 1ee33 - 'cN' // 1ee34-1ee37 #13 - 'E' // 1ee38 - 'N' // 1ee39 #13 - 'E' // 1ee3a - 'N' // 1ee3b #13 - 'eE' // 1ee3c-1ee41 - 'N' // 1ee42 #13 - 'cE' // 1ee43-1ee46 - 'N' // 1ee47 #13 - 'E' // 1ee48 - 'N' // 1ee49 #13 - 'E' // 1ee4a - 'N' // 1ee4b #13 - 'E' // 1ee4c - 'bN' // 1ee4d-1ee4f #13 - 'E' // 1ee50 - 'aN' // 1ee51-1ee52 #13 - 'E' // 1ee53 - 'N' // 1ee54 #13 - 'aE' // 1ee55-1ee56 - 'N' // 1ee57 #13 - 'E' // 1ee58 - 'N' // 1ee59 #13 - 'E' // 1ee5a - 'N' // 1ee5b #13 - 'E' // 1ee5c - 'N' // 1ee5d #13 - 'E' // 1ee5e - 'N' // 1ee5f #13 - 'E' // 1ee60 - 'aN' // 1ee61-1ee62 #13 - 'E' // 1ee63 - 'N' // 1ee64 #13 - 'aE' // 1ee65-1ee66 - 'cN' // 1ee67-1ee6a #13 - 'E' // 1ee6b - 'fN' // 1ee6c-1ee72 #13 - 'E' // 1ee73 - 'cN' // 1ee74-1ee77 #13 - 'E' // 1ee78 - 'cN' // 1ee79-1ee7c #13 - 'E' // 1ee7d - 'N' // 1ee7e #13 - 'E' // 1ee7f - 'iN' // 1ee80-1ee89 #13 - 'E' // 1ee8a - 'pN' // 1ee8b-1ee9b #13 - 'dE' // 1ee9c-1eea0 - 'bN' // 1eea1-1eea3 #13 - 'E' // 1eea4 - 'dN' // 1eea5-1eea9 #13 - 'E' // 1eeaa - 'pN' // 1eeab-1eebb #13 - '1yE' // 1eebc-1eeef - 'aN' // 1eef0-1eef1 #13 - '10iE' // 1eef2-1efff - 'cM' // 1f000-1f003 #12 - '1J' // 1f004 #35 - '1lM' // 1f005-1f02b #12 - 'cE' // 1f02c-1f02f - '3uM' // 1f030-1f093 #12 - 'kE' // 1f094-1f09f - 'nM' // 1f0a0-1f0ae #12 - 'aE' // 1f0af-1f0b0 - 'nM' // 1f0b1-1f0bf #12 - 'E' // 1f0c0 - 'mM' // 1f0c1-1f0ce #12 - '1J' // 1f0cf #35 - 'E' // 1f0d0 - '1jM' // 1f0d1-1f0f5 #12 - 'iE' // 1f0f6-1f0ff - 'l1K' // 1f100-1f10c #36 - 'bM' // 1f10d-1f10f #12 - '3n1K' // 1f110-1f16c #36 - 'bM' // 1f16d-1f16f #12 - 'a2X' // 1f170-1f171 #75 - 'k1K' // 1f172-1f17d #36 - 'a2X' // 1f17e-1f17f #75 - 'm1K' // 1f180-1f18d #36 - '2X' // 1f18e #75 - 'a1K' // 1f18f-1f190 #36 - 'i2D' // 1f191-1f19a #55 - 'q1K' // 1f19b-1f1ac #36 - 'M' // 1f1ad #12 - '2cE' // 1f1ae-1f1e5 - 'y14V' // 1f1e6-1f1ff #385 - 'A' // 1f200 #0 - 'a2D' // 1f201-1f202 #55 - 'lE' // 1f203-1f20f - 'iA' // 1f210-1f219 #0 - '2D' // 1f21a #55 - 'sA' // 1f21b-1f22e #0 - '2D' // 1f22f #55 - 'aA' // 1f230-1f231 #0 - 'h2D' // 1f232-1f23a #55 - 'A' // 1f23b #0 - 'cE' // 1f23c-1f23f - 'hA' // 1f240-1f248 #0 - 'fE' // 1f249-1f24f - 'a2D' // 1f250-1f251 #55 - '6qE' // 1f252-1f2ff - '1G' // 1f300 #32 - '1D' // 1f301 #29 - 'U' // 1f302 #20 - '1D' // 1f303 #29 - 'a1G' // 1f304-1f305 #32 - 'a1D' // 1f306-1f307 #29 - '14W' // 1f308 #386 - '1D' // 1f309 #29 - 'b1G' // 1f30a-1f30c #32 - 'b1I' // 1f30d-1f30f #34 - '8Q' // 1f310 #224 - 'c1G' // 1f311-1f314 #32 - '1I' // 1f315 #34 - 'c1G' // 1f316-1f319 #32 - 'a3D' // 1f31a-1f31b #81 - '4G' // 1f31c #110 - 'b3D' // 1f31d-1f31f #81 - '1G' // 1f320 #32 - '14O' // 1f321 #378 - 'aM' // 1f322-1f323 #12 - 'f1I' // 1f324-1f32a #34 - '4G' // 1f32b #110 - '1I' // 1f32c #34 - 'c1Y' // 1f32d-1f330 #50 - 'd1G' // 1f331-1f335 #32 - '3O' // 1f336 #92 - 'e1G' // 1f337-1f33c #32 - '1Y' // 1f33d #50 - '15E' // 1f33e #394 - 'd1G' // 1f33f-1f343 #32 - '15G' // 1f344 #396 - 'e1Y' // 1f345-1f34a #50 - '15D' // 1f34b #393 - '1l1Y' // 1f34c-1f372 #50 - '8S' // 1f373 #226 - 'c1Y' // 1f374-1f377 #50 - '3O' // 1f378 #92 - 'b1Y' // 1f379-1f37b #50 - '8S' // 1f37c #226 - '3O' // 1f37d #92 - 'a1Y' // 1f37e-1f37f #50 - 'a1H' // 1f380-1f381 #33 - '15B' // 1f382 #391 - '5O' // 1f383 #144 - '5N' // 1f384 #143 - '1C' // 1f385 #28 - 'b1H' // 1f386-1f388 #33 - '5O' // 1f389 #144 - 'a1H' // 1f38a-1f38b #33 - '1X' // 1f38c #49 - 'd1H' // 1f38d-1f391 #33 - 'U' // 1f392 #20 - '8D' // 1f393 #211 - 'aM' // 1f394-1f395 #12 - 'a1J' // 1f396-1f397 #35 - 'M' // 1f398 #12 - 'b1J' // 1f399-1f39b #35 - 'aM' // 1f39c-1f39d #12 - 'a1J' // 1f39e-1f39f #35 - 'b1D' // 1f3a0-1f3a2 #29 - '1H' // 1f3a3 #33 - '5N' // 1f3a4 #143 - '1H' // 1f3a5 #33 - 'X' // 1f3a6 #23 - '1J' // 1f3a7 #35 - '5N' // 1f3a8 #143 - '5L' // 1f3a9 #141 - '1D' // 1f3aa #29 - '1H' // 1f3ab #33 - 'b1J' // 1f3ac-1f3ae #35 - 'e1H' // 1f3af-1f3b4 #33 - 'aX' // 1f3b5-1f3b6 #23 - 'd1H' // 1f3b7-1f3bb #33 - 'X' // 1f3bc #23 - '5L' // 1f3bd #141 - 'b1H' // 1f3be-1f3c0 #33 - '1X' // 1f3c1 #49 - '3A' // 1f3c2 #78 - '3Q' // 1f3c3 #94 - '3A' // 1f3c4 #78 - '1H' // 1f3c5 #33 - '1J' // 1f3c6 #35 - '1C' // 1f3c7 #28 - 'a1H' // 1f3c8-1f3c9 #33 - 'b3A' // 1f3ca-1f3cc #78 - 'a1M' // 1f3cd-1f3ce #38 - 'd1H' // 1f3cf-1f3d3 #33 - '1I' // 1f3d4 #34 - '1M' // 1f3d5 #38 - '1I' // 1f3d6 #34 - 'd1M' // 1f3d7-1f3db #38 - 'b1I' // 1f3dc-1f3de #34 - 'a1M' // 1f3df-1f3e0 #38 - 'e1D' // 1f3e1-1f3e6 #29 - 'X' // 1f3e7 #23 - 'b1D' // 1f3e8-1f3ea #29 - '5P' // 1f3eb #145 - '1D' // 1f3ec #29 - '8E' // 1f3ed #212 - 'U' // 1f3ee #20 - 'a1D' // 1f3ef-1f3f0 #29 - 'aM' // 1f3f1-1f3f2 #12 - '14K' // 1f3f3 #374 - '1X' // 1f3f4 #49 - '1I' // 1f3f5 #34 - 'M' // 1f3f6 #12 - 'R' // 1f3f7 #17 - 'a1H' // 1f3f8-1f3f9 #33 - 'U' // 1f3fa #20 - 'd15H' // 1f3fb-1f3ff #397 - 'g1G' // 1f400-1f407 #32 - '1I' // 1f408 #34 - 'k1G' // 1f409-1f414 #32 - '1I' // 1f415 #34 - 'h1G' // 1f416-1f41e #32 - '1I' // 1f41f #34 - 'e1G' // 1f420-1f425 #32 - '14R' // 1f426 #381 - 'w1G' // 1f427-1f43e #32 - '1I' // 1f43f #34 - 'W' // 1f440 #22 - '14N' // 1f441 #377 - '2E' // 1f442 #56 - 'bW' // 1f443-1f445 #22 - 'c2E' // 1f446-1f449 #56 - 'aW' // 1f44a-1f44b #22 - 'b2E' // 1f44c-1f44e #56 - 'aW' // 1f44f-1f450 #22 - 'aU' // 1f451-1f452 #20 - 'R' // 1f453 #17 - 'nU' // 1f454-1f462 #20 - 'bW' // 1f463-1f465 #22 - 'a1C' // 1f466-1f467 #28 - 'a3Q' // 1f468-1f469 #94 - '3A' // 1f46a #78 - 'm1C' // 1f46b-1f478 #28 - 'bW' // 1f479-1f47b #22 - '1C' // 1f47c #28 - '2E' // 1f47d #56 - '5O' // 1f47e #144 - 'aW' // 1f47f-1f480 #22 - 'b1C' // 1f481-1f483 #28 - 'U' // 1f484 #20 - 'W' // 1f485 #22 - 'a1C' // 1f486-1f487 #28 - '1D' // 1f488 #29 - 'aU' // 1f489-1f48a #20 - '8T' // 1f48b #227 - '15A' // 1f48c #390 - 'aU' // 1f48d-1f48e #20 - '1C' // 1f48f #28 - '1G' // 1f490 #32 - '1C' // 1f491 #28 - '1D' // 1f492 #29 - 'eW' // 1f493-1f498 #22 - 'c3C' // 1f499-1f49c #80 - 'aW' // 1f49d-1f49e #22 - '3C' // 1f49f #80 - 'X' // 1f4a0 #23 - 'U' // 1f4a1 #20 - 'X' // 1f4a2 #23 - 'R' // 1f4a3 #17 - 'W' // 1f4a4 #22 - '8U' // 1f4a5 #228 - 'W' // 1f4a6 #22 - '1G' // 1f4a7 #32 - 'bW' // 1f4a8-1f4aa #22 - '3D' // 1f4ab #81 - 'aX' // 1f4ac-1f4ad #23 - '1G' // 1f4ae #32 - 'W' // 1f4af #22 - 'R' // 1f4b0 #17 - 'aX' // 1f4b1-1f4b2 #23 - 'R' // 1f4b3 #17 - 'dU' // 1f4b4-1f4b8 #20 - '1A' // 1f4b9 #26 - '1D' // 1f4ba #29 - '8D' // 1f4bb #211 - '5M' // 1f4bc #142 - 'aU' // 1f4bd-1f4be #20 - 'R' // 1f4bf #17 - 'gU' // 1f4c0-1f4c7 #20 - 'cR' // 1f4c8-1f4cb #17 - 'mU' // 1f4cc-1f4d9 #20 - 'R' // 1f4da #17 - 'X' // 1f4db #23 - 'bU' // 1f4dc-1f4de #20 - 'R' // 1f4df #17 - 'aU' // 1f4e0-1f4e1 #20 - '5L' // 1f4e2 #141 - 'U' // 1f4e3 #20 - 'bR' // 1f4e4-1f4e6 #17 - 'bU' // 1f4e7-1f4e9 #20 - 'cR' // 1f4ea-1f4ed #17 - 'cU' // 1f4ee-1f4f1 #20 - 'dX' // 1f4f2-1f4f6 #23 - '1J' // 1f4f7 #35 - '1H' // 1f4f8 #33 - 'b1J' // 1f4f9-1f4fb #35 - '1H' // 1f4fc #33 - '1J' // 1f4fd #35 - 'M' // 1f4fe #12 - 'U' // 1f4ff #20 - 'bX' // 1f500-1f502 #23 - '1A' // 1f503 #26 - 'bX' // 1f504-1f506 #23 - 'c1A' // 1f507-1f50a #26 - 'aU' // 1f50b-1f50c #20 - 'R' // 1f50d #17 - 'cU' // 1f50e-1f511 #20 - 'aR' // 1f512-1f513 #17 - 'U' // 1f514 #20 - 'X' // 1f515 #23 - 'aU' // 1f516-1f517 #20 - 'lX' // 1f518-1f524 #23 - '15F' // 1f525 #395 - 'U' // 1f526 #20 - '5M' // 1f527 #142 - 'aU' // 1f528-1f529 #20 - '1Y' // 1f52a #50 - '1H' // 1f52b #33 - '5M' // 1f52c #142 - 'aU' // 1f52d-1f52e #20 - 'nX' // 1f52f-1f53d #23 - 'gM' // 1f53e-1f545 #12 - 'bS' // 1f546-1f548 #18 - '2I' // 1f549 #60 - '1I' // 1f54a #34 - 'b1D' // 1f54b-1f54d #29 - 'X' // 1f54e #23 - 'S' // 1f54f #18 - 'wR' // 1f550-1f567 #17 - 'fM' // 1f568-1f56e #12 - 'aR' // 1f56f-1f570 #17 - 'aM' // 1f571-1f572 #12 - '4G' // 1f573 #110 - 'a3A' // 1f574-1f575 #78 - 'R' // 1f576 #17 - 'a1I' // 1f577-1f578 #34 - '1J' // 1f579 #35 - '1C' // 1f57a #28 - 'kM' // 1f57b-1f586 #12 - 'R' // 1f587 #17 - 'aM' // 1f588-1f589 #12 - 'aR' // 1f58a-1f58b #17 - 'a8C' // 1f58c-1f58d #210 - 'aM' // 1f58e-1f58f #12 - '2E' // 1f590 #56 - 'cM' // 1f591-1f594 #12 - 'aW' // 1f595-1f596 #22 - 'lM' // 1f597-1f5a3 #12 - '3C' // 1f5a4 #80 - 'R' // 1f5a5 #17 - 'aM' // 1f5a6-1f5a7 #12 - 'R' // 1f5a8 #17 - 'gM' // 1f5a9-1f5b0 #12 - 'aR' // 1f5b1-1f5b2 #17 - 'hM' // 1f5b3-1f5bb #12 - '1J' // 1f5bc #35 - 'dM' // 1f5bd-1f5c1 #12 - 'bR' // 1f5c2-1f5c4 #17 - 'kM' // 1f5c5-1f5d0 #12 - 'bR' // 1f5d1-1f5d3 #17 - 'gM' // 1f5d4-1f5db #12 - 'bR' // 1f5dc-1f5de #17 - 'aM' // 1f5df-1f5e0 #12 - 'R' // 1f5e1 #17 - 'M' // 1f5e2 #12 - '2E' // 1f5e3 #56 - 'cM' // 1f5e4-1f5e7 #12 - '1A' // 1f5e8 #26 - 'eM' // 1f5e9-1f5ee #12 - '1A' // 1f5ef #26 - 'bM' // 1f5f0-1f5f2 #12 - 'R' // 1f5f3 #17 - 'eM' // 1f5f4-1f5f9 #12 - '1M' // 1f5fa #38 - 'd1D' // 1f5fb-1f5ff #29 - 'oW' // 1f600-1f60f #22 - '8A' // 1f610 #208 - '1vW' // 1f611-1f641 #22 - '8U' // 1f642 #228 - 'aW' // 1f643-1f644 #22 - 'b1C' // 1f645-1f647 #28 - 'b3D' // 1f648-1f64a #81 - '1C' // 1f64b #28 - 'W' // 1f64c #22 - 'a1C' // 1f64d-1f64e #28 - 'W' // 1f64f #22 - '1uM' // 1f650-1f67f #12 - '5P' // 1f680 #145 - 'e1D' // 1f681-1f686 #29 - '1M' // 1f687 #38 - 'd1D' // 1f688-1f68c #29 - '1M' // 1f68d #38 - 'b1D' // 1f68e-1f690 #29 - '1M' // 1f691 #38 - '5P' // 1f692 #145 - '1D' // 1f693 #29 - '1M' // 1f694 #38 - 'b1D' // 1f695-1f697 #29 - '1M' // 1f698 #38 - 'i1D' // 1f699-1f6a2 #29 - '1C' // 1f6a3 #28 - 'd1D' // 1f6a4-1f6a8 #29 - '1X' // 1f6a9 #49 - 'U' // 1f6aa #20 - 'X' // 1f6ab #23 - 'U' // 1f6ac #20 - '1A' // 1f6ad #26 - 'cX' // 1f6ae-1f6b1 #23 - '1M' // 1f6b2 #38 - 'X' // 1f6b3 #23 - 'a1C' // 1f6b4-1f6b5 #28 - '3Q' // 1f6b6 #94 - 'aX' // 1f6b7-1f6b8 #23 - 'a1A' // 1f6b9-1f6ba #26 - 'X' // 1f6bb #23 - '1A' // 1f6bc #26 - 'U' // 1f6bd #20 - 'X' // 1f6be #23 - 'U' // 1f6bf #20 - '1C' // 1f6c0 #28 - 'U' // 1f6c1 #20 - 'cX' // 1f6c2-1f6c5 #23 - 'dM' // 1f6c6-1f6ca #12 - 'R' // 1f6cb #17 - '1C' // 1f6cc #28 - 'bR' // 1f6cd-1f6cf #17 - 'X' // 1f6d0 #23 - '8Q' // 1f6d1 #224 - 'U' // 1f6d2 #20 - 'aM' // 1f6d3-1f6d4 #12 - 'a1M' // 1f6d5-1f6d6 #38 - '1A' // 1f6d7 #26 - 'cE' // 1f6d8-1f6db - 'X' // 1f6dc #23 - 'b1D' // 1f6dd-1f6df #29 - 'aR' // 1f6e0-1f6e1 #17 - 'c1M' // 1f6e2-1f6e5 #38 - 'bM' // 1f6e6-1f6e8 #12 - '1M' // 1f6e9 #38 - 'M' // 1f6ea #12 - 'a1D' // 1f6eb-1f6ec #29 - 'bE' // 1f6ed-1f6ef - 'R' // 1f6f0 #17 - 'aM' // 1f6f1-1f6f2 #12 - '1M' // 1f6f3 #38 - 'b1D' // 1f6f4-1f6f6 #29 - '1J' // 1f6f7 #35 - '1M' // 1f6f8 #38 - '1J' // 1f6f9 #35 - 'a1M' // 1f6fa-1f6fb #38 - '1J' // 1f6fc #35 - 'bE' // 1f6fd-1f6ff - '4kS' // 1f700-1f773 #18 - 'bM' // 1f774-1f776 #12 - 'cE' // 1f777-1f77a - '3pM' // 1f77b-1f7d9 #12 - 'eE' // 1f7da-1f7df - 'h1A' // 1f7e0-1f7e8 #26 - '8B' // 1f7e9 #209 - '1A' // 1f7ea #26 - '8B' // 1f7eb #209 - 'cE' // 1f7ec-1f7ef - 'X' // 1f7f0 #23 - 'nE' // 1f7f1-1f7ff - 'kM' // 1f800-1f80b #12 - 'cE' // 1f80c-1f80f - '2cM' // 1f810-1f847 #12 - 'gE' // 1f848-1f84f - 'iM' // 1f850-1f859 #12 - 'eE' // 1f85a-1f85f - '1mM' // 1f860-1f887 #12 - 'gE' // 1f888-1f88f - '1cM' // 1f890-1f8ad #12 - 'aE' // 1f8ae-1f8af - 'aM' // 1f8b0-1f8b1 #12 - '2yE' // 1f8b2-1f8ff - 'kM' // 1f900-1f90b #12 - 'W' // 1f90c #22 - 'a3C' // 1f90d-1f90e #80 - 'mW' // 1f90f-1f91c #22 - '8T' // 1f91d #227 - 'gW' // 1f91e-1f925 #22 - '1C' // 1f926 #28 - 'hW' // 1f927-1f92f #22 - 'a1C' // 1f930-1f931 #28 - 'aW' // 1f932-1f933 #22 - 'f1C' // 1f934-1f93a #28 - 'M' // 1f93b #12 - 'b1C' // 1f93c-1f93e #28 - '1H' // 1f93f #33 - '1G' // 1f940 #32 - '1H' // 1f941 #33 - 'b1Y' // 1f942-1f944 #50 - '1H' // 1f945 #33 - 'M' // 1f946 #12 - 'h1H' // 1f947-1f94f #33 - '1e1Y' // 1f950-1f96f #50 - 'fW' // 1f970-1f976 #22 - '1C' // 1f977 #28 - 'bW' // 1f978-1f97a #22 - 'dU' // 1f97b-1f97f #20 - '1c1G' // 1f980-1f99d #32 - '8R' // 1f99e #225 - '1G' // 1f99f #32 - '3D' // 1f9a0 #81 - 'h1G' // 1f9a1-1f9a9 #32 - '8R' // 1f9aa #225 - 'c1G' // 1f9ab-1f9ae #32 - '14Z' // 1f9af #389 - 'c1C' // 1f9b0-1f9b3 #28 - 'cW' // 1f9b4-1f9b7 #22 - 'a1C' // 1f9b8-1f9b9 #28 - '14Y' // 1f9ba #388 - 'W' // 1f9bb #22 - 'a15C' // 1f9bc-1f9bd #392 - 'aW' // 1f9be-1f9bf #22 - 'k1Y' // 1f9c0-1f9cb #50 - 'a1C' // 1f9cc-1f9cd #28 - '3Q' // 1f9ce #94 - '1C' // 1f9cf #28 - 'W' // 1f9d0 #22 - 'a3Q' // 1f9d1-1f9d2 #94 - 'l1C' // 1f9d3-1f9df #28 - 'W' // 1f9e0 #22 - '3C' // 1f9e1 #80 - 'dU' // 1f9e2-1f9e6 #20 - 'b1H' // 1f9e7-1f9e9 #33 - 'bU' // 1f9ea-1f9ec #20 - '1D' // 1f9ed #29 - 'dU' // 1f9ee-1f9f2 #20 - '14X' // 1f9f3 #387 - 'U' // 1f9f4 #20 - 'a1H' // 1f9f5-1f9f6 #33 - 'hU' // 1f9f7-1f9ff #20 - '3eM' // 1fa00-1fa53 #12 - 'kE' // 1fa54-1fa5f - 'mM' // 1fa60-1fa6d #12 - 'aE' // 1fa6e-1fa6f - '1J' // 1fa70 #35 - '8C' // 1fa71 #210 - 'bR' // 1fa72-1fa74 #17 - 'b3C' // 1fa75-1fa77 #80 - '2E' // 1fa78 #56 - '14P' // 1fa79 #379 - 'R' // 1fa7a #17 - 'U' // 1fa7b #20 - '1D' // 1fa7c #29 - 'bE' // 1fa7d-1fa7f - 'a1J' // 1fa80-1fa81 #35 - '3A' // 1fa82 #78 - 'b1J' // 1fa83-1fa85 #35 - 'R' // 1fa86 #17 - 'a1H' // 1fa87-1fa88 #33 - '2T' // 1fa89 #71 - 'dE' // 1fa8a-1fa8e - '2T' // 1fa8f #71 - '1I' // 1fa90 #34 - 'bR' // 1fa91-1fa93 #17 - 'a1J' // 1fa94-1fa95 #35 - 'R' // 1fa96 #17 - 'a1J' // 1fa97-1fa98 #35 - 'gR' // 1fa99-1faa0 #17 - '1J' // 1faa1 #35 - 'eR' // 1faa2-1faa7 #17 - '1I' // 1faa8 #34 - '1H' // 1faa9 #33 - 'dU' // 1faaa-1faae #20 - 'X' // 1faaf #23 - 'f1I' // 1fab0-1fab6 #34 - 'f1G' // 1fab7-1fabd #32 - '2T' // 1fabe #71 - '1G' // 1fabf #32 - 'b2E' // 1fac0-1fac2 #56 - 'b1C' // 1fac3-1fac5 #28 - '2T' // 1fac6 #71 - 'fE' // 1fac7-1facd - 'a1G' // 1face-1facf #32 - 'f3O' // 1fad0-1fad6 #92 - 'd1Y' // 1fad7-1fadb #50 - '2T' // 1fadc #71 - 'aE' // 1fadd-1fade - '2T' // 1fadf #71 - 'fW' // 1fae0-1fae6 #22 - '1G' // 1fae7 #32 - 'W' // 1fae8 #22 - '2T' // 1fae9 #71 - 'eE' // 1faea-1faef - 'hW' // 1faf0-1faf8 #22 - 'fE' // 1faf9-1faff - '5pM' // 1fb00-1fb92 #12 - 'E' // 1fb93 - '2bM' // 1fb94-1fbca #12 - '1jE' // 1fbcb-1fbef - 'iM' // 1fbf0-1fbf9 #12 - '39zE' // 1fbfa-2000a - 'H' // 2000b #7 - 'tE' // 2000c-20020 - 'G' // 20021 #6 - '1aE' // 20022-2003d - 'G' // 2003e #6 - 'fE' // 2003f-20045 - 'G' // 20046 #6 - 'fE' // 20047-2004d - 'G' // 2004e #6 - 'xE' // 2004f-20067 - 'G' // 20068 #6 - '1bE' // 20069-20085 - 'G' // 20086 #6 - 'B' // 20087 #1 - 'E' // 20088 - 'F' // 20089 #5 - '1B' // 2008a #27 - 'hE' // 2008b-20093 - 'G' // 20094 #6 - 'lE' // 20095-200a1 - 'H' // 200a2 #7 - 'E' // 200a3 - 'H' // 200a4 #7 - 'jE' // 200a5-200af - 'H' // 200b0 #7 - 'xE' // 200b1-200c9 - 'aG' // 200ca-200cb #6 - 'B' // 200cc #1 - 'G' // 200cd #6 - 'bE' // 200ce-200d0 - 'G' // 200d1 #6 - 'dE' // 200d2-200d6 - 'P' // 200d7 #15 - 'uE' // 200d8-200ed - 'G' // 200ee #6 - 'eE' // 200ef-200f4 - 'H' // 200f5 #7 - 'uE' // 200f6-2010b - 'G' // 2010c #6 - 'E' // 2010d - 'G' // 2010e #6 - 'hE' // 2010f-20117 - 'G' // 20118 #6 - 'rE' // 20119-2012b - 'P' // 2012c #15 - '1pE' // 2012d-20157 - 'H' // 20158 #7 - 'jE' // 20159-20163 - 'D' // 20164 #3 - '2hE' // 20165-201a1 - 'H' // 201a2 #7 - 'E' // 201a3 - 'G' // 201a4 #6 - 'cE' // 201a5-201a8 - 'G' // 201a9 #6 - 'E' // 201aa - 'G' // 201ab #6 - 'tE' // 201ac-201c0 - 'G' // 201c1 #6 - 'qE' // 201c2-201d3 - 'G' // 201d4 #6 - '1bE' // 201d5-201f1 - 'G' // 201f2 #6 - 'pE' // 201f3-20203 - 'G' // 20204 #6 - 'fE' // 20205-2020b - 'G' // 2020c #6 - 'eE' // 2020d-20212 - 'H' // 20213 #7 - 'G' // 20214 #6 - '1iE' // 20215-20238 - 'G' // 20239 #6 - '1fE' // 2023a-2025a - 'G' // 2025b #6 - 'wE' // 2025c-20273 - 'aG' // 20274-20275 #6 - '1hE' // 20276-20298 - 'G' // 20299 #6 - 'cE' // 2029a-2029d - 'G' // 2029e #6 - 'E' // 2029f - 'G' // 202a0 #6 - 'uE' // 202a1-202b6 - 'G' // 202b7 #6 - 'fE' // 202b8-202be - 'aG' // 202bf-202c0 #6 - '1iE' // 202c1-202e4 - 'G' // 202e5 #6 - '1iE' // 202e6-20309 - 'G' // 2030a #6 - 'yE' // 2030b-20324 - 'G' // 20325 #6 - 'dE' // 20326-2032a - 'H' // 2032b #7 - 'tE' // 2032c-20340 - 'G' // 20341 #6 - 'bE' // 20342-20344 - 'bG' // 20345-20347 #6 - '1nE' // 20348-20370 - 'H' // 20371 #7 - 'kE' // 20372-2037d - 'bG' // 2037e-20380 #6 - 'H' // 20381 #7 - '1cE' // 20382-2039f - 'G' // 203a0 #6 - 'eE' // 203a1-203a6 - 'G' // 203a7 #6 - 'lE' // 203a8-203b4 - 'G' // 203b5 #6 - 'rE' // 203b6-203c8 - 'G' // 203c9 #6 - 'E' // 203ca - 'G' // 203cb #6 - '1nE' // 203cc-203f4 - 'G' // 203f5 #6 - 'bE' // 203f6-203f8 - 'H' // 203f9 #7 - 'aE' // 203fa-203fb - 'G' // 203fc #6 - 'uE' // 203fd-20412 - 'aG' // 20413-20414 #6 - 'iE' // 20415-2041e - 'G' // 2041f #6 - '1oE' // 20420-20449 - 'H' // 2044a #7 - 'yE' // 2044b-20464 - 'G' // 20465 #6 - '1fE' // 20466-20486 - 'G' // 20487 #6 - 'eE' // 20488-2048d - 'G' // 2048e #6 - 'aE' // 2048f-20490 - 'aG' // 20491-20492 #6 - 'oE' // 20493-204a2 - 'G' // 204a3 #6 - '1xE' // 204a4-204d6 - 'G' // 204d7 #6 - '1iE' // 204d8-204fb - 'G' // 204fc #6 - 'E' // 204fd - 'G' // 204fe #6 - 'iE' // 204ff-20508 - 'H' // 20509 #7 - '1zE' // 2050a-2053e - 'H' // 2053f #7 - 'fE' // 20540-20546 - 'G' // 20547 #6 - '2qE' // 20548-2058d - 'G' // 2058e #6 - 'uE' // 2058f-205a4 - 'G' // 205a5 #6 - 'jE' // 205a6-205b0 - 'H' // 205b1 #7 - 'E' // 205b2 - 'G' // 205b3 #6 - 'nE' // 205b4-205c2 - 'G' // 205c3 #6 - 'eE' // 205c4-205c9 - '2Q' // 205ca #68 - 'dE' // 205cb-205cf - 'G' // 205d0 #6 - 'cE' // 205d1-205d4 - 'G' // 205d5 #6 - 'H' // 205d6 #7 - 'gE' // 205d7-205de - 'aG' // 205df-205e0 #6 - 'iE' // 205e1-205ea - 'G' // 205eb #6 - '1jE' // 205ec-20610 - '1B' // 20611 #27 - 'bE' // 20612-20614 - 'G' // 20615 #6 - 'bE' // 20616-20618 - 'aG' // 20619-2061a #6 - 'lE' // 2061b-20627 - 'A' // 20628 #0 - 'fE' // 20629-2062f - 'G' // 20630 #6 - '1jE' // 20631-20655 - 'G' // 20656 #6 - '1dE' // 20657-20675 - 'B' // 20676 #1 - '4lE' // 20677-206eb - 'H' // 206ec #7 - '1fE' // 206ed-2070d - 'G' // 2070e #6 - '1gE' // 2070f-20730 - 'G' // 20731 #6 - '1bE' // 20732-2074e - 'H' // 2074f #7 - '1nE' // 20750-20778 - 'G' // 20779 #6 - '2yE' // 2077a-207c7 - 'H' // 207c8 #7 - '2iE' // 207c9-20806 - 'H' // 20807 #7 - '1iE' // 20808-2082b - 'G' // 2082c #6 - 'lE' // 2082d-20839 - 'H' // 2083a #7 - '2cE' // 2083b-20872 - 'G' // 20873 #6 - '2pE' // 20874-208b8 - 'H' // 208b9 #7 - 'zE' // 208ba-208d4 - 'G' // 208d5 #6 - '2cE' // 208d6-2090d - 'H' // 2090e #7 - 'fE' // 2090f-20915 - 'G' // 20916 #6 - 'kE' // 20917-20922 - 'G' // 20923 #6 - '1uE' // 20924-20953 - 'G' // 20954 #6 - '1iE' // 20955-20978 - 'G' // 20979 #6 - 'aE' // 2097a-2097b - 'H' // 2097c #7 - 'fE' // 2097d-20983 - 'O' // 20984 #14 - 'wE' // 20985-2099c - 'H' // 2099d #7 - '2tE' // 2099e-209e6 - 'G' // 209e7 #6 - '1nE' // 209e8-20a10 - 'G' // 20a11 #6 - '2iE' // 20a12-20a4f - 'G' // 20a50 #6 - 'rE' // 20a51-20a63 - 'H' // 20a64 #7 - 'iE' // 20a65-20a6e - 'G' // 20a6f #6 - 'yE' // 20a70-20a89 - 'G' // 20a8a #6 - '1nE' // 20a8b-20ab3 - 'G' // 20ab4 #6 - 'lE' // 20ab5-20ac1 - 'G' // 20ac2 #6 - 'iE' // 20ac3-20acc - 'G' // 20acd #6 - 'dE' // 20ace-20ad2 - 'H' // 20ad3 #7 - '2dE' // 20ad4-20b0c - 'G' // 20b0d #6 - 'nE' // 20b0e-20b1c - 'H' // 20b1d #7 - '4hE' // 20b1e-20b8e - 'G' // 20b8f #6 - 'nE' // 20b90-20b9e - '1B' // 20b9f #27 - 'gE' // 20ba0-20ba7 - 'aG' // 20ba8-20ba9 #6 - 'lE' // 20baa-20bb6 - 'H' // 20bb7 #7 - 'fE' // 20bb8-20bbe - 'G' // 20bbf #6 - 'eE' // 20bc0-20bc5 - 'G' // 20bc6 #6 - 'cE' // 20bc7-20bca - 'G' // 20bcb #6 - 'uE' // 20bcc-20be1 - 'G' // 20be2 #6 - 'gE' // 20be3-20bea - 'G' // 20beb #6 - 'nE' // 20bec-20bfa - 'G' // 20bfb #6 - 'bE' // 20bfc-20bfe - 'G' // 20bff #6 - 'jE' // 20c00-20c0a - 'G' // 20c0b #6 - 'E' // 20c0c - 'G' // 20c0d #6 - 'qE' // 20c0e-20c1f - 'G' // 20c20 #6 - 'rE' // 20c21-20c33 - 'G' // 20c34 #6 - 'dE' // 20c35-20c39 - 'aG' // 20c3a-20c3b #6 - 'dE' // 20c3c-20c40 - 'bG' // 20c41-20c43 #6 - 'nE' // 20c44-20c52 - 'G' // 20c53 #6 - 'pE' // 20c54-20c64 - 'G' // 20c65 #6 - 'pE' // 20c66-20c76 - 'aG' // 20c77-20c78 #6 - 'bE' // 20c79-20c7b - 'G' // 20c7c #6 - 'oE' // 20c7d-20c8c - 'G' // 20c8d #6 - 'gE' // 20c8e-20c95 - 'G' // 20c96 #6 - 'dE' // 20c97-20c9b - 'G' // 20c9c #6 - 'wE' // 20c9d-20cb4 - 'G' // 20cb5 #6 - 'aE' // 20cb6-20cb7 - 'G' // 20cb8 #6 - 'uE' // 20cb9-20cce - 'G' // 20ccf #6 - 'D' // 20cd0 #3 - 'aE' // 20cd1-20cd2 - 'cG' // 20cd3-20cd6 #6 - 'eE' // 20cd7-20cdc - 'G' // 20cdd #6 - 'nE' // 20cde-20cec - 'G' // 20ced #6 - 'pE' // 20cee-20cfe - 'G' // 20cff #6 - 'tE' // 20d00-20d14 - 'G' // 20d15 #6 - 'qE' // 20d16-20d27 - 'G' // 20d28 #6 - 'gE' // 20d29-20d30 - 'aG' // 20d31-20d32 #6 - 'qE' // 20d33-20d44 - 'H' // 20d45 #7 - 'cG' // 20d46-20d49 #6 - 'aE' // 20d4a-20d4b - 'bG' // 20d4c-20d4e #6 - 'hE' // 20d4f-20d57 - 'H' // 20d58 #7 - 'uE' // 20d59-20d6e - 'G' // 20d6f #6 - 'E' // 20d70 - 'G' // 20d71 #6 - 'aE' // 20d72-20d73 - 'G' // 20d74 #6 - 'fE' // 20d75-20d7b - 'G' // 20d7c #6 - 'E' // 20d7d - 'aG' // 20d7e-20d7f #6 - 'uE' // 20d80-20d95 - 'G' // 20d96 #6 - 'dE' // 20d97-20d9b - 'G' // 20d9c #6 - 'iE' // 20d9d-20da6 - 'G' // 20da7 #6 - 'iE' // 20da8-20db1 - 'G' // 20db2 #6 - 'tE' // 20db3-20dc7 - 'G' // 20dc8 #6 - 'wE' // 20dc9-20de0 - 'H' // 20de1 #7 - '1gE' // 20de2-20e03 - 'G' // 20e04 #6 - 'cE' // 20e05-20e08 - 'aG' // 20e09-20e0a #6 - 'aE' // 20e0b-20e0c - 'dG' // 20e0d-20e11 #6 - 'cE' // 20e12-20e15 - 'G' // 20e16 #6 - 'eE' // 20e17-20e1c - 'G' // 20e1d #6 - '1sE' // 20e1e-20e4b - 'G' // 20e4c #6 - 'vE' // 20e4d-20e63 - 'H' // 20e64 #7 - 'gE' // 20e65-20e6c - '1B' // 20e6d #27 - 'dE' // 20e6e-20e72 - 'G' // 20e73 #6 - 'E' // 20e74 - 'fG' // 20e75-20e7b #6 - 'oE' // 20e7c-20e8b - 'G' // 20e8c #6 - 'gE' // 20e8d-20e94 - 'H' // 20e95 #7 - 'G' // 20e96 #6 - 'E' // 20e97 - 'G' // 20e98 #6 - 'cE' // 20e99-20e9c - 'G' // 20e9d #6 - 'cE' // 20e9e-20ea1 - 'G' // 20ea2 #6 - 'fE' // 20ea3-20ea9 - 'bG' // 20eaa-20eac #6 - 'hE' // 20ead-20eb5 - 'G' // 20eb6 #6 - '1eE' // 20eb7-20ed6 - 'aG' // 20ed7-20ed8 #6 - 'cE' // 20ed9-20edc - 'G' // 20edd #6 - 'yE' // 20ede-20ef7 - 'cG' // 20ef8-20efb #6 - '1fE' // 20efc-20f1c - 'G' // 20f1d #6 - 'gE' // 20f1e-20f25 - 'G' // 20f26 #6 - 'eE' // 20f27-20f2c - 'aG' // 20f2d-20f2e #6 - 'E' // 20f2f - 'aG' // 20f30-20f31 #6 - 'hE' // 20f32-20f3a - 'G' // 20f3b #6 - 'oE' // 20f3c-20f4b - 'G' // 20f4c #6 - 'qE' // 20f4d-20f5e - 'H' // 20f5f #7 - 'cE' // 20f60-20f63 - 'G' // 20f64 #6 - '1mE' // 20f65-20f8c - 'G' // 20f8d #6 - 'aE' // 20f8e-20f8f - 'G' // 20f90 #6 - '1aE' // 20f91-20fac - 'G' // 20fad #6 - 'eE' // 20fae-20fb3 - 'bG' // 20fb4-20fb6 #6 - 'dE' // 20fb7-20fbb - 'G' // 20fbc #6 - '1gE' // 20fbd-20fde - 'G' // 20fdf #6 - 'iE' // 20fe0-20fe9 - 'cG' // 20fea-20fed #6 - '1kE' // 20fee-21013 - 'G' // 21014 #6 - 'gE' // 21015-2101c - 'aG' // 2101d-2101e #6 - '1uE' // 2101f-2104e - 'G' // 2104f #6 - 'kE' // 21050-2105b - 'G' // 2105c #6 - 'qE' // 2105d-2106e - 'G' // 2106f #6 - 'dE' // 21070-21074 - 'cG' // 21075-21078 #6 - 'aE' // 21079-2107a - 'G' // 2107b #6 - 'kE' // 2107c-21087 - 'G' // 21088 #6 - 'lE' // 21089-21095 - 'G' // 21096 #6 - 'eE' // 21097-2109c - 'G' // 2109d #6 - 'uE' // 2109e-210b3 - 'G' // 210b4 #6 - 'iE' // 210b5-210be - 'bG' // 210bf-210c1 #6 - 'dE' // 210c2-210c6 - 'bG' // 210c7-210c9 #6 - 'dE' // 210ca-210ce - 'G' // 210cf #6 - 'bE' // 210d0-210d2 - 'G' // 210d3 #6 - 'oE' // 210d4-210e3 - 'G' // 210e4 #6 - 'nE' // 210e5-210f3 - 'bG' // 210f4-210f6 #6 - '2cE' // 210f7-2112e - 'G' // 2112f #6 - 'jE' // 21130-2113a - 'G' // 2113b #6 - 'E' // 2113c - 'G' // 2113d #6 - 'fE' // 2113e-21144 - 'G' // 21145 #6 - 'aE' // 21146-21147 - 'G' // 21148 #6 - 'eE' // 21149-2114e - 'G' // 2114f #6 - 'dE' // 21150-21154 - 'P' // 21155 #15 - '1oE' // 21156-2117f - 'G' // 21180 #6 - 'eE' // 21181-21186 - 'G' // 21187 #6 - '3bE' // 21188-211d8 - 'G' // 211d9 #6 - '1lE' // 211da-21200 - 'H' // 21201 #7 - '2eE' // 21202-2123b - 'G' // 2123c #6 - 'H' // 2123d #7 - 'pE' // 2123e-2124e - 'G' // 2124f #6 - 'dE' // 21250-21254 - 'H' // 21255 #7 - '1cE' // 21256-21273 - 'H' // 21274 #7 - 'eE' // 21275-2127a - 'H' // 2127b #7 - 'G' // 2127c #6 - 'oE' // 2127d-2128c - 'P' // 2128d #15 - 'yE' // 2128e-212a7 - 'aG' // 212a8-212a9 #6 - 'eE' // 212aa-212af - 'G' // 212b0 #6 - '1kE' // 212b1-212d6 - 'H' // 212d7 #7 - 'jE' // 212d8-212e2 - 'G' // 212e3 #6 - 'H' // 212e4 #7 - 'wE' // 212e5-212fc - 'H' // 212fd #7 - 'G' // 212fe #6 - 'bE' // 212ff-21301 - 'cG' // 21302-21305 #6 - 'tE' // 21306-2131a - 'H' // 2131b #7 - 'yE' // 2131c-21335 - '1B' // 21336 #27 - 'bE' // 21337-21339 - 'G' // 2133a #6 - 'hE' // 2133b-21343 - 'H' // 21344 #7 - '1uE' // 21345-21374 - 'aG' // 21375-21376 #6 - 'vE' // 21377-2138d - 'G' // 2138e #6 - 'hE' // 2138f-21397 - 'G' // 21398 #6 - 'E' // 21399 - 'D' // 2139a #3 - 'E' // 2139b - 'G' // 2139c #6 - '1lE' // 2139d-213c3 - 'H' // 213c4 #7 - 'aG' // 213c5-213c6 #6 - '1kE' // 213c7-213ec - 'G' // 213ed #6 - 'oE' // 213ee-213fd - 'G' // 213fe #6 - 'sE' // 213ff-21412 - 'B' // 21413 #1 - 'aE' // 21414-21415 - 'G' // 21416 #6 - 'lE' // 21417-21423 - 'G' // 21424 #6 - 'yE' // 21425-2143e - 'G' // 2143f #6 - 'qE' // 21440-21451 - 'G' // 21452 #6 - 'E' // 21453 - 'aG' // 21454-21455 #6 - 'vE' // 21456-2146c - 'aH' // 2146d-2146e #7 - 'zE' // 2146f-21489 - 'G' // 2148a #6 - 'kE' // 2148b-21496 - 'G' // 21497 #6 - '1cE' // 21498-214b5 - 'G' // 214b6 #6 - '1vE' // 214b7-214e7 - 'G' // 214e8 #6 - 'sE' // 214e9-214fc - 'G' // 214fd #6 - '4pE' // 214fe-21576 - 'G' // 21577 #6 - 'iE' // 21578-21581 - 'G' // 21582 #6 - 'pE' // 21583-21593 - 'P' // 21594 #15 - 'E' // 21595 - 'G' // 21596 #6 - '2kE' // 21597-215d6 - 'F' // 215d7 #5 - '1wE' // 215d8-21609 - 'G' // 2160a #6 - 'gE' // 2160b-21612 - 'G' // 21613 #6 - 'dE' // 21614-21618 - 'G' // 21619 #6 - '1iE' // 2161a-2163d - 'G' // 2163e #6 - 'gE' // 2163f-21646 - 'H' // 21647 #7 - 'xE' // 21648-21660 - 'G' // 21661 #6 - '1uE' // 21662-21691 - 'G' // 21692 #6 - '1fE' // 21693-216b3 - 'H' // 216b4 #7 - 'bE' // 216b5-216b7 - 'G' // 216b8 #6 - 'E' // 216b9 - 'G' // 216ba #6 - 'dE' // 216bb-216bf - 'bG' // 216c0-216c2 #6 - 'oE' // 216c3-216d2 - 'G' // 216d3 #6 - 'E' // 216d4 - 'G' // 216d5 #6 - 'hE' // 216d6-216de - 'G' // 216df #6 - 'eE' // 216e0-216e5 - 'bG' // 216e6-216e8 #6 - 'pE' // 216e9-216f9 - 'bG' // 216fa-216fc #6 - 'E' // 216fd - 'G' // 216fe #6 - 'fE' // 216ff-21705 - 'H' // 21706 #7 - 'eE' // 21707-2170c - 'G' // 2170d #6 - 'aE' // 2170e-2170f - 'G' // 21710 #6 - 'tE' // 21711-21725 - 'G' // 21726 #6 - 'P' // 21727 #15 - 'qE' // 21728-21739 - 'bG' // 2173a-2173c #6 - 'dE' // 2173d-21741 - 'H' // 21742 #7 - 'sE' // 21743-21756 - 'G' // 21757 #6 - 'sE' // 21758-2176b - 'eG' // 2176c-21771 #6 - 'E' // 21772 - 'aG' // 21773-21774 #6 - '2aE' // 21775-217aa - 'G' // 217ab #6 - 'cE' // 217ac-217af - 'eG' // 217b0-217b5 #6 - 'lE' // 217b6-217c2 - 'G' // 217c3 #6 - 'bE' // 217c4-217c6 - 'G' // 217c7 #6 - 'pE' // 217c8-217d8 - 'cG' // 217d9-217dc #6 - 'aE' // 217dd-217de - 'G' // 217df #6 - 'nE' // 217e0-217ee - 'G' // 217ef #6 - 'dE' // 217f0-217f4 - 'aG' // 217f5-217f6 #6 - 'E' // 217f7 - 'dG' // 217f8-217fc #6 - '1hE' // 217fd-2181f - 'G' // 21820 #6 - 'fE' // 21821-21827 - 'bG' // 21828-2182a #6 - 'aE' // 2182b-2182c - 'G' // 2182d #6 - 'jE' // 2182e-21838 - 'bG' // 21839-2183b #6 - 'cE' // 2183c-2183f - 'G' // 21840 #6 - 'cE' // 21841-21844 - 'G' // 21845 #6 - 'kE' // 21846-21851 - 'G' // 21852 #6 - 'jE' // 21853-2185d - 'G' // 2185e #6 - 'aE' // 2185f-21860 - 'cG' // 21861-21864 #6 - 'qE' // 21865-21876 - 'G' // 21877 #6 - 'bE' // 21878-2187a - 'G' // 2187b #6 - 'fE' // 2187c-21882 - 'bG' // 21883-21885 #6 - 'wE' // 21886-2189d - 'dG' // 2189e-218a2 #6 - 'yE' // 218a3-218bc - 'H' // 218bd #7 - 'aG' // 218be-218bf #6 - 'pE' // 218c0-218d0 - 'G' // 218d1 #6 - 'cE' // 218d2-218d5 - 'cG' // 218d6-218d9 #6 - '1eE' // 218da-218f9 - 'G' // 218fa #6 - 'gE' // 218fb-21902 - 'bG' // 21903-21905 #6 - 'iE' // 21906-2190f - 'bG' // 21910-21912 #6 - 'aE' // 21913-21914 - 'G' // 21915 #6 - 'eE' // 21916-2191b - 'G' // 2191c #6 - 'dE' // 2191d-21921 - 'G' // 21922 #6 - 'cE' // 21923-21926 - 'G' // 21927 #6 - 'rE' // 21928-2193a - 'G' // 2193b #6 - 'gE' // 2193c-21943 - 'G' // 21944 #6 - 'rE' // 21945-21957 - 'G' // 21958 #6 - 'pE' // 21959-21969 - 'G' // 2196a #6 - 'pE' // 2196b-2197b - 'G' // 2197c #6 - 'bE' // 2197d-2197f - 'G' // 21980 #6 - 'aE' // 21981-21982 - 'G' // 21983 #6 - 'cE' // 21984-21987 - 'G' // 21988 #6 - 'lE' // 21989-21995 - 'G' // 21996 #6 - '1qE' // 21997-219c2 - 'H' // 219c3 #7 - 'vE' // 219c4-219da - 'G' // 219db #6 - 'vE' // 219dc-219f2 - 'G' // 219f3 #6 - '1kE' // 219f4-21a19 - 'H' // 21a1a #7 - 'qE' // 21a1b-21a2c - 'G' // 21a2d #6 - 'eE' // 21a2e-21a33 - 'G' // 21a34 #6 - 'oE' // 21a35-21a44 - 'G' // 21a45 #6 - 'dE' // 21a46-21a4a - 'G' // 21a4b #6 - 'vE' // 21a4c-21a62 - 'G' // 21a63 #6 - '8oE' // 21a64-21b43 - 'G' // 21b44 #6 - '4sE' // 21b45-21bc0 - 'aG' // 21bc1-21bc2 #6 - '3xE' // 21bc3-21c29 - 'G' // 21c2a #6 - '1pE' // 21c2b-21c55 - 'H' // 21c56 #7 - 'xE' // 21c57-21c6f - 'G' // 21c70 #6 - '1vE' // 21c71-21ca1 - 'G' // 21ca2 #6 - 'aE' // 21ca3-21ca4 - 'G' // 21ca5 #6 - 'eE' // 21ca6-21cab - 'G' // 21cac #6 - '4wE' // 21cad-21d2c - 'H' // 21d2d #7 - 'vE' // 21d2e-21d44 - 'H' // 21d45 #7 - 'G' // 21d46 #6 - 'kE' // 21d47-21d52 - 'G' // 21d53 #6 - 'iE' // 21d54-21d5d - 'G' // 21d5e #6 - 'bE' // 21d5f-21d61 - 'H' // 21d62 #7 - 'tE' // 21d63-21d77 - 'H' // 21d78 #7 - 'vE' // 21d79-21d8f - 'G' // 21d90 #6 - 'E' // 21d91 - 'H' // 21d92 #7 - 'hE' // 21d93-21d9b - 'H' // 21d9c #7 - 'cE' // 21d9d-21da0 - 'H' // 21da1 #7 - 'sE' // 21da2-21db5 - 'G' // 21db6 #6 - 'H' // 21db7 #7 - 'aE' // 21db8-21db9 - 'G' // 21dba #6 - 'nE' // 21dbb-21dc9 - 'G' // 21dca #6 - 'eE' // 21dcb-21dd0 - 'G' // 21dd1 #6 - 'mE' // 21dd2-21ddf - 'H' // 21de0 #7 - 'iE' // 21de1-21dea - 'G' // 21deb #6 - 'lE' // 21dec-21df8 - 'G' // 21df9 #6 - '1gE' // 21dfa-21e1b - 'G' // 21e1c #6 - 'eE' // 21e1d-21e22 - 'G' // 21e23 #6 - 'nE' // 21e24-21e32 - 'aH' // 21e33-21e34 #7 - 'aE' // 21e35-21e36 - 'G' // 21e37 #6 - 'dE' // 21e38-21e3c - 'G' // 21e3d #6 - '2vE' // 21e3e-21e88 - 'G' // 21e89 #6 - 'yE' // 21e8a-21ea3 - 'G' // 21ea4 #6 - 'bE' // 21ea5-21ea7 - 'G' // 21ea8 #6 - '1dE' // 21ea9-21ec7 - 'G' // 21ec8 #6 - 'kE' // 21ec9-21ed4 - 'G' // 21ed5 #6 - '2dE' // 21ed6-21f0e - 'G' // 21f0f #6 - 'dE' // 21f10-21f14 - 'G' // 21f15 #6 - 'gE' // 21f16-21f1d - 'H' // 21f1e #7 - '2hE' // 21f1f-21f5b - 'P' // 21f5c #15 - 'lE' // 21f5d-21f69 - 'G' // 21f6a #6 - 'jE' // 21f6b-21f75 - 'H' // 21f76 #7 - '1lE' // 21f77-21f9d - 'G' // 21f9e #6 - 'aE' // 21f9f-21fa0 - 'G' // 21fa1 #6 - '2qE' // 21fa2-21fe7 - 'G' // 21fe8 #6 - 'pE' // 21fe9-21ff9 - 'H' // 21ffa #7 - '2uE' // 21ffb-22044 - 'G' // 22045 #6 - 'bE' // 22046-22048 - 'G' // 22049 #6 - '1yE' // 2204a-2207d - 'G' // 2207e #6 - 'zE' // 2207f-22099 - 'G' // 2209a #6 - '1qE' // 2209b-220c6 - 'G' // 220c7 #6 - '1yE' // 220c8-220fb - 'G' // 220fc #6 - '1rE' // 220fd-22129 - 'G' // 2212a #6 - '1uE' // 2212b-2215a - 'G' // 2215b #6 - 'vE' // 2215c-22172 - 'G' // 22173 #6 - 'eE' // 22174-22179 - 'G' // 2217a #6 - 'H' // 2217b #7 - '1jE' // 2217c-221a0 - 'G' // 221a1 #6 - '1dE' // 221a2-221c0 - 'G' // 221c1 #6 - 'E' // 221c2 - 'G' // 221c3 #6 - '2oE' // 221c4-22207 - 'G' // 22208 #6 - 'nE' // 22209-22217 - 'H' // 22218 #7 - '3tE' // 22219-2227b - 'G' // 2227c #6 - '6dE' // 2227d-2231d - 'H' // 2231e #7 - 'aE' // 2231f-22320 - 'G' // 22321 #6 - 'bE' // 22322-22324 - 'G' // 22325 #6 - '5dE' // 22326-223ac - 'H' // 223ad #7 - 'nE' // 223ae-223bc - 'G' // 223bd #6 - 'qE' // 223be-223cf - 'G' // 223d0 #6 - 'eE' // 223d1-223d6 - 'G' // 223d7 #6 - '1gE' // 223d8-223f9 - 'G' // 223fa #6 - '4aE' // 223fb-22464 - 'G' // 22465 #6 - 'jE' // 22466-22470 - 'G' // 22471 #6 - 'xE' // 22472-2248a - 'G' // 2248b #6 - 'dE' // 2248c-22490 - 'G' // 22491 #6 - '1cE' // 22492-224af - '2Q' // 224b0 #68 - 'jE' // 224b1-224bb - 'G' // 224bc #6 - 'cE' // 224bd-224c0 - 'G' // 224c1 #6 - 'fE' // 224c2-224c8 - 'G' // 224c9 #6 - 'aE' // 224ca-224cb - 'G' // 224cc #6 - '1eE' // 224cd-224ec - '2Q' // 224ed #68 - '1jE' // 224ee-22512 - 'G' // 22513 #6 - 'fE' // 22514-2251a - 'G' // 2251b #6 - 'sE' // 2251c-2252f - 'G' // 22530 #6 - '1hE' // 22531-22553 - 'G' // 22554 #6 - '2cE' // 22555-2258c - 'G' // 2258d #6 - '1fE' // 2258e-225ae - 'G' // 225af #6 - 'mE' // 225b0-225bd - 'G' // 225be #6 - '2uE' // 225bf-22608 - 'H' // 22609 #7 - 'pE' // 2260a-2261a - 'aG' // 2261b-2261c #6 - 'mE' // 2261d-2262a - 'G' // 2262b #6 - '2gE' // 2262c-22667 - 'G' // 22668 #6 - 'pE' // 22669-22679 - 'G' // 2267a #6 - 'zE' // 2267b-22695 - 'G' // 22696 #6 - 'E' // 22697 - 'G' // 22698 #6 - '3kE' // 22699-226f2 - 'H' // 226f3 #7 - 'bG' // 226f4-226f6 #6 - 'zE' // 226f7-22711 - 'G' // 22712 #6 - 'E' // 22713 - 'G' // 22714 #6 - 'eE' // 22715-2271a - 'G' // 2271b #6 - 'bE' // 2271c-2271e - 'G' // 2271f #6 - 'iE' // 22720-22729 - 'G' // 2272a #6 - '2uE' // 2272b-22774 - 'G' // 22775 #6 - 'jE' // 22776-22780 - 'G' // 22781 #6 - 'sE' // 22782-22795 - 'G' // 22796 #6 - '1bE' // 22797-227b3 - 'aG' // 227b4-227b5 #6 - 'vE' // 227b6-227cc - 'G' // 227cd #6 - '1zE' // 227ce-22802 - 'G' // 22803 #6 - '3hE' // 22804-2285a - 'H' // 2285b #7 - 'bE' // 2285c-2285e - 'aG' // 2285f-22860 #6 - 'oE' // 22861-22870 - 'G' // 22871 #6 - '2dE' // 22872-228aa - 'H' // 228ab #7 - 'E' // 228ac - 'G' // 228ad #6 - 'rE' // 228ae-228c0 - 'G' // 228c1 #6 - '1zE' // 228c2-228f6 - 'G' // 228f7 #6 - '1sE' // 228f8-22925 - 'G' // 22926 #6 - 'qE' // 22927-22938 - 'G' // 22939 #6 - 'tE' // 2293a-2294e - '2Q' // 2294f #68 - 'vE' // 22950-22966 - 'G' // 22967 #6 - 'bE' // 22968-2296a - 'G' // 2296b #6 - 'sE' // 2296c-2297f - 'G' // 22980 #6 - 'mE' // 22981-2298e - 'F' // 2298f #5 - 'bE' // 22990-22992 - 'G' // 22993 #6 - '8aE' // 22994-22a65 - 'G' // 22a66 #6 - '3bE' // 22a67-22ab7 - 'H' // 22ab8 #7 - 'uE' // 22ab9-22ace - 'G' // 22acf #6 - 'dE' // 22ad0-22ad4 - 'G' // 22ad5 #6 - 'oE' // 22ad6-22ae5 - 'G' // 22ae6 #6 - 'E' // 22ae7 - 'G' // 22ae8 #6 - '1jE' // 22ae9-22b0d - 'G' // 22b0e #6 - 'rE' // 22b0f-22b21 - 'G' // 22b22 #6 - '1aE' // 22b23-22b3e - 'G' // 22b3f #6 - 'bE' // 22b40-22b42 - 'G' // 22b43 #6 - 'aE' // 22b44-22b45 - 'H' // 22b46 #7 - 'gE' // 22b47-22b4e - 'aH' // 22b4f-22b50 #7 - 'xE' // 22b51-22b69 - 'G' // 22b6a #6 - '2fE' // 22b6b-22ba5 - 'H' // 22ba6 #7 - '1hE' // 22ba7-22bc9 - 'G' // 22bca #6 - 'bE' // 22bcb-22bcd - 'G' // 22bce #6 - '2yE' // 22bcf-22c1c - 'H' // 22c1d #7 - 'eE' // 22c1e-22c23 - 'H' // 22c24 #7 - 'E' // 22c25 - 'aG' // 22c26-22c27 #6 - 'oE' // 22c28-22c37 - 'G' // 22c38 #6 - 'rE' // 22c39-22c4b - 'G' // 22c4c #6 - 'cE' // 22c4d-22c50 - 'G' // 22c51 #6 - 'bE' // 22c52-22c54 - 'G' // 22c55 #6 - 'kE' // 22c56-22c61 - 'G' // 22c62 #6 - 'kE' // 22c63-22c6e - 'P' // 22c6f #15 - 'wE' // 22c70-22c87 - 'G' // 22c88 #6 - 'qE' // 22c89-22c9a - 'G' // 22c9b #6 - 'dE' // 22c9c-22ca0 - 'G' // 22ca1 #6 - 'fE' // 22ca2-22ca8 - 'G' // 22ca9 #6 - 'gE' // 22caa-22cb1 - 'G' // 22cb2 #6 - 'cE' // 22cb3-22cb6 - 'G' // 22cb7 #6 - 'iE' // 22cb8-22cc1 - 'G' // 22cc2 #6 - 'bE' // 22cc3-22cc5 - 'G' // 22cc6 #6 - 'aE' // 22cc7-22cc8 - 'G' // 22cc9 #6 - '2hE' // 22cca-22d06 - 'aG' // 22d07-22d08 #6 - 'hE' // 22d09-22d11 - 'G' // 22d12 #6 - '1vE' // 22d13-22d43 - 'G' // 22d44 #6 - 'fE' // 22d45-22d4b - 'G' // 22d4c #6 - 'yE' // 22d4d-22d66 - 'G' // 22d67 #6 - '1jE' // 22d68-22d8c - 'G' // 22d8d #6 - 'fE' // 22d8e-22d94 - 'G' // 22d95 #6 - 'iE' // 22d96-22d9f - 'G' // 22da0 #6 - 'aE' // 22da1-22da2 - 'aG' // 22da3-22da4 #6 - 'qE' // 22da5-22db6 - 'G' // 22db7 #6 - '1nE' // 22db8-22de0 - 'H' // 22de1 #7 - 'kE' // 22de2-22ded - 'G' // 22dee #6 - '1cE' // 22def-22e0c - 'G' // 22e0d #6 - '1mE' // 22e0e-22e35 - 'G' // 22e36 #6 - 'jE' // 22e37-22e41 - '1B' // 22e42 #27 - '1zE' // 22e43-22e77 - 'G' // 22e78 #6 - 'qE' // 22e79-22e8a - 'G' // 22e8b #6 - '1lE' // 22e8c-22eb2 - 'G' // 22eb3 #6 - '1qE' // 22eb4-22edf - 'P' // 22ee0 #15 - 'mE' // 22ee1-22eee - 'G' // 22eef #6 - '5aE' // 22ef0-22f73 - 'G' // 22f74 #6 - '3hE' // 22f75-22fcb - 'G' // 22fcc #6 - 'uE' // 22fcd-22fe2 - 'G' // 22fe3 #6 - 'fE' // 22fe4-22fea - 'H' // 22feb #7 - '2rE' // 22fec-23032 - 'G' // 23033 #6 - 'oE' // 23034-23043 - 'G' // 23044 #6 - 'eE' // 23045-2304a - 'G' // 2304b #6 - 'yE' // 2304c-23065 - 'G' // 23066 #6 - 'uE' // 23067-2307c - 'aG' // 2307d-2307e #6 - 'nE' // 2307f-2308d - 'G' // 2308e #6 - '1mE' // 2308f-230b6 - 'G' // 230b7 #6 - 'cE' // 230b8-230bb - 'G' // 230bc #6 - '1bE' // 230bd-230d9 - 'G' // 230da #6 - '1gE' // 230db-230fc - 'P' // 230fd #15 - 'dE' // 230fe-23102 - 'G' // 23103 #6 - '2dE' // 23104-2313c - 'G' // 2313d #6 - '2jE' // 2313e-2317c - 'G' // 2317d #6 - 'cE' // 2317e-23181 - 'G' // 23182 #6 - '1fE' // 23183-231a3 - 'aG' // 231a4-231a5 #6 - 'lE' // 231a6-231b2 - 'G' // 231b3 #6 - 'aE' // 231b4-231b5 - 'H' // 231b6 #7 - 'kE' // 231b7-231c2 - 'aH' // 231c3-231c4 #7 - 'bE' // 231c5-231c7 - 'aG' // 231c8-231c9 #6 - '1eE' // 231ca-231e9 - 'G' // 231ea #6 - 'iE' // 231eb-231f4 - 'H' // 231f5 #7 - 'E' // 231f6 - 'bG' // 231f7-231f9 #6 - 'tE' // 231fa-2320e - 'G' // 2320f #6 - 'tE' // 23210-23224 - 'G' // 23225 #6 - 'hE' // 23226-2322e - 'G' // 2322f #6 - 'E' // 23230 - 'cG' // 23231-23234 #6 - '1fE' // 23235-23255 - 'G' // 23256 #6 - 'fE' // 23257-2325d - 'G' // 2325e #6 - 'bE' // 2325f-23261 - 'G' // 23262 #6 - '1cE' // 23263-23280 - 'G' // 23281 #6 - 'fE' // 23282-23288 - 'aG' // 23289-2328a #6 - '1eE' // 2328b-232aa - 'bG' // 232ab-232ad #6 - '1iE' // 232ae-232d1 - 'G' // 232d2 #6 - 'lE' // 232d3-232df - 'aG' // 232e0-232e1 #6 - '1cE' // 232e2-232ff - 'G' // 23300 #6 - 'hE' // 23301-23309 - 'G' // 2330a #6 - 'sE' // 2330b-2331e - 'G' // 2331f #6 - '1hE' // 23320-23342 - 'P' // 23343 #15 - '1sE' // 23344-23371 - 'H' // 23372 #7 - '2lE' // 23373-233b3 - 'G' // 233b4 #6 - 'vE' // 233b5-233cb - '1B' // 233cc #27 - 'bE' // 233cd-233cf - 'H' // 233d0 #7 - 'E' // 233d1 - 'aH' // 233d2-233d3 #7 - 'E' // 233d4 - 'H' // 233d5 #7 - 'cE' // 233d6-233d9 - 'H' // 233da #7 - 'bE' // 233db-233dd - 'G' // 233de #6 - 'H' // 233df #7 - 'cE' // 233e0-233e3 - 'H' // 233e4 #7 - 'E' // 233e5 - 'G' // 233e6 #6 - 'lE' // 233e7-233f3 - 'aG' // 233f4-233f5 #6 - 'bE' // 233f6-233f8 - 'aG' // 233f9-233fa #6 - 'bE' // 233fb-233fd - '1B' // 233fe #27 - 'E' // 233ff - 'G' // 23400 #6 - '2iE' // 23401-2343e - 'G' // 2343f #6 - 'iE' // 23440-23449 - 'aH' // 2344a-2344b #7 - 'cE' // 2344c-2344f - 'G' // 23450 #6 - 'H' // 23451 #7 - 'rE' // 23452-23464 - 'H' // 23465 #7 - 'hE' // 23466-2346e - 'G' // 2346f #6 - 'aE' // 23470-23471 - 'G' // 23472 #6 - '4hE' // 23473-234e3 - 'H' // 234e4 #7 - 'G' // 234e5 #6 - '1xE' // 234e6-23518 - 'G' // 23519 #6 - 'uE' // 2351a-2352f - 'G' // 23530 #6 - '1eE' // 23531-23550 - 'G' // 23551 #6 - 'gE' // 23552-23559 - '1B' // 2355a #27 - 'kE' // 2355b-23566 - 'G' // 23567 #6 - '1qE' // 23568-23593 - 'H' // 23594 #7 - 'G' // 23595 #6 - 'bE' // 23596-23598 - 'G' // 23599 #6 - 'aE' // 2359a-2359b - 'G' // 2359c #6 - '1cE' // 2359d-235ba - 'G' // 235bb #6 - 'gE' // 235bc-235c3 - 'H' // 235c4 #7 - 'eE' // 235c5-235ca - 'D' // 235cb #3 - 'E' // 235cc - 'bG' // 235cd-235cf #6 - '1hE' // 235d0-235f2 - 'G' // 235f3 #6 - 'kE' // 235f4-235ff - 'G' // 23600 #6 - 'uE' // 23601-23616 - 'G' // 23617 #6 - 'aE' // 23618-23619 - 'G' // 2361a #6 - '1bE' // 2361b-23637 - 'bH' // 23638-2363a #7 - 'P' // 2363b #15 - 'G' // 2363c #6 - 'bE' // 2363d-2363f - 'G' // 23640 #6 - 'eE' // 23641-23646 - 'H' // 23647 #7 - 'pE' // 23648-23658 - 'G' // 23659 #6 - 'dE' // 2365a-2365e - 'G' // 2365f #6 - 'vE' // 23660-23676 - 'G' // 23677 #6 - 'uE' // 23678-2368d - 'G' // 2368e #6 - 'nE' // 2368f-2369d - 'G' // 2369e #6 - 'fE' // 2369f-236a5 - 'G' // 236a6 #6 - 'eE' // 236a7-236ac - 'G' // 236ad #6 - 'kE' // 236ae-236b9 - 'G' // 236ba #6 - '1iE' // 236bb-236de - 'G' // 236df #6 - 'mE' // 236e0-236ed - 'G' // 236ee #6 - 'sE' // 236ef-23702 - 'G' // 23703 #6 - 'gE' // 23704-2370b - 'H' // 2370c #7 - 'hE' // 2370d-23715 - 'G' // 23716 #6 - 'dE' // 23717-2371b - 'H' // 2371c #7 - 'bE' // 2371d-2371f - 'G' // 23720 #6 - 'kE' // 23721-2372c - 'G' // 2372d #6 - 'E' // 2372e - 'G' // 2372f #6 - 'nE' // 23730-2373e - '1B' // 2373f #27 - '1hE' // 23740-23762 - 'aH' // 23763-23764 #7 - 'E' // 23765 - 'G' // 23766 #6 - 'yE' // 23767-23780 - 'G' // 23781 #6 - '1eE' // 23782-237a1 - 'G' // 237a2 #6 - 'xE' // 237a3-237bb - 'G' // 237bc #6 - 'dE' // 237bd-237c1 - 'G' // 237c2 #6 - 'qE' // 237c3-237d4 - 'bG' // 237d5-237d7 #6 - 'nE' // 237d8-237e6 - 'H' // 237e7 #7 - 'hE' // 237e8-237f0 - 'H' // 237f1 #7 - 'lE' // 237f2-237fe - 'H' // 237ff #7 - '1iE' // 23800-23823 - 'H' // 23824 #7 - 'tE' // 23825-23839 - 'G' // 2383a #6 - 'aE' // 2383b-2383c - 'H' // 2383d #7 - '14wE' // 2383e-239c1 - 'G' // 239c2 #6 - '8dE' // 239c3-23a97 - 'H' // 23a98 #7 - 'mE' // 23a99-23aa6 - 'G' // 23aa7 #6 - '1vE' // 23aa8-23ad8 - 'P' // 23ad9 #15 - 'E' // 23ada - 'G' // 23adb #6 - 'qE' // 23adc-23aed - 'G' // 23aee #6 - 'jE' // 23aef-23af9 - 'G' // 23afa #6 - '1dE' // 23afb-23b19 - 'G' // 23b1a #6 - '2jE' // 23b1b-23b59 - 'G' // 23b5a #6 - '10cE' // 23b5b-23c62 - 'G' // 23c63 #6 - 'zE' // 23c64-23c7e - 'H' // 23c7f #7 - 'vE' // 23c80-23c96 - 'aD' // 23c97-23c98 #3 - 'bG' // 23c99-23c9b #6 - 'xE' // 23c9c-23cb4 - 'G' // 23cb5 #6 - 'E' // 23cb6 - 'G' // 23cb7 #6 - 'eE' // 23cb8-23cbd - 'H' // 23cbe #7 - 'gE' // 23cbf-23cc6 - 'bG' // 23cc7-23cc9 #6 - '1wE' // 23cca-23cfb - 'aG' // 23cfc-23cfd #6 - '1B' // 23cfe #27 - 'G' // 23cff #6 - 'H' // 23d00 #7 - 'lE' // 23d01-23d0d - 'H' // 23d0e #7 - '1vE' // 23d0f-23d3f - '1B' // 23d40 #27 - 'yE' // 23d41-23d5a - 'G' // 23d5b #6 - '1gE' // 23d5c-23d7d - 'G' // 23d7e #6 - 'oE' // 23d7f-23d8e - 'G' // 23d8f #6 - '1kE' // 23d90-23db5 - 'gG' // 23db6-23dbd #6 - 'tE' // 23dbe-23dd2 - 'H' // 23dd3 #7 - 'nE' // 23dd4-23de2 - 'G' // 23de3 #6 - 'sE' // 23de4-23df7 - 'G' // 23df8 #6 - 'aH' // 23df9-23dfa #7 - 'jE' // 23dfb-23e05 - 'G' // 23e06 #6 - 'iE' // 23e07-23e10 - 'G' // 23e11 #6 - 'pE' // 23e12-23e22 - 'D' // 23e23 #3 - 'gE' // 23e24-23e2b - 'eG' // 23e2c-23e31 #6 - 'fE' // 23e32-23e38 - 'G' // 23e39 #6 - '2yE' // 23e3a-23e87 - 'cG' // 23e88-23e8b #6 - '1rE' // 23e8c-23eb8 - 'G' // 23eb9 #6 - 'dE' // 23eba-23ebe - 'G' // 23ebf #6 - 'vE' // 23ec0-23ed6 - 'G' // 23ed7 #6 - '1dE' // 23ed8-23ef6 - 'eG' // 23ef7-23efc #6 - '2cE' // 23efd-23f34 - 'G' // 23f35 #6 - 'jE' // 23f36-23f40 - 'G' // 23f41 #6 - 'gE' // 23f42-23f49 - 'G' // 23f4a #6 - 'uE' // 23f4b-23f60 - 'G' // 23f61 #6 - '1aE' // 23f62-23f7d - 'H' // 23f7e #7 - 'cG' // 23f7f-23f82 #6 - 'kE' // 23f83-23f8e - 'G' // 23f8f #6 - '1iE' // 23f90-23fb3 - 'G' // 23fb4 #6 - 'aE' // 23fb5-23fb6 - 'G' // 23fb7 #6 - 'gE' // 23fb8-23fbf - 'G' // 23fc0 #6 - 'cE' // 23fc1-23fc4 - 'G' // 23fc5 #6 - '1jE' // 23fc6-23fea - 'eG' // 23feb-23ff0 #6 - '1eE' // 23ff1-24010 - 'G' // 24011 #6 - '1lE' // 24012-24038 - 'dG' // 24039-2403d #6 - 'lE' // 2403e-2404a - 'H' // 2404b #7 - 'jE' // 2404c-24056 - 'G' // 24057 #6 - '1rE' // 24058-24084 - 'G' // 24085 #6 - 'dE' // 24086-2408a - 'bG' // 2408b-2408d #6 - 'bE' // 2408e-24090 - 'G' // 24091 #6 - 'cE' // 24092-24095 - 'H' // 24096 #7 - '1wE' // 24097-240c8 - 'G' // 240c9 #6 - 'vE' // 240ca-240e0 - 'G' // 240e1 #6 - 'iE' // 240e2-240eb - 'G' // 240ec #6 - 'uE' // 240ed-24102 - 'H' // 24103 #7 - 'G' // 24104 #6 - 'iE' // 24105-2410e - 'G' // 2410f #6 - 'hE' // 24110-24118 - 'G' // 24119 #6 - '1jE' // 2411a-2413e - 'aG' // 2413f-24140 #6 - 'bE' // 24141-24143 - 'G' // 24144 #6 - 'hE' // 24145-2414d - 'G' // 2414e #6 - 'eE' // 2414f-24154 - 'bG' // 24155-24157 #6 - 'cE' // 24158-2415b - 'G' // 2415c #6 - 'aE' // 2415d-2415e - 'G' // 2415f #6 - 'E' // 24160 - 'G' // 24161 #6 - 'tE' // 24162-24176 - 'G' // 24177 #6 - 'aE' // 24178-24179 - 'G' // 2417a #6 - '1mE' // 2417b-241a2 - 'bG' // 241a3-241a5 #6 - 'eE' // 241a6-241ab - 'G' // 241ac #6 - 'gE' // 241ad-241b4 - 'G' // 241b5 #6 - 'oE' // 241b6-241c5 - 'H' // 241c6 #7 - 'eE' // 241c7-241cc - 'G' // 241cd #6 - 'sE' // 241ce-241e1 - 'G' // 241e2 #6 - 'xE' // 241e3-241fb - 'G' // 241fc #6 - 'E' // 241fd - 'F' // 241fe #5 - 'nE' // 241ff-2420d - 'D' // 2420e #3 - 'kE' // 2420f-2421a - 'G' // 2421b #6 - '1tE' // 2421c-2424a - 'G' // 2424b #6 - 'iE' // 2424c-24255 - 'G' // 24256 #6 - 'aE' // 24257-24258 - 'G' // 24259 #6 - '1aE' // 2425a-24275 - 'bG' // 24276-24278 #6 - 'jE' // 24279-24283 - 'G' // 24284 #6 - 'mE' // 24285-24292 - 'G' // 24293 #6 - 'E' // 24294 - 'G' // 24295 #6 - 'nE' // 24296-242a4 - 'G' // 242a5 #6 - 'xE' // 242a6-242be - 'G' // 242bf #6 - 'E' // 242c0 - 'G' // 242c1 #6 - 'fE' // 242c2-242c8 - 'aG' // 242c9-242ca #6 - '1hE' // 242cb-242ed - '1B' // 242ee #27 - 'aE' // 242ef-242f0 - 'P' // 242f1 #15 - 'gE' // 242f2-242f9 - 'G' // 242fa #6 - 'qE' // 242fb-2430c - 'G' // 2430d #6 - 'kE' // 2430e-24319 - 'G' // 2431a #6 - 'xE' // 2431b-24333 - 'G' // 24334 #6 - 'rE' // 24335-24347 - 'G' // 24348 #6 - 'xE' // 24349-24361 - 'cG' // 24362-24365 #6 - '1kE' // 24366-2438b - 'G' // 2438c #6 - 'hE' // 2438d-24395 - 'G' // 24396 #6 - 'dE' // 24397-2439b - 'G' // 2439c #6 - 'P' // 2439d #15 - '1cE' // 2439e-243bb - 'H' // 243bc #7 - 'G' // 243bd #6 - 'bE' // 243be-243c0 - 'G' // 243c1 #6 - 'mE' // 243c2-243cf - 'H' // 243d0 #7 - 'wE' // 243d1-243e8 - 'aG' // 243e9-243ea #6 - 'fE' // 243eb-243f1 - 'G' // 243f2 #6 - 'dE' // 243f3-243f7 - 'G' // 243f8 #6 - 'jE' // 243f9-24403 - 'G' // 24404 #6 - '1uE' // 24405-24434 - 'aG' // 24435-24436 #6 - '1hE' // 24437-24459 - 'aG' // 2445a-2445b #6 - 'vE' // 2445c-24472 - 'G' // 24473 #6 - 'rE' // 24474-24486 - 'aG' // 24487-24488 #6 - '1uE' // 24489-244b8 - 'G' // 244b9 #6 - 'aE' // 244ba-244bb - 'G' // 244bc #6 - 'pE' // 244bd-244cd - 'G' // 244ce #6 - 'cE' // 244cf-244d2 - 'G' // 244d3 #6 - 'aE' // 244d4-244d5 - 'G' // 244d6 #6 - '1sE' // 244d7-24504 - 'G' // 24505 #6 - 'zE' // 24506-24520 - 'G' // 24521 #6 - '3gE' // 24522-24577 - 'G' // 24578 #6 - '2zE' // 24579-245c7 - 'G' // 245c8 #6 - '2zE' // 245c9-24617 - 'G' // 24618 #6 - 'oE' // 24619-24628 - 'H' // 24629 #7 - 'G' // 2462a #6 - '2eE' // 2462b-24664 - 'G' // 24665 #6 - 'mE' // 24666-24673 - 'G' // 24674 #6 - '1gE' // 24675-24696 - 'G' // 24697 #6 - 'lE' // 24698-246a4 - 'H' // 246a5 #7 - '1sE' // 246a6-246d3 - 'G' // 246d4 #6 - '1vE' // 246d5-24705 - 'G' // 24706 #6 - '1cE' // 24707-24724 - 'G' // 24725 #6 - 'hE' // 24726-2472e - 'G' // 2472f #6 - '3pE' // 24730-2478e - 'G' // 2478f #6 - '3aE' // 24790-247df - 'G' // 247e0 #6 - 'oE' // 247e1-247f0 - 'H' // 247f1 #7 - '1eE' // 247f2-24811 - 'G' // 24812 #6 - 'oE' // 24813-24822 - 'G' // 24823 #6 - '3oE' // 24824-24881 - 'G' // 24882 #6 - 'rE' // 24883-24895 - 'H' // 24896 #7 - '3cE' // 24897-248e8 - 'A' // 248e9 #0 - 'eE' // 248ea-248ef - '2Q' // 248f0 #68 - 'bG' // 248f1-248f3 #6 - 'fE' // 248f4-248fa - 'G' // 248fb #6 - 'bE' // 248fc-248fe - 'bG' // 248ff-24901 #6 - 'iE' // 24902-2490b - 'G' // 2490c #6 - 'hE' // 2490d-24915 - 'aG' // 24916-24917 #6 - 'E' // 24918 - 'G' // 24919 #6 - 'tE' // 2491a-2492e - 'G' // 2492f #6 - 'bE' // 24930-24932 - 'aG' // 24933-24934 #6 - 'hE' // 24935-2493d - 'eG' // 2493e-24943 #6 - '1cE' // 24944-24961 - 'aG' // 24962-24963 #6 - 'oE' // 24964-24973 - 'bG' // 24974-24976 #6 - 'cE' // 24977-2497a - 'G' // 2497b #6 - 'bE' // 2497c-2497e - 'G' // 2497f #6 - 'aE' // 24980-24981 - 'G' // 24982 #6 - 'dE' // 24983-24987 - 'gG' // 24988-2498f #6 - 'cE' // 24990-24993 - 'G' // 24994 #6 - 'nE' // 24995-249a3 - 'G' // 249a4 #6 - 'aE' // 249a5-249a6 - 'G' // 249a7 #6 - 'E' // 249a8 - 'G' // 249a9 #6 - 'E' // 249aa - 'bG' // 249ab-249ad #6 - 'hE' // 249ae-249b6 - 'dG' // 249b7-249bb #6 - 'hE' // 249bc-249c4 - 'G' // 249c5 #6 - 'iE' // 249c6-249cf - 'G' // 249d0 #6 - 'hE' // 249d1-249d9 - 'G' // 249da #6 - 'D' // 249db #3 - 'aE' // 249dc-249dd - 'aG' // 249de-249df #6 - 'bE' // 249e0-249e2 - 'G' // 249e3 #6 - 'E' // 249e4 - 'G' // 249e5 #6 - 'eE' // 249e6-249eb - 'aG' // 249ec-249ed #6 - 'gE' // 249ee-249f5 - 'cG' // 249f6-249f9 #6 - 'E' // 249fa - 'G' // 249fb #6 - 'dE' // 249fc-24a00 - 'K' // 24a01 #10 - 'kE' // 24a02-24a0d - 'G' // 24a0e #6 - 'bE' // 24a0f-24a11 - '2Q' // 24a12 #68 - 'G' // 24a13 #6 - 'E' // 24a14 - 'G' // 24a15 #6 - 'jE' // 24a16-24a20 - 'iG' // 24a21-24a2a #6 - 'rE' // 24a2b-24a3d - 'G' // 24a3e #6 - 'bE' // 24a3f-24a41 - 'G' // 24a42 #6 - 'aE' // 24a43-24a44 - 'G' // 24a45 #6 - 'cE' // 24a46-24a49 - 'G' // 24a4a #6 - 'aE' // 24a4b-24a4c - 'H' // 24a4d #7 - 'cG' // 24a4e-24a51 #6 - 'jE' // 24a52-24a5c - 'G' // 24a5d #6 - 'fE' // 24a5e-24a64 - 'bG' // 24a65-24a67 #6 - 'hE' // 24a68-24a70 - 'G' // 24a71 #6 - 'dE' // 24a72-24a76 - 'cG' // 24a77-24a7a #6 - 'aE' // 24a7b-24a7c - 'D' // 24a7d #3 - 'mE' // 24a7e-24a8b - 'G' // 24a8c #6 - 'eE' // 24a8d-24a92 - 'cG' // 24a93-24a96 #6 - 'lE' // 24a97-24aa3 - 'cG' // 24aa4-24aa7 #6 - 'hE' // 24aa8-24ab0 - 'bG' // 24ab1-24ab3 #6 - 'eE' // 24ab4-24ab9 - 'bG' // 24aba-24abc #6 - 'bE' // 24abd-24abf - 'G' // 24ac0 #6 - 'eE' // 24ac1-24ac6 - 'G' // 24ac7 #6 - 'E' // 24ac8 - 'D' // 24ac9 #3 - 'G' // 24aca #6 - 'eE' // 24acb-24ad0 - 'G' // 24ad1 #6 - 'lE' // 24ad2-24ade - 'G' // 24adf #6 - 'aE' // 24ae0-24ae1 - 'G' // 24ae2 #6 - 'eE' // 24ae3-24ae8 - 'G' // 24ae9 #6 - '1jE' // 24aea-24b0e - 'G' // 24b0f #6 - '2qE' // 24b10-24b55 - 'H' // 24b56 #7 - 'vE' // 24b57-24b6d - 'G' // 24b6e #6 - 'H' // 24b6f #7 - '5bE' // 24b70-24bf4 - 'G' // 24bf5 #6 - 'rE' // 24bf6-24c08 - 'G' // 24c09 #6 - 'kE' // 24c0a-24c15 - 'H' // 24c16 #7 - '5dE' // 24c17-24c9d - 'aG' // 24c9e-24c9f #6 - '1nE' // 24ca0-24cc8 - 'G' // 24cc9 #6 - 'nE' // 24cca-24cd8 - 'G' // 24cd9 #6 - '1qE' // 24cda-24d05 - 'G' // 24d06 #6 - 'kE' // 24d07-24d12 - 'G' // 24d13 #6 - 'H' // 24d14 #7 - '6fE' // 24d15-24db7 - 'G' // 24db8 #6 - '1vE' // 24db9-24de9 - 'aG' // 24dea-24deb #6 - 'wE' // 24dec-24e03 - 'H' // 24e04 #7 - 'hE' // 24e05-24e0d - 'H' // 24e0e #7 - '1mE' // 24e0f-24e36 - 'H' // 24e37 #7 - 'bE' // 24e38-24e3a - 'G' // 24e3b #6 - 'sE' // 24e3c-24e4f - 'G' // 24e50 #6 - 'xE' // 24e51-24e69 - 'H' // 24e6a #7 - '1eE' // 24e6b-24e8a - 'H' // 24e8b #7 - 'xE' // 24e8c-24ea4 - 'G' // 24ea5 #6 - 'E' // 24ea6 - 'G' // 24ea7 #6 - '3wE' // 24ea8-24f0d - 'G' // 24f0e #6 - '2xE' // 24f0f-24f5b - 'G' // 24f5c #6 - '1jE' // 24f5d-24f81 - 'G' // 24f82 #6 - 'bE' // 24f83-24f85 - 'G' // 24f86 #6 - 'oE' // 24f87-24f96 - 'G' // 24f97 #6 - 'aE' // 24f98-24f99 - 'G' // 24f9a #6 - 'mE' // 24f9b-24fa8 - 'G' // 24fa9 #6 - 'mE' // 24faa-24fb7 - 'G' // 24fb8 #6 - 'hE' // 24fb9-24fc1 - 'G' // 24fc2 #6 - '1tE' // 24fc3-24ff1 - 'H' // 24ff2 #7 - '2dE' // 24ff3-2502b - 'G' // 2502c #6 - '1bE' // 2502d-25049 - 'H' // 2504a #7 - 'fE' // 2504b-25051 - 'G' // 25052 #6 - 'aE' // 25053-25054 - 'O' // 25055 #14 - '2rE' // 25056-2509c - 'G' // 2509d #6 - '5aE' // 2509e-25121 - 'H' // 25122 #7 - 'gE' // 25123-2512a - 'G' // 2512b #6 - '1aE' // 2512c-25147 - 'G' // 25148 #6 - '1yE' // 25149-2517c - 'aG' // 2517d-2517e #6 - '1oE' // 2517f-251a8 - 'H' // 251a9 #7 - '1hE' // 251aa-251cc - '1B' // 251cd #27 - 'tE' // 251ce-251e2 - 'G' // 251e3 #6 - 'E' // 251e4 - 'H' // 251e5 #7 - 'aG' // 251e6-251e7 #6 - '2aE' // 251e8-2521d - 'H' // 2521e #7 - 'E' // 2521f - 'aG' // 25220-25221 #6 - '1oE' // 25222-2524b - 'H' // 2524c #7 - 'bE' // 2524d-2524f - 'G' // 25250 #6 - '2sE' // 25251-25298 - 'G' // 25299 #6 - '1rE' // 2529a-252c6 - 'G' // 252c7 #6 - 'oE' // 252c8-252d7 - 'G' // 252d8 #6 - '1zE' // 252d9-2530d - 'G' // 2530e #6 - 'aE' // 2530f-25310 - 'G' // 25311 #6 - 'E' // 25312 - 'G' // 25313 #6 - '1oE' // 25314-2533d - 'P' // 2533e #15 - '4mE' // 2533f-253b4 - 'P' // 253b5 #15 - '2sE' // 253b6-253fd - 'P' // 253fe #15 - 'yE' // 253ff-25418 - 'G' // 25419 #6 - 'jE' // 2541a-25424 - 'G' // 25425 #6 - 'gE' // 25426-2542d - 'H' // 2542e #7 - 'aG' // 2542f-25430 #6 - 'tE' // 25431-25445 - 'G' // 25446 #6 - '1jE' // 25447-2546b - 'G' // 2546c #6 - 'E' // 2546d - 'G' // 2546e #6 - '1dE' // 2546f-2548d - 'H' // 2548e #7 - 'jE' // 2548f-25499 - 'G' // 2549a #6 - '2iE' // 2549b-254d8 - 'H' // 254d9 #7 - '1yE' // 254da-2550d - 'H' // 2550e #7 - '1gE' // 2550f-25530 - 'G' // 25531 #6 - 'D' // 25532 #3 - 'aE' // 25533-25534 - 'G' // 25535 #6 - 'hE' // 25536-2553e - 'G' // 2553f #6 - 'zE' // 25540-2555a - 'cG' // 2555b-2555e #6 - 'bE' // 2555f-25561 - 'B' // 25562 #1 - 'aE' // 25563-25564 - 'aG' // 25565-25566 #6 - 'yE' // 25567-25580 - 'G' // 25581 #6 - 'aE' // 25582-25583 - 'G' // 25584 #6 - 'iE' // 25585-2558e - 'G' // 2558f #6 - 'vE' // 25590-255a6 - 'H' // 255a7 #7 - 'D' // 255a8 #3 - 'oE' // 255a9-255b8 - 'G' // 255b9 #6 - 'zE' // 255ba-255d4 - 'G' // 255d5 #6 - 'dE' // 255d6-255da - 'G' // 255db #6 - 'cE' // 255dc-255df - 'G' // 255e0 #6 - '1iE' // 255e1-25604 - 'G' // 25605 #6 - '1tE' // 25606-25634 - 'G' // 25635 #6 - 'zE' // 25636-25650 - 'G' // 25651 #6 - '1rE' // 25652-2567e - 'H' // 2567f #7 - 'bE' // 25680-25682 - 'G' // 25683 #6 - 'pE' // 25684-25694 - 'G' // 25695 #6 - '2xE' // 25696-256e2 - 'G' // 256e3 #6 - 'qE' // 256e4-256f5 - 'G' // 256f6 #6 - 'nE' // 256f7-25705 - 'G' // 25706 #6 - 'uE' // 25707-2571c - 'G' // 2571d #6 - 'fE' // 2571e-25724 - 'G' // 25725 #6 - 'vE' // 25726-2573c - 'G' // 2573d #6 - '1xE' // 2573e-25770 - 'H' // 25771 #7 - 'G' // 25772 #6 - '2aE' // 25773-257a8 - 'H' // 257a9 #7 - 'iE' // 257aa-257b3 - 'H' // 257b4 #7 - 'qE' // 257b5-257c6 - 'G' // 257c7 #6 - 'vE' // 257c8-257de - 'bG' // 257df-257e1 #6 - '3aE' // 257e2-25831 - 'P' // 25832 #15 - 'fE' // 25833-25839 - 'P' // 2583a #15 - '1aE' // 2583b-25856 - 'G' // 25857 #6 - 'dE' // 25858-2585c - 'G' // 2585d #6 - 'sE' // 2585e-25871 - 'G' // 25872 #6 - 'E' // 25873 - 'O' // 25874 #14 - '3dE' // 25875-258c7 - 'G' // 258c8 #6 - 'tE' // 258c9-258dd - 'G' // 258de #6 - 'aE' // 258df-258e0 - 'G' // 258e1 #6 - '1fE' // 258e2-25902 - 'G' // 25903 #6 - '2mE' // 25904-25945 - 'G' // 25946 #6 - 'nE' // 25947-25955 - 'G' // 25956 #6 - '1fE' // 25957-25977 - 'P' // 25978 #15 - '1xE' // 25979-259ab - 'G' // 259ac #6 - 'vE' // 259ad-259c3 - 'H' // 259c4 #7 - 'fE' // 259c5-259cb - '1B' // 259cc #27 - 'fE' // 259cd-259d3 - 'H' // 259d4 #7 - '4vE' // 259d5-25a53 - 'G' // 25a54 #6 - '2kE' // 25a55-25a94 - 'G' // 25a95 #6 - 'eE' // 25a96-25a9b - 'G' // 25a9c #6 - 'pE' // 25a9d-25aad - 'aG' // 25aae-25aaf #6 - '1lE' // 25ab0-25ad6 - 'A' // 25ad7 #0 - 'jE' // 25ad8-25ae2 - 'aH' // 25ae3-25ae4 #7 - 'cE' // 25ae5-25ae8 - 'G' // 25ae9 #6 - 'fE' // 25aea-25af0 - 'H' // 25af1 #7 - '4yE' // 25af2-25b73 - 'G' // 25b74 #6 - 'sE' // 25b75-25b88 - 'G' // 25b89 #6 - 'lE' // 25b8a-25b96 - 'P' // 25b97 #15 - 'yE' // 25b98-25bb1 - 'H' // 25bb2 #7 - 'aG' // 25bb3-25bb4 #6 - 'pE' // 25bb5-25bc5 - 'G' // 25bc6 #6 - '1bE' // 25bc7-25be3 - 'G' // 25be4 #6 - 'bE' // 25be5-25be7 - 'G' // 25be8 #6 - 'wE' // 25be9-25c00 - 'G' // 25c01 #6 - 'cE' // 25c02-25c05 - 'G' // 25c06 #6 - 'yE' // 25c07-25c20 - 'G' // 25c21 #6 - '1mE' // 25c22-25c49 - 'G' // 25c4a #6 - 'H' // 25c4b #7 - 'wE' // 25c4c-25c63 - 'H' // 25c64 #7 - 'G' // 25c65 #6 - '1pE' // 25c66-25c90 - 'G' // 25c91 #6 - 'qE' // 25c92-25ca3 - 'G' // 25ca4 #6 - 'zE' // 25ca5-25cbf - 'aG' // 25cc0-25cc1 #6 - '2gE' // 25cc2-25cfd - 'G' // 25cfe #6 - '1fE' // 25cff-25d1f - 'G' // 25d20 #6 - 'nE' // 25d21-25d2f - 'G' // 25d30 #6 - 'qE' // 25d31-25d42 - 'G' // 25d43 #6 - '3fE' // 25d44-25d98 - 'G' // 25d99 #6 - 'fE' // 25d9a-25da0 - 'H' // 25da1 #7 - 'vE' // 25da2-25db8 - 'G' // 25db9 #6 - '3eE' // 25dba-25e0d - 'G' // 25e0e #6 - '1dE' // 25e0f-25e2d - 'H' // 25e2e #7 - 'tE' // 25e2f-25e43 - 'P' // 25e44 #15 - 'cE' // 25e45-25e48 - 'G' // 25e49 #6 - 'kE' // 25e4a-25e55 - 'H' // 25e56 #7 - 'jE' // 25e57-25e61 - 'H' // 25e62 #7 - 'aE' // 25e63-25e64 - 'H' // 25e65 #7 - 'zE' // 25e66-25e80 - 'bG' // 25e81-25e83 #6 - '1gE' // 25e84-25ea5 - 'G' // 25ea6 #6 - 'tE' // 25ea7-25ebb - 'G' // 25ebc #6 - 'dE' // 25ebd-25ec1 - 'H' // 25ec2 #7 - 'sE' // 25ec3-25ed6 - 'B' // 25ed7 #1 - '1B' // 25ed8 #27 - 'nE' // 25ed9-25ee7 - 'H' // 25ee8 #7 - '1vE' // 25ee9-25f19 - 'G' // 25f1a #6 - 'gE' // 25f1b-25f22 - 'H' // 25f23 #7 - '1lE' // 25f24-25f4a - 'G' // 25f4b #6 - 'oE' // 25f4c-25f5b - 'H' // 25f5c #7 - '4nE' // 25f5d-25fd3 - 'H' // 25fd4 #7 - 'jE' // 25fd5-25fdf - 'H' // 25fe0 #7 - 'aG' // 25fe1-25fe2 #6 - 'wE' // 25fe3-25ffa - 'H' // 25ffb #7 - 'oE' // 25ffc-2600b - 'H' // 2600c #7 - 'iE' // 2600d-26016 - 'H' // 26017 #7 - 'hE' // 26018-26020 - 'G' // 26021 #6 - 'fE' // 26022-26028 - 'G' // 26029 #6 - '1cE' // 2602a-26047 - 'G' // 26048 #6 - 'mE' // 26049-26056 - 'P' // 26057 #15 - 'gE' // 26058-2605f - 'H' // 26060 #7 - 'bE' // 26061-26063 - 'G' // 26064 #6 - '1cE' // 26065-26082 - 'G' // 26083 #6 - 'rE' // 26084-26096 - 'G' // 26097 #6 - 'kE' // 26098-260a3 - 'aG' // 260a4-260a5 #6 - '2rE' // 260a6-260ec - 'H' // 260ed #7 - 'sE' // 260ee-26101 - 'G' // 26102 #6 - '1cE' // 26103-26120 - 'G' // 26121 #6 - '2bE' // 26122-26158 - 'cG' // 26159-2615c #6 - '3aE' // 2615d-261ac - 'aG' // 261ad-261ae #6 - 'bE' // 261af-261b1 - 'G' // 261b2 #6 - '1oE' // 261b3-261dc - 'G' // 261dd #6 - '2nE' // 261de-26220 - 'D' // 26221 #3 - 'H' // 26222 #7 - '1zE' // 26223-26257 - 'G' // 26258 #6 - 'gE' // 26259-26260 - 'G' // 26261 #6 - 'gE' // 26262-26269 - '1B' // 2626a #27 - 'G' // 2626b #6 - 'cE' // 2626c-2626f - 'H' // 26270 #7 - 'tE' // 26271-26285 - 'H' // 26286 #7 - '2tE' // 26287-262cf - 'G' // 262d0 #6 - '3uE' // 262d1-26334 - 'G' // 26335 #6 - 'tE' // 26336-2634a - 'G' // 2634b #6 - '1B' // 2634c #27 - 'cE' // 2634d-26350 - 'G' // 26351 #6 - '4cE' // 26352-263bd - 'G' // 263be #6 - '2aE' // 263bf-263f4 - 'G' // 263f5 #6 - 'aE' // 263f6-263f7 - 'G' // 263f8 #6 - 'hE' // 263f9-26401 - '1B' // 26402 #27 - 'lE' // 26403-2640f - 'bG' // 26410-26412 #6 - '2bE' // 26413-26449 - 'G' // 2644a #6 - '1cE' // 2644b-26468 - 'G' // 26469 #6 - 'yE' // 2646a-26483 - 'G' // 26484 #6 - 'bE' // 26485-26487 - 'aG' // 26488-26489 #6 - 'bE' // 2648a-2648c - 'B' // 2648d #1 - 'iE' // 2648e-26497 - 'G' // 26498 #6 - '4pE' // 26499-26511 - 'G' // 26512 #6 - '3pE' // 26513-26571 - 'G' // 26572 #6 - '1rE' // 26573-2659f - 'G' // 265a0 #6 - 'bE' // 265a1-265a3 - 'P' // 265a4 #15 - 'gE' // 265a5-265ac - 'G' // 265ad #6 - 'pE' // 265ae-265be - 'G' // 265bf #6 - '3cE' // 265c0-26611 - 'G' // 26612 #6 - 'rE' // 26613-26625 - 'G' // 26626 #6 - '2zE' // 26627-26675 - 'D' // 26676 #3 - 'fE' // 26677-2667d - 'H' // 2667e #7 - '1uE' // 2667f-266ae - 'G' // 266af #6 - 'H' // 266b0 #7 - 'G' // 266b1 #6 - 'bE' // 266b2-266b4 - 'G' // 266b5 #6 - '1iE' // 266b6-266d9 - 'G' // 266da #6 - 'lE' // 266db-266e7 - 'G' // 266e8 #6 - 'rE' // 266e9-266fb - 'G' // 266fc #6 - 'xE' // 266fd-26715 - 'G' // 26716 #6 - 'eE' // 26717-2671c - 'H' // 2671d #7 - '1hE' // 2671e-26740 - 'G' // 26741 #6 - '2eE' // 26742-2677b - 'D' // 2677c #3 - '1aE' // 2677d-26798 - 'G' // 26799 #6 - 'xE' // 2679a-267b2 - 'aG' // 267b3-267b4 #6 - 'vE' // 267b5-267cb - 'G' // 267cc #6 - 'jE' // 267cd-267d7 - 'P' // 267d8 #15 - '2nE' // 267d9-2681b - 'G' // 2681c #6 - '1nE' // 2681d-26845 - 'G' // 26846 #6 - 'vE' // 26847-2685d - 'G' // 2685e #6 - 'nE' // 2685f-2686d - 'G' // 2686e #6 - 'xE' // 2686f-26887 - 'G' // 26888 #6 - 'E' // 26889 - 'G' // 2688a #6 - 'gE' // 2688b-26892 - 'G' // 26893 #6 - '1xE' // 26894-268c6 - 'G' // 268c7 #6 - 'tE' // 268c8-268dc - 'H' // 268dd #7 - 'kE' // 268de-268e9 - 'H' // 268ea #7 - '1hE' // 268eb-2690d - 'G' // 2690e #6 - 'aE' // 2690f-26910 - 'G' // 26911 #6 - 'sE' // 26912-26925 - 'G' // 26926 #6 - 'qE' // 26927-26938 - 'G' // 26939 #6 - 'vE' // 2693a-26950 - 'A' // 26951 #0 - '1bE' // 26952-2696e - 'H' // 2696f #7 - '1nE' // 26970-26998 - 'H' // 26999 #7 - 'mE' // 2699a-269a7 - 'G' // 269a8 #6 - 'kE' // 269a9-269b4 - 'G' // 269b5 #6 - '1lE' // 269b6-269dc - 'H' // 269dd #7 - 'sE' // 269de-269f1 - 'G' // 269f2 #6 - 'fE' // 269f3-269f9 - 'G' // 269fa #6 - '1hE' // 269fb-26a1d - 'H' // 26a1e #7 - 'mE' // 26a1f-26a2c - 'aG' // 26a2d-26a2e #6 - 'dE' // 26a2f-26a33 - 'G' // 26a34 #6 - 'lE' // 26a35-26a41 - 'G' // 26a42 #6 - 'mE' // 26a43-26a50 - 'aG' // 26a51-26a52 #6 - 'dE' // 26a53-26a57 - 'H' // 26a58 #7 - '1xE' // 26a59-26a8b - 'H' // 26a8c #7 - '1oE' // 26a8d-26ab6 - 'H' // 26ab7 #7 - '2rE' // 26ab8-26afe - 'H' // 26aff #7 - 'dE' // 26b00-26b04 - 'G' // 26b05 #6 - 'cE' // 26b06-26b09 - 'G' // 26b0a #6 - 'gE' // 26b0b-26b12 - 'G' // 26b13 #6 - 'E' // 26b14 - 'G' // 26b15 #6 - 'lE' // 26b16-26b22 - 'G' // 26b23 #6 - 'cE' // 26b24-26b27 - 'G' // 26b28 #6 - '1lE' // 26b29-26b4f - 'cG' // 26b50-26b53 #6 - 'fE' // 26b54-26b5a - 'G' // 26b5b #6 - 'D' // 26b5c #3 - 'wE' // 26b5d-26b74 - 'G' // 26b75 #6 - 'kE' // 26b76-26b81 - 'G' // 26b82 #6 - 'rE' // 26b83-26b95 - 'aG' // 26b96-26b97 #6 - 'dE' // 26b98-26b9c - 'G' // 26b9d #6 - 'tE' // 26b9e-26bb2 - 'G' // 26bb3 #6 - 'kE' // 26bb4-26bbf - 'G' // 26bc0 #6 - '2aE' // 26bc1-26bf6 - 'G' // 26bf7 #6 - '1nE' // 26bf8-26c20 - 'B' // 26c21 #1 - 'fE' // 26c22-26c28 - 'H' // 26c29 #7 - 'uE' // 26c2a-26c3f - 'aG' // 26c40-26c41 #6 - 'cE' // 26c42-26c45 - 'G' // 26c46 #6 - '1qE' // 26c47-26c72 - 'H' // 26c73 #7 - 'iE' // 26c74-26c7d - 'dG' // 26c7e-26c82 #6 - 'zE' // 26c83-26c9d - 'H' // 26c9e #7 - 'dE' // 26c9f-26ca3 - 'G' // 26ca4 #6 - 'qE' // 26ca5-26cb6 - 'aG' // 26cb7-26cb8 #6 - 'cE' // 26cb9-26cbc - 'G' // 26cbd #6 - 'aE' // 26cbe-26cbf - 'G' // 26cc0 #6 - 'aE' // 26cc1-26cc2 - 'G' // 26cc3 #6 - 'lE' // 26cc4-26cd0 - 'G' // 26cd1 #6 - 'jE' // 26cd2-26cdc - 'H' // 26cdd #7 - '2oE' // 26cde-26d21 - 'hG' // 26d22-26d2a #6 - '1kE' // 26d2b-26d50 - 'G' // 26d51 #6 - '1gE' // 26d52-26d73 - 'G' // 26d74 #6 - '1pE' // 26d75-26d9f - 'gG' // 26da0-26da7 #6 - 'eE' // 26da8-26dad - 'G' // 26dae #6 - '1rE' // 26daf-26ddb - 'G' // 26ddc #6 - 'lE' // 26ddd-26de9 - 'aG' // 26dea-26deb #6 - 'cE' // 26dec-26def - 'G' // 26df0 #6 - 'nE' // 26df1-26dff - 'G' // 26e00 #6 - 'cE' // 26e01-26e04 - 'G' // 26e05 #6 - 'E' // 26e06 - 'G' // 26e07 #6 - 'iE' // 26e08-26e11 - 'G' // 26e12 #6 - '1rE' // 26e13-26e3f - 'H' // 26e40 #7 - 'E' // 26e41 - 'cG' // 26e42-26e45 #6 - '1dE' // 26e46-26e64 - 'H' // 26e65 #7 - 'gE' // 26e66-26e6d - 'G' // 26e6e #6 - 'bE' // 26e6f-26e71 - 'G' // 26e72 #6 - 'cE' // 26e73-26e76 - 'G' // 26e77 #6 - 'kE' // 26e78-26e83 - 'G' // 26e84 #6 - 'bE' // 26e85-26e87 - 'G' // 26e88 #6 - 'aE' // 26e89-26e8a - 'G' // 26e8b #6 - 'lE' // 26e8c-26e98 - 'G' // 26e99 #6 - '2aE' // 26e9a-26ecf - 'gG' // 26ed0-26ed7 #6 - '2yE' // 26ed8-26f25 - 'G' // 26f26 #6 - '2wE' // 26f27-26f72 - 'aG' // 26f73-26f74 #6 - '1dE' // 26f75-26f93 - 'H' // 26f94 #7 - 'iE' // 26f95-26f9e - 'G' // 26f9f #6 - 'E' // 26fa0 - 'G' // 26fa1 #6 - '1aE' // 26fa2-26fbd - 'G' // 26fbe #6 - '1dE' // 26fbf-26fdd - 'aG' // 26fde-26fdf #6 - 'uE' // 26fe0-26ff5 - 'bH' // 26ff6-26ff8 #7 - 'tE' // 26ff9-2700d - 'G' // 2700e #6 - '2gE' // 2700f-2704a - 'G' // 2704b #6 - 'eE' // 2704c-27051 - 'aG' // 27052-27053 #6 - '1yE' // 27054-27087 - 'G' // 27088 #6 - '1iE' // 27089-270ac - 'bG' // 270ad-270af #6 - '1bE' // 270b0-270cc - 'G' // 270cd #6 - 'cE' // 270ce-270d1 - 'G' // 270d2 #6 - '1bE' // 270d3-270ef - 'G' // 270f0 #6 - 'bE' // 270f1-270f3 - 'H' // 270f4 #7 - 'bE' // 270f5-270f7 - 'G' // 270f8 #6 - 'oE' // 270f9-27108 - 'G' // 27109 #6 - 'aE' // 2710a-2710b - 'G' // 2710c #6 - '1B' // 2710d #27 - 'wE' // 2710e-27125 - 'aG' // 27126-27127 #6 - 'pE' // 27128-27138 - 'H' // 27139 #7 - 'iE' // 2713a-27143 - 'P' // 27144 #15 - '1dE' // 27145-27163 - 'aG' // 27164-27165 #6 - 'nE' // 27166-27174 - 'G' // 27175 #6 - '3hE' // 27176-271cc - 'G' // 271cd #6 - '2xE' // 271ce-2721a - 'G' // 2721b #6 - '2vE' // 2721c-27266 - 'G' // 27267 #6 - 'wE' // 27268-2727f - 'G' // 27280 #6 - 'cE' // 27281-27284 - 'G' // 27285 #6 - 'dE' // 27286-2728a - 'G' // 2728b #6 - '1kE' // 2728c-272b1 - 'G' // 272b2 #6 - 'bE' // 272b3-272b5 - 'G' // 272b6 #6 - '1tE' // 272b7-272e5 - 'G' // 272e6 #6 - '4bE' // 272e7-27351 - 'G' // 27352 #6 - '2rE' // 27353-27399 - 'G' // 2739a #6 - '2jE' // 2739b-273d9 - 'aH' // 273da-273db #7 - '1gE' // 273dc-273fd - 'H' // 273fe #7 - 'G' // 273ff #6 - 'oE' // 27400-2740f - 'H' // 27410 #7 - 'pE' // 27411-27421 - 'G' // 27422 #6 - '1kE' // 27423-27448 - 'H' // 27449 #7 - 'eE' // 2744a-2744f - 'G' // 27450 #6 - '1xE' // 27451-27483 - 'G' // 27484 #6 - 'E' // 27485 - 'G' // 27486 #6 - '9bE' // 27487-27573 - 'G' // 27574 #6 - '1sE' // 27575-275a2 - 'G' // 275a3 #6 - '2gE' // 275a4-275df - 'G' // 275e0 #6 - 'bE' // 275e1-275e3 - 'G' // 275e4 #6 - 'wE' // 275e5-275fc - 'aG' // 275fd-275fe #6 - 'P' // 275ff #15 - 'fE' // 27600-27606 - 'G' // 27607 #6 - 'cE' // 27608-2760b - 'G' // 2760c #6 - 'fE' // 2760d-27613 - 'aH' // 27614-27615 #7 - 'nE' // 27616-27624 - 'P' // 27625 #15 - 'jE' // 27626-27630 - 'H' // 27631 #7 - 'G' // 27632 #6 - 'eE' // 27633-27638 - 'G' // 27639 #6 - 'zE' // 2763a-27654 - 'bG' // 27655-27657 #6 - '1qE' // 27658-27683 - 'H' // 27684 #7 - 'mE' // 27685-27692 - 'H' // 27693 #7 - 'G' // 27694 #6 - '4pE' // 27695-2770d - 'H' // 2770e #7 - 'G' // 2770f #6 - 'rE' // 27710-27722 - 'H' // 27723 #7 - 'pE' // 27724-27734 - 'aG' // 27735-27736 #6 - 'iE' // 27737-27740 - 'G' // 27741 #6 - 'oE' // 27742-27751 - 'H' // 27752 #7 - 'jE' // 27753-2775d - 'G' // 2775e #6 - '1jE' // 2775f-27783 - 'aG' // 27784-27785 #6 - '2qE' // 27786-277cb - 'G' // 277cc #6 - '5hE' // 277cd-27857 - 'G' // 27858 #6 - 'vE' // 27859-2786f - 'G' // 27870 #6 - '1qE' // 27871-2789c - 'G' // 2789d #6 - 'sE' // 2789e-278b1 - 'A' // 278b2 #0 - 'tE' // 278b3-278c7 - 'G' // 278c8 #6 - '3lE' // 278c9-27923 - 'G' // 27924 #6 - '2mE' // 27925-27966 - 'G' // 27967 #6 - 'qE' // 27968-27979 - 'G' // 2797a #6 - 'iE' // 2797b-27984 - 'H' // 27985 #7 - 'yE' // 27986-2799f - 'G' // 279a0 #6 - 'rE' // 279a1-279b3 - 'H' // 279b4 #7 - '1mE' // 279b5-279dc - 'G' // 279dd #6 - '1dE' // 279de-279fc - 'G' // 279fd #6 - 'kE' // 279fe-27a09 - 'G' // 27a0a #6 - 'bE' // 27a0b-27a0d - 'G' // 27a0e #6 - '1tE' // 27a0f-27a3d - 'G' // 27a3e #6 - 'qE' // 27a3f-27a50 - 'P' // 27a51 #15 - 'E' // 27a52 - 'G' // 27a53 #6 - 'dE' // 27a54-27a58 - 'G' // 27a59 #6 - '1dE' // 27a5a-27a78 - 'G' // 27a79 #6 - 'iE' // 27a7a-27a83 - '1B' // 27a84 #27 - '2cE' // 27a85-27abc - 'aG' // 27abd-27abe #6 - '1zE' // 27abf-27af3 - 'G' // 27af4 #6 - 'lE' // 27af5-27b01 - 'P' // 27b02 #15 - 'bE' // 27b03-27b05 - 'G' // 27b06 #6 - 'cE' // 27b07-27b0a - 'G' // 27b0b #6 - 'kE' // 27b0c-27b17 - 'G' // 27b18 #6 - '1dE' // 27b19-27b37 - 'bG' // 27b38-27b3a #6 - 'lE' // 27b3b-27b47 - 'G' // 27b48 #6 - '1aE' // 27b49-27b64 - 'G' // 27b65 #6 - '2xE' // 27b66-27bb2 - 'H' // 27bb3 #7 - 'iE' // 27bb4-27bbd - 'H' // 27bbe #7 - 'gE' // 27bbf-27bc6 - 'H' // 27bc7 #7 - '1lE' // 27bc8-27bee - 'G' // 27bef #6 - 'cE' // 27bf0-27bf3 - 'G' // 27bf4 #6 - '1bE' // 27bf5-27c11 - 'G' // 27c12 #6 - '1nE' // 27c13-27c3b - 'H' // 27c3c #7 - '1tE' // 27c3d-27c6b - 'G' // 27c6c #6 - '2oE' // 27c6d-27cb0 - 'G' // 27cb1 #6 - 'eE' // 27cb2-27cb7 - 'H' // 27cb8 #7 - 'kE' // 27cb9-27cc4 - 'G' // 27cc5 #6 - '1nE' // 27cc6-27cee - 'P' // 27cef #15 - '2jE' // 27cf0-27d2e - 'G' // 27d2f #6 - '1hE' // 27d30-27d52 - 'aG' // 27d53-27d54 #6 - 'pE' // 27d55-27d65 - 'G' // 27d66 #6 - 'kE' // 27d67-27d72 - '1B' // 27d73 #27 - 'oE' // 27d74-27d83 - 'G' // 27d84 #6 - 'iE' // 27d85-27d8e - 'G' // 27d8f #6 - 'gE' // 27d90-27d97 - 'G' // 27d98 #6 - 'fE' // 27d99-27d9f - 'H' // 27da0 #7 - '1aE' // 27da1-27dbc - 'G' // 27dbd #6 - '1cE' // 27dbe-27ddb - 'G' // 27ddc #6 - '1xE' // 27ddd-27e0f - 'H' // 27e10 #7 - '2gE' // 27e11-27e4c - 'G' // 27e4d #6 - 'E' // 27e4e - 'G' // 27e4f #6 - '1rE' // 27e50-27e7c - 'P' // 27e7d #15 - '1vE' // 27e7e-27eae - 'F' // 27eaf #5 - '4bE' // 27eb0-27f1a - 'P' // 27f1b #15 - 'qE' // 27f1c-27f2d - 'G' // 27f2e #6 - '5eE' // 27f2f-27fb6 - 'A' // 27fb7 #0 - '2lE' // 27fb8-27ff8 - 'B' // 27ff9 #1 - 'gE' // 27ffa-28001 - 'G' // 28002 #6 - 'eE' // 28003-28008 - 'G' // 28009 #6 - 'sE' // 2800a-2801d - 'G' // 2801e #6 - 'cE' // 2801f-28022 - 'aG' // 28023-28024 #6 - '1hE' // 28025-28047 - 'G' // 28048 #6 - '2eE' // 28049-28082 - 'G' // 28083 #6 - 'eE' // 28084-28089 - 'H' // 2808a #7 - 'dE' // 2808b-2808f - 'G' // 28090 #6 - '1oE' // 28091-280ba - 'H' // 280bb #7 - 'E' // 280bc - 'aG' // 280bd-280be #6 - '1nE' // 280bf-280e7 - 'aG' // 280e8-280e9 #6 - 'iE' // 280ea-280f3 - 'G' // 280f4 #6 - '2dE' // 280f5-2812d - 'G' // 2812e #6 - '1eE' // 2812f-2814e - 'G' // 2814f #6 - 'lE' // 28150-2815c - 'G' // 2815d #6 - 'pE' // 2815e-2816e - 'G' // 2816f #6 - 'xE' // 28170-28188 - 'G' // 28189 #6 - '1jE' // 2818a-281ae - 'G' // 281af #6 - 'kE' // 281b0-281bb - 'G' // 281bc #6 - '2uE' // 281bd-28206 - 'G' // 28207 #6 - 'oE' // 28208-28217 - 'G' // 28218 #6 - 'E' // 28219 - 'G' // 2821a #6 - '2fE' // 2821b-28255 - 'G' // 28256 #6 - '1eE' // 28257-28276 - 'H' // 28277 #7 - 'cE' // 28278-2827b - 'G' // 2827c #6 - 'dE' // 2827d-28281 - 'H' // 28282 #7 - 'wE' // 28283-2829a - 'G' // 2829b #6 - '1vE' // 2829c-282cc - 'G' // 282cd #6 - 'sE' // 282ce-282e1 - 'G' // 282e2 #6 - 'oE' // 282e3-282f2 - 'H' // 282f3 #7 - 'qE' // 282f4-28305 - 'G' // 28306 #6 - 'pE' // 28307-28317 - 'G' // 28318 #6 - 'uE' // 28319-2832e - 'G' // 2832f #6 - 'iE' // 28330-28339 - 'G' // 2833a #6 - '1oE' // 2833b-28364 - 'G' // 28365 #6 - 'fE' // 28366-2836c - 'G' // 2836d #6 - 'nE' // 2836e-2837c - 'G' // 2837d #6 - 'kE' // 2837e-28389 - 'G' // 2838a #6 - '2mE' // 2838b-283cc - 'H' // 283cd #7 - '1mE' // 283ce-283f5 - 'P' // 283f6 #15 - 'pE' // 283f7-28407 - 'D' // 28408 #3 - 'bE' // 28409-2840b - 'H' // 2840c #7 - 'dE' // 2840d-28411 - 'G' // 28412 #6 - '2mE' // 28413-28454 - 'H' // 28455 #7 - 'qE' // 28456-28467 - 'G' // 28468 #6 - 'bE' // 28469-2846b - 'G' // 2846c #6 - 'eE' // 2846d-28472 - 'G' // 28473 #6 - 'mE' // 28474-28481 - 'G' // 28482 #6 - '3jE' // 28483-284db - 'O' // 284dc #14 - '1iE' // 284dd-28500 - 'G' // 28501 #6 - '2eE' // 28502-2853b - 'aG' // 2853c-2853d #6 - '1rE' // 2853e-2856a - 'H' // 2856b #7 - 'G' // 2856c #6 - '3lE' // 2856d-285c7 - 'aH' // 285c8-285c9 #7 - '1cE' // 285ca-285e7 - 'G' // 285e8 #6 - 'jE' // 285e9-285f3 - 'G' // 285f4 #6 - 'jE' // 285f5-285ff - 'G' // 28600 #6 - 'iE' // 28601-2860a - 'G' // 2860b #6 - 'xE' // 2860c-28624 - 'G' // 28625 #6 - 'tE' // 28626-2863a - 'G' // 2863b #6 - '2gE' // 2863c-28677 - 'D' // 28678 #3 - '1aE' // 28679-28694 - 'D' // 28695 #3 - 'sE' // 28696-286a9 - 'aG' // 286aa-286ab #6 - 'eE' // 286ac-286b1 - 'G' // 286b2 #6 - 'hE' // 286b3-286bb - 'G' // 286bc #6 - 'yE' // 286bd-286d6 - 'H' // 286d7 #7 - 'G' // 286d8 #6 - 'lE' // 286d9-286e5 - 'G' // 286e6 #6 - 'rE' // 286e7-286f9 - 'H' // 286fa #7 - 'sE' // 286fb-2870e - 'G' // 2870f #6 - 'bE' // 28710-28712 - 'G' // 28713 #6 - '7uE' // 28714-287df - 'D' // 287e0 #3 - '1hE' // 287e1-28803 - 'G' // 28804 #6 - '1kE' // 28805-2882a - 'G' // 2882b #6 - '8pE' // 2882c-2890c - 'G' // 2890d #6 - '1jE' // 2890e-28932 - 'G' // 28933 #6 - 'qE' // 28934-28945 - 'H' // 28946 #7 - 'E' // 28947 - 'G' // 28948 #6 - '1B' // 28949 #27 - 'kE' // 2894a-28955 - 'G' // 28956 #6 - 'lE' // 28957-28963 - 'G' // 28964 #6 - 'bE' // 28965-28967 - 'G' // 28968 #6 - 'aE' // 28969-2896a - 'H' // 2896b #7 - 'aG' // 2896c-2896d #6 - 'oE' // 2896e-2897d - 'G' // 2897e #6 - 'gE' // 2897f-28986 - 'aH' // 28987-28988 #7 - 'G' // 28989 #6 - '1cE' // 2898a-289a7 - 'G' // 289a8 #6 - 'E' // 289a9 - 'aG' // 289aa-289ab #6 - 'kE' // 289ac-289b7 - 'G' // 289b8 #6 - 'E' // 289b9 - 'aH' // 289ba-289bb #7 - 'G' // 289bc #6 - 'bE' // 289bd-289bf - 'G' // 289c0 #6 - 'zE' // 289c1-289db - 'G' // 289dc #6 - 'E' // 289dd - 'G' // 289de #6 - 'aE' // 289df-289e0 - 'G' // 289e1 #6 - 'E' // 289e2 - 'aG' // 289e3-289e4 #6 - 'aE' // 289e5-289e6 - 'aG' // 289e7-289e8 #6 - 'oE' // 289e9-289f8 - 'cG' // 289f9-289fc #6 - 'qE' // 289fd-28a0e - 'G' // 28a0f #6 - 'eE' // 28a10-28a15 - 'G' // 28a16 #6 - 'fE' // 28a17-28a1d - 'H' // 28a1e #7 - 'eE' // 28a1f-28a24 - 'G' // 28a25 #6 - 'bE' // 28a26-28a28 - '1B' // 28a29 #27 - 'gE' // 28a2a-28a31 - 'G' // 28a32 #6 - 'bE' // 28a33-28a35 - 'G' // 28a36 #6 - 'kE' // 28a37-28a42 - 'H' // 28a43 #7 - 'gG' // 28a44-28a4b #6 - 'lE' // 28a4c-28a58 - 'aG' // 28a59-28a5a #6 - 'uE' // 28a5b-28a70 - 'H' // 28a71 #7 - 'nE' // 28a72-28a80 - 'bG' // 28a81-28a83 #6 - 'tE' // 28a84-28a98 - 'H' // 28a99 #7 - 'bG' // 28a9a-28a9c #6 - '1hE' // 28a9d-28abf - 'G' // 28ac0 #6 - 'dE' // 28ac1-28ac5 - 'G' // 28ac6 #6 - 'cE' // 28ac7-28aca - 'aG' // 28acb-28acc #6 - 'H' // 28acd #7 - 'G' // 28ace #6 - 'mE' // 28acf-28adc - 'H' // 28add #7 - 'eG' // 28ade-28ae3 #6 - 'H' // 28ae4 #7 - 'G' // 28ae5 #6 - 'cE' // 28ae6-28ae9 - 'G' // 28aea #6 - 'pE' // 28aeb-28afb - 'G' // 28afc #6 - 'nE' // 28afd-28b0b - 'G' // 28b0c #6 - 'eE' // 28b0d-28b12 - 'G' // 28b13 #6 - 'lE' // 28b14-28b20 - 'aG' // 28b21-28b22 #6 - 'gE' // 28b23-28b2a - 'bG' // 28b2b-28b2d #6 - 'E' // 28b2e - 'G' // 28b2f #6 - 'uE' // 28b30-28b45 - 'G' // 28b46 #6 - 'aE' // 28b47-28b48 - 'D' // 28b49 #3 - 'aE' // 28b4a-28b4b - 'G' // 28b4c #6 - 'E' // 28b4d - 'G' // 28b4e #6 - 'E' // 28b4f - 'G' // 28b50 #6 - 'qE' // 28b51-28b62 - 'cG' // 28b63-28b66 #6 - 'dE' // 28b67-28b6b - 'G' // 28b6c #6 - '1gE' // 28b6d-28b8e - 'G' // 28b8f #6 - 'hE' // 28b90-28b98 - 'G' // 28b99 #6 - 'aE' // 28b9a-28b9b - 'aG' // 28b9c-28b9d #6 - 'zE' // 28b9e-28bb8 - 'G' // 28bb9 #6 - 'fE' // 28bba-28bc0 - 'H' // 28bc1 #7 - 'G' // 28bc2 #6 - 'aE' // 28bc3-28bc4 - 'G' // 28bc5 #6 - 'mE' // 28bc6-28bd3 - 'G' // 28bd4 #6 - 'aE' // 28bd5-28bd6 - 'G' // 28bd7 #6 - 'E' // 28bd8 - 'aG' // 28bd9-28bda #6 - 'kE' // 28bdb-28be6 - 'eG' // 28be7-28bec #6 - 'aE' // 28bed-28bee - 'H' // 28bef #7 - 'dE' // 28bf0-28bf4 - 'G' // 28bf5 #6 - 'hE' // 28bf6-28bfe - 'G' // 28bff #6 - 'bE' // 28c00-28c02 - 'G' // 28c03 #6 - 'dE' // 28c04-28c08 - 'G' // 28c09 #6 - 'qE' // 28c0a-28c1b - 'aG' // 28c1c-28c1d #6 - 'dE' // 28c1e-28c22 - 'G' // 28c23 #6 - 'aE' // 28c24-28c25 - 'G' // 28c26 #6 - 'cE' // 28c27-28c2a - 'G' // 28c2b #6 - 'cE' // 28c2c-28c2f - 'G' // 28c30 #6 - 'gE' // 28c31-28c38 - 'G' // 28c39 #6 - 'E' // 28c3a - 'G' // 28c3b #6 - 'jE' // 28c3c-28c46 - 'D' // 28c47 #3 - 'fE' // 28c48-28c4e - 'D' // 28c4f #3 - 'E' // 28c50 - 'D' // 28c51 #3 - 'aE' // 28c52-28c53 - 'D' // 28c54 #3 - '4lE' // 28c55-28cc9 - 'G' // 28cca #6 - 'aE' // 28ccb-28ccc - 'G' // 28ccd #6 - 'cE' // 28cce-28cd1 - 'G' // 28cd2 #6 - 'iE' // 28cd3-28cdc - 'H' // 28cdd #7 - '1wE' // 28cde-28d0f - 'H' // 28d10 #7 - '1hE' // 28d11-28d33 - 'G' // 28d34 #6 - '2gE' // 28d35-28d70 - 'H' // 28d71 #7 - 'wE' // 28d72-28d89 - 'P' // 28d8a #15 - 'mE' // 28d8b-28d98 - 'G' // 28d99 #6 - 'fE' // 28d9a-28da0 - 'P' // 28da1 #15 - 'vE' // 28da2-28db8 - 'G' // 28db9 #6 - '2lE' // 28dba-28dfa - 'H' // 28dfb #7 - 'rE' // 28dfc-28e0e - 'A' // 28e0f #0 - 'fE' // 28e10-28e16 - 'H' // 28e17 #7 - 'fE' // 28e18-28e1e - 'H' // 28e1f #7 - 'uE' // 28e20-28e35 - '1B' // 28e36 #27 - 'aE' // 28e37-28e38 - 'G' // 28e39 #6 - '1pE' // 28e3a-28e64 - 'aG' // 28e65-28e66 #6 - '1gE' // 28e67-28e88 - 'H' // 28e89 #7 - 'lE' // 28e8a-28e96 - 'G' // 28e97 #6 - 'E' // 28e98 - 'D' // 28e99 #3 - 'qE' // 28e9a-28eab - 'G' // 28eac #6 - 'dE' // 28ead-28eb1 - 'aG' // 28eb2-28eb3 #6 - '1jE' // 28eb4-28ed8 - 'G' // 28ed9 #6 - 'lE' // 28eda-28ee6 - 'G' // 28ee7 #6 - 'bE' // 28ee8-28eea - 'H' // 28eeb #7 - 'iE' // 28eec-28ef5 - 'H' // 28ef6 #7 - '2fE' // 28ef7-28f31 - 'H' // 28f32 #7 - '5oE' // 28f33-28fc4 - 'G' // 28fc5 #6 - '1wE' // 28fc6-28ff7 - 'H' // 28ff8 #7 - '4wE' // 28ff9-29078 - 'G' // 29079 #6 - 'mE' // 2907a-29087 - 'G' // 29088 #6 - 'aE' // 29089-2908a - 'G' // 2908b #6 - 'fE' // 2908c-29092 - 'G' // 29093 #6 - 'zE' // 29094-290ae - 'bG' // 290af-290b1 #6 - 'mE' // 290b2-290bf - 'G' // 290c0 #6 - '1hE' // 290c1-290e3 - 'aG' // 290e4-290e5 #6 - 'eE' // 290e6-290eb - 'aG' // 290ec-290ed #6 - '1dE' // 290ee-2910c - 'G' // 2910d #6 - 'aE' // 2910e-2910f - 'G' // 29110 #6 - '1pE' // 29111-2913b - 'G' // 2913c #6 - 'oE' // 2913d-2914c - 'G' // 2914d #6 - 'lE' // 2914e-2915a - 'G' // 2915b #6 - 'aE' // 2915c-2915d - 'G' // 2915e #6 - 'pE' // 2915f-2916f - 'G' // 29170 #6 - '1pE' // 29171-2919b - 'G' // 2919c #6 - 'jE' // 2919d-291a7 - 'G' // 291a8 #6 - '1qE' // 291a9-291d4 - '2Q' // 291d5 #68 - 'lE' // 291d6-291e2 - 'P' // 291e3 #15 - 'fE' // 291e4-291ea - 'G' // 291eb #6 - '6wE' // 291ec-2929f - 'H' // 292a0 #7 - 'oE' // 292a1-292b0 - 'H' // 292b1 #7 - '13xE' // 292b2-2941c - 'G' // 2941d #6 - 'aE' // 2941e-2941f - 'G' // 29420 #6 - 'qE' // 29421-29432 - 'G' // 29433 #6 - 'jE' // 29434-2943e - 'G' // 2943f #6 - 'gE' // 29440-29447 - 'G' // 29448 #6 - '2rE' // 29449-2948f - 'H' // 29490 #7 - '2jE' // 29491-294cf - 'G' // 294d0 #6 - 'gE' // 294d1-294d8 - 'aG' // 294d9-294da #6 - 'bE' // 294db-294dd - 'P' // 294de #15 - 'eE' // 294df-294e4 - 'G' // 294e5 #6 - 'E' // 294e6 - 'G' // 294e7 #6 - '1fE' // 294e8-29508 - 'P' // 29509 #15 - '5qE' // 2950a-2959d - 'G' // 2959e #6 - 'pE' // 2959f-295af - 'G' // 295b0 #6 - 'fE' // 295b1-295b7 - 'G' // 295b8 #6 - 'uE' // 295b9-295ce - 'H' // 295cf #7 - 'fE' // 295d0-295d6 - 'G' // 295d7 #6 - 'pE' // 295d8-295e8 - 'G' // 295e9 #6 - 'iE' // 295ea-295f3 - 'G' // 295f4 #6 - '5gE' // 295f5-2967e - 'A' // 2967f #0 - '4gE' // 29680-296ef - 'H' // 296f0 #7 - '1mE' // 296f1-29718 - 'H' // 29719 #7 - 'eE' // 2971a-2971f - 'G' // 29720 #6 - 'pE' // 29721-29731 - 'G' // 29732 #6 - '1bE' // 29733-2974f - 'H' // 29750 #7 - '4zE' // 29751-297d3 - 'G' // 297d4 #6 - '2fE' // 297d5-2980f - 'A' // 29810 #0 - '1oE' // 29811-2983a - 'P' // 2983b #15 - 'zE' // 2983c-29856 - 'G' // 29857 #6 - '2wE' // 29858-298a3 - 'G' // 298a4 #6 - '1fE' // 298a5-298c5 - 'H' // 298c6 #7 - 'iE' // 298c7-298d0 - 'G' // 298d1 #6 - 'wE' // 298d2-298e9 - 'G' // 298ea #6 - 'eE' // 298eb-298f0 - 'G' // 298f1 #6 - 'gE' // 298f2-298f9 - 'G' // 298fa #6 - 'gE' // 298fb-29902 - 'G' // 29903 #6 - 'E' // 29904 - 'G' // 29905 #6 - '1nE' // 29906-2992e - 'G' // 2992f #6 - 'tE' // 29930-29944 - 'G' // 29945 #6 - 'E' // 29946 - 'bG' // 29947-29949 #6 - 'rE' // 2994a-2995c - 'G' // 2995d #6 - 'kE' // 2995e-29969 - 'G' // 2996a #6 - '1wE' // 2996b-2999c - 'G' // 2999d #6 - '1jE' // 2999e-299c2 - 'G' // 299c3 #6 - 'dE' // 299c4-299c8 - 'G' // 299c9 #6 - '3oE' // 299ca-29a27 - 'G' // 29a28 #6 - '1iE' // 29a29-29a4c - 'G' // 29a4d #6 - '1iE' // 29a4e-29a71 - 'H' // 29a72 #7 - '5oE' // 29a73-29b04 - 'G' // 29b05 #6 - 'gE' // 29b06-29b0d - 'G' // 29b0e #6 - '7oE' // 29b0f-29bd4 - 'G' // 29bd5 #6 - '5zE' // 29bd6-29c72 - 'G' // 29c73 #6 - '2dE' // 29c74-29cac - 'G' // 29cad #6 - '5mE' // 29cae-29d3d - 'G' // 29d3e #6 - 'kE' // 29d3f-29d4a - 'H' // 29d4b #7 - 'mE' // 29d4c-29d59 - 'G' // 29d5a #6 - '1fE' // 29d5b-29d7b - 'G' // 29d7c #6 - 'zE' // 29d7d-29d97 - 'G' // 29d98 #6 - 'aE' // 29d99-29d9a - 'G' // 29d9b #6 - '2jE' // 29d9c-29dda - 'H' // 29ddb #7 - 'yE' // 29ddc-29df5 - 'G' // 29df6 #6 - 'nE' // 29df7-29e05 - 'G' // 29e06 #6 - 'mE' // 29e07-29e14 - 'H' // 29e15 #7 - 'vE' // 29e16-29e2c - 'G' // 29e2d #6 - 'nE' // 29e2e-29e3c - 'H' // 29e3d #7 - 'jE' // 29e3e-29e48 - 'H' // 29e49 #7 - '1cE' // 29e4a-29e67 - 'G' // 29e68 #6 - '1fE' // 29e69-29e89 - 'H' // 29e8a #7 - '1fE' // 29e8b-29eab - 'G' // 29eac #6 - 'bE' // 29ead-29eaf - 'G' // 29eb0 #6 - 'qE' // 29eb1-29ec2 - 'G' // 29ec3 #6 - 'H' // 29ec4 #7 - 'uE' // 29ec5-29eda - 'H' // 29edb #7 - 'lE' // 29edc-29ee8 - 'H' // 29ee9 #7 - 'mE' // 29eea-29ef7 - 'G' // 29ef8 #6 - '1oE' // 29ef9-29f22 - 'G' // 29f23 #6 - 'kE' // 29f24-29f2f - 'G' // 29f30 #6 - '2xE' // 29f31-29f7d - 'D' // 29f7e #3 - 'cE' // 29f7f-29f82 - 'D' // 29f83 #3 - 'gE' // 29f84-29f8b - 'D' // 29f8c #3 - '1oE' // 29f8d-29fb6 - 'G' // 29fb7 #6 - 'uE' // 29fb8-29fcd - 'H' // 29fce #7 - 'gE' // 29fcf-29fd6 - 'H' // 29fd7 #7 - 'eE' // 29fd8-29fdd - 'G' // 29fde #6 - '1zE' // 29fdf-2a013 - 'G' // 2a014 #6 - 'dE' // 2a015-2a019 - 'H' // 2a01a #7 - 'sE' // 2a01b-2a02e - 'H' // 2a02f #7 - '3cE' // 2a030-2a081 - 'H' // 2a082 #7 - 'cE' // 2a083-2a086 - 'G' // 2a087 #6 - '1vE' // 2a088-2a0b8 - 'G' // 2a0b9 #6 - '1lE' // 2a0ba-2a0e0 - 'G' // 2a0e1 #6 - 'jE' // 2a0e2-2a0ec - 'G' // 2a0ed #6 - 'dE' // 2a0ee-2a0f2 - 'G' // 2a0f3 #6 - 'cE' // 2a0f4-2a0f7 - 'G' // 2a0f8 #6 - 'H' // 2a0f9 #7 - 'cE' // 2a0fa-2a0fd - 'G' // 2a0fe #6 - 'gE' // 2a0ff-2a106 - 'G' // 2a107 #6 - 'zE' // 2a108-2a122 - 'G' // 2a123 #6 - 'nE' // 2a124-2a132 - 'aG' // 2a133-2a134 #6 - 'zE' // 2a135-2a14f - 'G' // 2a150 #6 - '2jE' // 2a151-2a18f - 'H' // 2a190 #7 - 'E' // 2a191 - 'aG' // 2a192-2a193 #6 - 'vE' // 2a194-2a1aa - 'G' // 2a1ab #6 - 'gE' // 2a1ac-2a1b3 - 'aG' // 2a1b4-2a1b5 #6 - '1nE' // 2a1b6-2a1de - 'G' // 2a1df #6 - 'tE' // 2a1e0-2a1f4 - 'G' // 2a1f5 #6 - '1oE' // 2a1f6-2a21f - 'G' // 2a220 #6 - 'qE' // 2a221-2a232 - 'G' // 2a233 #6 - '3pE' // 2a234-2a292 - 'G' // 2a293 #6 - 'jE' // 2a294-2a29e - 'G' // 2a29f #6 - 'lE' // 2a2a0-2a2ac - 'P' // 2a2ad #15 - 'cE' // 2a2ae-2a2b1 - '1B' // 2a2b2 #27 - 'E' // 2a2b3 - 'G' // 2a2b4 #6 - 'E' // 2a2b5 - 'G' // 2a2b6 #6 - 'bE' // 2a2b7-2a2b9 - 'G' // 2a2ba #6 - 'aE' // 2a2bb-2a2bc - 'G' // 2a2bd #6 - '1fE' // 2a2be-2a2de - 'G' // 2a2df #6 - '1dE' // 2a2e0-2a2fe - 'G' // 2a2ff #6 - '3bE' // 2a300-2a350 - 'G' // 2a351 #6 - '2eE' // 2a352-2a38b - 'H' // 2a38c #7 - '1aE' // 2a38d-2a3a8 - 'G' // 2a3a9 #6 - '2nE' // 2a3aa-2a3ec - 'G' // 2a3ed #6 - '2qE' // 2a3ee-2a433 - 'G' // 2a434 #6 - 'aE' // 2a435-2a436 - 'H' // 2a437 #7 - '1hE' // 2a438-2a45a - 'G' // 2a45b #6 - '4kE' // 2a45c-2a4cf - 'P' // 2a4d0 #15 - '9jE' // 2a4d1-2a5c5 - 'G' // 2a5c6 #6 - 'cE' // 2a5c7-2a5ca - 'G' // 2a5cb #6 - '1jE' // 2a5cc-2a5f0 - 'H' // 2a5f1 #7 - 'nE' // 2a5f2-2a600 - 'G' // 2a601 #6 - 'H' // 2a602 #7 - 'vE' // 2a603-2a619 - 'H' // 2a61a #7 - 'vE' // 2a61b-2a631 - 'G' // 2a632 #6 - 'vE' // 2a633-2a649 - 'G' // 2a64a #6 - 'oE' // 2a64b-2a65a - 'G' // 2a65b #6 - 'gE' // 2a65c-2a663 - 'P' // 2a664 #15 - '2oE' // 2a665-2a6a8 - 'G' // 2a6a9 #6 - 'gE' // 2a6aa-2a6b1 - 'H' // 2a6b2 #7 - '11kE' // 2a6b3-2a7dc - 'D' // 2a7dd #3 - '10xE' // 2a7de-2a8fa - 'D' // 2a8fb #3 - 'zE' // 2a8fc-2a916 - 'D' // 2a917 #3 - '7wE' // 2a918-2a9e5 - 'H' // 2a9e6 #7 - '2tE' // 2a9e7-2aa2f - 'D' // 2aa30 #3 - 'dE' // 2aa31-2aa35 - 'D' // 2aa36 #3 - '1fE' // 2aa37-2aa57 - 'D' // 2aa58 #3 - '35wE' // 2aa59-2adfe - 'G' // 2adff #6 - '16aE' // 2ae00-2afa1 - 'D' // 2afa2 #3 - '14wE' // 2afa3-2b126 - 'aD' // 2b127-2b128 #3 - 'mE' // 2b129-2b136 - 'aD' // 2b137-2b138 #3 - '6wE' // 2b139-2b1ec - 'D' // 2b1ed #3 - '10mE' // 2b1ee-2b2ff - 'D' // 2b300 #3 - '3sE' // 2b301-2b362 - 'D' // 2b363 #3 - 'jE' // 2b364-2b36e - 'D' // 2b36f #3 - 'aE' // 2b370-2b371 - 'D' // 2b372 #3 - 'iE' // 2b373-2b37c - 'D' // 2b37d #3 - '5cE' // 2b37e-2b403 - 'D' // 2b404 #3 - 'jE' // 2b405-2b40f - 'D' // 2b410 #3 - 'aE' // 2b411-2b412 - 'D' // 2b413 #3 - '2xE' // 2b414-2b460 - 'D' // 2b461 #3 - '5bE' // 2b462-2b4e6 - 'D' // 2b4e7 #3 - 'fE' // 2b4e8-2b4ee - 'D' // 2b4ef #3 - 'eE' // 2b4f0-2b4f5 - 'D' // 2b4f6 #3 - 'aE' // 2b4f7-2b4f8 - 'D' // 2b4f9 #3 - 'rE' // 2b4fa-2b50c - 'aD' // 2b50d-2b50e #3 - '1lE' // 2b50f-2b535 - 'D' // 2b536 #3 - '4nE' // 2b537-2b5ad - 'aD' // 2b5ae-2b5af #3 - 'bE' // 2b5b0-2b5b2 - 'D' // 2b5b3 #3 - '1xE' // 2b5b4-2b5e6 - 'D' // 2b5e7 #3 - 'kE' // 2b5e8-2b5f3 - 'D' // 2b5f4 #3 - '1lE' // 2b5f5-2b61b - 'aD' // 2b61c-2b61d #3 - 'gE' // 2b61e-2b625 - 'bD' // 2b626-2b628 #3 - 'E' // 2b629 - 'D' // 2b62a #3 - 'E' // 2b62b - 'D' // 2b62c #3 - '3yE' // 2b62d-2b694 - 'aD' // 2b695-2b696 #3 - 'uE' // 2b697-2b6ac - 'D' // 2b6ad #3 - '2jE' // 2b6ae-2b6ec - 'D' // 2b6ed #3 - '3iE' // 2b6ee-2b745 - 'H' // 2b746 #7 - 'iE' // 2b747-2b750 - 'H' // 2b751 #7 - 'E' // 2b752 - 'H' // 2b753 #7 - 'eE' // 2b754-2b759 - 'H' // 2b75a #7 - 'E' // 2b75b - 'H' // 2b75c #7 - 'gE' // 2b75d-2b764 - 'H' // 2b765 #7 - 'oE' // 2b766-2b775 - 'aH' // 2b776-2b777 #7 - 'cE' // 2b778-2b77b - 'H' // 2b77c #7 - 'dE' // 2b77d-2b781 - 'H' // 2b782 #7 - 'eE' // 2b783-2b788 - 'H' // 2b789 #7 - 'E' // 2b78a - 'H' // 2b78b #7 - 'aE' // 2b78c-2b78d - 'H' // 2b78e #7 - 'dE' // 2b78f-2b793 - 'H' // 2b794 #7 - 'sE' // 2b795-2b7a8 - 'D' // 2b7a9 #3 - 'aE' // 2b7aa-2b7ab - 'H' // 2b7ac #7 - 'aE' // 2b7ad-2b7ae - 'H' // 2b7af #7 - 'lE' // 2b7b0-2b7bc - 'H' // 2b7bd #7 - 'fE' // 2b7be-2b7c4 - 'D' // 2b7c5 #3 - 'bE' // 2b7c6-2b7c8 - 'H' // 2b7c9 #7 - 'dE' // 2b7ca-2b7ce - 'H' // 2b7cf #7 - 'aE' // 2b7d0-2b7d1 - 'H' // 2b7d2 #7 - 'dE' // 2b7d3-2b7d7 - 'H' // 2b7d8 #7 - 'lE' // 2b7d9-2b7e5 - 'D' // 2b7e6 #3 - 'hE' // 2b7e7-2b7ef - 'H' // 2b7f0 #7 - 'gE' // 2b7f1-2b7f8 - 'D' // 2b7f9 #3 - 'aE' // 2b7fa-2b7fb - 'D' // 2b7fc #3 - 'hE' // 2b7fd-2b805 - 'D' // 2b806 #3 - 'bE' // 2b807-2b809 - 'D' // 2b80a #3 - 'aE' // 2b80b-2b80c - 'H' // 2b80d #7 - 'hE' // 2b80e-2b816 - 'H' // 2b817 #7 - 'aE' // 2b818-2b819 - 'H' // 2b81a #7 - 'E' // 2b81b - 'D' // 2b81c #3 - '5xE' // 2b81d-2b8b7 - 'D' // 2b8b8 #3 - '20eE' // 2b8b9-2bac6 - 'D' // 2bac7 #3 - '5tE' // 2bac8-2bb5e - 'D' // 2bb5f #3 - 'aE' // 2bb60-2bb61 - 'D' // 2bb62 #3 - 'xE' // 2bb63-2bb7b - 'D' // 2bb7c #3 - 'eE' // 2bb7d-2bb82 - 'D' // 2bb83 #3 - '5tE' // 2bb84-2bc1a - 'D' // 2bc1b #3 - '13hE' // 2bc1c-2bd76 - 'D' // 2bd77 #3 - 'nE' // 2bd78-2bd86 - 'D' // 2bd87 #3 - '4fE' // 2bd88-2bdf6 - 'D' // 2bdf7 #3 - '1vE' // 2bdf8-2be28 - 'D' // 2be29 #3 - '19pE' // 2be2a-2c028 - 'aD' // 2c029-2c02a #3 - '4uE' // 2c02b-2c0a8 - 'D' // 2c0a9 #3 - '1eE' // 2c0aa-2c0c9 - 'D' // 2c0ca #3 - '2uE' // 2c0cb-2c114 - 'P' // 2c115 #15 - '7hE' // 2c116-2c1d4 - 'D' // 2c1d5 #3 - 'bE' // 2c1d6-2c1d8 - 'D' // 2c1d9 #3 - '1dE' // 2c1da-2c1f8 - 'D' // 2c1f9 #3 - '4yE' // 2c1fa-2c27b - 'D' // 2c27c #3 - 'jE' // 2c27d-2c287 - 'D' // 2c288 #3 - 'zE' // 2c289-2c2a3 - 'D' // 2c2a4 #3 - '4iE' // 2c2a5-2c316 - 'D' // 2c317 #3 - '2nE' // 2c318-2c35a - 'D' // 2c35b #3 - 'dE' // 2c35c-2c360 - 'D' // 2c361 #3 - 'aE' // 2c362-2c363 - 'D' // 2c364 #3 - '11dE' // 2c365-2c487 - 'D' // 2c488 #3 - 'jE' // 2c489-2c493 - 'D' // 2c494 #3 - 'aE' // 2c495-2c496 - 'D' // 2c497 #3 - '6mE' // 2c498-2c541 - 'D' // 2c542 #3 - '7yE' // 2c543-2c612 - 'D' // 2c613 #3 - 'cE' // 2c614-2c617 - 'D' // 2c618 #3 - 'gE' // 2c619-2c620 - 'D' // 2c621 #3 - 'fE' // 2c622-2c628 - 'D' // 2c629 #3 - 'E' // 2c62a - 'bD' // 2c62b-2c62d #3 - 'E' // 2c62e - 'D' // 2c62f #3 - 'qE' // 2c630-2c641 - 'D' // 2c642 #3 - 'fE' // 2c643-2c649 - 'aD' // 2c64a-2c64b #3 - '8oE' // 2c64c-2c72b - 'D' // 2c72c #3 - 'aE' // 2c72d-2c72e - 'D' // 2c72f #3 - '4fE' // 2c730-2c79e - 'D' // 2c79f #3 - '1fE' // 2c7a0-2c7c0 - 'D' // 2c7c1 #3 - 'pE' // 2c7c2-2c7d2 - 'P' // 2c7d3 #15 - '1nE' // 2c7d4-2c7fc - 'D' // 2c7fd #3 - '8jE' // 2c7fe-2c8d8 - 'D' // 2c8d9 #3 - 'cE' // 2c8da-2c8dd - 'D' // 2c8de #3 - 'aE' // 2c8df-2c8e0 - 'D' // 2c8e1 #3 - 'pE' // 2c8e2-2c8f2 - 'D' // 2c8f3 #3 - 'rE' // 2c8f4-2c906 - 'D' // 2c907 #3 - 'aE' // 2c908-2c909 - 'D' // 2c90a #3 - 'qE' // 2c90b-2c91c - 'D' // 2c91d #3 - '8sE' // 2c91e-2ca01 - 'D' // 2ca02 #3 - 'jE' // 2ca03-2ca0d - 'D' // 2ca0e #3 - '4eE' // 2ca0f-2ca7c - 'D' // 2ca7d #3 - '1pE' // 2ca7e-2caa8 - 'D' // 2caa9 #3 - '4vE' // 2caaa-2cb28 - 'D' // 2cb29 #3 - 'bE' // 2cb2a-2cb2c - 'aD' // 2cb2d-2cb2e #3 - 'aE' // 2cb2f-2cb30 - 'D' // 2cb31 #3 - 'eE' // 2cb32-2cb37 - 'aD' // 2cb38-2cb39 #3 - 'E' // 2cb3a - 'D' // 2cb3b #3 - 'bE' // 2cb3c-2cb3e - 'D' // 2cb3f #3 - 'E' // 2cb40 - 'D' // 2cb41 #3 - 'gE' // 2cb42-2cb49 - 'D' // 2cb4a #3 - 'bE' // 2cb4b-2cb4d - 'D' // 2cb4e #3 - 'jE' // 2cb4f-2cb59 - 'aD' // 2cb5a-2cb5b #3 - 'gE' // 2cb5c-2cb63 - 'D' // 2cb64 #3 - 'cE' // 2cb65-2cb68 - 'D' // 2cb69 #3 - 'aE' // 2cb6a-2cb6b - 'D' // 2cb6c #3 - 'aE' // 2cb6d-2cb6e - 'D' // 2cb6f #3 - 'bE' // 2cb70-2cb72 - 'D' // 2cb73 #3 - 'aE' // 2cb74-2cb75 - 'D' // 2cb76 #3 - 'E' // 2cb77 - 'D' // 2cb78 #3 - 'bE' // 2cb79-2cb7b - 'D' // 2cb7c #3 - '1yE' // 2cb7d-2cbb0 - 'D' // 2cbb1 #3 - 'lE' // 2cbb2-2cbbe - 'aD' // 2cbbf-2cbc0 #3 - 'lE' // 2cbc1-2cbcd - 'D' // 2cbce #3 - '5dE' // 2cbcf-2cc55 - 'D' // 2cc56 #3 - 'gE' // 2cc57-2cc5e - 'D' // 2cc5f #3 - '5rE' // 2cc60-2ccf4 - 'aD' // 2ccf5-2ccf6 #3 - 'eE' // 2ccf7-2ccfc - 'D' // 2ccfd #3 - 'E' // 2ccfe - 'D' // 2ccff #3 - 'aE' // 2cd00-2cd01 - 'aD' // 2cd02-2cd03 #3 - 'eE' // 2cd04-2cd09 - 'D' // 2cd0a #3 - '4wE' // 2cd0b-2cd8a - 'D' // 2cd8b #3 - 'E' // 2cd8c - 'D' // 2cd8d #3 - 'E' // 2cd8e - 'aD' // 2cd8f-2cd90 #3 - 'mE' // 2cd91-2cd9e - 'aD' // 2cd9f-2cda0 #3 - 'fE' // 2cda1-2cda7 - 'D' // 2cda8 #3 - 'cE' // 2cda9-2cdac - 'aD' // 2cdad-2cdae #3 - '1kE' // 2cdaf-2cdd4 - 'D' // 2cdd5 #3 - '2mE' // 2cdd6-2ce17 - 'D' // 2ce18 #3 - 'E' // 2ce19 - 'D' // 2ce1a #3 - 'gE' // 2ce1b-2ce22 - 'D' // 2ce23 #3 - 'aE' // 2ce24-2ce25 - 'D' // 2ce26 #3 - 'bE' // 2ce27-2ce29 - 'D' // 2ce2a #3 - '3bE' // 2ce2b-2ce7b - 'D' // 2ce7c #3 - 'jE' // 2ce7d-2ce87 - 'D' // 2ce88 #3 - 'iE' // 2ce89-2ce92 - 'D' // 2ce93 #3 - '65uE' // 2ce94-2d543 - 'A' // 2d544 #0 - '129xE' // 2d545-2e277 - 'H' // 2e278 #7 - '28wE' // 2e279-2e568 - 'O' // 2e569 #14 - '14sE' // 2e56a-2e6e9 - 'H' // 2e6ea #7 - '168hE' // 2e6eb-2f803 - 'H' // 2f804 #7 - 'iE' // 2f805-2f80e - 'H' // 2f80f #7 - 'dE' // 2f810-2f814 - 'O' // 2f815 #14 - 'aE' // 2f816-2f817 - 'O' // 2f818 #14 - 'E' // 2f819 - 'O' // 2f81a #14 - 'fE' // 2f81b-2f821 - 'H' // 2f822 #7 - 'aE' // 2f823-2f824 - 'G' // 2f825 #6 - 'aE' // 2f826-2f827 - 'H' // 2f828 #7 - 'bE' // 2f829-2f82b - 'O' // 2f82c #14 - 'eE' // 2f82d-2f832 - 'O' // 2f833 #14 - 'fE' // 2f834-2f83a - 'G' // 2f83b #6 - 'bE' // 2f83c-2f83e - 'H' // 2f83f #7 - 'G' // 2f840 #6 - 'dE' // 2f841-2f845 - 'H' // 2f846 #7 - 'jE' // 2f847-2f851 - 'O' // 2f852 #14 - 'nE' // 2f853-2f861 - 'O' // 2f862 #14 - 'iE' // 2f863-2f86c - 'H' // 2f86d #7 - 'dE' // 2f86e-2f872 - 'H' // 2f873 #7 - 'bE' // 2f874-2f876 - 'O' // 2f877 #14 - 'G' // 2f878 #6 - 'jE' // 2f879-2f883 - 'J' // 2f884 #9 - 'nE' // 2f885-2f893 - 'G' // 2f894 #6 - 'cE' // 2f895-2f898 - 'aH' // 2f899-2f89a #7 - 'jE' // 2f89b-2f8a5 - '1B' // 2f8a6 #27 - 'dE' // 2f8a7-2f8ab - 'H' // 2f8ac #7 - 'dE' // 2f8ad-2f8b1 - 'O' // 2f8b2 #14 - 'bE' // 2f8b3-2f8b5 - 'F' // 2f8b6 #5 - 'uE' // 2f8b7-2f8cc - 'G' // 2f8cd #6 - 'dE' // 2f8ce-2f8d2 - 'H' // 2f8d3 #7 - 'fE' // 2f8d4-2f8da - '1B' // 2f8db #27 - 'H' // 2f8dc #7 - 'cE' // 2f8dd-2f8e0 - 'H' // 2f8e1 #7 - 'bE' // 2f8e2-2f8e4 - 'H' // 2f8e5 #7 - 'cE' // 2f8e6-2f8e9 - 'H' // 2f8ea #7 - 'aE' // 2f8eb-2f8ec - 'O' // 2f8ed #14 - 'mE' // 2f8ee-2f8fb - 'O' // 2f8fc #14 - 'eE' // 2f8fd-2f902 - 'H' // 2f903 #7 - 'fE' // 2f904-2f90a - 'H' // 2f90b #7 - 'bE' // 2f90c-2f90e - 'H' // 2f90f #7 - 'iE' // 2f910-2f919 - 'H' // 2f91a #7 - 'dE' // 2f91b-2f91f - 'O' // 2f920 #14 - 'H' // 2f921 #7 - '1hE' // 2f922-2f944 - 'H' // 2f945 #7 - 'E' // 2f946 - 'H' // 2f947 #7 - '1iE' // 2f948-2f96b - 'O' // 2f96c #14 - '1lE' // 2f96d-2f993 - 'G' // 2f994 #6 - 'H' // 2f995 #7 - '1aE' // 2f996-2f9b1 - 'G' // 2f9b2 #6 - 'hE' // 2f9b3-2f9bb - 'G' // 2f9bc #6 - 'rE' // 2f9bd-2f9cf - 'O' // 2f9d0 #14 - 'bE' // 2f9d1-2f9d3 - 'G' // 2f9d4 #6 - 'hE' // 2f9d5-2f9dd - 'H' // 2f9de #7 - 'O' // 2f9df #14 - 'sE' // 2f9e0-2f9f3 - 'H' // 2f9f4 #7 - '129yE' // 2f9f5-30728 - 'P' // 30729 #15 - '75tE' // 3072a-30edc - '7S' // 30edd #200 - '2J' // 30ede #61 - '15fE' // 30edf-3106b - 'C' // 3106c #2 - '27568tE' // 3106d-e0061 - 'a1X' // e0062-e0063 #49 - 'E' // e0064 - '1X' // e0065 #49 - 'E' // e0066 - '1X' // e0067 #49 - 'cE' // e0068-e006b - '1X' // e006c #49 - 'E' // e006d - '1X' // e006e #49 - 'cE' // e006f-e0072 - 'a1X' // e0073-e0074 #49 - 'aE' // e0075-e0076 - '1X' // e0077 #49 - 'fE' // e0078-e007e - '1X' // e007f #49 - '7556wE' // e0080-10ffff + 'A' // 36c6 + 'aI' // 36c7-36c8 #8 + 'eA' // 36c9-36ce + 'T' // 36cf #19 + 'bA' // 36d0-36d2 + 'aI' // 36d3-36d4 #8 + 'A' // 36d5 + 'I' // 36d6 #8 + 'eA' // 36d7-36dc + 'I' // 36dd #8 + 'bA' // 36de-36e0 + 'aI' // 36e1-36e2 #8 + 'aA' // 36e3-36e4 + 'aI' // 36e5-36e6 #8 + 'mA' // 36e7-36f4 + 'I' // 36f5 #8 + 'jA' // 36f6-3700 + 'I' // 3701 #8 + 'A' // 3702 + 'I' // 3703 #8 + 'cA' // 3704-3707 + 'I' // 3708 #8 + 'A' // 3709 + 'I' // 370a #8 + 'aA' // 370b-370c + 'I' // 370d #8 + 'mA' // 370e-371b + 'I' // 371c #8 + 'dA' // 371d-3721 + 'aI' // 3722-3723 #8 + 'A' // 3724 + 'I' // 3725 #8 + 'eA' // 3726-372b + 'aI' // 372c-372d #8 + 'aA' // 372e-372f + 'I' // 3730 #8 + 'A' // 3731 + 'aI' // 3732-3733 #8 + 'eA' // 3734-3739 + '35N' // 373a #923 + 'dA' // 373b-373f + 'I' // 3740 #8 + 'aA' // 3741-3742 + 'I' // 3743 #8 + '1bA' // 3744-3760 + 'T' // 3761 #19 + '26T' // 3762 #695 + 'gA' // 3763-376a + 'aT' // 376b-376c #19 + 'aA' // 376d-376e + 'I' // 376f #8 + 'dA' // 3770-3774 + 'T' // 3775 #19 + 'vA' // 3776-378c + 'T' // 378d #19 + 'hA' // 378e-3796 + 'I' // 3797 #8 + 'gA' // 3798-379f + 'I' // 37a0 #8 + 'wA' // 37a1-37b8 + 'I' // 37b9 #8 + 'cA' // 37ba-37bd + 'I' // 37be #8 + 'aA' // 37bf-37c0 + 'T' // 37c1 #19 + 'sA' // 37c2-37d5 + 'I' // 37d6 #8 + 'jA' // 37d7-37e1 + 'T' // 37e2 #19 + 'dA' // 37e3-37e7 + 'T' // 37e8 #19 + 'hA' // 37e9-37f1 + 'I' // 37f2 #8 + 'A' // 37f3 + 'T' // 37f4 #19 + 'bA' // 37f5-37f7 + 'I' // 37f8 #8 + 'aA' // 37f9-37fa + 'I' // 37fb #8 + 'A' // 37fc + 'T' // 37fd #19 + 'aA' // 37fe-37ff + 'T' // 3800 #19 + 'mA' // 3801-380e + 'I' // 380f #8 + 'hA' // 3810-3818 + 'I' // 3819 #8 + 'eA' // 381a-381f + 'I' // 3820 #8 + 'kA' // 3821-382c + 'I' // 382d #8 + 'A' // 382e + 'T' // 382f #19 + 'eA' // 3830-3835 + '26T' // 3836 #695 + 'A' // 3837 + 'I' // 3838 #8 + 'fA' // 3839-383f + 'T' // 3840 #19 + 'zA' // 3841-385b + 'T' // 385c #19 + 'cA' // 385d-3860 + 'T' // 3861 #19 + 'A' // 3862 + '35N' // 3863 #923 + 'pA' // 3864-3874 + 'I' // 3875 #8 + '1oA' // 3876-389f + 'I' // 38a0 #8 + 'T' // 38a1 #19 + 'eA' // 38a2-38a7 + '7J' // 38a8 #191 + 'cA' // 38a9-38ac + 'T' // 38ad #19 + 'tA' // 38ae-38c2 + 'I' // 38c3 #8 + 'gA' // 38c4-38cb + 'I' // 38cc #8 + 'cA' // 38cd-38d0 + 'I' // 38d1 #8 + 'aA' // 38d2-38d3 + 'I' // 38d4 #8 + '1jA' // 38d5-38f9 + '26T' // 38fa #695 + 'lA' // 38fb-3907 + 'I' // 3908 #8 + 'jA' // 3909-3913 + 'I' // 3914 #8 + 'aA' // 3915-3916 + 'T' // 3917 #19 + 'aA' // 3918-3919 + 'T' // 391a #19 + 'kA' // 391b-3926 + 'I' // 3927 #8 + 'iA' // 3928-3931 + 'I' // 3932 #8 + 'kA' // 3933-393e + 'I' // 393f #8 + 'lA' // 3940-394c + 'I' // 394d #8 + 'tA' // 394e-3962 + 'I' // 3963 #8 + 'jA' // 3964-396e + 'T' // 396f #19 + 'gA' // 3970-3977 + 'I' // 3978 #8 + 'fA' // 3979-397f + 'I' // 3980 #8 + 'gA' // 3981-3988 + 'aI' // 3989-398a #8 + 'fA' // 398b-3991 + 'I' // 3992 #8 + 'eA' // 3993-3998 + 'I' // 3999 #8 + 'A' // 399a + 'I' // 399b #8 + 'dA' // 399c-39a0 + 'I' // 39a1 #8 + 'aA' // 39a2-39a3 + '26T' // 39a4 #695 + 'rA' // 39a5-39b7 + '62C' // 39b8 #1614 + '1hA' // 39b9-39db + 'I' // 39dc #8 + 'dA' // 39dd-39e1 + 'I' // 39e2 #8 + 'aA' // 39e3-39e4 + 'I' // 39e5 #8 + 'eA' // 39e6-39eb + 'I' // 39ec #8 + 'jA' // 39ed-39f7 + 'I' // 39f8 #8 + 'aA' // 39f9-39fa + 'I' // 39fb #8 + 'aA' // 39fc-39fd + 'I' // 39fe #8 + 'aA' // 39ff-3a00 + 'I' // 3a01 #8 + 'A' // 3a02 + 'I' // 3a03 #8 + 'aA' // 3a04-3a05 + 'I' // 3a06 #8 + 'oA' // 3a07-3a16 + '35N' // 3a17 #923 + 'I' // 3a18 #8 + 'oA' // 3a19-3a28 + 'aI' // 3a29-3a2a #8 + 'hA' // 3a2b-3a33 + 'I' // 3a34 #8 + 'uA' // 3a35-3a4a + 'I' // 3a4b #8 + 'eA' // 3a4c-3a51 + '35N' // 3a52 #923 + 'cA' // 3a53-3a56 + 'I' // 3a57 #8 + 'cA' // 3a58-3a5b + '26T' // 3a5c #695 + 'A' // 3a5d + 'I' // 3a5e #8 + 'fA' // 3a5f-3a65 + 'aI' // 3a66-3a67 #8 + 'eA' // 3a68-3a6d + 'T' // 3a6e #19 + 'cA' // 3a6f-3a72 + 'T' // 3a73 #19 + 'pA' // 3a74-3a84 + 'T' // 3a85 #19 + 'pA' // 3a86-3a96 + 'I' // 3a97 #8 + 'rA' // 3a98-3aaa + 'I' // 3aab #8 + 'pA' // 3aac-3abc + 'I' // 3abd #8 + 'eA' // 3abe-3ac3 + 'T' // 3ac4 #19 + 'eA' // 3ac5-3aca + 'T' // 3acb #19 + 'iA' // 3acc-3ad5 + 'aT' // 3ad6-3ad7 #19 + 'eA' // 3ad8-3add + 'I' // 3ade #8 + 'A' // 3adf + 'I' // 3ae0 #8 + 'hA' // 3ae1-3ae9 + 'T' // 3aea #19 + 'dA' // 3aeb-3aef + 'I' // 3af0 #8 + 'A' // 3af1 + 'I' // 3af2 #8 + 'T' // 3af3 #19 + 'A' // 3af4 + 'I' // 3af5 #8 + 'dA' // 3af6-3afa + 'I' // 3afb #8 + 'qA' // 3afc-3b0d + '26T' // 3b0e #695 + 'iA' // 3b0f-3b18 + 'I' // 3b19 #8 + 'T' // 3b1a #19 + 'A' // 3b1b + 'T' // 3b1c #19 + 'dA' // 3b1d-3b21 + '62C' // 3b22 #1614 + 'gA' // 3b23-3b2a + 'I' // 3b2b #8 + 'hA' // 3b2c-3b34 + 'T' // 3b35 #19 + 'bA' // 3b36-3b38 + 'I' // 3b39 #8 + 'gA' // 3b3a-3b41 + 'I' // 3b42 #8 + 'tA' // 3b43-3b57 + 'I' // 3b58 #8 + 'fA' // 3b59-3b5f + 'I' // 3b60 #8 + 'kA' // 3b61-3b6c + 'T' // 3b6d #19 + 'bA' // 3b6e-3b70 + 'aI' // 3b71-3b72 #8 + 'cA' // 3b73-3b76 + 'T' // 3b77 #19 + 'bA' // 3b78-3b7a + 'aI' // 3b7b-3b7c #8 + 'bA' // 3b7d-3b7f + 'I' // 3b80 #8 + 'eA' // 3b81-3b86 + 'aT' // 3b87-3b88 #19 + 'cA' // 3b89-3b8c + 'T' // 3b8d #19 + 'fA' // 3b8e-3b94 + 'aL' // 3b95-3b96 #11 + 'aA' // 3b97-3b98 + 'L' // 3b99 #11 + 'fA' // 3b9a-3ba0 + 'L' // 3ba1 #11 + 'aA' // 3ba2-3ba3 + 'T' // 3ba4 #19 + 'pA' // 3ba5-3bb5 + 'T' // 3bb6 #19 + 'dA' // 3bb7-3bbb + 'L' // 3bbc #11 + 'A' // 3bbd + 'L' // 3bbe #11 + 'bA' // 3bbf-3bc1 + 'L' // 3bc2 #11 + 'T' // 3bc3 #19 + 'L' // 3bc4 #11 + 'gA' // 3bc5-3bcc + 'T' // 3bcd #19 + 'hA' // 3bce-3bd6 + '40W' // 3bd7 #1062 + 'dA' // 3bd8-3bdc + 'L' // 3bdd #11 + 'mA' // 3bde-3beb + 'L' // 3bec #11 + 'bA' // 3bed-3bef + 'T' // 3bf0 #19 + 'A' // 3bf1 + 'L' // 3bf2 #11 + '23V' // 3bf3 #619 + 'L' // 3bf4 #11 + 'wA' // 3bf5-3c0c + 'L' // 3c0d #11 + 'A' // 3c0e + 'T' // 3c0f #19 + 'A' // 3c10 + 'L' // 3c11 #11 + 'bA' // 3c12-3c14 + 'L' // 3c15 #11 + 'aA' // 3c16-3c17 + 'L' // 3c18 #11 + 'lA' // 3c19-3c25 + 'T' // 3c26 #19 + '1rA' // 3c27-3c53 + 'L' // 3c54 #11 + '2aA' // 3c55-3c8a + 'L' // 3c8b #11 + '2bA' // 3c8c-3cc2 + 'T' // 3cc3 #19 + 'fA' // 3cc4-3cca + 'L' // 3ccb #11 + 'A' // 3ccc + 'L' // 3ccd #11 + 'bA' // 3cce-3cd0 + 'L' // 3cd1 #11 + 'T' // 3cd2 #19 + 'bA' // 3cd3-3cd5 + 'L' // 3cd6 #11 + 'dA' // 3cd7-3cdb + 'L' // 3cdc #11 + 'mA' // 3cdd-3cea + 'L' // 3ceb #11 + 'bA' // 3cec-3cee + 'L' // 3cef #11 + '1fA' // 3cf0-3d10 + 'T' // 3d11 #19 + 'aL' // 3d12-3d13 #11 + 'hA' // 3d14-3d1c + 'L' // 3d1d #11 + 'T' // 3d1e #19 + 'qA' // 3d1f-3d30 + 'T' // 3d31 #19 + 'L' // 3d32 #11 + 'gA' // 3d33-3d3a + 'L' // 3d3b #11 + 'iA' // 3d3c-3d45 + 'L' // 3d46 #11 + 'dA' // 3d47-3d4b + 'L' // 3d4c #11 + 'A' // 3d4d + '23V' // 3d4e #619 + 'aA' // 3d4f-3d50 + 'L' // 3d51 #11 + 'lA' // 3d52-3d5e + 'L' // 3d5f #11 + 'aA' // 3d60-3d61 + 'L' // 3d62 #11 + 'A' // 3d63 + 'T' // 3d64 #19 + 'cA' // 3d65-3d68 + 'aL' // 3d69-3d6a #11 + 'cA' // 3d6b-3d6e + 'L' // 3d6f #11 + 'dA' // 3d70-3d74 + 'L' // 3d75 #11 + 'fA' // 3d76-3d7c + 'L' // 3d7d #11 + 'fA' // 3d7e-3d84 + 'L' // 3d85 #11 + 'aA' // 3d86-3d87 + 'L' // 3d88 #11 + 'A' // 3d89 + 'L' // 3d8a #11 + 'cA' // 3d8b-3d8e + 'L' // 3d8f #11 + 'A' // 3d90 + 'L' // 3d91 #11 + 'gA' // 3d92-3d99 + 'T' // 3d9a #19 + 'iA' // 3d9b-3da4 + 'L' // 3da5 #11 + 'dA' // 3da6-3daa + '7J' // 3dab #191 + 'A' // 3dac + 'L' // 3dad #11 + 'eA' // 3dae-3db3 + 'L' // 3db4 #11 + 'iA' // 3db5-3dbe + 'L' // 3dbf #11 + 'T' // 3dc0 #19 + 'dA' // 3dc1-3dc5 + 'aL' // 3dc6-3dc7 #11 + 'A' // 3dc8 + 'L' // 3dc9 #11 + 'aA' // 3dca-3dcb + '23V' // 3dcc #619 + 'L' // 3dcd #11 + 'dA' // 3dce-3dd2 + 'L' // 3dd3 #11 + 'T' // 3dd4 #19 + 'eA' // 3dd5-3dda + '40W' // 3ddb #1062 + 'jA' // 3ddc-3de6 + '62A' // 3de7 #1612 + 'L' // 3de8 #11 + 'aA' // 3de9-3dea + '62A' // 3deb #1612 + 'fA' // 3dec-3df2 + 'aL' // 3df3-3df4 #11 + 'aA' // 3df5-3df6 + 'L' // 3df7 #11 + 'cA' // 3df8-3dfb + 'aL' // 3dfc-3dfd #11 + 'fA' // 3dfe-3e04 + 'T' // 3e05 #19 + 'L' // 3e06 #11 + 'rA' // 3e07-3e19 + '7J' // 3e1a #191 + '1iA' // 3e1b-3e3e + 'T' // 3e3f #19 + '23V' // 3e40 #619 + 'aA' // 3e41-3e42 + 'L' // 3e43 #11 + 'cA' // 3e44-3e47 + 'L' // 3e48 #11 + 'kA' // 3e49-3e54 + 'L' // 3e55 #11 + 'iA' // 3e56-3e5f + 'T' // 3e60 #19 + 'dA' // 3e61-3e65 + 'T' // 3e66 #19 + 'A' // 3e67 + 'T' // 3e68 #19 + 'jA' // 3e69-3e73 + '40W' // 3e74 #1062 + 'mA' // 3e75-3e82 + 'T' // 3e83 #19 + 'eA' // 3e84-3e89 + 'T' // 3e8a #19 + 'hA' // 3e8b-3e93 + 'T' // 3e94 #19 + 'rA' // 3e95-3ea7 + 'bL' // 3ea8-3eaa #11 + 'aA' // 3eab-3eac + 'L' // 3ead #11 + 'bA' // 3eae-3eb0 + 'L' // 3eb1 #11 + 'eA' // 3eb2-3eb7 + 'L' // 3eb8 #11 + 'eA' // 3eb9-3ebe + 'L' // 3ebf #11 + 'aA' // 3ec0-3ec1 + 'L' // 3ec2 #11 + 'cA' // 3ec3-3ec6 + 'L' // 3ec7 #11 + 'aA' // 3ec8-3ec9 + 'L' // 3eca #11 + 'A' // 3ecb + 'L' // 3ecc #11 + 'bA' // 3ecd-3ecf + 'aL' // 3ed0-3ed1 #11 + 'cA' // 3ed2-3ed5 + 'aL' // 3ed6-3ed7 #11 + 'aA' // 3ed8-3ed9 + '23V' // 3eda #619 + 'L' // 3edb #11 + 'aA' // 3edc-3edd + 'L' // 3ede #11 + 'aA' // 3edf-3ee0 + 'aL' // 3ee1-3ee2 #11 + 'cA' // 3ee3-3ee6 + 'L' // 3ee7 #11 + 'A' // 3ee8 + 'L' // 3ee9 #11 + 'A' // 3eea + 'aL' // 3eeb-3eec #11 + 'bA' // 3eed-3eef + 'L' // 3ef0 #11 + 'aA' // 3ef1-3ef2 + 'aL' // 3ef3-3ef4 #11 + 'dA' // 3ef5-3ef9 + 'L' // 3efa #11 + 'A' // 3efb + 'L' // 3efc #11 + 'aA' // 3efd-3efe + 'aL' // 3eff-3f00 #11 + 'bA' // 3f01-3f03 + 'L' // 3f04 #11 + 'A' // 3f05 + 'aL' // 3f06-3f07 #11 + 'eA' // 3f08-3f0d + '40W' // 3f0e #1062 + 'kA' // 3f0f-3f1a + '7J' // 3f1b #191 + '2bA' // 3f1c-3f52 + 'L' // 3f53 #11 + 'bA' // 3f54-3f56 + 'T' // 3f57 #19 + 'aL' // 3f58-3f59 #11 + 'hA' // 3f5a-3f62 + 'L' // 3f63 #11 + 'hA' // 3f64-3f6c + '7J' // 3f6d #191 + 'cA' // 3f6e-3f71 + 'T' // 3f72 #19 + 'aA' // 3f73-3f74 + 'T' // 3f75 #19 + 'A' // 3f76 + 'T' // 3f77 #19 + 'cA' // 3f78-3f7b + 'L' // 3f7c #11 + 'uA' // 3f7d-3f92 + 'L' // 3f93 #11 + 'yA' // 3f94-3fad + 'T' // 3fae #19 + 'aA' // 3faf-3fb0 + 'T' // 3fb1 #19 + 'mA' // 3fb2-3fbf + 'L' // 3fc0 #11 + 'fA' // 3fc1-3fc7 + 'L' // 3fc8 #11 + 'T' // 3fc9 #19 + 'lA' // 3fca-3fd6 + '23V' // 3fd7 #619 + 'cA' // 3fd8-3fdb + '23V' // 3fdc #619 + 'gA' // 3fdd-3fe4 + 'L' // 3fe5 #11 + 'fA' // 3fe6-3fec + 'L' // 3fed #11 + 'jA' // 3fee-3ff8 + 'aL' // 3ff9-3ffa #11 + 'hA' // 3ffb-4003 + 'L' // 4004 #11 + 'cA' // 4005-4008 + 'L' // 4009 #11 + 'rA' // 400a-401c + 'L' // 401d #11 + 'zA' // 401e-4038 + '23V' // 4039 #619 + 'jA' // 403a-4044 + 'L' // 4045 #11 + 'lA' // 4046-4052 + 'L' // 4053 #11 + 'bA' // 4054-4056 + 'L' // 4057 #11 + 'T' // 4058 #19 + 'hA' // 4059-4061 + 'L' // 4062 #11 + 'aA' // 4063-4064 + 'L' // 4065 #11 + 'cA' // 4066-4069 + 'L' // 406a #11 + 'cA' // 406b-406e + 'L' // 406f #11 + 'A' // 4070 + 'L' // 4071 #11 + '1fA' // 4072-4092 + '1B' // 4093 #27 + 'sA' // 4094-40a7 + 'L' // 40a8 #11 + 'jA' // 40a9-40b3 + 'L' // 40b4 #11 + 'eA' // 40b5-40ba + 'L' // 40bb #11 + 'bA' // 40bc-40be + 'L' // 40bf #11 + 'gA' // 40c0-40c7 + 'L' // 40c8 #11 + 'nA' // 40c9-40d7 + 'L' // 40d8 #11 + 'eA' // 40d9-40de + 'L' // 40df #11 + 'wA' // 40e0-40f7 + 'L' // 40f8 #11 + 'A' // 40f9 + 'L' // 40fa #11 + 'fA' // 40fb-4101 + '62B' // 4102 #1613 + '116W' // 4103 #3038 + 'L' // 4104 #11 + '1B' // 4105 #27 + 'bA' // 4106-4108 + 'L' // 4109 #11 + 'cA' // 410a-410d + 'L' // 410e #11 + '1gA' // 410f-4130 + 'aL' // 4131-4132 #11 + 'tA' // 4133-4147 + '1B' // 4148 #27 + 'eA' // 4149-414e + '1B' // 414f #27 + 'rA' // 4150-4162 + '1B' // 4163 #27 + 'bA' // 4164-4166 + 'L' // 4167 #11 + 'cA' // 4168-416b + 'L' // 416c #11 + 'A' // 416d + 'L' // 416e #11 + 'lA' // 416f-417b + 'L' // 417c #11 + 'aA' // 417d-417e + 'L' // 417f #11 + 'A' // 4180 + '62B' // 4181 #1613 + 'mA' // 4182-418f + 'L' // 4190 #11 + '1fA' // 4191-41b1 + 'L' // 41b2 #11 + 'A' // 41b3 + '1B' // 41b4 #27 + 'iA' // 41b5-41be + '1B' // 41bf #27 + 'cA' // 41c0-41c3 + 'L' // 41c4 #11 + 'dA' // 41c5-41c9 + 'L' // 41ca #11 + 'cA' // 41cb-41ce + 'L' // 41cf #11 + 'jA' // 41d0-41da + 'G' // 41db #6 + 'iA' // 41dc-41e5 + '1B' // 41e6 #27 + 'eA' // 41e7-41ec + 'G' // 41ed #6 + '1B' // 41ee #27 + 'G' // 41ef #6 + 'bA' // 41f0-41f2 + '1B' // 41f3 #27 + 'dA' // 41f4-41f8 + 'G' // 41f9 #6 + 'lA' // 41fa-4206 + '1B' // 4207 #27 + 'eA' // 4208-420d + '1B' // 420e #27 + 'aA' // 420f-4210 + 'G' // 4211 #6 + 'pA' // 4212-4222 + 'G' // 4223 #6 + '1aA' // 4224-423f + 'G' // 4240 #6 + '1dA' // 4241-425f + 'G' // 4260 #6 + 'bA' // 4261-4263 + '1B' // 4264 #27 + 'dA' // 4265-4269 + 'G' // 426a #6 + 'jA' // 426b-4275 + 'G' // 4276 #6 + 'bA' // 4277-4279 + 'G' // 427a #6 + 'pA' // 427b-428b + 'G' // 428c #6 + 'eA' // 428d-4292 + '1B' // 4293 #27 + 'G' // 4294 #6 + 'lA' // 4295-42a1 + 'G' // 42a2 #6 + 'qA' // 42a3-42b4 + 'G' // 42b5 #6 + 'bA' // 42b6-42b8 + 'G' // 42b9 #6 + 'aA' // 42ba-42bb + 'G' // 42bc #6 + 'hA' // 42bd-42c5 + '1B' // 42c6 #27 + 'nA' // 42c7-42d5 + '1B' // 42d6 #27 + 'eA' // 42d7-42dc + '1B' // 42dd #27 + 'uA' // 42de-42f3 + 'G' // 42f4 #6 + 'eA' // 42f5-42fa + 'aG' // 42fb-42fc #6 + 'dA' // 42fd-4301 + '1B' // 4302 #27 + 'fA' // 4303-4309 + 'G' // 430a #6 + '1eA' // 430b-432a + '19E' // 432b #498 + 'vA' // 432c-4342 + '1B' // 4343 #27 + '1oA' // 4344-436d + 'G' // 436e #6 + '1mA' // 436f-4396 + 'G' // 4397 #6 + 'aA' // 4398-4399 + 'G' // 439a #6 + '1dA' // 439b-43b9 + 'G' // 43ba #6 + 'eA' // 43bb-43c0 + 'G' // 43c1 #6 + 'vA' // 43c2-43d8 + 'G' // 43d9 #6 + 'dA' // 43da-43de + 'G' // 43df #6 + 'lA' // 43e0-43ec + 'G' // 43ed #6 + '1B' // 43ee #27 + 'A' // 43ef + '19E' // 43f0 #498 + 'A' // 43f1 + 'G' // 43f2 #6 + 'mA' // 43f3-4400 + 'aG' // 4401-4402 #6 + 'dA' // 4403-4407 + '1B' // 4408 #27 + 'bA' // 4409-440b + '1B' // 440c #27 + 'eA' // 440d-4412 + 'G' // 4413 #6 + 'bA' // 4414-4416 + '1B' // 4417 #27 + 'cA' // 4418-441b + '1B' // 441c #27 + 'dA' // 441d-4421 + '1B' // 4422 #27 + 'aA' // 4423-4424 + 'G' // 4425 #6 + 'fA' // 4426-442c + 'G' // 442d #6 + '1jA' // 442e-4452 + '1B' // 4453 #27 + 'fA' // 4454-445a + '1B' // 445b #27 + 'yA' // 445c-4475 + '1B' // 4476 #27 + 'bA' // 4477-4479 + '19E' // 447a #498 + 'sA' // 447b-448e + 'G' // 448f #6 + 'A' // 4490 + '19E' // 4491 #498 + 'bA' // 4492-4494 + '7J' // 4495 #191 + 'hA' // 4496-449e + 'aG' // 449f-44a0 #6 + 'A' // 44a1 + 'G' // 44a2 #6 + 'lA' // 44a3-44af + 'G' // 44b0 #6 + 'aA' // 44b1-44b2 + '1B' // 44b3 #27 + 'bA' // 44b4-44b6 + 'G' // 44b7 #6 + 'dA' // 44b8-44bc + 'G' // 44bd #6 + '1B' // 44be #27 + 'A' // 44bf + 'G' // 44c0 #6 + 'aA' // 44c1-44c2 + 'G' // 44c3 #6 + 'A' // 44c4 + 'G' // 44c5 #6 + 'gA' // 44c6-44cd + 'G' // 44ce #6 + 'dA' // 44cf-44d3 + '1B' // 44d4 #27 + 'gA' // 44d5-44dc + 'bG' // 44dd-44df #6 + 'A' // 44e0 + 'G' // 44e1 #6 + 'aA' // 44e2-44e3 + 'G' // 44e4 #6 + 'cA' // 44e5-44e8 + 'cG' // 44e9-44ec #6 + 'fA' // 44ed-44f3 + 'G' // 44f4 #6 + 'mA' // 44f5-4502 + 'aG' // 4503-4504 #6 + 'bA' // 4505-4507 + '1B' // 4508 #27 + 'G' // 4509 #6 + 'A' // 450a + 'G' // 450b #6 + 'A' // 450c + '1B' // 450d #27 + 'gA' // 450e-4515 + 'G' // 4516 #6 + 'cA' // 4517-451a + 'G' // 451b #6 + 'A' // 451c + 'G' // 451d #6 + 'fA' // 451e-4524 + '1B' // 4525 #27 + 'A' // 4526 + 'G' // 4527 #6 + 'eA' // 4528-452d + 'G' // 452e #6 + 'cA' // 452f-4532 + 'G' // 4533 #6 + 'aA' // 4534-4535 + 'G' // 4536 #6 + 'cA' // 4537-453a + 'G' // 453b #6 + 'A' // 453c + 'G' // 453d #6 + 'A' // 453e + 'G' // 453f #6 + 'bA' // 4540-4542 + '19E' // 4543 #498 + 'lA' // 4544-4550 + 'G' // 4551 #6 + '61Z' // 4552 #1611 + 'aA' // 4553-4554 + 'G' // 4555 #6 + 'aA' // 4556-4557 + 'G' // 4558 #6 + 'bA' // 4559-455b + 'G' // 455c #6 + 'cA' // 455d-4560 + 'aG' // 4561-4562 #6 + 'fA' // 4563-4569 + 'G' // 456a #6 + 'aA' // 456b-456c + 'G' // 456d #6 + 'hA' // 456e-4576 + 'aG' // 4577-4578 #6 + 'A' // 4579 + '1B' // 457a #27 + 'iA' // 457b-4584 + 'G' // 4585 #6 + 'vA' // 4586-459c + '1B' // 459d #27 + 'gA' // 459e-45a5 + 'G' // 45a6 #6 + 'kA' // 45a7-45b2 + 'G' // 45b3 #6 + 'cA' // 45b4-45b7 + '1B' // 45b8 #27 + 'dA' // 45b9-45bd + '1B' // 45be #27 + 'zA' // 45bf-45d9 + 'G' // 45da #6 + 'iA' // 45db-45e4 + '1B' // 45e5 #27 + 'bA' // 45e6-45e8 + 'G' // 45e9 #6 + '19E' // 45ea #498 + 'wA' // 45eb-4602 + 'G' // 4603 #6 + 'aA' // 4604-4605 + 'G' // 4606 #6 + 'gA' // 4607-460e + '19E' // 460f #498 + '1B' // 4610 #27 + 'cA' // 4611-4614 + 'G' // 4615 #6 + 'A' // 4616 + 'G' // 4617 #6 + '1nA' // 4618-4640 + '1B' // 4641 #27 + 'xA' // 4642-465a + 'G' // 465b #6 + 'hA' // 465c-4664 + '1B' // 4665 #27 + 'sA' // 4666-4679 + 'G' // 467a #6 + 'dA' // 467b-467f + 'G' // 4680 #6 + '1eA' // 4681-46a0 + '19E' // 46a1 #498 + 'kA' // 46a2-46ad + '19E' // 46ae #498 + '1B' // 46af #27 + 'jA' // 46b0-46ba + 'G' // 46bb #6 + 'rA' // 46bc-46ce + 'aG' // 46cf-46d0 #6 + '1iA' // 46d1-46f4 + 'G' // 46f5 #6 + 'A' // 46f6 + 'G' // 46f7 #6 + 'sA' // 46f8-470b + '1B' // 470c #27 + 'eA' // 470d-4712 + 'G' // 4713 #6 + 'cA' // 4714-4717 + 'G' // 4718 #6 + 'eA' // 4719-471e + '1B' // 471f #27 + 'bA' // 4720-4722 + '7J' // 4723 #191 + 'qA' // 4724-4735 + 'G' // 4736 #6 + 'lA' // 4737-4743 + 'G' // 4744 #6 + 'hA' // 4745-474d + 'aG' // 474e-474f #6 + 'sA' // 4750-4763 + '1B' // 4764 #27 + 'vA' // 4765-477b + 'G' // 477c #6 + 'zA' // 477d-4797 + 'G' // 4798 #6 + 'lA' // 4799-47a5 + 'G' // 47a6 #6 + '1sA' // 47a7-47d4 + 'G' // 47d5 #6 + 'oA' // 47d6-47e5 + '1B' // 47e6 #27 + 'eA' // 47e7-47ec + 'G' // 47ed #6 + 'eA' // 47ee-47f3 + 'G' // 47f4 #6 + 'gA' // 47f5-47fc + '1B' // 47fd #27 + 'aA' // 47fe-47ff + 'G' // 4800 #6 + 'iA' // 4801-480a + 'G' // 480b #6 + 'iA' // 480c-4815 + '1B' // 4816 #27 + 'fA' // 4817-481d + '1B' // 481e #27 + 'wA' // 481f-4836 + '61Z' // 4837 #1611 + 'kA' // 4838-4843 + '1B' // 4844 #27 + 'hA' // 4845-484d + '1B' // 484e #27 + 'mA' // 484f-485c + 'G' // 485d #6 + 'rA' // 485e-4870 + 'G' // 4871 #6 + '1nA' // 4872-489a + 'G' // 489b #6 + 'pA' // 489c-48ac + 'aG' // 48ad-48ae #6 + 'eA' // 48af-48b4 + '1B' // 48b5 #27 + 'yA' // 48b6-48cf + 'G' // 48d0 #6 + 'kA' // 48d1-48dc + 'G' // 48dd #6 + 'nA' // 48de-48ec + 'G' // 48ed #6 + 'dA' // 48ee-48f2 + 'G' // 48f3 #6 + 'eA' // 48f4-48f9 + '116V' // 48fa #3037 + 'jA' // 48fb-4905 + 'G' // 4906 #6 + 'iA' // 4907-4910 + 'G' // 4911 #6 + 'kA' // 4912-491d + 'G' // 491e #6 + 'eA' // 491f-4924 + 'G' // 4925 #6 + 'cA' // 4926-4929 + 'G' // 492a #6 + 'aA' // 492b-492c + 'G' // 492d #6 + 'A' // 492e + 'aG' // 492f-4930 #6 + 'cA' // 4931-4934 + 'G' // 4935 #6 + 'eA' // 4936-493b + 'G' // 493c #6 + 'A' // 493d + 'G' // 493e #6 + 'eA' // 493f-4944 + 'G' // 4945 #6 + 'jA' // 4946-4950 + 'G' // 4951 #6 + 'A' // 4952 + 'G' // 4953 #6 + 'pA' // 4954-4964 + 'G' // 4965 #6 + 'cA' // 4966-4969 + 'G' // 496a #6 + 'fA' // 496b-4971 + 'G' // 4972 #6 + 'uA' // 4973-4988 + 'G' // 4989 #6 + 'vA' // 498a-49a0 + 'G' // 49a1 #6 + 'dA' // 49a2-49a6 + 'G' // 49a7 #6 + 'gA' // 49a8-49af + '1B' // 49b0 #27 + '1sA' // 49b1-49de + 'G' // 49df #6 + 'dA' // 49e0-49e4 + 'G' // 49e5 #6 + 'A' // 49e6 + '19E' // 49e7 #498 + 'qA' // 49e8-49f9 + '1B' // 49fa #27 + 'hA' // 49fb-4a03 + '1B' // 4a04 #27 + 'iA' // 4a05-4a0e + 'G' // 4a0f #6 + 'lA' // 4a10-4a1c + 'G' // 4a1d #6 + 'eA' // 4a1e-4a23 + 'G' // 4a24 #6 + 'cA' // 4a25-4a28 + '1B' // 4a29 #27 + 'jA' // 4a2a-4a34 + 'G' // 4a35 #6 + '3qA' // 4a36-4a95 + 'G' // 4a96 #6 + 'lA' // 4a97-4aa3 + 'G' // 4aa4 #6 + 'nA' // 4aa5-4ab3 + 'G' // 4ab4 #6 + 'bA' // 4ab5-4ab7 + 'G' // 4ab8 #6 + 'bA' // 4ab9-4abb + '1B' // 4abc #27 + 'sA' // 4abd-4ad0 + 'G' // 4ad1 #6 + 'qA' // 4ad2-4ae3 + '1Y' // 4ae4 #50 + 'yA' // 4ae5-4afe + '1Y' // 4aff #50 + 'oA' // 4b00-4b0f + '1Y' // 4b10 #50 + 'gA' // 4b11-4b18 + '1Y' // 4b19 #50 + 'eA' // 4b1a-4b1f + '1Y' // 4b20 #50 + 'jA' // 4b21-4b2b + '1Y' // 4b2c #50 + 'iA' // 4b2d-4b36 + '1Y' // 4b37 #50 + '1B' // 4b38 #27 + 'aA' // 4b39-4b3a + '1B' // 4b3b #27 + '1xA' // 4b3c-4b6e + 'a1Y' // 4b6f-4b70 #50 + 'A' // 4b71 + '1Y' // 4b72 #50 + 'gA' // 4b73-4b7a + '1Y' // 4b7b #50 + 'aA' // 4b7c-4b7d + '40S' // 4b7e #1058 + 'nA' // 4b7f-4b8d + '1Y' // 4b8e #50 + 'A' // 4b8f + '1Y' // 4b90 #50 + 'aA' // 4b91-4b92 + '1Y' // 4b93 #50 + 'aA' // 4b94-4b95 + 'a1Y' // 4b96-4b97 #50 + 'dA' // 4b98-4b9c + '1Y' // 4b9d #50 + '1dA' // 4b9e-4bbc + 'a1Y' // 4bbd-4bbe #50 + 'A' // 4bbf + '1Y' // 4bc0 #50 + 'A' // 4bc1 + '1B' // 4bc2 #27 + 'fA' // 4bc3-4bc9 + '1B' // 4bca #27 + 'fA' // 4bcb-4bd1 + '1B' // 4bd2 #27 + 'tA' // 4bd3-4be7 + '1B' // 4be8 #27 + 'zA' // 4be9-4c03 + '1Y' // 4c04 #50 + 'aA' // 4c05-4c06 + '1Y' // 4c07 #50 + 'eA' // 4c08-4c0d + '1Y' // 4c0e #50 + 'gA' // 4c0f-4c16 + '1B' // 4c17 #27 + 'gA' // 4c18-4c1f + '1B' // 4c20 #27 + 'pA' // 4c21-4c31 + '1Y' // 4c32 #50 + 'dA' // 4c33-4c37 + '1B' // 4c38 #27 + 'aA' // 4c39-4c3a + '1Y' // 4c3b #50 + 'aA' // 4c3c-4c3d + '1Y' // 4c3e #50 + 'A' // 4c3f + '1Y' // 4c40 #50 + 'eA' // 4c41-4c46 + '1Y' // 4c47 #50 + 'nA' // 4c48-4c56 + '1Y' // 4c57 #50 + 'bA' // 4c58-4c5a + '1Y' // 4c5b #50 + 'pA' // 4c5c-4c6c + '1Y' // 4c6d #50 + 'hA' // 4c6e-4c76 + '1Y' // 4c77 #50 + 'bA' // 4c78-4c7a + '1Y' // 4c7b #50 + 'A' // 4c7c + '1Y' // 4c7d #50 + 'bA' // 4c7e-4c80 + '1Y' // 4c81 #50 + 'bA' // 4c82-4c84 + '1Y' // 4c85 #50 + '1bA' // 4c86-4ca2 + '7J' // 4ca3 #191 + '1Y' // 4ca4 #50 + 'hA' // 4ca5-4cad + '1Y' // 4cae #50 + 'A' // 4caf + '1Y' // 4cb0 #50 + 'eA' // 4cb1-4cb6 + '1Y' // 4cb7 #50 + 'kA' // 4cb8-4cc3 + '1B' // 4cc4 #27 + 'gA' // 4cc5-4ccc + '1Y' // 4ccd #50 + 'bA' // 4cce-4cd0 + '1B' // 4cd1 #27 + 'nA' // 4cd2-4ce0 + '40S' // 4ce1 #1058 + '1Y' // 4ce2 #50 + 'iA' // 4ce3-4cec + '1Y' // 4ced #50 + 'xA' // 4cee-4d06 + '40S' // 4d07 #1058 + 'A' // 4d08 + '48K' // 4d09 #1258 + 'eA' // 4d0a-4d0f + '1Y' // 4d10 #50 + '1hA' // 4d11-4d33 + '1Y' // 4d34 #50 + '2lA' // 4d35-4d75 + '1Y' // 4d76 #50 + '40S' // 4d77 #1058 + 'pA' // 4d78-4d88 + '1Y' // 4d89 #50 + 'fA' // 4d8a-4d90 + '1Y' // 4d91 #50 + 'iA' // 4d92-4d9b + '1Y' // 4d9c #50 + '1hA' // 4d9d-4dbf + '2kF' // 4dc0-4dff #5 + '247B' // 4e00 #6423 + '205Y' // 4e01 #5354 + '19J' // 4e02 #503 + '220E' // 4e03 #5724 + '14O' // 4e04 #378 + '19J' // 4e05 #503 + '7J' // 4e06 #191 + '131G' // 4e07 #3412 + '169H' // 4e08 #4401 + '243R' // 4e09 #6335 + '69L' // 4e0a #1805 + '69J' // 4e0b #1803 + '14O' // 4e0c #378 + '246N' // 4e0d #6409 + '162E' // 4e0e #4216 + '14O' // 4e0f #378 + '116J' // 4e10 #3025 + '141I' // 4e11 #3674 + '1B' // 4e12 #27 + '7K' // 4e13 #192 + '226O' // 4e14 #5890 + '40T' // 4e15 #1059 + '238A' // 4e16 #6188 + '19J' // 4e17 #503 + '161X' // 4e18 #4209 + '65L' // 4e19 #1701 + '35T' // 4e1a #929 + '2R' // 4e1b #69 + '64Z' // 4e1c #1689 + '2D' // 4e1d #55 + '147O' // 4e1e #3836 + '179T' // 4e1f #4673 + '7J' // 4e20 #191 + '116P' // 4e21 #3031 + '116S' // 4e22 #3034 + '1B' // 4e23 #27 + '141L' // 4e24 #3677 + '2C' // 4e25 #54 + '235L' // 4e26 #6121 + '2Y' // 4e27 #76 + '135G' // 4e28 #3516 + '19J' // 4e29 #503 + '166C' // 4e2a #4318 + '141K' // 4e2b #3676 + '61V' // 4e2c #1607 + '247G' // 4e2d #6428 + '1B' // 4e2e #27 + '61V' // 4e2f #1607 + '124M' // 4e30 #3236 + '14O' // 4e31 #378 + '195V' // 4e32 #5091 + 'A' // 4e33 + '2C' // 4e34 #54 + '19J' // 4e35 #503 + '116L' // 4e36 #3027 + '14O' // 4e37 #378 + '183Z' // 4e38 #4783 + '203G' // 4e39 #5284 + '7K' // 4e3a #192 + '243B' // 4e3b #6319 + '136V' // 4e3c #3557 + '61Y' // 4e3d #1610 + '3Q' // 4e3e #94 + '14O' // 4e3f #378 + 'a19J' // 4e40-4e41 #503 + '40T' // 4e42 #1059 + '183M' // 4e43 #4770 + '19J' // 4e44 #503 + '222Y' // 4e45 #5796 + '7J' // 4e46 #191 + '14O' // 4e47 #378 + '159D' // 4e48 #4137 + '61Y' // 4e49 #1610 + 'A' // 4e4a + '241F' // 4e4b #6271 + '3N' // 4e4c #91 + '135F' // 4e4d #3515 + '208H' // 4e4e #5415 + '174R' // 4e4f #4541 + '3Q' // 4e50 #94 + '1B' // 4e51 #27 + '116R' // 4e52 #3033 + '116Q' // 4e53 #3032 + '1R' // 4e54 #43 + '1B' // 4e55 #27 + '166A' // 4e56 #4316 + '259A' // 4e57 #6734 + '194G' // 4e58 #5050 + '176A' // 4e59 #4576 + 'a14O' // 4e5a-4e5b #378 + '147P' // 4e5c #3837 + '221T' // 4e5d #5765 + '125C' // 4e5e #3252 + '241B' // 4e5f #6267 + '2D' // 4e60 #55 + '3I' // 4e61 #86 + 'a1B' // 4e62-4e63 #27 + 'a7J' // 4e64-4e65 #191 + '1Z' // 4e66 #51 + '7J' // 4e67 #191 + '1B' // 4e68 #27 + '14O' // 4e69 #378 + '1Y' // 4e6a #50 + '50F' // 4e6b #1305 + 'A' // 4e6c + '261D' // 4e6d #6789 + 'aA' // 4e6e-4e6f + '1Z' // 4e70 #51 + '257I' // 4e71 #6690 + 'A' // 4e72 + '205Q' // 4e73 #5346 + 'a1B' // 4e74-4e75 #27 + 'a50F' // 4e76-4e77 #1305 + '40V' // 4e78 #1061 + '1B' // 4e79 #27 + 'cA' // 4e7a-4e7d + '215H' // 4e7e #5597 + '19J' // 4e7f #503 + '116M' // 4e80 #3028 + '40V' // 4e81 #1061 + '208F' // 4e82 #5413 + 'aA' // 4e83-4e84 + '14O' // 4e85 #378 + '242G' // 4e86 #6298 + '40V' // 4e87 #1061 + '206M' // 4e88 #5368 + '116N' // 4e89 #3029 + '19J' // 4e8a #503 + '245L' // 4e8b #6381 + '68T' // 4e8c #1787 + '14O' // 4e8d #378 + '200Z' // 4e8e #5225 + '2Y' // 4e8f #76 + '50F' // 4e90 #1305 + '160G' // 4e91 #4166 + '211M' // 4e92 #5498 + '40V' // 4e93 #1061 + '234W' // 4e94 #6106 + '191T' // 4e95 #4985 + '19J' // 4e96 #503 + '1B' // 4e97 #27 + '40T' // 4e98 #1059 + '14O' // 4e99 #378 + '169V' // 4e9a #4415 + '233D' // 4e9b #6061 + '256P' // 4e9c #6671 + '1B' // 4e9d #27 + '226D' // 4e9e #5879 + 'a14O' // 4e9f-4ea0 #378 + '198B' // 4ea1 #5149 + '40T' // 4ea2 #1059 + '61X' // 4ea3 #1609 + '237Y' // 4ea4 #6186 + '35M' // 4ea5 #922 + '214W' // 4ea6 #5586 + '7K' // 4ea7 #192 + '165Z' // 4ea8 #4315 + '3H' // 4ea9 #85 + 'A' // 4eaa + '240R' // 4eab #6257 + '216R' // 4eac #5633 + '168F' // 4ead #4373 + '218S' // 4eae #5686 + '1B' // 4eaf #27 + '16C' // 4eb0 #418 + '8A' // 4eb1 #208 + '2D' // 4eb2 #55 + '13F' // 4eb3 #343 + 'a8A' // 4eb4-4eb5 #208 + '35M' // 4eb6 #922 + '48K' // 4eb7 #1258 + '8A' // 4eb8 #208 + '13F' // 4eb9 #343 + '247E' // 4eba #6426 + 'a13F' // 4ebb-4ebc #343 + 'a8A' // 4ebd-4ebe #208 + '116U' // 4ebf #3036 + '226K' // 4ec0 #5886 + '209Z' // 4ec1 #5459 + 'a13F' // 4ec2-4ec3 #343 + '35M' // 4ec4 #922 + '2D' // 4ec5 #55 + '116K' // 4ec6 #3026 + '166B' // 4ec7 #4317 + '13F' // 4ec8 #343 + '40U' // 4ec9 #1060 + '239I' // 4eca #6222 + '238U' // 4ecb #6208 + '8A' // 4ecc #208 + '208G' // 4ecd #5414 + '130D' // 4ece #3383 + '255D' // 4ecf #6633 + '16C' // 4ed0 #418 + '2K' // 4ed1 #62 + '8A' // 4ed2 #208 + '3N' // 4ed3 #91 + '218D' // 4ed4 #5671 + '184I' // 4ed5 #4792 + '245A' // 4ed6 #6370 + '65L' // 4ed7 #1701 + '224E' // 4ed8 #5828 + '204E' // 4ed9 #5308 + 'a16C' // 4eda-4edb #418 + '48K' // 4edc #1258 + '35M' // 4edd #922 + '13F' // 4ede #343 + '35M' // 4edf #922 + '16C' // 4ee0 #418 + '13F' // 4ee1 #343 + '1B' // 4ee2 #27 + '243Z' // 4ee3 #6343 + '242Z' // 4ee4 #6317 + '69G' // 4ee5 #1800 + 'a8A' // 4ee6-4ee7 #208 + '13F' // 4ee8 #343 + '40U' // 4ee9 #1060 + '116T' // 4eea #3035 + '13F' // 4eeb #343 + '7K' // 4eec #192 + '1B' // 4eed #27 + '116O' // 4eee #3030 + '16C' // 4eef #418 + '181R' // 4ef0 #4723 + '61W' // 4ef1 #1608 + '198Y' // 4ef2 #5172 + '13F' // 4ef3 #343 + '40U' // 4ef4 #1060 + '13F' // 4ef5 #343 + '243X' // 4ef6 #6341 + '141J' // 4ef7 #3675 + '61X' // 4ef8 #1609 + 'a8A' // 4ef9-4efa #208 + '235W' // 4efb #6132 + '16C' // 4efc #418 + '233B' // 4efd #6059 + '1B' // 4efe #27 + '179U' // 4eff #4674 + '13F' // 4f00 #343 + '223P' // 4f01 #5813 + '61W' // 4f02 #1608 + '13F' // 4f03 #343 + '8A' // 4f04 #208 + '40U' // 4f05 #1060 + 'aA' // 4f06-4f07 + '13F' // 4f08 #343 + '61S' // 4f09 #1604 + '205L' // 4f0a #5341 + '61S' // 4f0b #1604 + '1B' // 4f0c #27 + '172K' // 4f0d #4482 + '116B' // 4f0e #3017 + '175L' // 4f0f #4561 + '153O' // 4f10 #3992 + '223K' // 4f11 #5808 + '16C' // 4f12 #418 + '8A' // 4f13 #208 + 'A' // 4f14 + '115V' // 4f15 #3011 + '1B' // 4f16 #27 + '115W' // 4f17 #3012 + '143W' // 4f18 #3740 + '172L' // 4f19 #4483 + '245S' // 4f1a #6388 + '8A' // 4f1b #208 + '1B' // 4f1c #27 + '116E' // 4f1d #3020 + '2R' // 4f1e #69 + '3Y' // 4f1f #102 + '3Q' // 4f20 #94 + '8A' // 4f21 #208 + '61U' // 4f22 #1606 + 'A' // 4f23 + '2C' // 4f24 #54 + '8A' // 4f25 #208 + '3I' // 4f26 #86 + '8A' // 4f27 #208 + '116G' // 4f28 #3022 + '61U' // 4f29 #1606 + '2L' // 4f2a #63 + '16C' // 4f2b #418 + '8A' // 4f2c #208 + '21C' // 4f2d #548 + '16C' // 4f2e #418 + '195L' // 4f2f #5081 + '194H' // 4f30 #5051 + '16C' // 4f31 #418 + '21C' // 4f32 #548 + '16C' // 4f33 #418 + '215F' // 4f34 #5595 + '1B' // 4f35 #27 + '141H' // 4f36 #3673 + '40Q' // 4f37 #1056 + '204U' // 4f38 #5324 + '40Q' // 4f39 #1056 + '167Y' // 4f3a #4366 + '40R' // 4f3b #1057 + '221U' // 4f3c #5766 + '159S' // 4f3d #4152 + '40Q' // 4f3e #1056 + 'A' // 4f3f + '16C' // 4f40 #418 + '21C' // 4f41 #548 + '40R' // 4f42 #1057 + '115U' // 4f43 #3010 + 'A' // 4f44 + '40N' // 4f45 #1053 + '233H' // 4f46 #6065 + '115Z' // 4f47 #3015 + '208E' // 4f48 #5412 + '40Q' // 4f49 #1056 + 'A' // 4f4a + '115T' // 4f4b #3009 + '40R' // 4f4c #1057 + '242Y' // 4f4d #6316 + '229N' // 4f4e #5967 + '231H' // 4f4f #6013 + '191S' // 4f50 #4984 + '167Q' // 4f51 #4358 + '40R' // 4f52 #1057 + '214E' // 4f53 #5568 + '187B' // 4f54 #4863 + '237X' // 4f55 #6185 + '1B' // 4f56 #27 + 'a9H' // 4f57-4f58 #241 + '176P' // 4f59 #4591 + '115M' // 4f5a #3002 + '200X' // 4f5b #5223 + '246S' // 4f5c #6414 + 'a9H' // 4f5d-4f5e #241 + '115Q' // 4f5f #3006 + '240M' // 4f60 #6252 + '21C' // 4f61 #548 + '169S' // 4f62 #4412 + '115R' // 4f63 #3007 + '9H' // 4f64 #241 + '8A' // 4f65 #208 + 'A' // 4f66 + '21C' // 4f67 #548 + 'A' // 4f68 + '186Y' // 4f69 #4860 + '9H' // 4f6a #241 + 'A' // 4f6b + '65W' // 4f6c #1712 + 'A' // 4f6d + '9H' // 4f6e #241 + '17F' // 4f6f #447 + '152L' // 4f70 #3963 + '3F' // 4f71 #83 + '21C' // 4f72 #548 + '227E' // 4f73 #5906 + '21C' // 4f74 #548 + '197G' // 4f75 #5128 + '17F' // 4f76 #447 + 'b9H' // 4f77-4f79 #241 + '17F' // 4f7a #447 + '9H' // 4f7b #241 + '124L' // 4f7c #3235 + '9H' // 4f7d #241 + '17F' // 4f7e #447 + '244Z' // 4f7f #6369 + '21C' // 4f80 #548 + '17F' // 4f81 #447 + '9H' // 4f82 #241 + '124K' // 4f83 #3234 + '17F' // 4f84 #447 + '3F' // 4f85 #83 + '240T' // 4f86 #6259 + 'A' // 4f87 + '135D' // 4f88 #3513 + '9H' // 4f89 #241 + '17F' // 4f8a #447 + '222X' // 4f8b #5795 + '3F' // 4f8c #83 + '148Q' // 4f8d #3864 + '49W' // 4f8e #1296 + '17F' // 4f8f #447 + '9H' // 4f90 #241 + '115Y' // 4f91 #3014 + '9H' // 4f92 #241 + '49W' // 4f93 #1296 + '9H' // 4f94 #241 + '8A' // 4f95 #208 + '17F' // 4f96 #447 + '9H' // 4f97 #241 + '17F' // 4f98 #447 + '3F' // 4f99 #83 + '21B' // 4f9a #547 + '237W' // 4f9b #6184 + 'A' // 4f9c + '234M' // 4f9d #6096 + '9H' // 4f9e #241 + '3F' // 4f9f #83 + '251A' // 4fa0 #6526 + '259Z' // 4fa1 #6759 + '23U' // 4fa2 #618 + '1R' // 4fa3 #43 + 'A' // 4fa4 + '8A' // 4fa5 #208 + '2R' // 4fa6 #69 + '3N' // 4fa7 #91 + '116H' // 4fa8 #3023 + 'a8A' // 4fa9-4faa #208 + '21B' // 4fab #547 + '8A' // 4fac #208 + '3F' // 4fad #83 + '173A' // 4fae #4498 + '165Y' // 4faf #4314 + '40N' // 4fb0 #1053 + 'A' // 4fb1 + '21B' // 4fb2 #547 + '21C' // 4fb3 #548 + '40N' // 4fb4 #1053 + '210D' // 4fb5 #5463 + '180C' // 4fb6 #4682 + '9H' // 4fb7 #241 + '8A' // 4fb8 #208 + '21B' // 4fb9 #547 + '23U' // 4fba #618 + 'a3F' // 4fbb-4fbc #83 + '115O' // 4fbd #3004 + '3F' // 4fbe #83 + '236Y' // 4fbf #6160 + 'a21B' // 4fc0-4fc1 #547 + '222W' // 4fc2 #5794 + '203F' // 4fc3 #5283 + '186Z' // 4fc4 #4861 + '9H' // 4fc5 #241 + '49W' // 4fc6 #1296 + '23U' // 4fc7 #618 + '40O' // 4fc8 #1054 + '70L' // 4fc9 #1831 + '189D' // 4fca #4917 + '21B' // 4fcb #547 + '40O' // 4fcc #1054 + '9H' // 4fcd #241 + '17F' // 4fce #447 + '65W' // 4fcf #1712 + '141F' // 4fd0 #3671 + '19D' // 4fd1 #497 + '3F' // 4fd2 #83 + '19D' // 4fd3 #497 + '115N' // 4fd4 #3003 + 'aA' // 4fd5-4fd6 + '197B' // 4fd7 #5123 + '115P' // 4fd8 #3005 + '23U' // 4fd9 #618 + '19D' // 4fda #497 + 'a15X' // 4fdb-4fdc #413 + '68V' // 4fdd #1789 + '131L' // 4fde #3417 + '19D' // 4fdf #497 + '179Q' // 4fe0 #4670 + '49G' // 4fe1 #1280 + '13I' // 4fe2 #346 + '252T' // 4fe3 #6571 + '15X' // 4fe4 #413 + '21B' // 4fe5 #547 + '13I' // 4fe6 #346 + 'A' // 4fe7 + '16H' // 4fe8 #423 + '2Y' // 4fe9 #76 + '3G' // 4fea #84 + '16H' // 4feb #423 + '61T' // 4fec #1605 + '16H' // 4fed #423 + '230K' // 4fee #5990 + '135C' // 4fef #3512 + '40O' // 4ff0 #1054 + '194F' // 4ff1 #5049 + '40O' // 4ff2 #1054 + '116C' // 4ff3 #3018 + '23U' // 4ff4 #618 + '116A' // 4ff5 #3016 + '15X' // 4ff6 #413 + 'A' // 4ff7 + '115X' // 4ff8 #3013 + '23U' // 4ff9 #618 + '116D' // 4ffa #3019 + 'A' // 4ffb + '3F' // 4ffc #83 + '21B' // 4ffd #547 + '147N' // 4ffe #3835 + '3F' // 4fff #83 + '21B' // 5000 #547 + '13I' // 5001 #346 + '70L' // 5002 #1831 + '40N' // 5003 #1053 + '3F' // 5004 #83 + '15X' // 5005 #413 + '65V' // 5006 #1711 + '3F' // 5007 #83 + '23U' // 5008 #618 + '191L' // 5009 #4977 + '3F' // 500a #83 + '246L' // 500b #6407 + '15X' // 500c #413 + '205V' // 500d #5351 + '3F' // 500e #83 + '15X' // 500f #413 + '3F' // 5010 #83 + '240H' // 5011 #6247 + '212G' // 5012 #5518 + 'a15X' // 5013-5014 #413 + '61T' // 5015 #1605 + '129R' // 5016 #3371 + '13I' // 5017 #346 + '135E' // 5018 #3514 + '219U' // 5019 #5714 + '135A' // 501a #3510 + '15X' // 501b #413 + '19D' // 501c #497 + '13I' // 501d #346 + '19D' // 501e #497 + '197V' // 501f #5143 + 'A' // 5020 + '147M' // 5021 #3834 + '15X' // 5022 #413 + '19D' // 5023 #497 + '258Q' // 5024 #6724 + '15X' // 5025 #413 + '135B' // 5026 #3511 + 'a19D' // 5027-5028 #497 + '141G' // 5029 #3672 + '141E' // 502a #3670 + '210N' // 502b #5473 + 'a19D' // 502c-502d #497 + '15X' // 502e #413 + 'A' // 502f + '15X' // 5030 #413 + '23U' // 5031 #618 + '3F' // 5032 #83 + '13I' // 5033 #346 + '116F' // 5034 #3021 + '3F' // 5035 #83 + '255A' // 5036 #6630 + 'a16H' // 5037-5038 #423 + '13I' // 5039 #346 + '2Y' // 503a #76 + '19D' // 503b #497 + '231T' // 503c #6025 + 'A' // 503d + '1R' // 503e #43 + 'A' // 503f + '115S' // 5040 #3008 + '40P' // 5041 #1055 + '13I' // 5042 #346 + '61R' // 5043 #1603 + 'A' // 5044 + 'a40P' // 5045-5046 #1055 + '226C' // 5047 #5878 + '61R' // 5048 #1603 + '196I' // 5049 #5104 + '40P' // 504a #1055 + '116I' // 504b #3024 + '40P' // 504c #1055 + '40M' // 504d #1052 + '12I' // 504e #320 + '202V' // 504f #5273 + '3F' // 5050 #83 + '12I' // 5051 #320 + '3F' // 5052 #83 + '12I' // 5053 #320 + 'A' // 5054 + '64W' // 5055 #1686 + '61Q' // 5056 #1602 + '13I' // 5057 #346 + '48F' // 5058 #1253 + '3F' // 5059 #83 + '233A' // 505a #6058 + '16H' // 505b #423 + '220U' // 505c #5740 + '19C' // 505d #496 + '40M' // 505e #1052 + '21A' // 505f #546 + '12I' // 5060 #320 + 'A' // 5061 + '13I' // 5062 #346 + '12I' // 5063 #320 + 'A' // 5064 + '236B' // 5065 #6137 + '40L' // 5066 #1051 + '13I' // 5067 #346 + 'aA' // 5068-5069 + '12I' // 506a #320 + 'A' // 506b + '61Q' // 506c #1602 + '3F' // 506d #83 + 'A' // 506e + '19C' // 506f #496 + '12I' // 5070 #320 + '13I' // 5071 #346 + '12I' // 5072 #320 + 'A' // 5073 + '199B' // 5074 #5175 + '175N' // 5075 #4563 + '202F' // 5076 #5257 + '200Y' // 5077 #5224 + '250Z' // 5078 #6525 + 'A' // 5079 + '19C' // 507a #496 + '16H' // 507b #423 + 'A' // 507c + '168O' // 507d #4382 + '16H' // 507e #423 + '2L' // 507f #63 + '48H' // 5080 #1255 + '40L' // 5081 #1051 + 'A' // 5082 + 'a3F' // 5083-5084 #83 + '179R' // 5085 #4671 + '3F' // 5086 #83 + 'A' // 5087 + '12I' // 5088 #320 + '16H' // 5089 #423 + '3F' // 508a #83 + 'a19C' // 508b-508c #496 + '142L' // 508d #3703 + '21A' // 508e #546 + '3F' // 508f #83 + '40L' // 5090 #1051 + '195I' // 5091 #5078 + '12I' // 5092 #320 + 'a3F' // 5093-5094 #83 + '12I' // 5095 #320 + '21A' // 5096 #546 + '16H' // 5097 #423 + '167S' // 5098 #4360 + '236X' // 5099 #6159 + 'b12I' // 509a-509c #320 + '40M' // 509d #1052 + '13I' // 509e #346 + 'b3F' // 509f-50a1 #83 + '179S' // 50a2 #4672 + '12I' // 50a3 #320 + 'A' // 50a4 + '16H' // 50a5 #423 + '115H' // 50a6 #2997 + '16H' // 50a7 #423 + '3Y' // 50a8 #102 + '16H' // 50a9 #423 + '3F' // 50aa #83 + 'A' // 50ab + '169L' // 50ac #4405 + '64W' // 50ad #1686 + 'A' // 50ae + '21A' // 50af #546 + '3F' // 50b0 #83 + '21A' // 50b1 #546 + '172J' // 50b2 #4481 + '240G' // 50b3 #6246 + '21A' // 50b4 #546 + '174G' // 50b5 #4530 + 'A' // 50b6 + '215O' // 50b7 #5604 + 'A' // 50b8 + '3F' // 50b9 #83 + '12I' // 50ba #320 + '187A' // 50bb #4862 + '48F' // 50bc #1253 + '3F' // 50bd #83 + '182N' // 50be #4745 + 'A' // 50bf + '3F' // 50c0 #83 + 'A' // 50c1 + '21A' // 50c2 #546 + '3F' // 50c3 #83 + '13I' // 50c4 #346 + '218J' // 50c5 #5677 + '19C' // 50c6 #496 + '12I' // 50c7 #320 + '19C' // 50c8 #496 + '114Y' // 50c9 #2988 + '48H' // 50ca #1255 + 'A' // 50cb + '3F' // 50cc #83 + '115F' // 50cd #2995 + '21A' // 50ce #546 + '238W' // 50cf #6210 + '40L' // 50d0 #1051 + '65V' // 50d1 #1711 + 'A' // 50d2 + 'a3F' // 50d3-50d4 #83 + '143M' // 50d5 #3730 + '48H' // 50d6 #1255 + 'A' // 50d7 + '3F' // 50d8 #83 + '21A' // 50d9 #546 + '125T' // 50da #3269 + 'A' // 50db + '3F' // 50dc #83 + '14N' // 50dd #377 + '250U' // 50de #6520 + '14N' // 50df #377 + 'A' // 50e0 + '14N' // 50e1 #377 + '2Q' // 50e2 #68 + '14N' // 50e3 #377 + '2Q' // 50e4 #68 + '40K' // 50e5 #1050 + '31G' // 50e6 #812 + '142M' // 50e7 #3704 + 'a14N' // 50e8-50e9 #377 + '19C' // 50ea #496 + 'A' // 50eb + '40M' // 50ec #1052 + '40K' // 50ed #1050 + '31G' // 50ee #812 + '14N' // 50ef #377 + '31G' // 50f0 #812 + '141C' // 50f1 #3668 + '2Q' // 50f2 #68 + '31G' // 50f3 #812 + '48G' // 50f4 #1254 + '141D' // 50f5 #3669 + '2Q' // 50f6 #68 + 'aA' // 50f7-50f8 + '232Y' // 50f9 #6056 + '250V' // 50fa #6521 + '40K' // 50fb #1050 + '48F' // 50fc #1253 + 'A' // 50fd + '31G' // 50fe #812 + '261C' // 50ff #6788 + '203B' // 5100 #5279 + '40K' // 5101 #1050 + '31G' // 5102 #812 + '2Q' // 5103 #68 + '203T' // 5104 #5297 + '19C' // 5105 #496 + '31F' // 5106 #811 + '10E' // 5107 #264 + '14N' // 5108 #377 + '31F' // 5109 #811 + '15A' // 510a #390 + '10E' // 510b #264 + '14N' // 510c #377 + '10E' // 510d #264 + '14N' // 510e #377 + '15A' // 510f #390 + '10E' // 5110 #264 + 'A' // 5111 + '159C' // 5112 #4136 + '48J' // 5113 #1257 + '14N' // 5114 #377 + '10E' // 5115 #264 + '2Q' // 5116 #68 + '10E' // 5117 #264 + '179O' // 5118 #4668 + '2Q' // 5119 #68 + '10E' // 511a #264 + '7R' // 511b #199 + '10E' // 511c #264 + '2Q' // 511d #68 + '7R' // 511e #199 + '176B' // 511f #4577 + '19C' // 5120 #496 + '31F' // 5121 #811 + 'A' // 5122 + '2Q' // 5123 #68 + 'A' // 5124 + '19C' // 5125 #496 + 'A' // 5126 + '2Q' // 5127 #68 + '7R' // 5128 #199 + 'A' // 5129 + '237M' // 512a #6174 + '48J' // 512b #1257 + 'a7R' // 512c-512d #199 + 'A' // 512e + '2Q' // 512f #68 + 'A' // 5130 + '10E' // 5131 #264 + '201S' // 5132 #5244 + '7R' // 5133 #199 + 'a10E' // 5134-5135 #264 + 'A' // 5136 + '134X' // 5137 #3507 + 'a10E' // 5138-5139 #264 + '61N' // 513a #1599 + '14N' // 513b #377 + '31F' // 513c #811 + 'aA' // 513d-513e + '134Z' // 513f #3509 + '31F' // 5140 #811 + '172I' // 5141 #4480 + '7R' // 5142 #199 + '238M' // 5143 #6200 + '198P' // 5144 #5163 + '221S' // 5145 #5764 + '174Q' // 5146 #4540 + '165X' // 5147 #4313 + '239C' // 5148 #6216 + '68T' // 5149 #1787 + '2Q' // 514a #68 + '226Y' // 514b #5900 + '172H' // 514c #4479 + '234N' // 514d #6097 + '253I' // 514e #6586 + '7R' // 514f #199 + '258T' // 5150 #6727 + '115J' // 5151 #2999 + '232Z' // 5152 #6057 + '2Q' // 5153 #68 + '186X' // 5154 #4859 + '10E' // 5155 #264 + '48J' // 5156 #1257 + '10E' // 5157 #264 + '7R' // 5158 #199 + 'A' // 5159 + '115D' // 515a #2993 + 'A' // 515b + '147W' // 515c #3844 + 'aA' // 515d-515e + '14N' // 515f #377 + '48G' // 5160 #1254 + 'A' // 5161 + '31F' // 5162 #811 + 'A' // 5163 + '2Q' // 5164 #68 + '246X' // 5165 #6419 + '2Q' // 5166 #68 + '240F' // 5167 #6245 + '41F' // 5168 #1071 + '232X' // 5169 #6055 + '61N' // 516a #1599 + '228O' // 516b #5942 + '69H' // 516c #1801 + '227S' // 516d #5920 + '114T' // 516e #2983 + 'A' // 516f + '2C' // 5170 #54 + '236Z' // 5171 #6161 + '15A' // 5172 #390 + '250Y' // 5173 #6524 + '114X' // 5174 #2987 + '205U' // 5175 #5350 + '240V' // 5176 #6261 + '236W' // 5177 #6158 + '229W' // 5178 #5976 + '114V' // 5179 #2985 + 'A' // 517a + '250X' // 517b #6523 + '197Q' // 517c #5138 + '3N' // 517d #91 + '2Q' // 517e #68 + '15A' // 517f #390 + '129Q' // 5180 #3370 + '15A' // 5181 #390 + '10E' // 5182 #264 + 'a7R' // 5183-5184 #199 + '260K' // 5185 #6770 + '115G' // 5186 #2996 + '169R' // 5187 #4411 + '115I' // 5188 #2998 + '114U' // 5189 #2984 + '233P' // 518a #6073 + '7R' // 518b #199 + '70K' // 518c #1830 + '242P' // 518d #6307 + '2Q' // 518e #68 + '10E' // 518f #264 + '2Q' // 5190 #68 + '10E' // 5191 #264 + '203O' // 5192 #5292 + '14N' // 5193 #377 + 'A' // 5194 + '114S' // 5195 #2982 + '10E' // 5196 #264 + '115A' // 5197 #2990 + '2Q' // 5198 #68 + '260A' // 5199 #6760 + '48I' // 519a #1256 + '2D' // 519b #55 + '115K' // 519c #3000 + '2Q' // 519d #68 + '115L' // 519e #3001 + 'A' // 519f + '202U' // 51a0 #5272 + '2Q' // 51a1 #68 + '35L' // 51a2 #921 + '7R' // 51a3 #199 + '147K' // 51a4 #3832 + '142A' // 51a5 #3692 + '7R' // 51a6 #199 + '48I' // 51a7 #1256 + '115B' // 51a8 #2991 + '7R' // 51a9 #199 + '129P' // 51aa #3369 + '35L' // 51ab #921 + '205P' // 51ac #5345 + '7R' // 51ad #199 + '48I' // 51ae #1256 + '2Y' // 51af #76 + '214V' // 51b0 #5585 + '35L' // 51b1 #921 + '114W' // 51b2 #2986 + '61O' // 51b3 #1600 + '115C' // 51b4 #2992 + '61O' // 51b5 #1600 + '114Z' // 51b6 #2989 + '221D' // 51b7 #5749 + '48G' // 51b8 #1254 + 'A' // 51b9 + '2Q' // 51ba #68 + '2L' // 51bb #63 + '35L' // 51bc #921 + '114R' // 51bd #2981 + '61P' // 51be #1601 + '7R' // 51bf #199 + '3Y' // 51c0 #102 + 'A' // 51c1 + '2Q' // 51c2 #68 + '35L' // 51c3 #921 + '115E' // 51c4 #2994 + '61P' // 51c5 #1601 + '195A' // 51c6 #5070 + '40J' // 51c7 #1049 + '141B' // 51c8 #3667 + '114A' // 51c9 #2964 + '10D' // 51ca #263 + '31E' // 51cb #810 + '188K' // 51cc #4898 + '196Y' // 51cd #5120 + '40J' // 51ce #1049 + '114E' // 51cf #2968 + '48E' // 51d0 #1252 + '114C' // 51d1 #2966 + 'a31D' // 51d2-51d3 #809 + '10D' // 51d4 #263 + '2Q' // 51d5 #68 + '7R' // 51d6 #199 + 'A' // 51d7 + '2Q' // 51d8 #68 + 'aA' // 51d9-51da + '114I' // 51db #2972 + '114G' // 51dc #2970 + '174P' // 51dd #4539 + '250T' // 51de #6519 + '15W' // 51df #412 + '147L' // 51e0 #3833 + '201Q' // 51e1 #5242 + '31D' // 51e2 #809 + 'A' // 51e3 + '114P' // 51e4 #2979 + '2Q' // 51e5 #68 + '258N' // 51e6 #6721 + '2Q' // 51e7 #68 + '15A' // 51e8 #390 + '7R' // 51e9 #199 + '253A' // 51ea #6578 + '15A' // 51eb #390 + '2Q' // 51ec #68 + '114D' // 51ed #2967 + '2Q' // 51ee #68 + '3N' // 51ef #91 + '153B' // 51f0 #3979 + '201I' // 51f1 #5234 + '2Q' // 51f2 #68 + '134Y' // 51f3 #3508 + '15W' // 51f4 #412 + '10D' // 51f5 #263 + '153U' // 51f6 #3998 + '2Q' // 51f7 #68 + '167B' // 51f8 #4343 + '153Y' // 51f9 #4002 + '41F' // 51fa #1071 + '3Q' // 51fb #94 + '40J' // 51fc #1049 + '175A' // 51fd #4550 + '31D' // 51fe #809 + '15A' // 51ff #390 + '203E' // 5200 #5282 + '114B' // 5201 #2965 + '10D' // 5202 #263 + '142W' // 5203 #3714 + '2Q' // 5204 #68 + '10D' // 5205 #263 + '69K' // 5206 #1804 + '223Y' // 5207 #5822 + '114H' // 5208 #2971 + 'A' // 5209 + '228A' // 520a #5928 + '10D' // 520b #263 + 'A' // 520c + '15A' // 520d #390 + '31E' // 520e #810 + 'aA' // 520f-5210 + '182M' // 5211 #4744 + '179P' // 5212 #4669 + '15W' // 5213 #412 + '2Q' // 5214 #68 + '7R' // 5215 #199 + '10D' // 5216 #263 + '234D' // 5217 #6087 + '250W' // 5218 #6522 + '2D' // 5219 #55 + '3I' // 521a #86 + '3Q' // 521b #94 + 'A' // 521c + '224C' // 521d #5826 + 'A' // 521e + '40F' // 521f #1045 + '114Q' // 5220 #2980 + 'A' // 5221 + '2Q' // 5222 #68 + 'A' // 5223 + '206B' // 5224 #5357 + '239B' // 5225 #6215 + 'a15W' // 5226-5227 #412 + '10D' // 5228 #263 + '245I' // 5229 #6378 + '208D' // 522a #5411 + '70K' // 522b #1830 + 'a15A' // 522c-522d #390 + '172G' // 522e #4478 + 'A' // 522f + '246H' // 5230 #6403 + '2Q' // 5231 #68 + '15W' // 5232 #412 + '10D' // 5233 #263 + '48E' // 5234 #1252 + '2Q' // 5235 #68 + '231E' // 5236 #6010 + '205B' // 5237 #5331 + '205O' // 5238 #5344 + '251V' // 5239 #6547 + '205D' // 523a #5333 + '215S' // 523b #5608 + '10D' // 523c #263 + '15A' // 523d #390 + 'A' // 523e + 'a15A' // 523f-5240 #390 + '114O' // 5241 #2978 + '1R' // 5242 #43 + '125H' // 5243 #3257 + '15W' // 5244 #412 + '7R' // 5245 #199 + 'A' // 5246 + '233X' // 5247 #6081 + 'A' // 5248 + '10D' // 5249 #263 + '162D' // 524a #4215 + '134V' // 524b #3505 + '31E' // 524c #810 + '245Q' // 524d #6386 + '143U' // 524e #3738 + '31D' // 524f #809 + '15A' // 5250 #390 + '3N' // 5251 #91 + '48E' // 5252 #1252 + 'A' // 5253 + '134W' // 5254 #3506 + '15W' // 5255 #412 + '153J' // 5256 #3987 + '15W' // 5257 #412 + '2Q' // 5258 #68 + '40F' // 5259 #1045 + '2Q' // 525a #68 + '219E' // 525b #5698 + '10D' // 525c #263 + '152K' // 525d #3962 + '10D' // 525e #263 + '7R' // 525f #199 + '31D' // 5260 #809 + '31E' // 5261 #810 + 'A' // 5262 + '256O' // 5263 #6670 + '257A' // 5264 #6682 + '254N' // 5265 #6617 + '2Q' // 5266 #68 + '2C' // 5267 #54 + '40F' // 5268 #1045 + '194E' // 5269 #5048 + '194L' // 526a #5055 + 'A' // 526b + '2Q' // 526c #68 + 'A' // 526d + '15W' // 526e #412 + '203W' // 526f #5300 + '253P' // 5270 #6593 + '2Q' // 5271 #68 + '184D' // 5272 #4787 + '10D' // 5273 #263 + '15W' // 5274 #412 + '234L' // 5275 #6095 + 'A' // 5276 + '10D' // 5277 #263 + '2Q' // 5278 #68 + '31D' // 5279 #809 + 'bA' // 527a-527c + '31E' // 527d #810 + 'A' // 527e + '31E' // 527f #810 + '2Q' // 5280 #68 + '40J' // 5281 #1049 + '10D' // 5282 #263 + '214U' // 5283 #5584 + '10D' // 5284 #263 + '7R' // 5285 #199 + 'A' // 5286 + '220G' // 5287 #5726 + '141A' // 5288 #3666 + '200W' // 5289 #5222 + '15W' // 528a #412 + '12T' // 528b #331 + '15W' // 528c #412 + '67B' // 528d #1743 + 'A' // 528e + '114N' // 528f #2977 + '40F' // 5290 #1045 + '67B' // 5291 #1743 + '250Q' // 5292 #6516 + '23T' // 5293 #617 + '40I' // 5294 #1048 + 'c3L' // 5295-5298 #89 + 'A' // 5299 + '113Y' // 529a #2962 + '68Y' // 529b #1792 + '3L' // 529c #89 + '3H' // 529d #85 + '1Z' // 529e #51 + '234A' // 529f #6084 + '41D' // 52a0 #1069 + '66M' // 52a1 #1728 + '12T' // 52a2 #331 + '161R' // 52a3 #4203 + '113W' // 52a4 #2960 + '3L' // 52a5 #89 + '23T' // 52a6 #617 + '9J' // 52a7 #243 + '66M' // 52a8 #1728 + '235E' // 52a9 #6114 + '197F' // 52aa #5127 + '159B' // 52ab #4135 + 'a23T' // 52ac-52ad #617 + 'A' // 52ae + '9J' // 52af #243 + '3L' // 52b0 #89 + '254T' // 52b1 #6623 + '1R' // 52b2 #43 + '3Y' // 52b3 #102 + '257E' // 52b4 #6686 + '23T' // 52b5 #617 + 'b3L' // 52b6-52b8 #89 + '114K' // 52b9 #2974 + '3L' // 52ba #89 + 'a23T' // 52bb-52bc #617 + '3L' // 52bd #89 + '31C' // 52be #808 + '2D' // 52bf #55 + '40I' // 52c0 #1048 + '194C' // 52c1 #5046 + 'A' // 52c2 + '161H' // 52c3 #4193 + '3L' // 52c4 #89 + '31C' // 52c5 #808 + '3L' // 52c6 #89 + '197L' // 52c7 #5133 + '3L' // 52c8 #89 + '155G' // 52c9 #4036 + '9J' // 52ca #243 + '2R' // 52cb #69 + '26S' // 52cc #694 + '31C' // 52cd #808 + 'A' // 52ce + '3L' // 52cf #89 + '23T' // 52d0 #617 + '40I' // 52d1 #1048 + '186V' // 52d2 #4857 + 'A' // 52d3 + '3L' // 52d4 #89 + '246Y' // 52d5 #6420 + '31C' // 52d6 #808 + '23T' // 52d7 #617 + '136W' // 52d8 #3558 + '242O' // 52d9 #6306 + 'A' // 52da + '31C' // 52db #808 + '3L' // 52dc #89 + '213J' // 52dd #5547 + '67O' // 52de #1756 + '199G' // 52df #5180 + '23T' // 52e0 #617 + '26S' // 52e1 #694 + '221X' // 52e2 #5769 + '31C' // 52e3 #808 + '191C' // 52e4 #4968 + '9J' // 52e5 #243 + '40I' // 52e6 #1048 + '256V' // 52e7 #6677 + 'b9J' // 52e8-52ea #243 + '61M' // 52eb #1598 + '9J' // 52ec #243 + 'b12T' // 52ed-52ef #331 + '15V' // 52f0 #411 + '40H' // 52f1 #1047 + '253F' // 52f2 #6583 + '66D' // 52f3 #1719 + '3L' // 52f4 #89 + '67O' // 52f5 #1756 + '9J' // 52f6 #243 + '15V' // 52f7 #411 + '165U' // 52f8 #4310 + '15V' // 52f9 #411 + '113X' // 52fa #2961 + '66D' // 52fb #1719 + '12T' // 52fc #331 + 'A' // 52fd + '180A' // 52fe #4680 + '209C' // 52ff #5436 + '250R' // 5300 #6517 + '40H' // 5301 #1047 + '254K' // 5302 #6614 + '3L' // 5303 #89 + '12T' // 5304 #331 + '234H' // 5305 #6091 + '129N' // 5306 #3367 + '3L' // 5307 #89 + '134U' // 5308 #3504 + 'A' // 5309 + '40H' // 530a #1047 + '15V' // 530b #411 + '3L' // 530c #89 + '40G' // 530d #1046 + 'A' // 530e + 'a40G' // 530f-5310 #1046 + '3L' // 5311 #89 + 'A' // 5312 + '3L' // 5313 #89 + 'A' // 5314 + '40G' // 5315 #1046 + '68X' // 5316 #1791 + '243H' // 5317 #6325 + '3L' // 5318 #89 + '172F' // 5319 #4477 + '15V' // 531a #411 + '3L' // 531b #89 + 'a15V' // 531c-531d #411 + 'a3L' // 531e-531f #89 + '161K' // 5320 #4196 + '124J' // 5321 #3233 + 'A' // 5322 + '140Z' // 5323 #3665 + '26S' // 5324 #694 + '3L' // 5325 #89 + '12T' // 5326 #331 + '26S' // 5327 #694 + 'a3L' // 5328-5329 #89 + '65K' // 532a #1700 + '3L' // 532b #89 + '26S' // 532c #694 + '40H' // 532d #1047 + '12T' // 532e #331 + '208C' // 532f #5410 + '9J' // 5330 #243 + '15V' // 5331 #411 + 'a26S' // 5332-5333 #694 + 'A' // 5334 + '3L' // 5335 #89 + 'aA' // 5336-5337 + '15V' // 5338 #411 + '161I' // 5339 #4194 + '259I' // 533a #6742 + '114J' // 533b #2973 + 'b15V' // 533c-533e #411 + '161O' // 533f #4200 + '240E' // 5340 #6244 + '235H' // 5341 #6117 + '26S' // 5342 #694 + '68L' // 5343 #1779 + '114L' // 5344 #2975 + '15V' // 5345 #411 + '3L' // 5346 #89 + '226J' // 5347 #5885 + '215N' // 5348 #5603 + '152I' // 5349 #3960 + '68L' // 534a #1779 + '9J' // 534b #243 + '15V' // 534c #411 + '114F' // 534d #2969 + '65M' // 534e #1702 + '1Z' // 534f #51 + '12T' // 5350 #331 + '154I' // 5351 #4012 + '126C' // 5352 #3278 + '190J' // 5353 #4949 + '216E' // 5354 #5620 + '3Q' // 5355 #94 + '2C' // 5356 #54 + '236F' // 5357 #6141 + '259U' // 5358 #6754 + '3L' // 5359 #89 + '220T' // 535a #5739 + '3L' // 535b #89 + '166D' // 535c #4319 + '114M' // 535d #2976 + '40G' // 535e #1046 + '61M' // 535f #1598 + '190V' // 5360 #4961 + '232W' // 5361 #6054 + '2L' // 5362 #63 + '15V' // 5363 #411 + '113Z' // 5364 #2963 + '3L' // 5365 #89 + '179M' // 5366 #4666 + '113G' // 5367 #2944 + '261B' // 5368 #6787 + '10C' // 5369 #262 + 'A' // 536a + '2D' // 536b #55 + '10C' // 536c #262 + '48B' // 536d #1249 + '10C' // 536e #262 + '113N' // 536f #2951 + '230F' // 5370 #5985 + '204T' // 5371 #5323 + '10C' // 5372 #262 + '234S' // 5373 #6102 + '113T' // 5374 #2957 + '155B' // 5375 #4031 + 'A' // 5376 + '194D' // 5377 #5047 + '175V' // 5378 #4571 + '10C' // 5379 #262 + '9J' // 537a #243 + '218A' // 537b #5668 + '31B' // 537c #807 + '61J' // 537d #1595 + '48B' // 537e #1249 + '65K' // 537f #1700 + 'aA' // 5380-5381 + '61K' // 5382 #1596 + '3L' // 5383 #89 + '136K' // 5384 #3546 + '3I' // 5385 #86 + '1Z' // 5386 #51 + 'a3L' // 5387-5388 #89 + '250S' // 5389 #6518 + 'A' // 538a + '2C' // 538b #54 + '2Y' // 538c #76 + '12T' // 538d #331 + '10C' // 538e #262 + 'A' // 538f + '12T' // 5390 #331 + 'A' // 5391 + '31B' // 5392 #807 + '61J' // 5393 #1595 + '10C' // 5394 #262 + '2R' // 5395 #69 + '10C' // 5396 #262 + 'A' // 5397 + '147I' // 5398 #3830 + '48C' // 5399 #1250 + '67T' // 539a #1761 + 'a12T' // 539b-539c #331 + '147J' // 539d #3831 + 'A' // 539e + '243M' // 539f #6330 + '61I' // 53a0 #1594 + '9J' // 53a1 #243 + '113V' // 53a2 #2959 + '12T' // 53a3 #331 + '10C' // 53a4 #262 + '61I' // 53a5 #1594 + '113B' // 53a6 #2939 + 'A' // 53a7 + '113Q' // 53a8 #2954 + '10C' // 53a9 #262 + 'a48B' // 53aa-53ab #1249 + 'A' // 53ac + '172E' // 53ad #4476 + '10C' // 53ae #262 + '3L' // 53af #89 + '10C' // 53b0 #262 + 'A' // 53b1 + '179N' // 53b2 #4667 + '257U' // 53b3 #6702 + '48C' // 53b4 #1250 + '9J' // 53b5 #243 + '10C' // 53b6 #262 + 'a9J' // 53b7-53b8 #243 + '31B' // 53b9 #807 + '3L' // 53ba #89 + '68Q' // 53bb #1784 + '12T' // 53bc #331 + '3L' // 53bd #89 + '12T' // 53be #331 + '3I' // 53bf #86 + '3L' // 53c0 #89 + '10C' // 53c1 #262 + '143Q' // 53c2 #3734 + '232V' // 53c3 #6053 + '9J' // 53c4 #243 + '113C' // 53c5 #2940 + 'a12T' // 53c6-53c7 #331 + '233Q' // 53c8 #6074 + '172Z' // 53c9 #4497 + '241I' // 53ca #6274 + '242F' // 53cb #6297 + '137B' // 53cc #3563 + '230R' // 53cd #5997 + '259M' // 53ce #6746 + '9J' // 53cf #243 + '48A' // 53d0 #1248 + '177D' // 53d1 #4605 + '10C' // 53d2 #262 + '9J' // 53d3 #243 + '173I' // 53d4 #4506 + '9J' // 53d5 #243 + '239G' // 53d6 #6220 + '238T' // 53d7 #6207 + '131N' // 53d8 #3419 + '113J' // 53d9 #2947 + '10C' // 53da #262 + '152H' // 53db #3959 + 'A' // 53dc + '3L' // 53dd #89 + '9J' // 53de #243 + '10C' // 53df #262 + '113F' // 53e0 #2943 + '113L' // 53e1 #2949 + '159A' // 53e2 #4134 + '238L' // 53e3 #6199 + '231I' // 53e4 #6014 + '203S' // 53e5 #5296 + '226A' // 53e6 #5876 + '36J' // 53e7 #945 + '23S' // 53e8 #616 + '113R' // 53e9 #2955 + '233G' // 53ea #6064 + '209L' // 53eb #5445 + '181V' // 53ec #4727 + '165T' // 53ed #4309 + '152J' // 53ee #3961 + '69G' // 53ef #1800 + '246K' // 53f0 #6406 + '113M' // 53f1 #2950 + '229B' // 53f2 #5955 + '212O' // 53f3 #5526 + '36J' // 53f4 #945 + '23S' // 53f5 #616 + '113S' // 53f6 #2956 + '155P' // 53f7 #4045 + '234K' // 53f8 #6094 + '2Y' // 53f9 #76 + '36J' // 53fa #945 + 'a31A' // 53fb-53fc #806 + '7Z' // 53fd #207 + '48D' // 53fe #1251 + 'a7Z' // 53ff-5400 #207 + '113D' // 5401 #2941 + '36J' // 5402 #945 + '225Z' // 5403 #5875 + '236O' // 5404 #6150 + '7Z' // 5405 #207 + 'a31A' // 5406-5407 #806 + '49H' // 5408 #1281 + '212V' // 5409 #5533 + '188O' // 540a #4902 + '186W' // 540b #4858 + '49G' // 540c #1280 + '68Z' // 540d #1793 + '201C' // 540e #5228 + '61H' // 540f #1593 + '189M' // 5410 #4926 + '231P' // 5411 #6021 + '23S' // 5412 #616 + '129O' // 5413 #3368 + '31A' // 5414 #806 + '2Y' // 5415 #76 + '31A' // 5416 #806 + '1Z' // 5417 #51 + 'a7Z' // 5418-5419 #207 + '23S' // 541a #616 + '198S' // 541b #5166 + 'A' // 541c + '129L' // 541d #3365 + '165W' // 541e #4312 + '148F' // 541f #3853 + '113K' // 5420 #2948 + '23S' // 5421 #616 + '7Z' // 5422 #207 + '48D' // 5423 #1251 + '48C' // 5424 #1250 + '31A' // 5425 #806 + '219S' // 5426 #5712 + '226B' // 5427 #5877 + '113E' // 5428 #2942 + '61H' // 5429 #1593 + '36J' // 542a #945 + '222V' // 542b #5793 + '113I' // 542c #2946 + 'a23S' // 542d-542e #616 + '61K' // 542f #1596 + 'A' // 5430 + '23S' // 5431 #616 + '31A' // 5432 #806 + '208B' // 5433 #5409 + '113H' // 5434 #2945 + '165V' // 5435 #4311 + '129M' // 5436 #3366 + '31B' // 5437 #807 + '219T' // 5438 #5713 + '198R' // 5439 #5165 + 'A' // 543a + '172X' // 543b #4495 + '147H' // 543c #3829 + '23S' // 543d #616 + '161M' // 543e #4198 + '113A' // 543f #2938 + '194A' // 5440 #5044 + '31B' // 5441 #807 + '176M' // 5442 #4588 + '64J' // 5443 #1673 + '4E' // 5444 #108 + '31B' // 5445 #807 + '173T' // 5446 #4517 + '26R' // 5447 #693 + '196C' // 5448 #5098 + '253O' // 5449 #6592 + '243W' // 544a #6340 + '17E' // 544b #446 + '15U' // 544c #410 + '26R' // 544d #693 + '147F' // 544e #3827 + '4E' // 544f #108 + '3G' // 5450 #84 + '253U' // 5451 #6598 + 'a7Z' // 5452-5453 #207 + '17E' // 5454 #446 + '250P' // 5455 #6515 + '7Z' // 5456 #207 + '3H' // 5457 #85 + '3Q' // 5458 #94 + '7Z' // 5459 #207 + 'A' // 545a + 'a7Z' // 545b-545c #207 + 'A' // 545d + '4E' // 545e #108 + '252U' // 545f #6572 + '61L' // 5460 #1597 + '7Z' // 5461 #207 + '68D' // 5462 #1771 + '17E' // 5463 #446 + '15U' // 5464 #410 + 'A' // 5465 + '134T' // 5466 #3503 + '4E' // 5467 #108 + '231D' // 5468 #6009 + '112Y' // 5469 #2936 + '113P' // 546a #2953 + 'b15U' // 546b-546d #410 + '31Q' // 546e #822 + '17E' // 546f #446 + '15U' // 5470 #410 + '30Z' // 5471 #805 + '17E' // 5472 #446 + '231O' // 5473 #6020 + '15U' // 5474 #410 + '165P' // 5475 #4305 + '15U' // 5476 #410 + '48W' // 5477 #1270 + '17E' // 5478 #446 + 'A' // 5479 + '7Z' // 547a #207 + '112Z' // 547b #2937 + '205N' // 547c #5343 + '229G' // 547d #5960 + '17E' // 547e #446 + '15U' // 547f #410 + '158W' // 5480 #4130 + '172C' // 5481 #4474 + '17E' // 5482 #446 + '31Q' // 5483 #822 + '15U' // 5484 #410 + '26R' // 5485 #693 + '30Z' // 5486 #805 + '61L' // 5487 #1597 + '26R' // 5488 #693 + '4E' // 5489 #108 + '31Q' // 548a #822 + '124H' // 548b #3231 + '243G' // 548c #6324 + '15U' // 548d #410 + '30Z' // 548e #805 + '61G' // 548f #1592 + '30Z' // 5490 #805 + '15U' // 5491 #410 + '152G' // 5492 #3958 + '48A' // 5493 #1248 + '17E' // 5494 #446 + '158Y' // 5495 #4132 + '207Z' // 5496 #5407 + '162O' // 5497 #4226 + '17E' // 5498 #446 + '7Z' // 5499 #207 + '113U' // 549a #2958 + '7Z' // 549b #207 + '26R' // 549c #693 + '7Z' // 549d #207 + '48A' // 549e #1248 + 'a4E' // 549f-54a0 #108 + 'a15U' // 54a1-54a2 #410 + '17E' // 54a3 #446 + '30Z' // 54a4 #805 + '113O' // 54a5 #2952 + '48W' // 54a6 #1270 + '64J' // 54a7 #1673 + '134R' // 54a8 #3501 + '165R' // 54a9 #4307 + '193Z' // 54aa #5043 + '30Z' // 54ab #805 + '179L' // 54ac #4665 + '15U' // 54ad #410 + '26R' // 54ae #693 + '61G' // 54af #1592 + 'A' // 54b0 + '140X' // 54b1 #3663 + '125V' // 54b2 #3271 + '141W' // 54b3 #3688 + '48D' // 54b4 #1251 + '7Z' // 54b5 #207 + 'A' // 54b6 + '26R' // 54b7 #693 + '140V' // 54b8 #3661 + '61E' // 54b9 #1590 + '4E' // 54ba #108 + '20Y' // 54bb #544 + '40E' // 54bc #1044 + '136D' // 54bd #3539 + 'a20Y' // 54be-54bf #544 + '167X' // 54c0 #4365 + '35V' // 54c1 #931 + '48W' // 54c2 #1270 + '31Q' // 54c3 #822 + '124C' // 54c4 #3226 + 'A' // 54c5 + '124F' // 54c6 #3229 + '165S' // 54c7 #4308 + '207W' // 54c8 #5404 + '136T' // 54c9 #3555 + '31Q' // 54ca #822 + '137P' // 54cb #3577 + '30Y' // 54cc #804 + '124I' // 54cd #3232 + '64A' // 54ce #1664 + 'a30Y' // 54cf-54d0 #804 + '2K' // 54d1 #62 + '2R' // 54d2 #69 + '7Z' // 54d3 #207 + '3H' // 54d4 #85 + '7Z' // 54d5 #207 + '30Y' // 54d6 #804 + '2W' // 54d7 #74 + '4E' // 54d8 #108 + '7Z' // 54d9 #207 + '30Y' // 54da #804 + 'A' // 54db + 'a7Z' // 54dc-54dd #207 + '30Y' // 54de #804 + '2K' // 54df #62 + '4E' // 54e0 #108 + '243V' // 54e1 #6339 + '20Y' // 54e2 #544 + '30X' // 54e3 #803 + '26Q' // 54e4 #692 + '214T' // 54e5 #5583 + '194B' // 54e6 #5045 + '30Y' // 54e7 #804 + '129K' // 54e8 #3364 + '172D' // 54e9 #4475 + '217Z' // 54ea #5667 + '26Q' // 54eb #692 + '4E' // 54ec #108 + '193Y' // 54ed #5042 + '63J' // 54ee #1647 + '61B' // 54ef #1587 + 'A' // 54f0 + '4E' // 54f1 #108 + '189U' // 54f2 #4934 + '20Y' // 54f3 #544 + '7Z' // 54f4 #207 + 'A' // 54f5 + '4E' // 54f6 #108 + 'A' // 54f7 + 'a7Z' // 54f8-54f9 #207 + '152P' // 54fa #3967 + 'A' // 54fb + '64A' // 54fc #1664 + '20Y' // 54fd #544 + '31Q' // 54fe #822 + '20Y' // 54ff #544 + '4E' // 5500 #108 + '20Y' // 5501 #544 + '26Q' // 5502 #692 + 'A' // 5503 + '112S' // 5504 #2930 + '20Y' // 5505 #544 + '112O' // 5506 #2926 + '174A' // 5507 #4524 + '4E' // 5508 #108 + '147G' // 5509 #3828 + '26Q' // 550a #692 + 'A' // 550b + '20Y' // 550c #544 + '61E' // 550d #1590 + '112M' // 550e #2924 + '17D' // 550f #445 + '189N' // 5510 #4927 + 'b20Z' // 5511-5513 #545 + '186U' // 5514 #4856 + '4E' // 5515 #108 + '31P' // 5516 #821 + '20Z' // 5517 #545 + '30X' // 5518 #803 + '10N' // 5519 #273 + '26Q' // 551a #692 + '10N' // 551b #273 + '261A' // 551c #6786 + '10N' // 551d #273 + '20Z' // 551e #545 + 'A' // 551f + '10N' // 5520 #273 + 'A' // 5521 + '10N' // 5522 #273 + '61F' // 5523 #1591 + '2L' // 5524 #63 + '30X' // 5525 #803 + '20Z' // 5526 #545 + '17D' // 5527 #445 + '30X' // 5528 #803 + 'A' // 5529 + '17D' // 552a #445 + '61D' // 552b #1589 + '20Z' // 552c #545 + '26Q' // 552d #692 + '225Y' // 552e #5874 + '203K' // 552f #5288 + '20Z' // 5530 #545 + '202S' // 5531 #5270 + 'a17D' // 5532-5533 #445 + '10N' // 5534 #273 + '47Y' // 5535 #1246 + '40E' // 5536 #1044 + '191V' // 5537 #4987 + '152F' // 5538 #3957 + '4E' // 5539 #108 + 'A' // 553a + 'a17D' // 553b-553c #445 + '31P' // 553d #821 + '112R' // 553e #2929 + '20Z' // 553f #545 + '31P' // 5540 #821 + '17D' // 5541 #445 + 'A' // 5542 + '63M' // 5543 #1650 + '47Y' // 5544 #1246 + '40E' // 5545 #1044 + '243K' // 5546 #6328 + '17D' // 5547 #445 + '10N' // 5548 #273 + '17D' // 5549 #445 + '208A' // 554a #5408 + '20Z' // 554b #545 + '4E' // 554c #108 + '17D' // 554d #445 + '26Q' // 554e #692 + '245E' // 554f #6374 + '17D' // 5550 #445 + '40E' // 5551 #1044 + 'A' // 5552 + '112V' // 5553 #2933 + 'A' // 5554 + '20Z' // 5555 #545 + '47Y' // 5556 #1246 + '17D' // 5557 #445 + '31P' // 5558 #821 + 'A' // 5559 + '4E' // 555a #108 + '31P' // 555b #821 + '8H' // 555c #215 + '61D' // 555d #1589 + '134P' // 555e #3499 + '224H' // 555f #5831 + '4E' // 5560 #108 + '207Y' // 5561 #5406 + '35K' // 5562 #920 + '112N' // 5563 #2925 + '158Z' // 5564 #4133 + '66P' // 5565 #1731 + '68D' // 5566 #1771 + 'a10N' // 5567-5568 #273 + '10Y' // 5569 #284 + '137S' // 556a #3580 + '10Y' // 556b #284 + 'd10N' // 556c-5570 #273 + '131K' // 5571 #3416 + '162N' // 5572 #4225 + '30X' // 5573 #803 + '10N' // 5574 #273 + 'b10Y' // 5575-5577 #284 + '3G' // 5578 #84 + '35K' // 5579 #920 + 'A' // 557a + '8H' // 557b #215 + '23R' // 557c #615 + '4E' // 557d #108 + '63Z' // 557e #1663 + '8H' // 557f #215 + '134Q' // 5580 #3500 + '8H' // 5581 #215 + '140Y' // 5582 #3664 + '8H' // 5583 #215 + '221C' // 5584 #5748 + '10N' // 5585 #273 + '23R' // 5586 #615 + '179K' // 5587 #4664 + '8H' // 5588 #215 + '167E' // 5589 #4346 + '186T' // 558a #4855 + '112T' // 558b #2931 + '10N' // 558c #273 + 'A' // 558d + '63Z' // 558e #1663 + '8H' // 558f #215 + '61F' // 5590 #1591 + '8H' // 5591 #215 + '4E' // 5592 #108 + '31P' // 5593 #821 + '207X' // 5594 #5405 + 'aA' // 5595-5596 + '4E' // 5597 #108 + '148P' // 5598 #3863 + '23R' // 5599 #615 + '175R' // 559a #4567 + 'A' // 559b + '234Q' // 559c #6100 + '208O' // 559d #5422 + '4E' // 559e #108 + '8H' // 559f #215 + 'A' // 55a0 + '35K' // 55a1 #920 + 'A' // 55a2 + '35J' // 55a3 #919 + '4E' // 55a4 #108 + 'a10Y' // 55a5-55a6 #284 + '130S' // 55a7 #3398 + '8H' // 55a8 #215 + '23R' // 55a9 #615 + '174F' // 55aa #4529 + '112W' // 55ab #2934 + '186S' // 55ac #4854 + '35J' // 55ad #919 + '240D' // 55ae #6243 + 'A' // 55af + '112U' // 55b0 #2932 + '10Y' // 55b1 #284 + '140W' // 55b2 #3662 + '10Y' // 55b3 #284 + '30X' // 55b4 #803 + '184J' // 55b5 #4793 + '259Y' // 55b6 #6758 + '3N' // 55b7 #91 + 'A' // 55b8 + '10Y' // 55b9 #284 + '143T' // 55ba #3737 + '65X' // 55bb #1713 + '10Y' // 55bc #284 + 'a10N' // 55bd-55be #273 + '4E' // 55bf #108 + 'A' // 55c0 + '61B' // 55c1 #1587 + 'A' // 55c2 + '4E' // 55c3 #108 + '8H' // 55c4 #215 + '112P' // 55c5 #2927 + '158X' // 55c6 #4131 + '23R' // 55c7 #615 + 'A' // 55c8 + '8H' // 55c9 #215 + 'A' // 55ca + '4E' // 55cb #108 + '8H' // 55cc #215 + '10Y' // 55cd #284 + '225X' // 55ce #5873 + '10N' // 55cf #273 + '10Y' // 55d0 #284 + '124E' // 55d1 #3228 + '8H' // 55d2 #215 + '124G' // 55d3 #3230 + '23R' // 55d4 #615 + 'a10Y' // 55d5-55d6 #284 + 'a8H' // 55d7-55d8 #215 + '10Y' // 55d9 #284 + '140U' // 55da #3660 + '8H' // 55db #215 + '142D' // 55dc #3695 + 'a8H' // 55dd-55de #215 + '23R' // 55df #615 + 'A' // 55e0 + '10Y' // 55e1 #284 + '35J' // 55e2 #919 + '112Q' // 55e3 #2928 + '23R' // 55e4 #615 + 'a10Y' // 55e5-55e6 #284 + 'A' // 55e7 + '65X' // 55e8 #1713 + '35J' // 55e9 #919 + '10Y' // 55ea #284 + '10N' // 55eb #273 + '8H' // 55ec #215 + '10N' // 55ed #273 + '8H' // 55ee #215 + '162P' // 55ef #4227 + '63M' // 55f0 #1650 + '8H' // 55f1 #215 + '10Y' // 55f2 #284 + '10N' // 55f3 #273 + 'A' // 55f4 + '47Z' // 55f5 #1247 + 'a61C' // 55f6-55f7 #1588 + '4E' // 55f8 #108 + '35J' // 55f9 #919 + '35K' // 55fa #920 + '112X' // 55fb #2935 + 'A' // 55fc + '63J' // 55fd #1647 + '112L' // 55fe #2923 + '4E' // 55ff #108 + 'a47Z' // 5600-5601 #1247 + '35K' // 5602 #920 + 'aA' // 5603-5604 + '165Q' // 5605 #4306 + '173P' // 5606 #4513 + '4E' // 5607 #108 + '61C' // 5608 #1588 + '218Q' // 5609 #5684 + '4E' // 560a #108 + 'A' // 560b + '47Z' // 560c #1247 + '124D' // 560d #3227 + '134S' // 560e #3502 + '112C' // 560f #2914 + '4E' // 5610 #108 + '112B' // 5611 #2913 + '112D' // 5612 #2915 + '35I' // 5613 #918 + '136J' // 5614 #3545 + '35I' // 5615 #918 + '13E' // 5616 #342 + '186R' // 5617 #4853 + '255L' // 5618 #6641 + '4B' // 5619 #105 + '12S' // 561a #330 + '179J' // 561b #4663 + '23Q' // 561c #614 + 'A' // 561d + '23Q' // 561e #614 + '137R' // 561f #3579 + '35H' // 5620 #917 + '112J' // 5621 #2921 + '149S' // 5622 #3892 + '23Q' // 5623 #614 + '12S' // 5624 #330 + '23Q' // 5625 #614 + 'A' // 5626 + '23Q' // 5627 #614 + '4B' // 5628 #105 + '125M' // 5629 #3262 + '35I' // 562a #918 + '12S' // 562b #330 + '13E' // 562c #342 + 'a23Q' // 562d-562e #614 + '64I' // 562f #1672 + '13E' // 5630 #342 + '250O' // 5631 #6514 + '147C' // 5632 #3824 + '4B' // 5633 #105 + '200U' // 5634 #5220 + '35H' // 5635 #917 + '61A' // 5636 #1586 + '23P' // 5637 #613 + 'a13E' // 5638-5639 #342 + '23Q' // 563a #614 + '158V' // 563b #4129 + '27C' // 563c #704 + '4B' // 563d #105 + 'A' // 563e + '147D' // 563f #3825 + '13E' // 5640 #342 + '49A' // 5641 #1274 + '112G' // 5642 #2918 + '35H' // 5643 #917 + '4B' // 5644 #105 + 'A' // 5645 + 'a4B' // 5646-5647 #105 + 'A' // 5648 + '13E' // 5649 #342 + 'A' // 564a + '4B' // 564b #105 + '112F' // 564c #2917 + 'c13E' // 564d-5650 #342 + 'A' // 5651 + '35G' // 5652 #916 + '158T' // 5653 #4127 + '13E' // 5654 #342 + '12S' // 5655 #330 + 'A' // 5656 + '162M' // 5657 #4224 + 'b23Q' // 5658-565a #614 + '254J' // 565b #6613 + '3W' // 565c #100 + '35I' // 565d #918 + '4B' // 565e #105 + 'A' // 565f + '124A' // 5660 #3224 + '23P' // 5661 #613 + '140T' // 5662 #3659 + '27C' // 5663 #704 + '13E' // 5664 #342 + '35I' // 5665 #918 + '13E' // 5666 #342 + 'A' // 5667 + '236V' // 5668 #6157 + '111Y' // 5669 #2910 + '147E' // 566a #3826 + '61A' // 566b #1586 + '134M' // 566c #3496 + '35H' // 566d #917 + 'A' // 566e + '13E' // 566f #342 + '12S' // 5670 #330 + '13E' // 5671 #342 + '35H' // 5672 #917 + '12S' // 5673 #330 + '195U' // 5674 #5090 + '27C' // 5675 #704 + '13E' // 5676 #342 + '12S' // 5677 #330 + '64V' // 5678 #1685 + '137O' // 5679 #3576 + '13E' // 567a #342 + 'a14M' // 567b-567c #376 + 'A' // 567d + '12S' // 567e #330 + 'A' // 567f + '17C' // 5680 #444 + 'bA' // 5681-5683 + 'a10B' // 5684-5685 #261 + '40C' // 5686 #1042 + '193X' // 5687 #5041 + '4B' // 5688 #105 + '35G' // 5689 #916 + 'a23P' // 568a-568b #613 + '10B' // 568c #261 + 'A' // 568d + '14M' // 568e #376 + '10B' // 568f #261 + '169Q' // 5690 #4410 + '12S' // 5691 #330 + 'a14M' // 5692-5693 #376 + '4B' // 5694 #105 + '49A' // 5695 #1274 + 'A' // 5696 + '14M' // 5697 #376 + '40D' // 5698 #1043 + '17C' // 5699 #444 + '4B' // 569a #105 + '12S' // 569b #330 + '14M' // 569c #376 + '4B' // 569d #105 + '10B' // 569e #261 + '49A' // 569f #1274 + '4B' // 56a0 #105 + '14M' // 56a1 #376 + '251Y' // 56a2 #6550 + '2K' // 56a3 #62 + '14M' // 56a4 #376 + '40C' // 56a5 #1042 + 'a10B' // 56a6-56a7 #261 + '129J' // 56a8 #3363 + '27C' // 56a9 #704 + 'A' // 56aa + '17C' // 56ab #444 + '40C' // 56ac #1042 + '10B' // 56ad #261 + '147B' // 56ae #3823 + '14M' // 56af #376 + 'A' // 56b0 + '111X' // 56b1 #2909 + '4B' // 56b2 #105 + '17C' // 56b3 #444 + '207V' // 56b4 #5403 + '40D' // 56b5 #1043 + '10B' // 56b6 #261 + '158U' // 56b7 #4128 + '12S' // 56b8 #330 + '35G' // 56b9 #916 + 'aA' // 56ba-56bb + '134L' // 56bc #3495 + 'A' // 56bd + '4B' // 56be #105 + '14M' // 56bf #376 + '17C' // 56c0 #444 + '112E' // 56c1 #2916 + '64V' // 56c2 #1685 + '10B' // 56c3 #261 + 'A' // 56c4 + '17C' // 56c5 #444 + 'aA' // 56c6-56c7 + '17C' // 56c8 #444 + '200V' // 56c9 #5221 + '172B' // 56ca #4473 + 'a17C' // 56cb-56cc #444 + '64I' // 56cd #1672 + 'b4B' // 56ce-56d0 #105 + '40C' // 56d1 #1042 + 'A' // 56d2 + '17C' // 56d3 #444 + '14M' // 56d4 #376 + 'A' // 56d5 + '14M' // 56d6 #376 + '10B' // 56d7 #261 + 'a27C' // 56d8-56d9 #704 + '148D' // 56da #3851 + '68Q' // 56db #1784 + '4B' // 56dc #105 + '10B' // 56dd #261 + '244J' // 56de #6353 + '10B' // 56df #261 + '234G' // 56e0 #6090 + '10B' // 56e1 #261 + '112K' // 56e2 #2922 + '258S' // 56e3 #6726 + 'a10B' // 56e4-56e5 #261 + '27C' // 56e6 #704 + '134N' // 56e7 #3497 + '27C' // 56e8 #704 + '12S' // 56e9 #330 + '14M' // 56ea #376 + '10B' // 56eb #261 + '12S' // 56ec #330 + '134O' // 56ed #3498 + '10B' // 56ee #261 + '14M' // 56ef #376 + '67T' // 56f0 #1761 + '10B' // 56f1 #261 + '258I' // 56f2 #6716 + '259G' // 56f3 #6740 + '2D' // 56f4 #55 + '12S' // 56f5 #330 + '4B' // 56f6 #105 + '10B' // 56f7 #261 + '12S' // 56f8 #330 + '111W' // 56f9 #2908 + '212U' // 56fa #5532 + '35G' // 56fb #916 + '7I' // 56fc #190 + '191U' // 56fd #4986 + '7K' // 56fe #192 + 'a20X' // 56ff-5700 #543 + 'a4B' // 5701-5702 #105 + 'a47X' // 5703-5704 #1245 + 'A' // 5705 + '3I' // 5706 #86 + '17C' // 5707 #444 + '214S' // 5708 #5582 + 'a20X' // 5709-570a #543 + '246E' // 570b #6400 + '20X' // 570c #543 + '217Y' // 570d #5666 + 'A' // 570e + '255Y' // 570f #6654 + 'A' // 5710 + '4B' // 5711 #105 + '236U' // 5712 #6156 + '207U' // 5713 #5402 + 'A' // 5714 + '23P' // 5715 #613 + '240C' // 5716 #6242 + 'A' // 5717 + '232U' // 5718 #6052 + '7I' // 5719 #190 + 'a4B' // 571a-571b #105 + '20X' // 571c #543 + '23P' // 571d #613 + '7I' // 571e #190 + '223V' // 571f #5819 + 'b4B' // 5720-5722 #105 + '112A' // 5723 #2912 + '4B' // 5724 #105 + '24A' // 5725 #624 + '4B' // 5726 #105 + '257T' // 5727 #6701 + '246M' // 5728 #6408 + 'a20X' // 5729-572a #543 + 'A' // 572b + '20X' // 572c #543 + '136U' // 572d #3556 + 'a20X' // 572e-572f #543 + '246U' // 5730 #6416 + 'A' // 5731 + '35G' // 5732 #916 + '165O' // 5733 #4304 + '20X' // 5734 #543 + '7I' // 5735 #190 + 'A' // 5736 + 'a4B' // 5737-5738 #105 + '7I' // 5739 #190 + '3Q' // 573a #94 + '47X' // 573b #1245 + 'A' // 573c + '23P' // 573d #613 + '67A' // 573e #1742 + '23P' // 573f #613 + '226G' // 5740 #5882 + '112I' // 5741 #2920 + '143N' // 5742 #3731 + '112H' // 5743 #2919 + 'A' // 5744 + '24A' // 5745 #624 + '23P' // 5746 #613 + '220L' // 5747 #5731 + 'A' // 5748 + '7I' // 5749 #190 + '203A' // 574a #5278 + '40D' // 574b #1043 + '20X' // 574c #543 + '47X' // 574d #1245 + '147A' // 574e #3822 + '111Z' // 574f #2911 + '208M' // 5750 #5420 + '179I' // 5751 #4662 + '17C' // 5752 #444 + 'A' // 5753 + '40D' // 5754 #1043 + 'aA' // 5755-5756 + '155S' // 5757 #4048 + 'A' // 5758 + '4B' // 5759 #105 + '3I' // 575a #86 + '111V' // 575b #2907 + '7I' // 575c #190 + 'a2K' // 575d-575e #62 + '111L' // 575f #2897 + '2Y' // 5760 #76 + '200T' // 5761 #5219 + '4B' // 5762 #105 + '7I' // 5763 #190 + '158S' // 5764 #4126 + '4B' // 5765 #105 + '187G' // 5766 #4868 + '47U' // 5767 #1242 + 'a15T' // 5768-5769 #409 + '181C' // 576a #4708 + '15T' // 576b #409 + 'A' // 576c + '15T' // 576d #409 + '250N' // 576e #6513 + '111K' // 576f #2896 + '30V' // 5770 #801 + '4B' // 5771 #105 + '47W' // 5772 #1244 + '15T' // 5773 #409 + '111M' // 5774 #2898 + '30V' // 5775 #801 + 'A' // 5776 + '15T' // 5777 #409 + 'A' // 5778 + '4B' // 5779 #105 + '47U' // 577a #1242 + '15T' // 577b #409 + '30V' // 577c #801 + '47W' // 577d #1244 + 'a47U' // 577e-577f #1242 + '47W' // 5780 #1244 + '4B' // 5781 #105 + '168N' // 5782 #4381 + '67A' // 5783 #1742 + '2W' // 5784 #74 + 'a7I' // 5785-5786 #190 + 'A' // 5787 + '30V' // 5788 #801 + '4B' // 5789 #105 + '23O' // 578a #612 + '237L' // 578b #6173 + '15T' // 578c #409 + '23O' // 578d #612 + 'a7I' // 578e-578f #190 + '23O' // 5790 #612 + 'A' // 5791 + '3X' // 5792 #101 + '30V' // 5793 #801 + '24A' // 5794 #624 + '15T' // 5795 #409 + 'A' // 5796 + '4B' // 5797 #105 + 'A' // 5798 + '24A' // 5799 #624 + '15T' // 579a #409 + '111T' // 579b #2905 + '47V' // 579c #1243 + 'a24A' // 579d-579e #624 + '15T' // 579f #409 + '30V' // 57a0 #801 + '15T' // 57a1 #409 + '130W' // 57a2 #3402 + '130Z' // 57a3 #3405 + '15T' // 57a4 #409 + 'A' // 57a5 + '7I' // 57a6 #190 + '47V' // 57a7 #1243 + 'a24A' // 57a8-57a9 #624 + '47V' // 57aa #1243 + '4C' // 57ab #106 + '24A' // 57ac #624 + '7I' // 57ad #190 + '124B' // 57ae #3225 + '7I' // 57af #190 + '24A' // 57b0 #624 + 'a7I' // 57b1-57b2 #190 + '35F' // 57b3 #915 + '40B' // 57b4 #1041 + '20W' // 57b5 #542 + '7I' // 57b6 #190 + 'A' // 57b7 + '10A' // 57b8 #260 + '20W' // 57b9 #542 + 'a30W' // 57ba-57bb #802 + 'A' // 57bc + '36I' // 57bd #944 + '40B' // 57be #1041 + '7I' // 57bf #190 + '2P' // 57c0 #67 + 'A' // 57c1 + '20W' // 57c2 #542 + '165M' // 57c3 #4302 + '40B' // 57c4 #1041 + '7I' // 57c5 #190 + '10A' // 57c6 #260 + 'a39Z' // 57c7-57c8 #1039 + 'aA' // 57c9-57ca + '182T' // 57cb #4751 + '10A' // 57cc #260 + '7I' // 57cd #190 + '230G' // 57ce #5986 + '10A' // 57cf #260 + 'aA' // 57d0-57d1 + '10A' // 57d2 #260 + '2P' // 57d3 #67 + '186Q' // 57d4 #4852 + '140R' // 57d5 #3657 + '36I' // 57d6 #944 + '134K' // 57d7 #3494 + 'c7I' // 57d8-57db #190 + 'b10A' // 57dc-57de #260 + '213R' // 57df #5555 + '123Y' // 57e0 #3222 + '10A' // 57e1 #260 + '20W' // 57e2 #542 + '2P' // 57e3 #67 + '123Z' // 57e4 #3223 + '20W' // 57e5 #542 + '35F' // 57e6 #915 + '10A' // 57e7 #260 + 'A' // 57e8 + '2P' // 57e9 #67 + 'A' // 57ea + '7I' // 57eb #190 + 'A' // 57ec + '10A' // 57ed #260 + '30W' // 57ee #802 + '40B' // 57ef #1041 + '30U' // 57f0 #800 + 'aA' // 57f1-57f2 + '30W' // 57f3 #802 + '39Z' // 57f4 #1039 + 'a10A' // 57f5-57f6 #260 + '204K' // 57f7 #5314 + '10A' // 57f8 #260 + '202B' // 57f9 #5253 + '237K' // 57fa #6172 + '26P' // 57fb #691 + '111S' // 57fc #2904 + '10A' // 57fd #260 + '35F' // 57fe #915 + '2P' // 57ff #67 + '111P' // 5800 #2901 + '20W' // 5801 #542 + '221R' // 5802 #5763 + '10A' // 5803 #260 + '26P' // 5804 #691 + '202A' // 5805 #5252 + '187F' // 5806 #4867 + '20W' // 5807 #542 + '36H' // 5808 #943 + '39Z' // 5809 #1039 + '30U' // 580a #800 + '10A' // 580b #260 + '36I' // 580c #944 + '10A' // 580d #260 + '20W' // 580e #542 + 'A' // 580f + '30W' // 5810 #802 + '7I' // 5811 #190 + '23O' // 5812 #612 + 'A' // 5813 + '20W' // 5814 #542 + '253Z' // 5815 #6603 + 'aA' // 5816-5817 + '23O' // 5818 #612 + '10A' // 5819 #260 + 'A' // 581a + '36I' // 581b #944 + '7I' // 581c #190 + '10A' // 581d #260 + '39Z' // 581e #1039 + '36I' // 581f #944 + '9Z' // 5820 #259 + '186P' // 5821 #4851 + '40A' // 5822 #1040 + '17B' // 5823 #443 + '160M' // 5824 #4172 + '10M' // 5825 #272 + '9Z' // 5826 #259 + '36H' // 5827 #943 + 'aA' // 5828-5829 + '175Q' // 582a #4566 + 'A' // 582b + '17B' // 582c #443 + '26P' // 582d #691 + 'A' // 582e + '134J' // 582f #3493 + '111J' // 5830 #2895 + '245D' // 5831 #6373 + '2P' // 5832 #67 + 'A' // 5833 + '245F' // 5834 #6375 + '65J' // 5835 #1699 + 'bA' // 5836-5838 + '2P' // 5839 #67 + '111N' // 583a #2899 + 'aA' // 583b-583c + '12L' // 583d #323 + 'A' // 583e + 'a9Z' // 583f-5840 #259 + '252Z' // 5841 #6577 + '10M' // 5842 #272 + 'A' // 5843 + '40A' // 5844 #1040 + 'a10M' // 5845-5846 #272 + '23O' // 5847 #612 + '17B' // 5848 #443 + '2P' // 5849 #67 + '208T' // 584a #5427 + '30U' // 584b #800 + '146Z' // 584c #3821 + '9Z' // 584d #259 + 'A' // 584e + '30U' // 584f #800 + '2P' // 5850 #67 + '200S' // 5851 #5218 + '9Z' // 5852 #259 + 'A' // 5853 + '202I' // 5854 #5260 + '2P' // 5855 #67 + 'A' // 5856 + '190U' // 5857 #4960 + '165L' // 5858 #4301 + '9Z' // 5859 #259 + '111R' // 585a #2903 + 'A' // 585b + '17B' // 585c #443 + '10M' // 585d #272 + '195H' // 585e #5077 + '35F' // 585f #915 + 'A' // 5860 + '36H' // 5861 #943 + '140O' // 5862 #3654 + 'A' // 5863 + '30U' // 5864 #800 + 'aA' // 5865-5866 + '2P' // 5867 #67 + '9Z' // 5868 #259 + '111Q' // 5869 #2902 + 'A' // 586a + '201F' // 586b #5231 + '17B' // 586c #443 + '9Z' // 586d #259 + 'A' // 586e + '10M' // 586f #272 + '12L' // 5870 #323 + '17B' // 5871 #443 + '9Z' // 5872 #259 + '40A' // 5873 #1040 + 'A' // 5874 + '187Y' // 5875 #4886 + 'aA' // 5876-5877 + '2P' // 5878 #67 + '39Y' // 5879 #1038 + 'aA' // 587a-587b + '30U' // 587c #800 + '17B' // 587d #443 + '111O' // 587e #2900 + '26P' // 587f #691 + 'a9Z' // 5880-5881 #259 + 'A' // 5882 + '229V' // 5883 #5975 + 'A' // 5884 + '65J' // 5885 #1699 + 'A' // 5886 + '26P' // 5887 #691 + '9Z' // 5888 #259 + '39Y' // 5889 #1038 + '67J' // 588a #1751 + 'a2P' // 588b-588c #67 + '12L' // 588d #323 + '30W' // 588e #802 + '2P' // 588f #67 + '26P' // 5890 #691 + '30W' // 5891 #802 + '17B' // 5892 #443 + '167R' // 5893 #4359 + '2P' // 5894 #67 + 'A' // 5895 + '9Z' // 5896 #259 + '258M' // 5897 #6720 + '9Z' // 5898 #259 + '111U' // 5899 #2906 + '40A' // 589a #1040 + 'A' // 589b + '166M' // 589c #4328 + '9Z' // 589d #259 + '225W' // 589e #5872 + '135L' // 589f #3521 + '12L' // 58a0 #323 + '9Z' // 58a1 #259 + '2P' // 58a2 #67 + '17B' // 58a3 #443 + 'aA' // 58a4-58a5 + '9Z' // 58a6 #259 + '23O' // 58a7 #612 + '196B' // 58a8 #5097 + '39Y' // 58a9 #1038 + '35F' // 58aa #915 + '12L' // 58ab #323 + '17B' // 58ac #443 + '10M' // 58ad #272 + '146Y' // 58ae #3820 + 'A' // 58af + '17B' // 58b0 #443 + '26P' // 58b1 #691 + '2P' // 58b2 #67 + '129H' // 58b3 #3361 + 'A' // 58b4 + 'a23O' // 58b5-58b6 #612 + 'A' // 58b7 + 'a2P' // 58b8-58b9 #67 + '36H' // 58ba #943 + '39Y' // 58bb #1038 + '9Z' // 58bc #259 + '10M' // 58bd #272 + '152E' // 58be #3956 + '10M' // 58bf #272 + 'A' // 58c0 + '198O' // 58c1 #5162 + '30T' // 58c2 #799 + '12L' // 58c3 #323 + '2P' // 58c4 #67 + '26O' // 58c5 #690 + '60Z' // 58c6 #1585 + '209J' // 58c7 #5443 + '12L' // 58c8 #323 + 'A' // 58c9 + '256Z' // 58ca #6681 + '23M' // 58cb #610 + '252K' // 58cc #6562 + '2P' // 58cd #67 + '26O' // 58ce #690 + 'A' // 58cf + '39X' // 58d0 #1037 + '26O' // 58d1 #690 + '12L' // 58d2 #323 + '217V' // 58d3 #5663 + '2P' // 58d4 #67 + '26O' // 58d5 #690 + 'a2P' // 58d6-58d7 #67 + '158Q' // 58d8 #4124 + '26O' // 58d9 #690 + '39X' // 58da #1037 + 'A' // 58db + '60V' // 58dc #1581 + '2P' // 58dd #67 + '67W' // 58de #1764 + '26O' // 58df #690 + '39X' // 58e0 #1037 + '2P' // 58e1 #67 + '165N' // 58e2 #4303 + 'A' // 58e3 + '140P' // 58e4 #3655 + '2P' // 58e5 #67 + 'bA' // 58e6-58e8 + '30T' // 58e9 #799 + 'A' // 58ea + '236D' // 58eb #6139 + '26O' // 58ec #690 + 'A' // 58ed + '254L' // 58ee #6615 + '179G' // 58ef #4660 + '143P' // 58f0 #3733 + '253S' // 58f1 #6596 + '111H' // 58f2 #2893 + '110X' // 58f3 #2883 + '2P' // 58f4 #67 + '10M' // 58f5 #272 + '2R' // 58f6 #69 + '12L' // 58f7 #323 + '10M' // 58f8 #272 + '186O' // 58f9 #4850 + '173G' // 58fa #4504 + '110U' // 58fb #2880 + '39X' // 58fc #1037 + '67N' // 58fd #1755 + 'aA' // 58fe-58ff + '10M' // 5900 #272 + 'A' // 5901 + '30T' // 5902 #799 + 'A' // 5903 + '63N' // 5904 #1651 + '60V' // 5905 #1581 + '30T' // 5906 #799 + '143X' // 5907 #3741 + '10M' // 5908 #272 + '260E' // 5909 #6764 + '30T' // 590a #799 + '12L' // 590b #323 + '30T' // 590c #799 + '140S' // 590d #3658 + '60Z' // 590e #1585 + '216K' // 590f #5626 + '12L' // 5910 #323 + '19B' // 5911 #495 + 'a2P' // 5912-5913 #67 + '39W' // 5914 #1036 + '176L' // 5915 #4587 + '69B' // 5916 #1795 + 'A' // 5917 + '2P' // 5918 #67 + '39W' // 5919 #1036 + '69H' // 591a #1801 + '12L' // 591b #323 + '223F' // 591c #5803 + '110V' // 591d #2881 + '10M' // 591e #272 + '110Y' // 591f #2884 + '224G' // 5920 #5830 + '2P' // 5921 #67 + '221Q' // 5922 #5762 + '12L' // 5923 #323 + '30S' // 5924 #798 + '67J' // 5925 #1751 + 'A' // 5926 + '247C' // 5927 #6424 + '12L' // 5928 #323 + '243S' // 5929 #6336 + '236C' // 592a #6138 + '216P' // 592b #5631 + '30S' // 592c #798 + '123X' // 592d #3221 + '205X' // 592e #5353 + '179H' // 592f #4661 + '2P' // 5930 #67 + '229F' // 5931 #5959 + '30S' // 5932 #798 + '2P' // 5933 #67 + '64Z' // 5934 #1689 + '2P' // 5935 #67 + '12L' // 5936 #323 + '152O' // 5937 #3966 + '110W' // 5938 #2882 + '250M' // 5939 #6512 + '1R' // 593a #43 + 'A' // 593b + '10M' // 593c #272 + '2P' // 593d #67 + '67W' // 593e #1764 + '2P' // 593f #67 + '111I' // 5940 #2894 + 'a10M' // 5941-5942 #272 + '2P' // 5943 #67 + '110Z' // 5944 #2885 + 'A' // 5945 + '2P' // 5946 #67 + '227M' // 5947 #5914 + '199F' // 5948 #5179 + '174Z' // 5949 #4549 + 'A' // 594a + '1R' // 594b #43 + 'a10M' // 594c-594d #272 + '110T' // 594e #2879 + '182X' // 594f #4755 + '39W' // 5950 #1036 + '183Q' // 5951 #4774 + '2P' // 5952 #67 + '30S' // 5953 #798 + '179Z' // 5954 #4679 + '158R' // 5955 #4125 + '2D' // 5956 #55 + '217W' // 5957 #5664 + '30S' // 5958 #798 + '2P' // 5959 #67 + '39W' // 595a #1036 + '2P' // 595b #67 + 'A' // 595c + 'b2P' // 595d-595f #67 + '129I' // 5960 #3362 + '30S' // 5961 #798 + '173H' // 5962 #4505 + '2P' // 5963 #67 + 'A' // 5964 + '126B' // 5965 #3277 + '10M' // 5966 #272 + '67N' // 5967 #1755 + '256K' // 5968 #6666 + '60X' // 5969 #1583 + '189T' // 596a #4933 + '36H' // 596b #943 + 'a110S' // 596c-596d #2878 + '182S' // 596e #4750 + '2P' // 596f #67 + 'aA' // 5970-5971 + '2P' // 5972 #67 + '244C' // 5973 #6346 + '169B' // 5974 #4395 + '60X' // 5975 #1583 + '207T' // 5976 #5401 + '9Y' // 5977 #258 + '140Q' // 5978 #3656 + '217X' // 5979 #5665 + 'A' // 597a + 'a60W' // 597b-597c #1582 + '243P' // 597d #6333 + '19B' // 597e #495 + 'A' // 597f + '19B' // 5980 #495 + '60W' // 5981 #1582 + '241A' // 5982 #6266 + '160P' // 5983 #4175 + '143E' // 5984 #3722 + 'A' // 5985 + '3Y' // 5986 #102 + 'a3I' // 5987-5988 #86 + '23M' // 5989 #610 + '111E' // 598a #2890 + 'a5S' // 598b-598c #148 + '158P' // 598d #4123 + '5S' // 598e #148 + '9Y' // 598f #258 + 'aA' // 5990-5991 + '129G' // 5992 #3360 + '129U' // 5993 #3374 + '23M' // 5994 #610 + '5S' // 5995 #148 + '175U' // 5996 #4570 + '110R' // 5997 #2877 + '9Y' // 5998 #258 + '198A' // 5999 #5148 + '60Y' // 599a #1584 + '5S' // 599b #148 + '7Y' // 599c #206 + '214R' // 599d #5581 + '169T' // 599e #4413 + '23N' // 599f #611 + 'a9Y' // 59a0-59a1 #258 + '19B' // 59a2 #495 + '8Y' // 59a3 #232 + '63Y' // 59a4 #1662 + '166Y' // 59a5 #4340 + '19B' // 59a6 #495 + '8Y' // 59a7 #232 + '160F' // 59a8 #4165 + '3W' // 59a9 #100 + 'a7Y' // 59aa-59ab #206 + '111C' // 59ac #2888 + '49V' // 59ad #1295 + '186N' // 59ae #4849 + '8Y' // 59af #232 + '39V' // 59b0 #1035 + '19B' // 59b1 #495 + '8Y' // 59b2 #232 + '193W' // 59b3 #5040 + 'A' // 59b4 + '7Y' // 59b5 #206 + '9Y' // 59b6 #258 + '39V' // 59b7 #1035 + '9Y' // 59b8 #258 + '215M' // 59b9 #5602 + '8Y' // 59ba #232 + '205I' // 59bb #5338 + '5S' // 59bc #148 + '7Y' // 59bd #206 + '26N' // 59be #689 + '23M' // 59bf #610 + '7Y' // 59c0 #206 + '8Y' // 59c1 #232 + 'A' // 59c2 + '26N' // 59c3 #689 + '47T' // 59c4 #1241 + 'A' // 59c5 + '186L' // 59c6 #4847 + '9Y' // 59c7 #258 + '8Y' // 59c8 #232 + '111G' // 59c9 #2892 + '186M' // 59ca #4848 + '231G' // 59cb #6012 + '7Y' // 59cc #206 + '8Y' // 59cd #232 + '19B' // 59ce #495 + '7Y' // 59cf #206 + '208J' // 59d0 #5417 + '180H' // 59d1 #4687 + '8Y' // 59d2 #232 + '201N' // 59d3 #5239 + '211I' // 59d4 #5494 + '7Y' // 59d5 #206 + '9Y' // 59d6 #258 + '3X' // 59d7 #101 + '9Y' // 59d8 #258 + '26N' // 59d9 #689 + '158O' // 59da #4122 + '7Y' // 59db #206 + '153I' // 59dc #3986 + '26N' // 59dd #689 + '8Y' // 59de #232 + '49V' // 59df #1295 + '9Y' // 59e0 #258 + 'A' // 59e1 + '7Y' // 59e2 #206 + 'b8Y' // 59e3-59e5 #232 + '161N' // 59e6 #4199 + '49V' // 59e7 #1295 + '152C' // 59e8 #3954 + '9Y' // 59e9 #258 + '111A' // 59ea #2886 + '111F' // 59eb #2891 + '165K' // 59ec #4300 + 'A' // 59ed + '26N' // 59ee #689 + '39V' // 59ef #1035 + '23M' // 59f0 #610 + '8Y' // 59f1 #232 + '23N' // 59f2 #611 + '9Y' // 59f3 #258 + '5S' // 59f4 #148 + '9Y' // 59f5 #258 + '23N' // 59f6 #611 + '8Y' // 59f7 #232 + '26N' // 59f8 #689 + '9Y' // 59f9 #258 + 'A' // 59fa + '172Q' // 59fb #4488 + '19B' // 59fc #495 + '9Y' // 59fd #258 + 'A' // 59fe + '191J' // 59ff #4975 + '23N' // 5a00 #611 + '214Z' // 5a01 #5589 + '23M' // 5a02 #610 + '66Z' // 5a03 #1741 + '250L' // 5a04 #6511 + '2K' // 5a05 #62 + '7Y' // 5a06 #206 + '4C' // 5a07 #106 + '7Y' // 5a08 #206 + '8Y' // 5a09 #232 + '7Y' // 5a0a #206 + '23M' // 5a0b #610 + '8Y' // 5a0c #232 + '47T' // 5a0d #1241 + '5S' // 5a0e #148 + 'aA' // 5a0f-5a10 + '26N' // 5a11 #689 + '47T' // 5a12 #1241 + '8Y' // 5a13 #232 + 'A' // 5a14 + '7Y' // 5a15 #206 + '9Y' // 5a16 #258 + '23N' // 5a17 #611 + '205T' // 5a18 #5349 + 'A' // 5a19 + '8Y' // 5a1a #232 + '225V' // 5a1b #5871 + '193V' // 5a1c #5039 + 'A' // 5a1d + '23N' // 5a1e #611 + '65I' // 5a1f #1698 + '111D' // 5a20 #2889 + '60Y' // 5a21 #1584 + '7Y' // 5a22 #206 + '8Y' // 5a23 #232 + '23N' // 5a24 #611 + '140N' // 5a25 #3653 + 'A' // 5a26 + '39V' // 5a27 #1035 + '5S' // 5a28 #148 + '111B' // 5a29 #2887 + '23N' // 5a2a #611 + '19B' // 5a2b #495 + '23M' // 5a2c #610 + '8Y' // 5a2d #232 + '9Y' // 5a2e #258 + '253E' // 5a2f #6582 + '5S' // 5a30 #148 + '1Z' // 5a31 #51 + '7Y' // 5a32 #206 + '19B' // 5a33 #495 + '2W' // 5a34 #74 + '5S' // 5a35 #148 + '158N' // 5a36 #4121 + 'A' // 5a37 + '26M' // 5a38 #688 + 'bA' // 5a39-5a3b + '110O' // 5a3c #2874 + '9G' // 5a3d #240 + 'a7Y' // 5a3e-5a3f #206 + '35E' // 5a40 #914 + '110I' // 5a41 #2868 + 'a26M' // 5a42-5a43 #688 + '35E' // 5a44 #914 + '110K' // 5a45 #2870 + '195D' // 5a46 #5073 + '35E' // 5a47 #914 + '110N' // 5a48 #2873 + '152B' // 5a49 #3953 + '26M' // 5a4a #688 + 'A' // 5a4b + '35E' // 5a4c #914 + '26M' // 5a4d #688 + 'A' // 5a4e + '7Y' // 5a4f #206 + '35E' // 5a50 #914 + '26M' // 5a51 #688 + 'A' // 5a52 + '26M' // 5a53 #688 + '9G' // 5a54 #240 + '63Y' // 5a55 #1662 + '26M' // 5a56 #688 + '30R' // 5a57 #797 + 'A' // 5a58 + '9G' // 5a59 #240 + '216J' // 5a5a #5625 + 'aA' // 5a5b-5a5c + '20V' // 5a5d #541 + '16Z' // 5a5e #441 + 'A' // 5a5f + '30R' // 5a60 #797 + '20V' // 5a61 #541 + '110H' // 5a62 #2867 + '35D' // 5a63 #913 + 'A' // 5a64 + '16Z' // 5a65 #441 + '212N' // 5a66 #5525 + '16Z' // 5a67 #441 + '9G' // 5a68 #240 + 'A' // 5a69 + '16Z' // 5a6a #441 + '9G' // 5a6b #240 + 'a16Z' // 5a6c-5a6d #441 + '9G' // 5a6e #240 + 'aA' // 5a6f-5a70 + '9G' // 5a71 #240 + 'a7Y' // 5a72-5a73 #206 + '3Y' // 5a74 #102 + 'a7Y' // 5a75-5a76 #206 + '172A' // 5a77 #4472 + 'A' // 5a78 + '110Q' // 5a79 #2876 + 'a16Z' // 5a7a-5a7b #441 + '30R' // 5a7c #797 + 'A' // 5a7d + '35D' // 5a7e #913 + '124S' // 5a7f #3242 + 'A' // 5a80 + 'b30R' // 5a81-5a83 #797 + '16Z' // 5a84 #441 + 'A' // 5a85 + '9G' // 5a86 #240 + 'A' // 5a87 + '9G' // 5a88 #240 + 'aA' // 5a89-5a8a + '5S' // 5a8b #148 + '30R' // 5a8c #797 + 'A' // 5a8d + '7Y' // 5a8e #206 + 'A' // 5a8f + '17A' // 5a90 #442 + '9G' // 5a91 #240 + '218P' // 5a92 #5683 + '16Z' // 5a93 #441 + 'aA' // 5a94-5a95 + '16Z' // 5a96 #441 + '30R' // 5a97 #797 + 'A' // 5a98 + '35D' // 5a99 #913 + '154G' // 5a9a #4010 + '155F' // 5a9b #4035 + '16Z' // 5a9c #441 + 'A' // 5a9d + '16Z' // 5a9e #441 + '17A' // 5a9f #442 + '4D' // 5aa0 #107 + '9G' // 5aa1 #240 + '5S' // 5aa2 #148 + '8T' // 5aa3 #227 + '60U' // 5aa4 #1580 + 'aA' // 5aa5-5aa6 + '17A' // 5aa7 #442 + 'aA' // 5aa8-5aa9 + '47S' // 5aaa #1240 + '20V' // 5aab #541 + '17A' // 5aac #442 + 'A' // 5aad + 'a9X' // 5aae-5aaf #257 + 'A' // 5ab0 + '4D' // 5ab1 #107 + '48Y' // 5ab2 #1272 + '152D' // 5ab3 #3955 + '9X' // 5ab4 #257 + '4D' // 5ab5 #107 + 'A' // 5ab6 + '8T' // 5ab7 #227 + '4D' // 5ab8 #107 + '8T' // 5ab9 #227 + '4D' // 5aba #107 + 'a17A' // 5abb-5abc #442 + '217T' // 5abd #5661 + 'a4D' // 5abe-5abf #107 + 'A' // 5ac0 + '183G' // 5ac1 #4764 + '65I' // 5ac2 #1698 + '9X' // 5ac3 #257 + '19A' // 5ac4 #494 + 'A' // 5ac5 + '4D' // 5ac6 #107 + '9X' // 5ac7 #257 + '4D' // 5ac8 #107 + '110P' // 5ac9 #2875 + '9X' // 5aca #257 + '4D' // 5acb #107 + '183S' // 5acc #4776 + '8T' // 5acd #227 + '9G' // 5ace #240 + '4D' // 5acf #107 + '27B' // 5ad0 #703 + '9X' // 5ad1 #257 + '8T' // 5ad2 #227 + '9G' // 5ad3 #240 + '8T' // 5ad4 #227 + '20V' // 5ad5 #541 + '110L' // 5ad6 #2871 + '17A' // 5ad7 #442 + 'a9X' // 5ad8-5ad9 #257 + '4D' // 5ada #107 + '20V' // 5adb #541 + '4D' // 5adc #107 + 'aA' // 5add-5ade + '8T' // 5adf #227 + '4D' // 5ae0 #107 + '19A' // 5ae1 #494 + '8T' // 5ae2 #227 + '110M' // 5ae3 #2872 + '47S' // 5ae4 #1240 + '17A' // 5ae5 #442 + '19A' // 5ae6 #494 + 'A' // 5ae7 + '9X' // 5ae8 #257 + '66Z' // 5ae9 #1741 + '4D' // 5aea #107 + '9X' // 5aeb #257 + 'a8T' // 5aec-5aed #227 + '17A' // 5aee #442 + 'A' // 5aef + '4D' // 5af0 #107 + '8T' // 5af1 #227 + '9X' // 5af2 #257 + '8T' // 5af3 #227 + 'A' // 5af4 + '4D' // 5af5 #107 + '5S' // 5af6 #148 + 'aA' // 5af7-5af8 + '8T' // 5af9 #227 + '4D' // 5afa #107 + '48Y' // 5afb #1272 + 'A' // 5afc + '27B' // 5afd #703 + '9G' // 5afe #240 + '20V' // 5aff #541 + '5S' // 5b00 #148 + '4D' // 5b01 #107 + 'bA' // 5b02-5b04 + '60U' // 5b05 #1580 + 'aA' // 5b06-5b07 + '4D' // 5b08 #107 + '137G' // 5b09 #3568 + 'A' // 5b0a + '19A' // 5b0b #494 + '172W' // 5b0c #4494 + '9G' // 5b0d #240 + 'bA' // 5b0e-5b10 + '9X' // 5b11 #257 + 'bA' // 5b12-5b14 + '9G' // 5b15 #240 + '19A' // 5b16 #494 + '4D' // 5b17 #107 + 'A' // 5b18 + '17A' // 5b19 #442 + 'A' // 5b1a + '4D' // 5b1b #107 + 'A' // 5b1c + '17A' // 5b1d #442 + 'A' // 5b1e + '9G' // 5b1f #240 + 'A' // 5b20 + '4D' // 5b21 #107 + '255K' // 5b22 #6640 + '20V' // 5b23 #541 + '149R' // 5b24 #3891 + '5S' // 5b25 #148 + 'A' // 5b26 + '8T' // 5b27 #227 + '9X' // 5b28 #257 + '8T' // 5b29 #227 + '19A' // 5b2a #494 + '9X' // 5b2b #257 + 'a4D' // 5b2c-5b2d #107 + '8T' // 5b2e #227 + 'A' // 5b2f + '200R' // 5b30 #5217 + 'A' // 5b31 + '4D' // 5b32 #107 + 'A' // 5b33 + '4D' // 5b34 #107 + 'A' // 5b35 + '27B' // 5b36 #703 + '8T' // 5b37 #227 + '4D' // 5b38 #107 + 'dA' // 5b39-5b3d + '4D' // 5b3e #107 + '9X' // 5b3f #257 + '19A' // 5b40 #494 + '35D' // 5b41 #913 + 'A' // 5b42 + '19A' // 5b43 #494 + '20V' // 5b44 #541 + '4D' // 5b45 #107 + '20V' // 5b46 #541 + 'bA' // 5b47-5b49 + '47S' // 5b4a #1240 + '4D' // 5b4b #107 + '17A' // 5b4c #442 + 'aA' // 5b4d-5b4e + '9G' // 5b4f #240 + '246V' // 5b50 #6417 + '19A' // 5b51 #494 + '27B' // 5b52 #703 + '9X' // 5b53 #257 + '194Z' // 5b54 #5069 + '201J' // 5b55 #5235 + '48Y' // 5b56 #1272 + '242B' // 5b57 #6293 + '230Q' // 5b58 #5996 + '3Y' // 5b59 #102 + '63I' // 5b5a #1646 + '4D' // 5b5b #107 + '110J' // 5b5c #2869 + '196H' // 5b5d #5103 + '5S' // 5b5e #148 + '171Z' // 5b5f #4471 + 'aA' // 5b60-5b61 + '9X' // 5b62 #257 + '222I' // 5b63 #5780 + '188T' // 5b64 #4907 + '4D' // 5b65 #107 + '169P' // 5b66 #4409 + '8T' // 5b67 #227 + '35D' // 5b68 #913 + '217S' // 5b69 #5660 + '8T' // 5b6a #227 + '196R' // 5b6b #5113 + 'a9X' // 5b6c-5b6d #257 + '4D' // 5b6e #107 + '5S' // 5b6f #148 + '19A' // 5b70 #494 + '60Q' // 5b71 #1576 + '110G' // 5b72 #2866 + '60R' // 5b73 #1577 + '35C' // 5b74 #912 + '109Q' // 5b75 #2850 + '47Q' // 5b76 #1238 + 'A' // 5b77 + '240N' // 5b78 #6253 + 'A' // 5b79 + '60Q' // 5b7a #1576 + '110C' // 5b7b #2862 + '109P' // 5b7c #2849 + '109T' // 5b7d #2853 + '5S' // 5b7e #148 + 'a60R' // 5b7f-5b80 #1577 + '109U' // 5b81 #2854 + '109V' // 5b82 #2855 + '217U' // 5b83 #5662 + '109S' // 5b84 #2852 + '213Q' // 5b85 #5554 + '5S' // 5b86 #148 + '205H' // 5b87 #5337 + '212T' // 5b88 #5531 + '244A' // 5b89 #6344 + '5S' // 5b8a #148 + '179F' // 5b8b #4659 + '242Q' // 5b8c #6308 + '27B' // 5b8d #703 + '5S' // 5b8e #148 + '196E' // 5b8f #5100 + '47Q' // 5b90 #1238 + '27B' // 5b91 #703 + '15S' // 5b92 #408 + '47R' // 5b93 #1239 + '27B' // 5b94 #703 + '109W' // 5b95 #2856 + '109R' // 5b96 #2851 + '67R' // 5b97 #1759 + '227V' // 5b98 #5923 + '169A' // 5b99 #4394 + '69C' // 5b9a #1796 + '154F' // 5b9b #4009 + '219D' // 5b9c #5697 + '169I' // 5b9d #4402 + '35T' // 5b9e #929 + '110B' // 5b9f #2861 + '3Y' // 5ba0 #102 + '2C' // 5ba1 #54 + '242N' // 5ba2 #6305 + '210M' // 5ba3 #5472 + '230Y' // 5ba4 #6004 + '134I' // 5ba5 #3492 + '47R' // 5ba6 #1239 + '15S' // 5ba7 #408 + '60S' // 5ba8 #1578 + '5S' // 5ba9 #148 + '110F' // 5baa #2865 + '3Y' // 5bab #102 + '47R' // 5bac #1239 + '60S' // 5bad #1578 + '213V' // 5bae #5559 + '5S' // 5baf #148 + '148I' // 5bb0 #3856 + 'a5S' // 5bb1-5bb2 #148 + '222S' // 5bb3 #5790 + '189S' // 5bb4 #4932 + '159V' // 5bb5 #4155 + '246Q' // 5bb6 #6412 + '47Q' // 5bb7 #1238 + '63I' // 5bb8 #1646 + '243J' // 5bb9 #6327 + '5S' // 5bba #148 + 'A' // 5bbb + '1S' // 5bbc #44 + 'a3Y' // 5bbd-5bbe #102 + '213G' // 5bbf #5544 + '30Q' // 5bc0 #796 + '23L' // 5bc1 #609 + '161J' // 5bc2 #4195 + '30Q' // 5bc3 #796 + '222U' // 5bc4 #5792 + '130J' // 5bc5 #3389 + '228N' // 5bc6 #5941 + '129F' // 5bc7 #3359 + 'A' // 5bc8 + '1S' // 5bc9 #44 + 'A' // 5bca + '8T' // 5bcb #227 + '230E' // 5bcc #5984 + 'b1S' // 5bcd-5bcf #44 + '30Q' // 5bd0 #796 + '60T' // 5bd1 #1579 + '198F' // 5bd2 #5153 + '171Y' // 5bd3 #4470 + '30Q' // 5bd4 #796 + '60T' // 5bd5 #1579 + '60P' // 5bd6 #1575 + '30Q' // 5bd7 #796 + '60P' // 5bd8 #1575 + '1S' // 5bd9 #44 + '250J' // 5bda #6509 + '109Y' // 5bdb #2858 + '8T' // 5bdc #227 + '258A' // 5bdd #6708 + '146X' // 5bde #3819 + '211V' // 5bdf #5507 + '1S' // 5be0 #44 + '146V' // 5be1 #3817 + '171X' // 5be2 #4469 + 'A' // 5be3 + 'a30Q' // 5be4-5be5 #796 + '240A' // 5be6 #6240 + '204D' // 5be7 #5307 + '152A' // 5be8 #3952 + '197U' // 5be9 #5142 + 'A' // 5bea + '225U' // 5beb #5870 + '200P' // 5bec #5215 + 'A' // 5bed + '160S' // 5bee #4178 + '47O' // 5bef #1236 + '63X' // 5bf0 #1661 + '1S' // 5bf1 #44 + '7X' // 5bf2 #205 + '8G' // 5bf3 #214 + '1S' // 5bf4 #44 + '217R' // 5bf5 #5659 + '225T' // 5bf6 #5869 + 'A' // 5bf7 + '67R' // 5bf8 #1759 + '7K' // 5bf9 #192 + '176T' // 5bfa #4595 + '3I' // 5bfb #86 + '3Q' // 5bfc #94 + '1S' // 5bfd #44 + '260D' // 5bfe #6763 + '109Z' // 5bff #2859 + 'A' // 5c00 + '210G' // 5c01 #5466 + '259R' // 5c02 #6751 + '8G' // 5c03 #214 + '211L' // 5c04 #5497 + '8G' // 5c05 #214 + '257F' // 5c06 #6687 + 'a239Z' // 5c07-5c08 #6239 + '47O' // 5c09 #1236 + '196V' // 5c0a #5117 + '233K' // 5c0b #6068 + '8G' // 5c0c #214 + '240B' // 5c0d #6241 + '234V' // 5c0e #6105 + '69J' // 5c0f #1803 + '12H' // 5c10 #319 + '238N' // 5c11 #6201 + 'a8G' // 5c12-5c13 #214 + '123W' // 5c14 #3220 + '12H' // 5c15 #319 + '194V' // 5c16 #5065 + '31O' // 5c17 #820 + '1R' // 5c18 #43 + '250I' // 5c19 #6508 + '233O' // 5c1a #6072 + '7X' // 5c1b #205 + '39U' // 5c1c #1034 + '1R' // 5c1d #43 + 'a8G' // 5c1e-5c1f #214 + '60M' // 5c20 #1572 + 'A' // 5c21 + '8G' // 5c22 #214 + '60M' // 5c23 #1572 + '200Q' // 5c24 #5216 + '12H' // 5c25 #319 + '1S' // 5c26 #44 + '2W' // 5c27 #74 + '47O' // 5c28 #1236 + '1S' // 5c29 #44 + '8G' // 5c2a #214 + '31O' // 5c2b #820 + '165J' // 5c2c #4299 + 'a1S' // 5c2d-5c2e #44 + '7X' // 5c2f #205 + '31O' // 5c30 #820 + '241G' // 5c31 #6272 + '1S' // 5c32 #44 + 'A' // 5c33 + '2Y' // 5c34 #76 + 'a1S' // 5c35-5c36 #44 + '155Q' // 5c37 #4046 + '109N' // 5c38 #2847 + '146W' // 5c39 #3818 + '208V' // 5c3a #5429 + '125X' // 5c3b #3273 + '218L' // 5c3c #5679 + '255R' // 5c3d #6647 + '212F' // 5c3e #5517 + '189L' // 5c3f #4925 + '222H' // 5c40 #5779 + '172R' // 5c41 #4489 + '2C' // 5c42 #54 + '7X' // 5c43 #205 + '12H' // 5c44 #319 + '229M' // 5c45 #5966 + '193U' // 5c46 #5038 + '12H' // 5c47 #319 + '168M' // 5c48 #4380 + '110D' // 5c49 #2863 + '110A' // 5c4a #2860 + '223X' // 5c4b #5821 + '149V' // 5c4c #3895 + '179D' // 5c4d #4657 + '151Y' // 5c4e #3950 + '207S' // 5c4f #5400 + '8G' // 5c50 #214 + '135R' // 5c51 #3527 + 'A' // 5c52 + '23L' // 5c53 #609 + 'A' // 5c54 + '235I' // 5c55 #6118 + 'aA' // 5c56-5c57 + '15S' // 5c58 #408 + '8G' // 5c59 #214 + '1S' // 5c5a #44 + '70J' // 5c5b #1829 + '63H' // 5c5c #1645 + '15S' // 5c5d #408 + '126E' // 5c5e #3280 + '1S' // 5c5f #44 + '146U' // 5c60 #3816 + '250K' // 5c61 #6510 + '48Z' // 5c62 #1273 + '8G' // 5c63 #214 + '219N' // 5c64 #5707 + '169K' // 5c65 #4404 + '7X' // 5c66 #205 + '1S' // 5c67 #44 + '23L' // 5c68 #609 + '1S' // 5c69 #44 + 'aA' // 5c6a-5c6b + '225S' // 5c6c #5868 + '23L' // 5c6d #609 + '8G' // 5c6e #214 + '179E' // 5c6f #4658 + '1S' // 5c70 #44 + '238Y' // 5c71 #6212 + 'aA' // 5c72-5c73 + '8G' // 5c74 #214 + 'a1S' // 5c75-5c76 #44 + 'A' // 5c77 + '39U' // 5c78 #1034 + '30P' // 5c79 #795 + 'b8G' // 5c7a-5c7c #214 + '31O' // 5c7d #820 + '12H' // 5c7e #319 + '3G' // 5c7f #84 + '7X' // 5c80 #205 + '2C' // 5c81 #54 + '3G' // 5c82 #84 + '39U' // 5c83 #1034 + '7X' // 5c84 #205 + '35C' // 5c85 #912 + '15S' // 5c86 #408 + '1S' // 5c87 #44 + '8G' // 5c88 #214 + 'A' // 5c89 + '23L' // 5c8a #609 + '12H' // 5c8b #319 + '8G' // 5c8c #214 + '12H' // 5c8d #319 + 'A' // 5c8e + '1S' // 5c8f #44 + '137F' // 5c90 #3567 + '48Z' // 5c91 #1273 + '23L' // 5c92 #609 + '15S' // 5c93 #408 + '8G' // 5c94 #214 + '12H' // 5c95 #319 + '7X' // 5c96 #205 + '1R' // 5c97 #43 + '7X' // 5c98 #205 + '12H' // 5c99 #319 + '110E' // 5c9a #2864 + '3I' // 5c9b #86 + '12H' // 5c9c #319 + '1S' // 5c9d #44 + '39U' // 5c9e #1034 + '1S' // 5c9f #44 + '23L' // 5ca0 #609 + '184G' // 5ca1 #4790 + 'a8G' // 5ca2-5ca3 #214 + 'A' // 5ca4 + '15S' // 5ca5 #408 + 'a23L' // 5ca6-5ca7 #609 + '1S' // 5ca8 #44 + '176U' // 5ca9 #4596 + '1S' // 5caa #44 + '30P' // 5cab #795 + '109X' // 5cac #2857 + '109O' // 5cad #2848 + 'bA' // 5cae-5cb0 + '129E' // 5cb1 #3358 + '1S' // 5cb2 #44 + '161G' // 5cb3 #4192 + '1S' // 5cb4 #44 + '30P' // 5cb5 #795 + '1S' // 5cb6 #44 + '30P' // 5cb7 #795 + '204C' // 5cb8 #5306 + 'A' // 5cb9 + '30P' // 5cba #795 + 'a1S' // 5cbb-5cbc #44 + '7X' // 5cbd #205 + '70J' // 5cbe #1829 + '7X' // 5cbf #205 + '71E' // 5cc0 #1850 + '12H' // 5cc1 #319 + '35C' // 5cc2 #912 + 'a7X' // 5cc3-5cc4 #205 + '1S' // 5cc5 #44 + 'A' // 5cc6 + '63X' // 5cc7 #1661 + '15S' // 5cc8 #408 + '1S' // 5cc9 #44 + '15S' // 5cca #408 + '8G' // 5ccb #214 + 'aA' // 5ccc-5ccd + '15S' // 5cce #408 + 'A' // 5ccf + '1S' // 5cd0 #44 + '35C' // 5cd1 #912 + '8G' // 5cd2 #214 + 'aA' // 5cd3-5cd4 + '35C' // 5cd5 #912 + '15S' // 5cd6 #408 + '1S' // 5cd7 #44 + '12H' // 5cd8 #319 + '30P' // 5cd9 #795 + '15S' // 5cda #408 + 'aA' // 5cdb-5cdc + '1S' // 5cdd #44 + 'A' // 5cde + '12H' // 5cdf #319 + '253C' // 5ce0 #6580 + '252W' // 5ce1 #6574 + 'A' // 5ce2 + 'a7X' // 5ce3-5ce4 #205 + '12H' // 5ce5 #319 + '31O' // 5ce6 #820 + 'A' // 5ce7 + '60N' // 5ce8 #1573 + 'a60O' // 5ce9-5cea #1574 + 'aA' // 5ceb-5cec + '60O' // 5ced #1574 + '47P' // 5cee #1237 + '135Z' // 5cef #3535 + '196M' // 5cf0 #5108 + '47P' // 5cf1 #1237 + '1S' // 5cf2 #44 + '7X' // 5cf3 #205 + '60N' // 5cf4 #1573 + '47P' // 5cf5 #1237 + '223U' // 5cf6 #5818 + 'A' // 5cf7 + '47N' // 5cf8 #1235 + 'A' // 5cf9 + '1S' // 5cfa #44 + '140K' // 5cfb #3650 + '30M' // 5cfc #792 + '165I' // 5cfd #4298 + 'A' // 5cfe + '15R' // 5cff #407 + '47N' // 5d00 #1235 + '140L' // 5d01 #3651 + 'b7X' // 5d02-5d04 #205 + 'A' // 5d05 + '20U' // 5d06 #540 + '173V' // 5d07 #4519 + '7X' // 5d08 #205 + 'aA' // 5d09-5d0a + '20U' // 5d0b #540 + '47N' // 5d0c #1235 + '109F' // 5d0d #2839 + '169M' // 5d0e #4406 + '7X' // 5d0f #205 + '63H' // 5d10 #1645 + '48Z' // 5d11 #1273 + '31O' // 5d12 #820 + '7X' // 5d13 #205 + '151Z' // 5d14 #3951 + '20U' // 5d15 #540 + '136A' // 5d16 #3536 + '158M' // 5d17 #4120 + '20U' // 5d18 #540 + '151X' // 5d19 #3949 + '20U' // 5d1a #540 + '140M' // 5d1b #3652 + 'A' // 5d1c + '20U' // 5d1d #540 + '7X' // 5d1e #205 + 'a20U' // 5d1f-5d20 #540 + '7X' // 5d21 #205 + '20U' // 5d22 #540 + '1S' // 5d23 #44 + '20U' // 5d24 #540 + '7X' // 5d25 #205 + '14L' // 5d26 #375 + '30N' // 5d27 #793 + '15R' // 5d28 #407 + '183B' // 5d29 #4759 + 'A' // 5d2a + '1S' // 5d2b #44 + '15R' // 5d2c #407 + '2W' // 5d2d #74 + '16Y' // 5d2e #440 + '35B' // 5d2f #911 + '5E' // 5d30 #134 + '31N' // 5d31 #819 + '5E' // 5d32 #134 + '16Y' // 5d33 #440 + '14L' // 5d34 #375 + '15R' // 5d35 #407 + 'bA' // 5d36-5d38 + '1S' // 5d39 #44 + 'aA' // 5d3a-5d3b + '5E' // 5d3c #134 + '14L' // 5d3d #375 + '35B' // 5d3e #911 + '30O' // 5d3f #794 + 'A' // 5d40 + '5E' // 5d41 #134 + 'a14L' // 5d42-5d43 #375 + '5E' // 5d44 #134 + 'A' // 5d45 + 'a14L' // 5d46-5d47 #375 + '109G' // 5d48 #2840 + '15R' // 5d49 #407 + '14L' // 5d4a #375 + '30N' // 5d4b #793 + '152T' // 5d4c #3971 + 'A' // 5d4d + '14L' // 5d4e #375 + 'A' // 5d4f + '155A' // 5d50 #4030 + '1S' // 5d51 #44 + '14L' // 5d52 #375 + '1S' // 5d53 #44 + 'A' // 5d54 + '31N' // 5d55 #819 + '35B' // 5d56 #911 + '16Y' // 5d57 #440 + '5E' // 5d58 #134 + '30O' // 5d59 #794 + '5E' // 5d5a #134 + '16Y' // 5d5b #440 + '31N' // 5d5c #819 + '5E' // 5d5d #134 + 'A' // 5d5e + 'c1S' // 5d5f-5d62 #44 + 'A' // 5d63 + '1S' // 5d64 #44 + '15R' // 5d65 #407 + 'aA' // 5d66-5d67 + '16Y' // 5d68 #440 + '30N' // 5d69 #793 + '1S' // 5d6a #44 + '16Y' // 5d6b #440 + '30N' // 5d6c #793 + '1S' // 5d6d #44 + 'A' // 5d6e + '30N' // 5d6f #793 + '109E' // 5d70 #2838 + 'aA' // 5d71-5d72 + '1S' // 5d73 #44 + '16Y' // 5d74 #440 + 'A' // 5d75 + '1S' // 5d76 #44 + 'A' // 5d77 + '30M' // 5d78 #792 + 'a1S' // 5d79-5d7a #44 + '30M' // 5d7b #792 + 'aA' // 5d7c-5d7d + '30O' // 5d7e #794 + '14L' // 5d7f #375 + 'A' // 5d80 + '30O' // 5d81 #794 + '14L' // 5d82 #375 + '31N' // 5d83 #819 + '134H' // 5d84 #3491 + 'a16Y' // 5d85-5d86 #440 + '30N' // 5d87 #793 + '14L' // 5d88 #375 + '5E' // 5d89 #134 + '1S' // 5d8a #44 + '109J' // 5d8b #2843 + '31N' // 5d8c #819 + 'A' // 5d8d + '30M' // 5d8e #792 + '5E' // 5d8f #134 + '1S' // 5d90 #44 + 'A' // 5d91 + '14L' // 5d92 #375 + '31N' // 5d93 #819 + '30O' // 5d94 #794 + '1S' // 5d95 #44 + 'A' // 5d96 + '30O' // 5d97 #794 + 'A' // 5d98 + '14L' // 5d99 #375 + 'A' // 5d9a + '1X' // 5d9b #49 + 'A' // 5d9c + '23I' // 5d9d #606 + 'A' // 5d9e + '1X' // 5d9f #49 + '47M' // 5da0 #1234 + '15R' // 5da1 #407 + '47M' // 5da2 #1234 + 'A' // 5da3 + '35A' // 5da4 #910 + 'aA' // 5da5-5da6 + '23K' // 5da7 #608 + 'A' // 5da8 + '15R' // 5da9 #407 + '109K' // 5daa #2844 + '35A' // 5dab #910 + '1X' // 5dac #49 + 'A' // 5dad + '23K' // 5dae #608 + 'A' // 5daf + '1X' // 5db0 #49 + 'A' // 5db1 + '20T' // 5db2 #539 + 'A' // 5db3 + '23K' // 5db4 #608 + 'A' // 5db5 + '35B' // 5db6 #911 + '20T' // 5db7 #539 + '47M' // 5db8 #1234 + '35A' // 5db9 #910 + '166Q' // 5dba #4332 + 'A' // 5dbb + '158K' // 5dbc #4118 + '23I' // 5dbd #606 + 'bA' // 5dbe-5dc0 + '30M' // 5dc1 #792 + '16Y' // 5dc2 #440 + '20T' // 5dc3 #539 + 'A' // 5dc4 + '2R' // 5dc5 #69 + '5E' // 5dc6 #134 + '36G' // 5dc7 #942 + 'A' // 5dc8 + '20T' // 5dc9 #539 + 'A' // 5dca + '23K' // 5dcb #608 + '36G' // 5dcc #942 + '108Z' // 5dcd #2833 + '1X' // 5dce #49 + 'A' // 5dcf + 'a1X' // 5dd0-5dd1 #49 + '23I' // 5dd2 #606 + '1X' // 5dd3 #49 + '137N' // 5dd4 #3575 + 'A' // 5dd5 + '23I' // 5dd6 #606 + '109B' // 5dd7 #2835 + '20T' // 5dd8 #539 + '1X' // 5dd9 #49 + 'A' // 5dda + '20T' // 5ddb #539 + '5E' // 5ddc #134 + '214D' // 5ddd #5567 + '212Z' // 5dde #5537 + 'A' // 5ddf + '23K' // 5de0 #608 + '182W' // 5de1 #4754 + '165G' // 5de2 #4296 + '254R' // 5de3 #6621 + '1X' // 5de4 #49 + '242M' // 5de5 #6304 + '221P' // 5de6 #5761 + '214Y' // 5de7 #5588 + '211U' // 5de8 #5506 + '250E' // 5de9 #6504 + 'A' // 5dea + '153M' // 5deb #3990 + 'A' // 5dec + '5E' // 5ded #134 + '229L' // 5dee #5965 + '5E' // 5def #134 + '15R' // 5df0 #407 + '234F' // 5df1 #6089 + '239Y' // 5df2 #6238 + '109H' // 5df3 #2841 + '226N' // 5df4 #5889 + '23K' // 5df5 #608 + '5E' // 5df6 #134 + '201E' // 5df7 #5230 + '1X' // 5df8 #49 + '23K' // 5df9 #608 + '5E' // 5dfa #134 + '257S' // 5dfb #6700 + '5E' // 5dfc #134 + '23I' // 5dfd #606 + '180Z' // 5dfe #4705 + '129D' // 5dff #3357 + '36G' // 5e00 #942 + '3I' // 5e01 #86 + '68X' // 5e02 #1791 + '229U' // 5e03 #5974 + '15R' // 5e04 #407 + '3N' // 5e05 #91 + '167P' // 5e06 #4357 + '36G' // 5e07 #942 + '1Z' // 5e08 #51 + '30M' // 5e09 #792 + '15R' // 5e0a #407 + '35A' // 5e0b #910 + '223L' // 5e0c #5809 + '1X' // 5e0d #49 + 'A' // 5e0e + '5E' // 5e0f #134 + '2C' // 5e10 #54 + '23I' // 5e11 #606 + '35A' // 5e12 #910 + '5E' // 5e13 #134 + '20T' // 5e14 #539 + '165H' // 5e15 #4297 + '188E' // 5e16 #4892 + 'A' // 5e17 + '109D' // 5e18 #2837 + '23I' // 5e19 #606 + '20T' // 5e1a #539 + '23I' // 5e1b #606 + '3W' // 5e1c #100 + '202Z' // 5e1d #5277 + 'A' // 5e1e + 'a23K' // 5e1f-5e20 #608 + '15R' // 5e21 #407 + '16Y' // 5e22 #440 + 'aA' // 5e23-5e24 + '193T' // 5e25 #5037 + '1Z' // 5e26 #51 + '3W' // 5e27 #100 + '20T' // 5e28 #539 + 'A' // 5e29 + '5E' // 5e2a #134 + '235R' // 5e2b #6127 + 'A' // 5e2c + '205G' // 5e2d #5336 + '158L' // 5e2e #4119 + '259F' // 5e2f #6739 + '258L' // 5e30 #6719 + '5E' // 5e31 #134 + '1X' // 5e32 #49 + '228G' // 5e33 #5934 + '16Y' // 5e34 #440 + '1X' // 5e35 #49 + '232T' // 5e36 #6051 + '109C' // 5e37 #2836 + '242X' // 5e38 #6315 + 'c5E' // 5e39-5e3c #134 + '197E' // 5e3d #5126 + '36G' // 5e3e #942 + '71E' // 5e3f #1850 + '47L' // 5e40 #1233 + 'A' // 5e41 + '109M' // 5e42 #2846 + '39T' // 5e43 #1033 + '47L' // 5e44 #1233 + '191I' // 5e45 #4974 + '5E' // 5e46 #134 + '70I' // 5e47 #1828 + '35B' // 5e48 #911 + '1X' // 5e49 #49 + 'A' // 5e4a + '1X' // 5e4b #49 + '125U' // 5e4c #3270 + 'A' // 5e4d + '1X' // 5e4e #49 + '5E' // 5e4f #134 + 'a1X' // 5e50-5e51 #49 + 'A' // 5e52 + '5E' // 5e53 #134 + '23J' // 5e54 #607 + '219R' // 5e55 #5711 + '1X' // 5e56 #49 + '23J' // 5e57 #607 + '39T' // 5e58 #1033 + '109L' // 5e59 #2845 + 'A' // 5e5a + '23J' // 5e5b #607 + '1X' // 5e5c #49 + 'A' // 5e5d + '23J' // 5e5e #607 + '47L' // 5e5f #1233 + 'A' // 5e60 + '109I' // 5e61 #2842 + '109A' // 5e62 #2834 + '208R' // 5e63 #5425 + '1X' // 5e64 #49 + 'bA' // 5e65-5e67 + '1X' // 5e68 #49 + 'A' // 5e69 + '23J' // 5e6a #607 + '225R' // 5e6b #5867 + '39T' // 5e6c #1033 + '1X' // 5e6d #49 + '39T' // 5e6e #1033 + 'A' // 5e6f + '1X' // 5e70 #49 + 'A' // 5e71 + '190P' // 5e72 #4955 + '244L' // 5e73 #6355 + '245M' // 5e74 #6382 + '23J' // 5e75 #607 + '146S' // 5e76 #3814 + '70I' // 5e77 #1828 + '216F' // 5e78 #5621 + '197Z' // 5e79 #5147 + '23J' // 5e7a #607 + '203J' // 5e7b #5287 + '211T' // 5e7c #5505 + '180X' // 5e7d #4703 + '226V' // 5e7e #5897 + '146T' // 5e7f #3815 + '23J' // 5e80 #607 + '256G' // 5e81 #6662 + 'A' // 5e82 + '108T' // 5e83 #2827 + '154R' // 5e84 #4021 + '5E' // 5e85 #134 + '108Y' // 5e86 #2832 + '140H' // 5e87 #3647 + '30L' // 5e88 #791 + 'A' // 5e89 + '211B' // 5e8a #5487 + '60I' // 5e8b #1568 + 'aA' // 5e8c-5e8d + '1X' // 5e8e #49 + '227A' // 5e8f #5902 + '3W' // 5e90 #100 + '5E' // 5e91 #134 + '108W' // 5e92 #2830 + '1Z' // 5e93 #51 + '3Q' // 5e94 #94 + '227X' // 5e95 #5925 + '60I' // 5e96 #1568 + '238S' // 5e97 #6206 + '14Z' // 5e98 #389 + '108I' // 5e99 #2816 + '65H' // 5e9a #1697 + '30K' // 5e9b #790 + '216H' // 5e9c #5623 + '14Z' // 5e9d #389 + '2R' // 5e9e #69 + '1R' // 5e9f #43 + '39S' // 5ea0 #1032 + '14Z' // 5ea1 #389 + '4P' // 5ea2 #119 + '14Z' // 5ea3 #389 + 'a4P' // 5ea4-5ea5 #119 + '41D' // 5ea6 #1069 + '230J' // 5ea7 #5989 + '27A' // 5ea8 #702 + 'A' // 5ea9 + '1X' // 5eaa #49 + '223J' // 5eab #5807 + '1X' // 5eac #49 + '221K' // 5ead #5756 + 'A' // 5eae + '14Z' // 5eaf #389 + 'A' // 5eb0 + '1X' // 5eb1 #49 + 'A' // 5eb2 + '4P' // 5eb3 #119 + '30L' // 5eb4 #791 + '108P' // 5eb5 #2823 + '108M' // 5eb6 #2820 + '235S' // 5eb7 #6128 + '65H' // 5eb8 #1697 + '4P' // 5eb9 #119 + 'bA' // 5eba-5ebc + '60G' // 5ebd #1566 + '39S' // 5ebe #1032 + '1X' // 5ebf #49 + 'A' // 5ec0 + '66L' // 5ec1 #1727 + '151W' // 5ec2 #3948 + '256J' // 5ec3 #6665 + '30L' // 5ec4 #791 + 'A' // 5ec5 + '4P' // 5ec6 #119 + 'A' // 5ec7 + '171W' // 5ec8 #4468 + '187Q' // 5ec9 #4878 + '173Z' // 5eca #4523 + 'a4P' // 5ecb-5ecc #119 + '30L' // 5ecd #791 + 'a1X' // 5ece-5ecf #49 + '108G' // 5ed0 #2814 + 'a4P' // 5ed1-5ed2 #119 + '129C' // 5ed3 #3356 + '4P' // 5ed4 #119 + '30J' // 5ed5 #789 + '165F' // 5ed6 #4295 + 'A' // 5ed7 + '30L' // 5ed8 #791 + '4P' // 5ed9 #119 + '67V' // 5eda #1763 + '39S' // 5edb #1032 + '1X' // 5edc #49 + '4P' // 5edd #119 + '1X' // 5ede #49 + '179C' // 5edf #4656 + '217Q' // 5ee0 #5658 + '30J' // 5ee1 #789 + '193S' // 5ee2 #5036 + '233F' // 5ee3 #6063 + 'A' // 5ee4 + '1X' // 5ee5 #49 + 'aA' // 5ee6-5ee7 + '4P' // 5ee8 #119 + '30J' // 5ee9 #789 + '14Z' // 5eea #389 + '1X' // 5eeb #49 + '39S' // 5eec #1032 + 'aA' // 5eed-5eee + '30K' // 5eef #790 + '27A' // 5ef0 #702 + '1X' // 5ef1 #49 + 'A' // 5ef2 + '67V' // 5ef3 #1763 + '4P' // 5ef4 #119 + 'A' // 5ef5 + '211C' // 5ef6 #5488 + '180G' // 5ef7 #4686 + '4P' // 5ef8 #119 + '60G' // 5ef9 #1566 + '235D' // 5efa #6113 + '108O' // 5efb #2822 + '4P' // 5efc #119 + '1X' // 5efd #49 + '4P' // 5efe #119 + '140I' // 5eff #3648 + '250H' // 5f00 #6507 + '108S' // 5f01 #2826 + '108J' // 5f02 #2817 + '250G' // 5f03 #6506 + '195P' // 5f04 #5085 + '30L' // 5f05 #791 + '1X' // 5f06 #49 + '4P' // 5f07 #119 + '108H' // 5f08 #2815 + '1X' // 5f09 #49 + '136Y' // 5f0a #3560 + 'c4P' // 5f0b-5f0e #119 + '69C' // 5f0f #1796 + '252S' // 5f10 #6570 + '70H' // 5f11 #1827 + '30K' // 5f12 #790 + '148V' // 5f13 #3869 + '108K' // 5f14 #2818 + '231R' // 5f15 #6023 + '1X' // 5f16 #49 + '151V' // 5f17 #3947 + '176D' // 5f18 #4579 + '1X' // 5f19 #49 + '14Z' // 5f1a #389 + '108F' // 5f1b #2813 + '1X' // 5f1c #49 + '30J' // 5f1d #789 + '1X' // 5f1e #49 + '204B' // 5f1f #5305 + '1Z' // 5f20 #51 + '1X' // 5f21 #49 + '4P' // 5f22 #119 + 'a1X' // 5f23-5f24 #49 + '108Q' // 5f25 #2824 + '154O' // 5f26 #4018 + '140F' // 5f27 #3645 + '4P' // 5f28 #119 + '34Z' // 5f29 #909 + 'A' // 5f2a + '1X' // 5f2b #49 + '27A' // 5f2c #702 + '4P' // 5f2d #119 + '1X' // 5f2e #49 + '250F' // 5f2f #6505 + '30J' // 5f30 #789 + '198N' // 5f31 #5161 + 'aA' // 5f32-5f33 + '1X' // 5f34 #49 + '236N' // 5f35 #6149 + '4P' // 5f36 #119 + '237Q' // 5f37 #6178 + '4P' // 5f38 #119 + '3I' // 5f39 #86 + '140G' // 5f3a #3646 + '60H' // 5f3b #1567 + '34Z' // 5f3c #909 + '27A' // 5f3d #702 + '257N' // 5f3e #6695 + '27A' // 5f3f #702 + '4P' // 5f40 #119 + '27A' // 5f41 #702 + 'a14Z' // 5f42-5f43 #389 + '1X' // 5f44 #49 + '4P' // 5f45 #119 + '30K' // 5f46 #790 + '27A' // 5f47 #702 + '207R' // 5f48 #5399 + '14Z' // 5f49 #389 + '34Z' // 5f4a #909 + 'A' // 5f4b + '165E' // 5f4c #4294 + '60H' // 5f4d #1567 + '171V' // 5f4e #4467 + 'A' // 5f4f + '4P' // 5f50 #119 + '30J' // 5f51 #789 + '2C' // 5f52 #54 + '260F' // 5f53 #6765 + '4P' // 5f54 #119 + '7K' // 5f55 #192 + 'a34Z' // 5f56-5f57 #909 + '4P' // 5f58 #119 + '173N' // 5f59 #4511 + 'A' // 5f5a + '70H' // 5f5b #1827 + 'a4P' // 5f5c-5f5d #119 + '30K' // 5f5e #790 + '14Z' // 5f5f #389 + '1X' // 5f60 #49 + '108N' // 5f61 #2821 + '231C' // 5f62 #6008 + '4P' // 5f63 #119 + '140J' // 5f64 #3649 + '66L' // 5f65 #1727 + '255S' // 5f66 #6648 + '34Z' // 5f67 #909 + '14Z' // 5f68 #389 + '227Z' // 5f69 #5927 + '129B' // 5f6a #3355 + '108R' // 5f6b #2825 + '146R' // 5f6c #3813 + '171S' // 5f6d #4464 + '14Z' // 5f6e #389 + '31M' // 5f6f #818 + '201Z' // 5f70 #5251 + '241Y' // 5f71 #6290 + 'b60E' // 5f72-5f74 #1564 + '31M' // 5f75 #818 + '30K' // 5f76 #790 + '152U' // 5f77 #3972 + '60E' // 5f78 #1564 + '176V' // 5f79 #4597 + '31M' // 5f7a #818 + '108X' // 5f7b #2831 + '184A' // 5f7c #4784 + '108E' // 5f7d #2812 + '31M' // 5f7e #818 + '151Q' // 5f7f #3942 + '226Z' // 5f80 #5901 + '181Q' // 5f81 #4722 + 'a30I' // 5f82-5f83 #788 + '255E' // 5f84 #6634 + '223O' // 5f85 #5812 + 'A' // 5f86 + '39R' // 5f87 #1031 + '232R' // 5f88 #6049 + '30I' // 5f89 #788 + '60J' // 5f8a #1569 + '220D' // 5f8b #5723 + '245B' // 5f8c #6371 + '31M' // 5f8d #818 + 'A' // 5f8e + '3C' // 5f8f #80 + '188S' // 5f90 #4906 + '186I' // 5f91 #4844 + '191N' // 5f92 #4979 + '257D' // 5f93 #6685 + 'A' // 5f94 + '14Z' // 5f95 #389 + '3C' // 5f96 #80 + '243O' // 5f97 #6332 + '60J' // 5f98 #1569 + '39R' // 5f99 #1031 + 'A' // 5f9a + '60L' // 5f9b #1571 + '30I' // 5f9c #788 + '3C' // 5f9d #80 + '233C' // 5f9e #6060 + 'A' // 5f9f + '39R' // 5fa0 #1031 + '183Y' // 5fa1 #4782 + '3C' // 5fa2 #80 + 'A' // 5fa3 + '60F' // 5fa4 #1565 + 'A' // 5fa5 + '108V' // 5fa6 #2829 + '30I' // 5fa7 #788 + '39R' // 5fa8 #1031 + '221W' // 5fa9 #5768 + '181B' // 5faa #4707 + '60F' // 5fab #1565 + 'a30I' // 5fac-5fad #788 + '227L' // 5fae #5913 + '31M' // 5faf #818 + '3C' // 5fb0 #80 + '108D' // 5fb1 #2811 + 'A' // 5fb2 + '257R' // 5fb3 #6699 + '257P' // 5fb4 #6697 + '214Q' // 5fb5 #5580 + 'A' // 5fb6 + '225Q' // 5fb7 #5866 + '3C' // 5fb8 #80 + '176I' // 5fb9 #4584 + '60D' // 5fba #1563 + 'A' // 5fbb + '30I' // 5fbc #788 + '151U' // 5fbd #3946 + 'cA' // 5fbe-5fc1 + '60D' // 5fc2 #1563 + '246O' // 5fc3 #6410 + '16X' // 5fc4 #439 + '238K' // 5fc5 #6198 + '3N' // 5fc6 #91 + 'a3C' // 5fc7-5fc8 #80 + '16X' // 5fc9 #439 + 'A' // 5fca + '17I' // 5fcb #450 + '180T' // 5fcc #4699 + '197K' // 5fcd #5132 + '6U' // 5fce #176 + '60K' // 5fcf #1570 + 'b16X' // 5fd0-5fd2 #439 + '17I' // 5fd3 #450 + '16X' // 5fd4 #439 + '60K' // 5fd5 #1570 + '108L' // 5fd6 #2819 + '221B' // 5fd7 #5747 + '221O' // 5fd8 #5760 + '203N' // 5fd9 #5291 + '6U' // 5fda #176 + '108U' // 5fdb #2828 + '260B' // 5fdc #6761 + 'a16X' // 5fdd-5fde #439 + '60L' // 5fdf #1571 + '196L' // 5fe0 #5107 + '16X' // 5fe1 #439 + '17I' // 5fe2 #450 + 'A' // 5fe3 + '16X' // 5fe4 #439 + '6U' // 5fe5 #176 + 'A' // 5fe6 + '3N' // 5fe7 #91 + 'a3C' // 5fe8-5fe9 #80 + '16X' // 5fea #439 + '241L' // 5feb #6277 + '3C' // 5fec #80 + 'a16X' // 5fed-5fee #439 + 'a3C' // 5fef-5ff0 #80 + '16X' // 5ff1 #439 + '17I' // 5ff2 #450 + '16X' // 5ff3 #439 + 'A' // 5ff4 + '223E' // 5ff5 #5802 + '17I' // 5ff6 #450 + 'A' // 5ff7 + '5J' // 5ff8 #139 + 'A' // 5ff9 + '18Z' // 5ffa #493 + '5J' // 5ffb #139 + '3C' // 5ffc #80 + '179A' // 5ffd #4654 + '6U' // 5ffe #176 + '16V' // 5fff #437 + '108C' // 6000 #2810 + '3Q' // 6001 #94 + 'd6U' // 6002-6006 #176 + '3C' // 6007 #80 + 'aA' // 6008-6009 + '5J' // 600a #139 + 'aA' // 600b-600c + '5J' // 600d #139 + '217P' // 600e #5657 + '16V' // 600f #437 + '18Z' // 6010 #493 + 'A' // 6011 + '190T' // 6012 #4959 + '3C' // 6013 #80 + '5J' // 6014 #139 + '207Q' // 6015 #5398 + '190R' // 6016 #4957 + '18Z' // 6017 #493 + '3C' // 6018 #80 + '5J' // 6019 #139 + '18Z' // 601a #493 + '5J' // 601b #139 + '107Q' // 601c #2798 + '231S' // 601d #6024 + '26L' // 601e #687 + '3C' // 601f #80 + '107S' // 6020 #2800 + '178Z' // 6021 #4653 + '18Z' // 6022 #493 + '108A' // 6023 #2808 + '3C' // 6024 #80 + '213P' // 6025 #5553 + '5J' // 6026 #139 + '244Q' // 6027 #6360 + '172P' // 6028 #4487 + '5J' // 6029 #139 + '210S' // 602a #5478 + '5J' // 602b #139 + '26L' // 602c #687 + '3C' // 602d #80 + '39Q' // 602e #1030 + '107P' // 602f #2797 + 'A' // 6030 + '5J' // 6031 #139 + 'A' // 6032 + '5J' // 6033 #139 + '26L' // 6034 #687 + '5J' // 6035 #139 + 'bA' // 6036-6038 + '39Q' // 6039 #1030 + '3C' // 603a #80 + '126K' // 603b #3286 + '2K' // 603c #62 + 'aA' // 603d-603e + '6U' // 603f #176 + '18Z' // 6040 #493 + 'b16V' // 6041-6043 #437 + 'A' // 6044 + '26L' // 6045 #687 + '179B' // 6046 #4655 + '18Z' // 6047 #493 + 'a3C' // 6048-6049 #80 + '18Z' // 604a #493 + '107Z' // 604b #2807 + '18Z' // 604c #493 + '123V' // 604d #3219 + 'A' // 604e + '6U' // 604f #176 + '211S' // 6050 #5504 + '3C' // 6051 #80 + '154P' // 6052 #4019 + '39Q' // 6053 #1030 + '17I' // 6054 #450 + '165D' // 6055 #4293 + 'a3C' // 6056-6057 #80 + '6U' // 6058 #176 + '16V' // 6059 #437 + '5J' // 605a #139 + '39Q' // 605b #1030 + 'A' // 605c + '16V' // 605d #437 + '6U' // 605e #176 + '3C' // 605f #80 + '17I' // 6060 #450 + '3C' // 6061 #80 + '171U' // 6062 #4466 + '16V' // 6063 #437 + '151T' // 6064 #3945 + '155D' // 6065 #4033 + 'A' // 6066 + '5J' // 6067 #139 + '173O' // 6068 #4512 + '209O' // 6069 #5448 + '16V' // 606a #437 + '5J' // 606b #139 + '134G' // 606c #3490 + '173Y' // 606d #4522 + '26L' // 606e #687 + '234J' // 606f #6093 + '158J' // 6070 #4117 + '3C' // 6071 #80 + '26L' // 6072 #687 + '2W' // 6073 #74 + 'A' // 6074 + '107Y' // 6075 #2806 + '3I' // 6076 #86 + '26K' // 6077 #686 + 'a6U' // 6078-6079 #176 + '3G' // 607a #84 + '6U' // 607b #176 + '2Y' // 607c #76 + '6U' // 607d #176 + '26K' // 607e #686 + '5J' // 607f #139 + '26L' // 6080 #687 + '18Z' // 6081 #493 + '3C' // 6082 #80 + '5J' // 6083 #139 + '186K' // 6084 #4846 + '186H' // 6085 #4843 + '5J' // 6086 #139 + '6U' // 6087 #176 + '3C' // 6088 #80 + '186J' // 6089 #4845 + '5J' // 608a #139 + '3C' // 608b #80 + '16V' // 608c #437 + '151S' // 608d #3944 + '5J' // 608e #139 + '6U' // 608f #176 + 'A' // 6090 + '3C' // 6091 #80 + '5J' // 6092 #139 + '17I' // 6093 #450 + '175Z' // 6094 #4575 + '5J' // 6095 #139 + '16V' // 6096 #437 + '5J' // 6097 #139 + '3C' // 6098 #80 + 'A' // 6099 + '151R' // 609a #3943 + '16V' // 609b #437 + '6U' // 609c #176 + '5J' // 609d #139 + '26K' // 609e #686 + '168R' // 609f #4385 + '189K' // 60a0 #4924 + 'A' // 60a1 + '5J' // 60a2 #139 + '189R' // 60a3 #4931 + '107L' // 60a4 #2793 + '3C' // 60a5 #80 + '107T' // 60a6 #2801 + '16V' // 60a7 #437 + '232S' // 60a8 #6050 + '257Z' // 60a9 #6707 + '258R' // 60aa #6725 + '6U' // 60ab #176 + '1R' // 60ac #43 + '6U' // 60ad #176 + 'A' // 60ae + '6U' // 60af #176 + '34Y' // 60b0 #908 + '16W' // 60b1 #438 + '190S' // 60b2 #4958 + 'a34Y' // 60b3-60b4 #908 + '16W' // 60b5 #438 + '167D' // 60b6 #4345 + '3C' // 60b7 #80 + '34Y' // 60b8 #908 + 'aA' // 60b9-60ba + '16W' // 60bb #438 + '130I' // 60bc #3388 + '34Y' // 60bd #908 + '16W' // 60be #438 + 'A' // 60bf + '47K' // 60c0 #1232 + '6U' // 60c1 #176 + '17I' // 60c2 #450 + '6U' // 60c3 #176 + '3C' // 60c4 #80 + '69D' // 60c5 #1797 + '16W' // 60c6 #438 + '34Y' // 60c7 #908 + 'a17I' // 60c8-60c9 #450 + '107O' // 60ca #2796 + '16W' // 60cb #438 + 'aA' // 60cc-60cd + '17I' // 60ce #450 + '3C' // 60cf #80 + 'A' // 60d0 + '190Z' // 60d1 #4965 + 'A' // 60d2 + 'a16W' // 60d3-60d4 #438 + '107N' // 60d5 #2795 + 'A' // 60d6 + '47K' // 60d7 #1232 + 'a16W' // 60d8-60d9 #438 + '107X' // 60da #2805 + '16W' // 60db #438 + '195O' // 60dc #5084 + '16W' // 60dd #438 + '26K' // 60de #686 + '158I' // 60df #4116 + '226I' // 60e0 #5884 + '207P' // 60e1 #5397 + '16W' // 60e2 #438 + '107U' // 60e3 #2802 + '6U' // 60e4 #176 + '3C' // 60e5 #80 + '47K' // 60e6 #1232 + '107R' // 60e7 #2799 + '107W' // 60e8 #2804 + '108B' // 60e9 #2809 + '6U' // 60ea #176 + '2W' // 60eb #74 + '6U' // 60ec #176 + '10L' // 60ed #271 + '250D' // 60ee #6503 + '1R' // 60ef #43 + '34X' // 60f0 #907 + '171R' // 60f1 #4463 + '30H' // 60f2 #787 + '242I' // 60f3 #6300 + '39P' // 60f4 #1029 + '3C' // 60f5 #80 + '34X' // 60f6 #907 + 'a3C' // 60f7-60f8 #80 + '188H' // 60f9 #4895 + '34X' // 60fa #907 + '107M' // 60fb #2794 + '3C' // 60fc #80 + '26K' // 60fd #686 + 'A' // 60fe + '60C' // 60ff #1562 + '39P' // 6100 #1029 + '152V' // 6101 #3973 + '3C' // 6102 #80 + '30H' // 6103 #787 + 'aA' // 6104-6105 + '34X' // 6106 #907 + '26K' // 6107 #686 + '171T' // 6108 #4465 + '174M' // 6109 #4536 + '30H' // 610a #787 + '60C' // 610b #1562 + '26K' // 610c #686 + 'a34X' // 610d-610e #907 + '68Y' // 610f #1792 + '30H' // 6110 #787 + '3C' // 6111 #80 + '39P' // 6112 #1029 + '30H' // 6113 #787 + '39P' // 6114 #1029 + '107V' // 6115 #2803 + '30H' // 6116 #787 + '3E' // 6117 #82 + 'A' // 6118 + '60B' // 6119 #1561 + '154L' // 611a #4015 + '243Y' // 611b #6342 + '123U' // 611c #3218 + 'A' // 611d + '3E' // 611e #82 + '68W' // 611f #1790 + '34W' // 6120 #906 + '3E' // 6121 #82 + '60B' // 6122 #1561 + '47J' // 6123 #1231 + '2Y' // 6124 #76 + 'A' // 6125 + '10L' // 6126 #271 + '146P' // 6127 #3811 + '34W' // 6128 #906 + '47J' // 6129 #1231 + '36F' // 612a #941 + 'a34W' // 612b-612c #906 + '10L' // 612d #271 + 'a47J' // 612e-612f #1231 + '60A' // 6130 #1560 + '3E' // 6131 #82 + 'aA' // 6132-6133 + '107K' // 6134 #2792 + '3E' // 6135 #82 + '34W' // 6136 #906 + '123T' // 6137 #3217 + 'A' // 6138 + '3E' // 6139 #82 + '36F' // 613a #941 + 'A' // 613b + '250B' // 613c #6501 + '34W' // 613d #906 + '60A' // 613e #1560 + '107J' // 613f #2791 + 'A' // 6140 + '3E' // 6141 #82 + '106Y' // 6142 #2780 + 'A' // 6143 + '47G' // 6144 #1228 + '3E' // 6145 #82 + '15P' // 6146 #405 + '47G' // 6147 #1228 + '188D' // 6148 #4891 + '18Y' // 6149 #492 + '47G' // 614a #1228 + '230P' // 614b #5995 + '153X' // 614c #4001 + '18Y' // 614d #492 + '175O' // 614e #4564 + 'A' // 614f + '59X' // 6150 #1557 + '10L' // 6151 #271 + '30G' // 6152 #786 + '14K' // 6153 #374 + 'A' // 6154 + '180B' // 6155 #4681 + 'aA' // 6156-6157 + '186G' // 6158 #4842 + '59Y' // 6159 #1558 + '15P' // 615a #405 + 'A' // 615b + '47I' // 615c #1230 + '14K' // 615d #374 + '18Y' // 615e #492 + '14K' // 615f #374 + '20S' // 6160 #538 + '10L' // 6161 #271 + '211A' // 6162 #5486 + '197Y' // 6163 #5146 + '14K' // 6164 #374 + '15P' // 6165 #405 + 'A' // 6166 + '214X' // 6167 #5587 + '141V' // 6168 #3687 + 'A' // 6169 + '30G' // 616a #786 + '14K' // 616b #374 + '15P' // 616c #405 + 'A' // 616d + '197T' // 616e #5141 + '18Y' // 616f #492 + '174Y' // 6170 #4548 + '15P' // 6171 #405 + '18Y' // 6172 #492 + 'b15P' // 6173-6175 #405 + '209Q' // 6176 #5450 + '14K' // 6177 #374 + '3E' // 6178 #82 + 'A' // 6179 + '30G' // 617a #786 + '3E' // 617b #82 + '18Y' // 617c #492 + '47F' // 617d #1227 + '171P' // 617e #4461 + '3E' // 617f #82 + '18Y' // 6180 #492 + '47F' // 6181 #1227 + '195Y' // 6182 #5094 + 'a3E' // 6183-6184 #82 + 'aA' // 6185-6186 + '15P' // 6187 #405 + 'aA' // 6188-6189 + '14K' // 618a #374 + '107A' // 618b #2782 + '30G' // 618c #786 + '15P' // 618d #405 + '107D' // 618e #2785 + 'A' // 618f + '159U' // 6190 #4154 + '187P' // 6191 #4877 + 'a18Y' // 6192-6193 #492 + '14K' // 6194 #374 + '59X' // 6195 #1557 + 'a3E' // 6196-6197 #82 + '47F' // 6198 #1227 + 'a14K' // 6199-619a #374 + '30G' // 619b #786 + '106Z' // 619c #2781 + '3E' // 619d #82 + 'A' // 619e + '15P' // 619f #405 + '3E' // 61a0 #82 + '47I' // 61a1 #1230 + 'A' // 61a2 + '10L' // 61a3 #271 + '159N' // 61a4 #4147 + '3E' // 61a5 #82 + 'A' // 61a6 + '107F' // 61a7 #2787 + '15P' // 61a8 #405 + '143C' // 61a9 #3720 + '18Y' // 61aa #492 + 'a14K' // 61ab-61ac #374 + '15P' // 61ad #405 + '59Y' // 61ae #1558 + '30G' // 61af #786 + 'aA' // 61b0-61b1 + '175K' // 61b2 #4560 + '10L' // 61b3 #271 + 'A' // 61b4 + '10L' // 61b5 #271 + '204M' // 61b6 #5316 + '47H' // 61b7 #1229 + '18Y' // 61b8 #492 + '20S' // 61b9 #538 + '14K' // 61ba #374 + '10L' // 61bb #271 + '3E' // 61bc #82 + 'A' // 61bd + '158G' // 61be #4114 + '47I' // 61bf #1230 + '20S' // 61c0 #538 + '3E' // 61c1 #82 + '207N' // 61c2 #5395 + '14K' // 61c3 #374 + '10L' // 61c4 #271 + 'A' // 61c5 + '15P' // 61c6 #405 + '136S' // 61c7 #3554 + '123S' // 61c8 #3216 + '240P' // 61c9 #6255 + 'a14K' // 61ca-61cb #374 + 'a15Q' // 61cc-61cd #406 + '3E' // 61ce #82 + '20S' // 61cf #538 + '107G' // 61d0 #2788 + '10L' // 61d1 #271 + '2L' // 61d2 #63 + '47H' // 61d3 #1229 + '10L' // 61d4 #271 + '3E' // 61d5 #82 + 'A' // 61d6 + '10L' // 61d7 #271 + 'aA' // 61d8-61d9 + '47H' // 61da #1229 + 'A' // 61db + 'a3E' // 61dc-61dd #82 + '15Q' // 61de #406 + '26J' // 61df #685 + '39O' // 61e0 #1028 + '36F' // 61e1 #941 + '20S' // 61e2 #538 + '15Q' // 61e3 #406 + 'A' // 61e4 + '3E' // 61e5 #82 + '34U' // 61e6 #904 + '3E' // 61e7 #82 + '15Q' // 61e8 #406 + '3E' // 61e9 #82 + 'aA' // 61ea-61eb + '3E' // 61ec #82 + '15Q' // 61ed #406 + '39O' // 61ee #1028 + '3E' // 61ef #82 + '10L' // 61f0 #271 + 'A' // 61f1 + '148H' // 61f2 #3855 + 'A' // 61f3 + '3E' // 61f4 #82 + '59Z' // 61f5 #1559 + '193N' // 61f6 #5031 + '214P' // 61f7 #5579 + '175T' // 61f8 #4569 + '34V' // 61f9 #905 + '34U' // 61fa #904 + 'A' // 61fb + '158F' // 61fc #4113 + 'a26J' // 61fd-61fe #685 + '128Z' // 61ff #3353 + '207M' // 6200 #5394 + '3E' // 6201 #82 + 'A' // 6202 + 'a3E' // 6203-6204 #82 + 'A' // 6205 + '10L' // 6206 #271 + '34U' // 6207 #904 + '146O' // 6208 #3810 + '26J' // 6209 #685 + '146N' // 620a #3809 + '10L' // 620b #271 + '147V' // 620c #3843 + 'a34U' // 620d-620e #904 + '1Z' // 620f #51 + '41D' // 6210 #1069 + '246I' // 6211 #6404 + '181G' // 6212 #4712 + '36F' // 6213 #941 + 'a26J' // 6214-6215 #685 + '240Q' // 6216 #6256 + '10L' // 6217 #271 + '1Z' // 6218 #51 + '39O' // 6219 #1028 + '153T' // 621a #3997 + '26J' // 621b #685 + 'b36F' // 621c-621e #941 + '107C' // 621f #2784 + '26J' // 6220 #685 + '34U' // 6221 #904 + 'a26J' // 6222-6223 #685 + 'A' // 6224 + '39O' // 6225 #1028 + '259E' // 6226 #6738 + '15Q' // 6227 #406 + 'A' // 6228 + '15Q' // 6229 #406 + '193O' // 622a #5032 + '15Q' // 622b #406 + '39N' // 622c #1027 + 'A' // 622d + '34T' // 622e #903 + '70X' // 622f #1843 + '225O' // 6230 #5864 + '249Z' // 6231 #6499 + '232P' // 6232 #6047 + '140E' // 6233 #3644 + '201M' // 6234 #5238 + 'A' // 6235 + '225P' // 6236 #5865 + '35T' // 6237 #929 + '70Y' // 6238 #1844 + '20S' // 6239 #538 + 'A' // 623a + '259Q' // 623b #6750 + 'A' // 623c + '16U' // 623d #436 + '34T' // 623e #903 + '229A' // 623f #5954 + '41D' // 6240 #1069 + '165A' // 6241 #4290 + '15Q' // 6242 #406 + '16U' // 6243 #436 + '3E' // 6244 #82 + 'A' // 6245 + '16U' // 6246 #436 + '175C' // 6247 #4552 + '34T' // 6248 #903 + '107E' // 6249 #2786 + 'A' // 624a + '35V' // 624b #931 + '16U' // 624c #436 + '233S' // 624d #6076 + '165C' // 624e #4292 + 'A' // 624f + '3E' // 6250 #82 + '107B' // 6251 #2783 + '146Q' // 6252 #3812 + '234P' // 6253 #6099 + '129A' // 6254 #3354 + '258H' // 6255 #6715 + '3E' // 6256 #82 + 'A' // 6257 + '193P' // 6258 #5033 + '39N' // 6259 #1027 + '15Q' // 625a #406 + '134F' // 625b #3489 + '3E' // 625c #82 + 'A' // 625d + '16U' // 625e #436 + 'A' // 625f + 'a16U' // 6260-6261 #436 + '34V' // 6262 #905 + '207O' // 6263 #5396 + '3E' // 6264 #82 + 'a39N' // 6265-6266 #1027 + '3I' // 6267 #86 + '20S' // 6268 #538 + '3N' // 6269 #91 + '11M' // 626a #298 + '2D' // 626b #55 + '3N' // 626c #91 + '171Q' // 626d #4462 + '187C' // 626e #4864 + '165B' // 626f #4291 + '107I' // 6270 #2790 + '107H' // 6271 #2789 + '34V' // 6272 #905 + '59Z' // 6273 #1559 + 'aA' // 6274-6275 + '173U' // 6276 #4518 + 'aA' // 6277-6278 + '209S' // 6279 #5452 + '16U' // 627a #436 + '15Q' // 627b #406 + '34T' // 627c #903 + '16U' // 627d #436 + '232Q' // 627e #6048 + '216G' // 627f #5622 + '235V' // 6280 #6131 + 'A' // 6281 + '20S' // 6282 #538 + '16U' // 6283 #436 + '152X' // 6284 #3975 + '15Q' // 6285 #406 + '39N' // 6286 #1027 + 'aA' // 6287-6288 + '34T' // 6289 #903 + '226X' // 628a #5899 + 'A' // 628b + '34V' // 628c #905 + '3E' // 628d #82 + '16U' // 628e #436 + '3E' // 628f #82 + '20S' // 6290 #538 + '168Z' // 6291 #4393 + '123R' // 6292 #3215 + '193R' // 6293 #5035 + '16U' // 6294 #436 + '231N' // 6295 #6019 + '151P' // 6296 #3941 + '210K' // 6297 #5470 + '220S' // 6298 #5738 + '3E' // 6299 #82 + '2Y' // 629a #76 + '250A' // 629b #6500 + '70Y' // 629c #1844 + '34V' // 629d #905 + '259D' // 629e #6737 + '11M' // 629f #298 + '2K' // 62a0 #62 + '11M' // 62a1 #298 + '3I' // 62a2 #86 + 'A' // 62a3 + '64L' // 62a4 #1675 + '7K' // 62a5 #192 + '39L' // 62a6 #1025 + 'A' // 62a7 + '34S' // 62a8 #902 + 'aA' // 62a9-62aa + '176C' // 62ab #4578 + '158H' // 62ac #4115 + 'aA' // 62ad-62ae + '47E' // 62af #1226 + 'A' // 62b0 + '211E' // 62b1 #5490 + 'A' // 62b2 + '39L' // 62b3 #1025 + 'A' // 62b4 + '202R' // 62b5 #5269 + '39L' // 62b6 #1025 + '3E' // 62b7 #82 + 'A' // 62b8 + '180Y' // 62b9 #4704 + '250C' // 62ba #6502 + '34S' // 62bb #902 + '162F' // 62bc #4217 + '209R' // 62bd #5451 + '39L' // 62be #1025 + '34S' // 62bf #902 + 'A' // 62c0 + '30F' // 62c1 #785 + '59W' // 62c2 #1556 + '30F' // 62c3 #785 + '34S' // 62c4 #902 + '106T' // 62c5 #2775 + '193Q' // 62c6 #5034 + '123Q' // 62c7 #3214 + '59W' // 62c8 #1556 + '226P' // 62c9 #5891 + '34S' // 62ca #902 + '177A' // 62cb #4602 + '167J' // 62cc #4351 + '233J' // 62cd #6067 + '140D' // 62ce #3643 + '59V' // 62cf #1555 + '135N' // 62d0 #3523 + '34R' // 62d1 #901 + '196Q' // 62d2 #5112 + '168I' // 62d3 #4376 + '186D' // 62d4 #4839 + '106K' // 62d5 #2766 + '67M' // 62d6 #1754 + '106N' // 62d7 #2769 + '168E' // 62d8 #4372 + '106M' // 62d9 #2768 + '164X' // 62da #4287 + '219H' // 62db #5701 + '67M' // 62dc #1754 + '70X' // 62dd #1843 + 'A' // 62de + '106W' // 62df #2778 + '256I' // 62e0 #6664 + '257O' // 62e1 #6696 + '3G' // 62e2 #84 + 'a11M' // 62e3-62e4 #298 + '106X' // 62e5 #2779 + '3H' // 62e6 #85 + '11M' // 62e7 #298 + '2L' // 62e8 #63 + '1Z' // 62e9 #51 + '4A' // 62ea #104 + '11M' // 62eb #298 + '210L' // 62ec #5471 + '142T' // 62ed #3711 + '59V' // 62ee #1555 + '146L' // 62ef #3807 + '11M' // 62f0 #298 + '134C' // 62f1 #3486 + '4A' // 62f2 #104 + '174V' // 62f3 #4545 + 'a34R' // 62f4-62f5 #901 + '106R' // 62f6 #2773 + '106P' // 62f7 #2771 + 'c47E' // 62f8-62fb #1226 + '200N' // 62fc #5213 + '106I' // 62fd #2764 + '167O' // 62fe #4356 + '217O' // 62ff #5656 + '47E' // 6300 #1226 + '238J' // 6301 #6197 + '106J' // 6302 #2765 + 'a4A' // 6303-6304 #104 + 'aA' // 6305-6306 + '238I' // 6307 #6196 + '34R' // 6308 #901 + '225N' // 6309 #5863 + 'a31L' // 630a-630b #817 + 'a34R' // 630c-630d #901 + '106U' // 630e #2776 + 'A' // 630f + '34R' // 6310 #901 + '211F' // 6311 #5491 + '11M' // 6312 #298 + '8X' // 6313 #231 + 'aA' // 6314-6315 + '178Y' // 6316 #4652 + '11M' // 6317 #298 + '59U' // 6318 #1554 + '256U' // 6319 #6676 + '3X' // 631a #101 + '31L' // 631b #817 + 'A' // 631c + 'a11M' // 631d-631e #298 + '70W' // 631f #1842 + '3W' // 6320 #100 + '2L' // 6321 #63 + '11M' // 6322 #298 + '3H' // 6323 #85 + '2Y' // 6324 #76 + '3N' // 6325 #91 + '11M' // 6326 #298 + '4A' // 6327 #104 + '149E' // 6328 #3878 + '31L' // 6329 #817 + '151O' // 632a #3940 + '153H' // 632b #3985 + 'A' // 632c + '18X' // 632d #491 + '26I' // 632e #684 + '191Q' // 632f #4982 + 'A' // 6330 + '47D' // 6331 #1225 + '8X' // 6332 #231 + 'A' // 6333 + '26H' // 6334 #683 + 'a8X' // 6335-6336 #231 + '26H' // 6337 #683 + '26I' // 6338 #684 + '8X' // 6339 #231 + '186E' // 633a #4840 + '39K' // 633b #1024 + '8X' // 633c #231 + '148C' // 633d #3850 + '39K' // 633e #1024 + '255X' // 633f #6653 + '26H' // 6340 #683 + '4A' // 6341 #104 + '106G' // 6342 #2762 + '8X' // 6343 #231 + '18X' // 6344 #491 + '106V' // 6345 #2777 + '106H' // 6346 #2763 + '11M' // 6347 #298 + 'A' // 6348 + '167C' // 6349 #4344 + '4A' // 634a #104 + '8X' // 634b #231 + '39K' // 634c #1024 + '128X' // 634d #3351 + '8X' // 634e #231 + '153A' // 634f #3978 + '178W' // 6350 #4650 + 'A' // 6351 + 'a4A' // 6352-6353 #104 + '31L' // 6354 #817 + '190I' // 6355 #4948 + 'A' // 6356 + '106Q' // 6357 #2772 + 'a4A' // 6358-6359 #104 + '26I' // 635a #684 + '4A' // 635b #104 + '70W' // 635c #1842 + '47D' // 635d #1225 + '2K' // 635e #62 + '3Y' // 635f #102 + 'A' // 6360 + '3H' // 6361 #85 + '1Z' // 6362 #51 + '3G' // 6363 #84 + '30F' // 6364 #785 + '18X' // 6365 #491 + '4A' // 6366 #104 + '166P' // 6367 #4331 + '176F' // 6368 #4581 + '8X' // 6369 #231 + 'A' // 636a + 'a18X' // 636b-636c #491 + '8X' // 636d #231 + '160O' // 636e #4174 + 'a26H' // 636f-6370 #683 + '63G' // 6371 #1644 + '193M' // 6372 #5030 + 'A' // 6373 + '4A' // 6374 #104 + '18X' // 6375 #491 + '8X' // 6376 #231 + '67U' // 6377 #1762 + '4A' // 6378 #104 + '30F' // 6379 #785 + '39K' // 637a #1024 + '106O' // 637b #2770 + '4A' // 637c #104 + '8X' // 637d #231 + 'A' // 637e + '106E' // 637f #2760 + '171O' // 6380 #4460 + '26I' // 6381 #684 + '63G' // 6382 #1644 + '205F' // 6383 #5335 + '18X' // 6384 #491 + 'aA' // 6385-6386 + '8X' // 6387 #231 + '211H' // 6388 #5493 + '67U' // 6389 #1762 + '8X' // 638a #231 + '47D' // 638b #1225 + '208Q' // 638c #5424 + '26H' // 638d #683 + '8X' // 638e #231 + '140C' // 638f #3642 + '106F' // 6390 #2761 + '11M' // 6391 #298 + '233R' // 6392 #6075 + '11M' // 6393 #298 + '8X' // 6394 #231 + '31L' // 6395 #817 + '106D' // 6396 #2759 + '26H' // 6397 #683 + '161T' // 6398 #4205 + '140B' // 6399 #3641 + '4A' // 639a #104 + '221J' // 639b #5755 + 'aA' // 639c-639d + '8X' // 639e #231 + '106L' // 639f #2767 + '134B' // 63a0 #3485 + '216N' // 63a1 #5629 + '214C' // 63a2 #5566 + 'a8X' // 63a3-63a4 #231 + '236A' // 63a5 #6136 + '4A' // 63a6 #104 + '219G' // 63a7 #5700 + '241K' // 63a8 #6276 + '158D' // 63a9 #4111 + '188R' // 63aa #4905 + '4A' // 63ab #104 + 'b8X' // 63ac-63ae #231 + '18X' // 63af #491 + '149T' // 63b0 #3893 + '26H' // 63b1 #683 + '259T' // 63b2 #6753 + '11M' // 63b3 #298 + '254D' // 63b4 #6607 + '31L' // 63b5 #817 + 'A' // 63b6 + '2W' // 63b7 #74 + '14Y' // 63b8 #388 + '39M' // 63b9 #1026 + '14Y' // 63ba #388 + '252R' // 63bb #6569 + '14Y' // 63bc #388 + '18X' // 63bd #491 + '18W' // 63be #490 + 'A' // 63bf + '140A' // 63c0 #3640 + '59U' // 63c1 #1554 + 'A' // 63c2 + '106S' // 63c3 #2774 + '47C' // 63c4 #1224 + '4A' // 63c5 #104 + '64H' // 63c6 #1671 + 'A' // 63c7 + '18W' // 63c8 #490 + '148W' // 63c9 #3870 + 'bA' // 63ca-63cc + '39M' // 63cd #1026 + '18W' // 63ce #490 + '205C' // 63cf #5332 + '242R' // 63d0 #6309 + '18W' // 63d1 #490 + '200O' // 63d2 #5214 + 'b4A' // 63d3-63d5 #104 + '47C' // 63d6 #1224 + 'bA' // 63d7-63d9 + '197J' // 63da #5131 + '230I' // 63db #5988 + '18X' // 63dc #491 + 'A' // 63dd + '39M' // 63de #1026 + 'A' // 63df + '18W' // 63e0 #490 + '67Q' // 63e1 #1758 + '30F' // 63e2 #785 + '18W' // 63e3 #490 + '26I' // 63e4 #684 + '4A' // 63e5 #104 + '26I' // 63e6 #684 + 'aA' // 63e7-63e8 + '18W' // 63e9 #490 + '164W' // 63ea #4286 + 'a4A' // 63eb-63ec #104 + '193L' // 63ed #5029 + '196X' // 63ee #5119 + 'A' // 63ef + '26I' // 63f0 #684 + 'A' // 63f1 + '18W' // 63f2 #490 + '18X' // 63f3 #491 + '213I' // 63f4 #5546 + '18W' // 63f5 #490 + '47C' // 63f6 #1224 + '249Y' // 63f7 #6498 + '18W' // 63f8 #490 + '134D' // 63f9 #3487 + '255J' // 63fa #6639 + '30F' // 63fb #785 + '39M' // 63fc #1026 + '2K' // 63fd #62 + '30E' // 63fe #784 + 'a14Y' // 63ff-6400 #388 + 'a3W' // 6401-6402 #100 + 'aA' // 6403-6404 + '3H' // 6405 #85 + '30D' // 6406 #783 + '26G' // 6407 #682 + 'A' // 6408 + 'a59S' // 6409-640a #1552 + 'a30E' // 640b-640c #784 + '205A' // 640d #5330 + 'A' // 640e + '146K' // 640f #3806 + '30D' // 6410 #783 + 'A' // 6411 + '4A' // 6412 #104 + '134E' // 6413 #3488 + '59R' // 6414 #1551 + '59T' // 6415 #1553 + '193J' // 6416 #5027 + '64H' // 6417 #1671 + '59S' // 6418 #1552 + 'aA' // 6419-641a + '30E' // 641b #784 + '240L' // 641c #6251 + 'A' // 641d + '207L' // 641e #5393 + '59T' // 641f #1553 + '30D' // 6420 #783 + '30E' // 6421 #784 + '59R' // 6422 #1551 + '30E' // 6423 #784 + '4A' // 6424 #104 + 'a30D' // 6425-6426 #783 + '30E' // 6427 #784 + '30D' // 6428 #783 + '4A' // 6429 #104 + '30D' // 642a #783 + '13D' // 642b #341 + '188N' // 642c #4901 + '219M' // 642d #5706 + 'A' // 642e + 'a20R' // 642f-6430 #537 + '14Y' // 6431 #388 + '10X' // 6432 #283 + 'A' // 6433 + '10W' // 6434 #282 + '49B' // 6435 #1275 + '207K' // 6436 #5392 + '10X' // 6437 #283 + '26G' // 6438 #682 + 'A' // 6439 + '106C' // 643a #2758 + '26G' // 643b #682 + 'A' // 643c + '10W' // 643d #282 + '106B' // 643e #2757 + '10W' // 643f #282 + 'a10X' // 6440-6441 #283 + '254X' // 6442 #6627 + '13D' // 6443 #341 + '2C' // 6444 #54 + '14Y' // 6445 #388 + '3N' // 6446 #91 + '1R' // 6447 #43 + '14Y' // 6448 #388 + 'A' // 6449 + '2R' // 644a #69 + '20R' // 644b #537 + 'aA' // 644c-644d + '20R' // 644e #537 + '4A' // 644f #104 + '10X' // 6450 #283 + 'a10W' // 6451-6452 #282 + '20R' // 6453 #537 + '164Y' // 6454 #4288 + 'bA' // 6455-6457 + '197D' // 6458 #5125 + '13D' // 6459 #341 + '105W' // 645a #2752 + 'a10W' // 645b-645c #282 + '70G' // 645d #1826 + '10X' // 645e #283 + '123O' // 645f #3212 + '105Y' // 6460 #2754 + '20R' // 6461 #537 + '14Y' // 6462 #388 + '4A' // 6463 #104 + 'A' // 6464 + '10X' // 6465 #283 + 'A' // 6466 + '128Y' // 6467 #3352 + '10X' // 6468 #283 + '219F' // 6469 #5699 + 'aA' // 646a-646b + '13D' // 646c #341 + '10W' // 646d #282 + '13D' // 646e #341 + '141O' // 646f #3680 + '10X' // 6470 #283 + '47B' // 6471 #1223 + '13D' // 6472 #341 + '10W' // 6473 #282 + '20R' // 6474 #537 + '13D' // 6475 #341 + '20R' // 6476 #537 + '10X' // 6477 #283 + '178V' // 6478 #4649 + '34Q' // 6479 #900 + '179W' // 647a #4676 + '10W' // 647b #282 + '47B' // 647c #1223 + '10W' // 647d #282 + 'cA' // 647e-6481 + '10X' // 6482 #283 + '257Y' // 6483 #6706 + '14Y' // 6484 #388 + '10W' // 6485 #282 + 'A' // 6486 + '49B' // 6487 #1275 + '151N' // 6488 #3939 + 'aA' // 6489-648a + '13D' // 648b #341 + '10X' // 648c #283 + '26G' // 648d #682 + 'A' // 648e + '4A' // 648f #104 + '178X' // 6490 #4651 + '105X' // 6491 #2753 + '173F' // 6492 #4503 + '34Q' // 6493 #900 + 'A' // 6494 + '158E' // 6495 #4112 + 'a10X' // 6496-6497 #283 + 'a10W' // 6498-6499 #282 + '34Q' // 649a #900 + '4A' // 649b #104 + 'A' // 649c + '10W' // 649d #282 + '193K' // 649e #5028 + '20R' // 649f #537 + '10X' // 64a0 #283 + '4A' // 64a1 #104 + '13D' // 64a2 #341 + '20R' // 64a3 #537 + '167N' // 64a4 #4355 + '187O' // 64a5 #4876 + '4A' // 64a6 #104 + 'A' // 64a7 + '4A' // 64a8 #104 + '146M' // 64a9 #3808 + 'A' // 64aa + '160L' // 64ab #4171 + '10W' // 64ac #282 + '218G' // 64ad #5674 + '126F' // 64ae #3281 + '26G' // 64af #682 + '172O' // 64b0 #4486 + '10X' // 64b1 #283 + '168D' // 64b2 #4371 + '10W' // 64b3 #282 + '10X' // 64b4 #283 + '14Y' // 64b5 #388 + '47B' // 64b6 #1223 + '14Y' // 64b7 #388 + '2R' // 64b8 #69 + '254G' // 64b9 #6610 + '14Y' // 64ba #388 + '34Q' // 64bb #900 + '164Z' // 64bc #4289 + '4A' // 64bd #104 + '10W' // 64be #282 + '49B' // 64bf #1275 + '10X' // 64c0 #283 + '208S' // 64c1 #5426 + '123P' // 64c2 #3213 + '13D' // 64c3 #341 + '34Q' // 64c4 #900 + '164V' // 64c5 #4285 + 'A' // 64c6 + '68I' // 64c7 #1776 + 'A' // 64c8 + '70G' // 64c9 #1826 + '68I' // 64ca #1776 + '186F' // 64cb #4841 + '4A' // 64cc #104 + '204S' // 64cd #5322 + '164U' // 64ce #4284 + 'A' // 64cf + '10W' // 64d0 #282 + '4A' // 64d1 #104 + '105V' // 64d2 #2751 + '26G' // 64d3 #682 + '214O' // 64d4 #5578 + '3K' // 64d5 #88 + 'A' // 64d6 + '39I' // 64d7 #1022 + '59P' // 64d8 #1549 + 'A' // 64d9 + '225L' // 64da #5861 + 'aA' // 64db-64dc + '26G' // 64dd #682 + '14Y' // 64de #388 + 'A' // 64df + '171N' // 64e0 #4459 + '59P' // 64e1 #1549 + '105Z' // 64e2 #2755 + '18V' // 64e3 #489 + '39I' // 64e4 #1022 + '105U' // 64e5 #2750 + '181J' // 64e6 #4715 + '59Q' // 64e7 #1550 + 'A' // 64e8 + '39I' // 64e9 #1022 + '46Z' // 64ea #1221 + 'A' // 64eb + '195F' // 64ec #5075 + '18V' // 64ed #489 + 'A' // 64ee + '18V' // 64ef #489 + '39I' // 64f0 #1022 + '128W' // 64f1 #3350 + '48X' // 64f2 #1271 + '13D' // 64f3 #341 + '193H' // 64f4 #5025 + 'a3K' // 64f5-64f6 #88 + '64G' // 64f7 #1670 + '39J' // 64f8 #1023 + 'A' // 64f9 + '67L' // 64fa #1753 + '18V' // 64fb #489 + '39J' // 64fc #1023 + '3K' // 64fd #88 + '67L' // 64fe #1753 + '23H' // 64ff #605 + '151L' // 6500 #3937 + '3K' // 6501 #88 + 'A' // 6502 + '8S' // 6503 #226 + '59Q' // 6504 #1550 + '3K' // 6505 #88 + '13D' // 6506 #341 + 'A' // 6507 + '3K' // 6508 #88 + '23H' // 6509 #605 + '46Z' // 650a #1221 + 'cA' // 650b-650e + '64G' // 650f #1670 + 'A' // 6510 + '13D' // 6511 #341 + '3W' // 6512 #100 + '3K' // 6513 #88 + '146J' // 6514 #3805 + 'A' // 6515 + '18V' // 6516 #489 + 'A' // 6517 + '47A' // 6518 #1222 + '18V' // 6519 #489 + 'A' // 651a + '18V' // 651b #489 + '193I' // 651c #5026 + '68C' // 651d #1770 + '123N' // 651e #3211 + '46Z' // 651f #1221 + 'a13D' // 6520-6521 #341 + '23H' // 6522 #605 + '106A' // 6523 #2756 + '178U' // 6524 #4648 + '39J' // 6525 #1023 + '18V' // 6526 #489 + 'aA' // 6527-6528 + '18V' // 6529 #489 + '151J' // 652a #3935 + '47A' // 652b #1222 + '65G' // 652c #1696 + 'A' // 652d + '23H' // 652e #605 + '231B' // 652f #6007 + '39J' // 6530 #1023 + '14R' // 6531 #381 + '23H' // 6532 #605 + 'A' // 6533 + 'a23H' // 6534-6535 #605 + '239X' // 6536 #6237 + 'a47A' // 6537-6538 #1222 + '235U' // 6539 #6130 + '14R' // 653a #381 + '220R' // 653b #5737 + '14R' // 653c #381 + '23H' // 653d #605 + '236T' // 653e #6155 + '234Z' // 653f #6109 + 'aA' // 6540-6541 + '8S' // 6542 #226 + '18V' // 6543 #489 + '3K' // 6544 #88 + '228Q' // 6545 #5944 + 'A' // 6546 + '3K' // 6547 #88 + '225M' // 6548 #5862 + '23H' // 6549 #605 + 'a8S' // 654a-654b #226 + '1R' // 654c #43 + '105E' // 654d #2734 + '249X' // 654e #6497 + '203I' // 654f #5286 + '3K' // 6550 #88 + '210Z' // 6551 #5485 + '3K' // 6552 #88 + '8S' // 6553 #226 + 'a59O' // 6554-6555 #1548 + '151K' // 6556 #3936 + '211K' // 6557 #5496 + '164S' // 6558 #4282 + '238D' // 6559 #6191 + '34P' // 655a #899 + '3W' // 655b #100 + 'A' // 655c + '59O' // 655d #1548 + '146H' // 655e #3803 + '39F' // 655f #1019 + '3K' // 6560 #88 + 'A' // 6561 + '201P' // 6562 #5241 + '212E' // 6563 #5516 + '105Q' // 6564 #2746 + '39H' // 6565 #1021 + '188J' // 6566 #4897 + '16T' // 6567 #435 + 'A' // 6568 + '8S' // 6569 #226 + 'A' // 656a + '6Z' // 656b #181 + '67Q' // 656c #1758 + '105M' // 656d #2742 + 'a8S' // 656e-656f #226 + '260I' // 6570 #6768 + '8S' // 6571 #226 + '66K' // 6572 #1726 + '23G' // 6573 #604 + '235Q' // 6574 #6126 + '191H' // 6575 #4973 + '23G' // 6576 #604 + '162A' // 6577 #4212 + '239W' // 6578 #6236 + '23G' // 6579 #604 + '6Z' // 657a #181 + '23G' // 657b #604 + '8S' // 657c #226 + '14R' // 657d #381 + '260Z' // 657e #6785 + 'aA' // 657f-6580 + '6Z' // 6581 #181 + '48X' // 6582 #1271 + '65G' // 6583 #1696 + '3K' // 6584 #88 + '59N' // 6585 #1547 + '23G' // 6586 #604 + '246W' // 6587 #6418 + '16T' // 6588 #435 + '105K' // 6589 #2740 + '3K' // 658a #88 + '105R' // 658b #2747 + '146I' // 658c #3804 + 'A' // 658d + '255I' // 658e #6638 + '8S' // 658f #226 + '142R' // 6590 #3709 + '180R' // 6591 #4697 + '14R' // 6592 #381 + '8S' // 6593 #226 + 'A' // 6594 + '6Z' // 6595 #181 + '8S' // 6596 #226 + '189J' // 6597 #4923 + '3K' // 6598 #88 + '244Y' // 6599 #6368 + 'A' // 659a + '20Q' // 659b #536 + '182A' // 659c #4732 + '6Z' // 659d #181 + 'A' // 659e + '20Q' // 659f #536 + '6Z' // 65a0 #181 + '20Q' // 65a1 #536 + 'A' // 65a2 + '14R' // 65a3 #381 + '178T' // 65a4 #4647 + '66K' // 65a5 #1726 + '3K' // 65a6 #88 + '135K' // 65a7 #3520 + 'A' // 65a8 + '2R' // 65a9 #69 + 'A' // 65aa + '20Q' // 65ab #536 + '161F' // 65ac #4191 + '259N' // 65ad #6747 + '3K' // 65ae #88 + '226H' // 65af #5883 + '247D' // 65b0 #6425 + 'A' // 65b1 + '6Z' // 65b2 #181 + '16T' // 65b3 #435 + '3K' // 65b4 #88 + '16T' // 65b5 #435 + '8S' // 65b6 #226 + '68C' // 65b7 #1770 + '3K' // 65b8 #88 + '49H' // 65b9 #1281 + '8S' // 65ba #226 + '23G' // 65bb #604 + '246C' // 65bc #6398 + '216O' // 65bd #5630 + 'a6Z' // 65be-65bf #181 + '8S' // 65c0 #226 + '200M' // 65c1 #5212 + 'b6Z' // 65c2-65c4 #181 + '236M' // 65c5 #6148 + '6Z' // 65c6 #181 + '8S' // 65c7 #226 + '14R' // 65c8 #381 + '3K' // 65c9 #88 + 'A' // 65ca + '195T' // 65cb #5089 + '20Q' // 65cc #536 + 'A' // 65cd + '6Z' // 65ce #181 + '222O' // 65cf #5786 + '14R' // 65d0 #381 + '34P' // 65d1 #899 + '20Q' // 65d2 #536 + '8S' // 65d3 #226 + '39F' // 65d4 #1019 + 'A' // 65d5 + '6Z' // 65d6 #181 + '201Y' // 65d7 #5250 + 'a3K' // 65d8-65d9 #88 + 'A' // 65da + '6Z' // 65db #181 + 'A' // 65dc + '8S' // 65dd #226 + 'A' // 65de + '3K' // 65df #88 + '151M' // 65e0 #3938 + '6Z' // 65e1 #181 + '198H' // 65e2 #5155 + '20Q' // 65e3 #536 + 'A' // 65e4 + '247J' // 65e5 #6431 + '183F' // 65e6 #4763 + '257B' // 65e7 #6683 + '168U' // 65e8 #4388 + '230X' // 65e9 #6003 + 'aA' // 65ea-65eb + '155E' // 65ec #4034 + '168L' // 65ed #4379 + 'a23G' // 65ee-65ef #604 + '6Z' // 65f0 #181 + '123M' // 65f1 #3210 + '16T' // 65f2 #435 + '23G' // 65f3 #604 + '20Q' // 65f4 #536 + '14R' // 65f5 #381 + '7K' // 65f6 #192 + '3W' // 65f7 #100 + '8S' // 65f8 #226 + '3K' // 65f9 #88 + '194U' // 65fa #5064 + '48X' // 65fb #1271 + '20Q' // 65fc #536 + '105N' // 65fd #2743 + '14R' // 65fe #381 + '59N' // 65ff #1547 + '6Z' // 6600 #181 + 'A' // 6601 + '159J' // 6602 #4143 + '6Z' // 6603 #181 + '16T' // 6604 #435 + '39H' // 6605 #1021 + '167I' // 6606 #4350 + '183L' // 6607 #4769 + '3K' // 6608 #88 + '39G' // 6609 #1020 + '123L' // 660a #3209 + '8S' // 660b #226 + '189I' // 660c #4922 + '16T' // 660d #435 + '244N' // 660e #6357 + '173E' // 660f #4502 + '105L' // 6610 #2741 + '39G' // 6611 #1020 + '3K' // 6612 #88 + '233Z' // 6613 #6083 + '169G' // 6614 #4400 + '128V' // 6615 #3349 + '3K' // 6616 #88 + 'A' // 6617 + '34P' // 6618 #899 + 'a8S' // 6619-661a #226 + 'A' // 661b + 'a6Z' // 661c-661d #181 + '39G' // 661e #1020 + '241Q' // 661f #6282 + '206H' // 6620 #5363 + '6Z' // 6621 #181 + '16T' // 6622 #435 + '39F' // 6623 #1019 + '6Z' // 6624 #181 + '68M' // 6625 #1780 + '6Z' // 6626 #181 + '148U' // 6627 #3868 + '198X' // 6628 #5171 + '3K' // 6629 #88 + '14R' // 662a #381 + '6Z' // 662b #181 + '14R' // 662c #381 + '242W' // 662d #6314 + '14R' // 662e #381 + '246G' // 662f #6402 + '39G' // 6630 #1020 + '64U' // 6631 #1684 + 'A' // 6632 + '26F' // 6633 #681 + '15O' // 6634 #404 + '105H' // 6635 #2737 + '15O' // 6636 #404 + '3K' // 6637 #88 + 'A' // 6638 + '16T' // 6639 #435 + '15O' // 663a #404 + '249W' // 663b #6496 + '257H' // 663c #6689 + '10K' // 663d #270 + '1Z' // 663e #51 + 'a3K' // 663f-6640 #88 + '15O' // 6641 #404 + '246Z' // 6642 #6421 + '154E' // 6643 #4008 + '15O' // 6644 #404 + '26F' // 6645 #681 + '3K' // 6646 #88 + 'A' // 6647 + '39F' // 6648 #1019 + '171M' // 6649 #4458 + '16T' // 664a #435 + '105J' // 664b #2739 + '26F' // 664c #681 + '34P' // 664d #899 + '3K' // 664e #88 + '64U' // 664f #1684 + 'A' // 6650 + '3K' // 6651 #88 + '160V' // 6652 #4181 + '105S' // 6653 #2748 + '10K' // 6654 #270 + '2L' // 6655 #63 + '2K' // 6656 #62 + '105G' // 6657 #2736 + '3K' // 6658 #88 + '15O' // 6659 #404 + '217N' // 665a #5655 + '15O' // 665b #404 + '3K' // 665c #88 + 'a15O' // 665d-665e #404 + '134A' // 665f #3484 + '105F' // 6660 #2735 + 'b26F' // 6661-6663 #681 + 'a15O' // 6664-6665 #404 + '105I' // 6666 #2738 + '15O' // 6667 #404 + '193G' // 6668 #5024 + '255F' // 6669 #6635 + '3K' // 666a #88 + '105D' // 666b #2733 + '26F' // 666c #681 + '3K' // 666d #88 + '222N' // 666e #5785 + '228Z' // 666f #5953 + '164T' // 6670 #4283 + '10K' // 6671 #270 + '39H' // 6672 #1021 + '15O' // 6673 #404 + '183T' // 6674 #4777 + '3K' // 6675 #88 + '202Y' // 6676 #5276 + 'a15O' // 6677-6678 #404 + '26F' // 6679 #681 + '227H' // 667a #5909 + '16T' // 667b #435 + '26F' // 667c #681 + '39H' // 667d #1021 + '105C' // 667e #2732 + '3U' // 667f #98 + '49U' // 6680 #1294 + '253N' // 6681 #6591 + '3I' // 6682 #86 + '3U' // 6683 #98 + '26E' // 6684 #680 + '105O' // 6685 #2744 + 'A' // 6686 + '131A' // 6687 #3406 + '164Q' // 6688 #4280 + '141X' // 6689 #3689 + 'A' // 668a + 'b46Y' // 668b-668d #1220 + '26E' // 668e #680 + 'A' // 668f + '26E' // 6690 #680 + '175S' // 6691 #4568 + '46Y' // 6692 #1220 + 'aA' // 6693-6694 + '10K' // 6695 #270 + '210F' // 6696 #5465 + '205E' // 6697 #5334 + '26E' // 6698 #680 + '3U' // 6699 #98 + '105B' // 669a #2731 + 'a3U' // 669b-669c #98 + '26E' // 669d #680 + 'A' // 669e + '46Y' // 669f #1220 + '26E' // 66a0 #680 + 'A' // 66a1 + '67I' // 66a2 #1750 + '10K' // 66a3 #270 + '59L' // 66a4 #1545 + 'A' // 66a5 + '70V' // 66a6 #1841 + '3W' // 66a7 #100 + '177B' // 66a8 #4603 + 'A' // 66a9 + '105P' // 66aa #2745 + '201L' // 66ab #5237 + 'A' // 66ac + '59L' // 66ad #1545 + '137J' // 66ae #3571 + 'aA' // 66af-66b0 + '66Y' // 66b1 #1740 + '26E' // 66b2 #680 + '105A' // 66b3 #2730 + '210Y' // 66b4 #5484 + '59M' // 66b5 #1546 + '34P' // 66b6 #899 + 'A' // 66b7 + '59M' // 66b8 #1546 + '46X' // 66b9 #1219 + '105T' // 66ba #2749 + '46X' // 66bb #1219 + '3U' // 66bc #98 + 'A' // 66bd + '46X' // 66be #1219 + '26C' // 66bf #678 + '3U' // 66c0 #98 + '49U' // 66c1 #1294 + 'a3U' // 66c2-66c3 #98 + '104N' // 66c4 #2717 + 'A' // 66c5 + '200K' // 66c6 #5210 + '104V' // 66c7 #2725 + '7P' // 66c8 #197 + '67I' // 66c9 #1750 + 'aA' // 66ca-66cb + '7P' // 66cc #197 + '39E' // 66cd #1018 + '26C' // 66ce #678 + '9W' // 66cf #256 + 'cA' // 66d0-66d3 + '3U' // 66d4 #98 + '10K' // 66d5 #270 + '135X' // 66d6 #3533 + '10K' // 66d7 #270 + '23F' // 66d8 #603 + '104T' // 66d9 #2723 + 'a7P' // 66da-66db #197 + '143O' // 66dc #3732 + '200L' // 66dd #5211 + '10K' // 66de #270 + '49U' // 66df #1294 + '133Y' // 66e0 #3482 + 'a10K' // 66e1-66e2 #270 + 'bA' // 66e3-66e5 + '128T' // 66e6 #3347 + '39E' // 66e7 #1018 + 'a7P' // 66e8-66e9 #197 + '10K' // 66ea #270 + '3U' // 66eb #98 + '66Y' // 66ec #1740 + 'A' // 66ed + '3U' // 66ee #98 + 'A' // 66ef + '130H' // 66f0 #3387 + '23F' // 66f1 #603 + '212S' // 66f2 #5530 + '147Y' // 66f3 #3846 + '244I' // 66f4 #6352 + '3U' // 66f5 #98 + 'A' // 66f6 + '18U' // 66f7 #488 + '239D' // 66f8 #6217 + '160E' // 66f9 #4164 + '249T' // 66fa #6493 + '3U' // 66fb #98 + '193F' // 66fc #5023 + '70V' // 66fd #1841 + '218B' // 66fe #5669 + '206C' // 66ff #5358 + '41F' // 6700 #1071 + '3U' // 6701 #98 + '46W' // 6702 #1218 + '246B' // 6703 #6397 + 'A' // 6704 + '7P' // 6705 #197 + 'A' // 6706 + '3U' // 6707 #98 + '247I' // 6708 #6430 + '69I' // 6709 #1802 + '23F' // 670a #603 + '226R' // 670b #5893 + '26C' // 670c #678 + '241X' // 670d #6289 + '26C' // 670e #678 + 'a7P' // 670f-6710 #197 + 'A' // 6711 + '3U' // 6712 #98 + '7P' // 6713 #197 + '59I' // 6714 #1542 + '18U' // 6715 #488 + '26C' // 6716 #678 + '197S' // 6717 #5140 + '34O' // 6718 #898 + '7P' // 6719 #197 + 'A' // 671a + '231M' // 671b #6018 + '3U' // 671c #98 + '206G' // 671d #5362 + '104L' // 671e #2715 + '243U' // 671f #6338 + '9W' // 6720 #256 + 'A' // 6721 + '7P' // 6722 #197 + '23F' // 6723 #603 + 'A' // 6724 + '7P' // 6725 #197 + '18U' // 6726 #488 + '104R' // 6727 #2721 + '224A' // 6728 #5824 + '10K' // 6729 #270 + '237J' // 672a #6171 + '206A' // 672b #5356 + '247F' // 672c #6427 + '162B' // 672d #4213 + '18U' // 672e #488 + '3Q' // 672f #94 + 'A' // 6730 + '188P' // 6731 #4903 + '10K' // 6732 #270 + '7P' // 6733 #197 + '148L' // 6734 #3859 + '186B' // 6735 #4837 + '18U' // 6736 #488 + '3U' // 6737 #98 + '9W' // 6738 #256 + '23F' // 6739 #603 + '167M' // 673a #4354 + '10K' // 673b #270 + 'A' // 673c + '125B' // 673d #3251 + '3U' // 673e #98 + '7P' // 673f #197 + '3I' // 6740 #86 + '3U' // 6741 #98 + '2C' // 6742 #54 + '249V' // 6743 #6495 + '46W' // 6744 #1218 + '9W' // 6745 #256 + '133Z' // 6746 #3483 + '9W' // 6747 #256 + '7P' // 6748 #197 + '176H' // 6749 #4583 + 'A' // 674a + '34O' // 674b #898 + 'a7P' // 674c-674d #197 + '218N' // 674e #5681 + '168H' // 674f #4375 + '230W' // 6750 #6002 + '213U' // 6751 #5558 + 'A' // 6752 + '18U' // 6753 #488 + '3U' // 6754 #98 + '7P' // 6755 #197 + '135Y' // 6756 #3534 + 'aA' // 6757-6758 + '9W' // 6759 #256 + 'aA' // 675a-675b + '188C' // 675c #4890 + '9W' // 675d #256 + '59I' // 675e #1542 + '210X' // 675f #5483 + '104P' // 6760 #2719 + '162I' // 6761 #4220 + '7P' // 6762 #197 + 'a3U' // 6763-6764 #98 + '260C' // 6765 #6762 + '3U' // 6766 #98 + '23F' // 6767 #603 + '3I' // 6768 #86 + '10K' // 6769 #270 + '7P' // 676a #197 + '46W' // 676b #1218 + '7P' // 676c #197 + '147U' // 676d #3842 + '7P' // 676e #197 + '210J' // 676f #5469 + '164R' // 6770 #4281 + '244O' // 6771 #6358 + '7P' // 6772 #197 + '18U' // 6773 #488 + '9W' // 6774 #256 + '104S' // 6775 #2722 + '9W' // 6776 #256 + '18U' // 6777 #488 + 'aA' // 6778-6779 + '23F' // 677a #603 + '18U' // 677b #488 + '7P' // 677c #197 + '10K' // 677d #270 + '206D' // 677e #5359 + '230D' // 677f #5983 + '3U' // 6780 #98 + '128U' // 6781 #3348 + '23F' // 6782 #603 + '10K' // 6783 #270 + '123K' // 6784 #3208 + '3U' // 6785 #98 + '34O' // 6786 #898 + '18U' // 6787 #488 + 'A' // 6788 + '123J' // 6789 #3207 + '10K' // 678a #270 + '26D' // 678b #679 + '15N' // 678c #403 + '7H' // 678d #189 + '9W' // 678e #256 + '26D' // 678f #679 + '211D' // 6790 #5489 + '9W' // 6791 #256 + '15N' // 6792 #403 + '26D' // 6793 #679 + 'A' // 6794 + '168T' // 6795 #4387 + '23Z' // 6796 #623 + '234X' // 6797 #6107 + '15N' // 6798 #403 + '9W' // 6799 #256 + '155M' // 679a #4042 + '3U' // 679b #98 + '243D' // 679c #6321 + '182L' // 679d #4743 + '7H' // 679e #189 + '59K' // 679f #1544 + '104W' // 67a0 #2726 + '23Z' // 67a1 #623 + '252B' // 67a2 #6553 + '3H' // 67a3 #85 + '26C' // 67a4 #678 + '7H' // 67a5 #189 + '3U' // 67a6 #98 + 'a7H' // 67a7-67a8 #189 + '23Z' // 67a9 #623 + '3N' // 67aa #91 + '2R' // 67ab #69 + '59K' // 67ac #1544 + '7H' // 67ad #189 + '34O' // 67ae #898 + '142O' // 67af #3706 + '26D' // 67b0 #679 + '15N' // 67b1 #403 + '9W' // 67b2 #256 + '26D' // 67b3 #679 + '9W' // 67b4 #256 + '15N' // 67b5 #403 + '218W' // 67b6 #5690 + '26D' // 67b7 #679 + '104M' // 67b8 #2716 + '15N' // 67b9 #403 + '34O' // 67ba #898 + '15N' // 67bb #403 + '23Z' // 67bc #623 + '3U' // 67bd #98 + '249S' // 67be #6492 + '39E' // 67bf #1018 + 'a15N' // 67c0-67c1 #403 + '9W' // 67c2 #256 + '15N' // 67c3 #403 + '149K' // 67c4 #3884 + 'a15N' // 67c5-67c6 #403 + '7H' // 67c7 #189 + 'a15N' // 67c8-67c9 #403 + '104U' // 67ca #2724 + 'bA' // 67cb-67cd + '9W' // 67ce #256 + '196P' // 67cf #5111 + '202J' // 67d0 #5261 + '136L' // 67d1 #3547 + '26D' // 67d2 #679 + '204Z' // 67d3 #5329 + '198E' // 67d4 #5152 + 'A' // 67d5 + '39E' // 67d6 #1018 + '104O' // 67d7 #2718 + 'a15N' // 67d8-67d9 #403 + '148O' // 67da #3862 + '9W' // 67db #256 + '104Q' // 67dc #2720 + '59H' // 67dd #1541 + '30C' // 67de #782 + '46V' // 67df #1217 + '2K' // 67e0 #62 + '3U' // 67e1 #98 + '30C' // 67e2 #782 + '7H' // 67e3 #189 + '30C' // 67e4 #782 + '239L' // 67e5 #6225 + 'a23Z' // 67e6-67e7 #623 + '7H' // 67e8 #189 + '59H' // 67e9 #1541 + '104Z' // 67ea #2729 + 'A' // 67eb + '139Z' // 67ec #3639 + 'A' // 67ed + '3U' // 67ee #98 + '186A' // 67ef #4836 + '59G' // 67f0 #1540 + '175J' // 67f1 #4559 + '59J' // 67f2 #1543 + '175Y' // 67f3 #4574 + '175I' // 67f4 #4558 + '129Y' // 67f5 #3378 + '104K' // 67f6 #2714 + '30C' // 67f7 #782 + '46V' // 67f8 #1217 + '26C' // 67f9 #678 + '30C' // 67fa #782 + '258D' // 67fb #6711 + '3U' // 67fc #98 + '7H' // 67fd #189 + '59G' // 67fe #1540 + '125K' // 67ff #3260 + '46V' // 6800 #1217 + '30C' // 6801 #782 + '59J' // 6802 #1543 + '104X' // 6803 #2727 + '104Y' // 6804 #2728 + '249U' // 6805 #6494 + '7H' // 6806 #189 + '3Q' // 6807 #94 + '3H' // 6808 #85 + 'a7H' // 6809-680a #189 + '2Y' // 680b #76 + '7H' // 680c #189 + '34L' // 680d #895 + '7H' // 680e #189 + '2C' // 680f #54 + '59C' // 6810 #1536 + '3I' // 6811 #86 + '104G' // 6812 #2710 + '125J' // 6813 #3259 + '59D' // 6814 #1537 + 'A' // 6815 + '104C' // 6816 #2706 + '196U' // 6817 #5116 + '59D' // 6818 #1537 + '3U' // 6819 #98 + 'A' // 681a + '34L' // 681b #895 + '26B' // 681c #677 + '23E' // 681d #602 + '104A' // 681e #2704 + '23Z' // 681f #623 + '26B' // 6820 #677 + '223I' // 6821 #5806 + '39D' // 6822 #1017 + 'A' // 6823 + '7H' // 6824 #189 + '59F' // 6825 #1539 + '7H' // 6826 #189 + '23Z' // 6827 #623 + 'a23E' // 6828-6829 #602 + '69D' // 682a #1797 + '23E' // 682b #602 + 'a23Z' // 682c-682d #623 + '26B' // 682e #677 + '103Y' // 682f #2702 + '3U' // 6830 #98 + 'c23E' // 6831-6834 #602 + '7H' // 6835 #189 + '34L' // 6836 #895 + '137T' // 6837 #3581 + '209F' // 6838 #5439 + '222T' // 6839 #5791 + '26B' // 683a #677 + '23E' // 683b #602 + '68V' // 683c #1789 + '161A' // 683d #4186 + '23E' // 683e #602 + '3U' // 683f #98 + '39D' // 6840 #1017 + '104E' // 6841 #2708 + '174E' // 6842 #4528 + '219K' // 6843 #5704 + 'a23E' // 6844-6845 #602 + '186C' // 6846 #4838 + '34L' // 6847 #895 + '231A' // 6848 #6006 + '23E' // 6849 #602 + '59C' // 684a #1536 + 'A' // 684b + '207J' // 684c #5391 + '3U' // 684d #98 + '39D' // 684e #1017 + 'A' // 684f + '154Y' // 6850 #4028 + '174U' // 6851 #4544 + '3U' // 6852 #98 + '39D' // 6853 #1017 + '123I' // 6854 #3206 + '9V' // 6855 #255 + '59F' // 6856 #1539 + 'b2U' // 6857-6859 #72 + 'A' // 685a + '2U' // 685b #72 + '257V' // 685c #6703 + '9V' // 685d #255 + 'A' // 685e + '36E' // 685f #940 + 'b7H' // 6860-6862 #189 + '249R' // 6863 #6491 + '7H' // 6864 #189 + '104J' // 6865 #2713 + '7H' // 6866 #189 + '36E' // 6867 #940 + '7H' // 6868 #189 + '3X' // 6869 #101 + 'A' // 686a + '9V' // 686b #255 + 'A' // 686c + '104H' // 686d #2711 + '2U' // 686e #72 + '9V' // 686f #255 + '2U' // 6870 #72 + '14J' // 6871 #373 + '9V' // 6872 #255 + 'A' // 6873 + '9V' // 6874 #255 + '14J' // 6875 #373 + '187L' // 6876 #4873 + '9V' // 6877 #255 + 'A' // 6878 + '14J' // 6879 #373 + '2U' // 687a #72 + 'a14J' // 687b-687c #373 + 'A' // 687d + '14J' // 687e #373 + '171L' // 687f #4457 + '26B' // 6880 #677 + '187V' // 6881 #4883 + '14J' // 6882 #373 + '9V' // 6883 #255 + '30A' // 6884 #780 + '198Q' // 6885 #5164 + '9V' // 6886 #255 + 'A' // 6887 + '30A' // 6888 #780 + 'cA' // 6889-688c + 'a2U' // 688d-688e #72 + '46S' // 688f #1214 + '2U' // 6890 #72 + 'a26B' // 6891-6892 #677 + '152Z' // 6893 #3977 + '103T' // 6894 #2697 + 'A' // 6895 + '14J' // 6896 #373 + '153F' // 6897 #3983 + '14J' // 6898 #373 + 'a2U' // 6899-689a #72 + '9V' // 689b #255 + '14J' // 689c #373 + '240Z' // 689d #6265 + 'A' // 689e + '46S' // 689f #1214 + '9V' // 68a0 #255 + '249Q' // 68a1 #6490 + '46S' // 68a2 #1214 + '9V' // 68a3 #255 + 'A' // 68a4 + '2U' // 68a5 #72 + '103X' // 68a6 #2701 + '128S' // 68a7 #3346 + '183U' // 68a8 #4778 + '14J' // 68a9 #373 + 'a2U' // 68aa-68ab #72 + 'A' // 68ac + '158B' // 68ad #4109 + '2U' // 68ae #72 + '66X' // 68af #1739 + '190H' // 68b0 #4947 + '104F' // 68b1 #2709 + '14J' // 68b2 #373 + '65U' // 68b3 #1710 + '14J' // 68b4 #373 + '158C' // 68b5 #4110 + '104B' // 68b6 #2705 + 'aA' // 68b7-68b8 + '103V' // 68b9 #2699 + '36E' // 68ba #940 + '2U' // 68bb #72 + '36E' // 68bc #940 + '59E' // 68bd #1538 + 'a7H' // 68be-68bf #189 + '2C' // 68c0 #54 + '59E' // 68c1 #1538 + '7H' // 68c2 #189 + '9V' // 68c3 #255 + '202P' // 68c4 #5267 + '103Q' // 68c5 #2694 + '9V' // 68c6 #255 + 'A' // 68c7 + '9V' // 68c8 #255 + '66X' // 68c9 #1739 + '9V' // 68ca #255 + '167L' // 68cb #4353 + '36E' // 68cc #940 + '146G' // 68cd #3802 + 'A' // 68ce + '2U' // 68cf #72 + 'a9V' // 68d0-68d1 #255 + '210C' // 68d2 #5462 + '26A' // 68d3 #676 + '2U' // 68d4 #72 + '65U' // 68d5 #1710 + '14J' // 68d6 #373 + '64F' // 68d7 #1669 + '123H' // 68d8 #3205 + '2U' // 68d9 #72 + '161W' // 68da #4208 + 'A' // 68db + '2U' // 68dc #72 + '19I' // 68dd #502 + 'A' // 68de + '174T' // 68df #4543 + '133S' // 68e0 #3476 + '26A' // 68e1 #676 + 'A' // 68e2 + '26A' // 68e3 #676 + '19I' // 68e4 #502 + '2U' // 68e5 #72 + '46T' // 68e6 #1215 + '158A' // 68e7 #4108 + '46R' // 68e8 #1213 + '46T' // 68e9 #1215 + 'b26A' // 68ea-68ec #676 + '19I' // 68ed #502 + '216M' // 68ee #5628 + 'a26A' // 68ef-68f0 #676 + '103W' // 68f1 #2700 + '148N' // 68f2 #3861 + '26B' // 68f3 #677 + '13O' // 68f4 #352 + '133X' // 68f5 #3481 + 'a26A' // 68f6-68f7 #676 + 'A' // 68f8 + '46R' // 68f9 #1213 + '124R' // 68fa #3241 + 'b26A' // 68fb-68fd #676 + 'A' // 68fe + '13O' // 68ff #352 + '46R' // 6900 #1213 + '103U' // 6901 #2698 + '46T' // 6902 #1215 + '30A' // 6903 #780 + '30B' // 6904 #781 + '189H' // 6905 #4921 + 'a16S' // 6906-6907 #434 + '19I' // 6908 #502 + '30B' // 6909 #781 + '2U' // 690a #72 + '16S' // 690b #434 + '2U' // 690c #72 + '204Y' // 690d #5328 + '149B' // 690e #3875 + '30B' // 690f #781 + '16S' // 6910 #434 + '2U' // 6911 #72 + '166J' // 6912 #4325 + '2U' // 6913 #72 + 'aA' // 6914-6915 + '2U' // 6916 #72 + '30B' // 6917 #781 + '34L' // 6918 #895 + '30A' // 6919 #780 + 'a16S' // 691a-691b #434 + '260H' // 691c #6767 + 'aA' // 691d-691e + 'a13O' // 691f-6920 #352 + 'b2U' // 6921-6923 #72 + '13O' // 6924 #352 + '16S' // 6925 #434 + '2U' // 6926 #72 + '260Y' // 6927 #6784 + '2U' // 6928 #72 + 'A' // 6929 + '16S' // 692a #434 + 'A' // 692b + '46U' // 692c #1216 + '13O' // 692d #352 + 'aA' // 692e-692f + '151I' // 6930 #3934 + '2U' // 6931 #72 + '46U' // 6932 #1216 + '2U' // 6933 #72 + '16S' // 6934 #434 + '2U' // 6935 #72 + '30A' // 6936 #780 + 'A' // 6937 + '2U' // 6938 #72 + '16S' // 6939 #434 + 'A' // 693a + '2U' // 693b #72 + '34N' // 693c #897 + '34M' // 693d #896 + '34N' // 693e #897 + '104D' // 693f #2707 + '34N' // 6940 #897 + '13O' // 6941 #352 + '16S' // 6942 #434 + '104I' // 6943 #2712 + '13O' // 6944 #352 + '19I' // 6945 #502 + '30A' // 6946 #780 + 'aA' // 6947-6948 + '30B' // 6949 #781 + '201B' // 694a #5227 + '13O' // 694b #352 + 'aA' // 694c-694d + '2U' // 694e #72 + 'bA' // 694f-6951 + '34N' // 6952 #897 + '166V' // 6953 #4337 + '34M' // 6954 #896 + '103Z' // 6955 #2703 + '46U' // 6956 #1216 + '34M' // 6957 #896 + 'A' // 6958 + '34M' // 6959 #896 + '201U' // 695a #5246 + '16S' // 695b #434 + '30B' // 695c #781 + '16S' // 695d #434 + '103R' // 695e #2695 + '34N' // 695f #897 + '153N' // 6960 #3991 + '103S' // 6961 #2696 + 'a34M' // 6962-6963 #896 + '8F' // 6964 #213 + '29Z' // 6965 #779 + '8F' // 6966 #213 + '25Z' // 6967 #675 + '20O' // 6968 #534 + 'a8F' // 6969-696a #213 + '20O' // 696b #534 + '8F' // 696c #213 + '244X' // 696d #6367 + 'a20O' // 696e-696f #534 + '29Z' // 6970 #779 + '19I' // 6971 #502 + '39C' // 6972 #1016 + 'a8F' // 6973-6974 #213 + '229K' // 6975 #5964 + '25Z' // 6976 #675 + '133T' // 6977 #3477 + 'a20O' // 6978-6979 #534 + '29Z' // 697a #779 + '2U' // 697b #72 + '253B' // 697c #6579 + '71A' // 697d #1846 + '2U' // 697e #72 + '19I' // 697f #502 + '8F' // 6980 #213 + '2U' // 6981 #72 + '213S' // 6982 #5556 + 'A' // 6983 + '3X' // 6984 #101 + '20P' // 6985 #535 + '103D' // 6986 #2681 + 'b13O' // 6987-6989 #352 + '103H' // 698a #2685 + 'aA' // 698b-698c + '8F' // 698d #213 + '103J' // 698e #2687 + 'A' // 698f + '25Z' // 6990 #675 + '29Z' // 6991 #779 + '2U' // 6992 #72 + 'A' // 6993 + '8F' // 6994 #213 + '64F' // 6995 #1669 + '8F' // 6996 #213 + '20P' // 6997 #535 + '8F' // 6998 #213 + '20P' // 6999 #535 + '25Z' // 699a #675 + '103G' // 699b #2684 + '207I' // 699c #5390 + 'A' // 699d + '25Z' // 699e #675 + '12G' // 699f #318 + 'a2U' // 69a0-69a1 #72 + '12G' // 69a2 #318 + 'a20P' // 69a3-69a4 #535 + '103C' // 69a5 #2680 + '8F' // 69a6 #213 + '20O' // 69a7 #534 + '133W' // 69a8 #3480 + 'aA' // 69a9-69aa + '8F' // 69ab #213 + 'A' // 69ac + '8F' // 69ad #213 + '208I' // 69ae #5416 + '29Z' // 69af #779 + '2U' // 69b0 #72 + '8F' // 69b1 #213 + '39C' // 69b2 #1016 + '25Z' // 69b3 #675 + '133U' // 69b4 #3478 + 'A' // 69b5 + '25Z' // 69b6 #675 + '8F' // 69b7 #213 + '2U' // 69b8 #72 + 'A' // 69b9 + '2U' // 69ba #72 + '20O' // 69bb #534 + '8F' // 69bc #213 + 'A' // 69bd + '2U' // 69be #72 + '29Z' // 69bf #779 + '39C' // 69c0 #1016 + '20O' // 69c1 #534 + 'A' // 69c2 + '20O' // 69c3 #534 + '20P' // 69c4 #535 + '19I' // 69c5 #502 + 'A' // 69c6 + '19I' // 69c7 #502 + '2U' // 69c8 #72 + 'A' // 69c9 + '8F' // 69ca #213 + '213O' // 69cb #5552 + '103F' // 69cc #2683 + '194Y' // 69cd #5068 + '8F' // 69ce #213 + '2U' // 69cf #72 + '20O' // 69d0 #534 + '8F' // 69d1 #213 + 'A' // 69d2 + '133V' // 69d3 #3479 + '20P' // 69d4 #535 + '12G' // 69d5 #318 + '39C' // 69d6 #1016 + '19I' // 69d7 #502 + '71A' // 69d8 #1846 + '8F' // 69d9 #213 + '13O' // 69da #352 + '3X' // 69db #101 + 'A' // 69dc + 'a1Q' // 69dd-69de #42 + '3G' // 69df #84 + '13O' // 69e0 #352 + '12G' // 69e1 #318 + 'a1Q' // 69e2-69e3 #42 + '20P' // 69e4 #535 + '1Q' // 69e5 #42 + '13O' // 69e6 #352 + '1Q' // 69e7 #42 + '103A' // 69e8 #2678 + '29Y' // 69e9 #778 + '249P' // 69ea #6489 + '1Q' // 69eb #42 + '13O' // 69ec #352 + '59A' // 69ed #1534 + '29Y' // 69ee #778 + '1Q' // 69ef #42 + 'A' // 69f0 + 'b59A' // 69f1-69f3 #1534 + '29Y' // 69f4 #778 + '1Q' // 69f5 #42 + '29Y' // 69f6 #778 + 'A' // 69f7 + '20P' // 69f8 #535 + '23D' // 69f9 #601 + '20P' // 69fa #535 + '103L' // 69fb #2689 + '13O' // 69fc #352 + '174L' // 69fd #4535 + '12K' // 69fe #322 + '58Z' // 69ff #1533 + '46O' // 6a00 #1210 + '123G' // 6a01 #3204 + '240K' // 6a02 #6250 + '23D' // 6a03 #601 + 'A' // 6a04 + '29Y' // 6a05 #778 + '11L' // 6a06 #297 + 'aA' // 6a07-6a08 + '11L' // 6a09 #297 + '102Z' // 6a0a #2677 + '103K' // 6a0b #2688 + '23D' // 6a0c #601 + 'aA' // 6a0d-6a0e + '1Q' // 6a0f #42 + 'A' // 6a10 + '65T' // 6a11 #1709 + '1Q' // 6a12 #42 + '225J' // 6a13 #5859 + 'a1Q' // 6a14-6a15 #42 + 'A' // 6a16 + '58Z' // 6a17 #1533 + '103P' // 6a18 #2693 + '235C' // 6a19 #6112 + '23D' // 6a1a #601 + '46O' // 6a1b #1210 + '12G' // 6a1c #318 + '1Q' // 6a1d #42 + '123F' // 6a1e #3203 + '128Q' // 6a1f #3344 + '1Q' // 6a20 #42 + '228Y' // 6a21 #5952 + '1Q' // 6a22 #42 + '232O' // 6a23 #6046 + '1Q' // 6a24 #42 + 'bA' // 6a25-6a27 + '46O' // 6a28 #1210 + '103M' // 6a29 #2690 + '258O' // 6a2a #6722 + '103I' // 6a2b #2686 + 'a12G' // 6a2c-6a2d #318 + '1Q' // 6a2e #42 + '11L' // 6a2f #297 + '12K' // 6a30 #322 + '4C' // 6a31 #106 + '29Y' // 6a32 #778 + '23D' // 6a33 #601 + '1Q' // 6a34 #42 + '34K' // 6a35 #894 + 'a1Q' // 6a36-6a37 #42 + '65T' // 6a38 #1709 + '211Z' // 6a39 #5511 + '152R' // 6a3a #3969 + '16R' // 6a3b #433 + '11L' // 6a3c #297 + '136N' // 6a3d #3549 + 'a16R' // 6a3e-6a3f #433 + '46P' // 6a40 #1211 + 'aA' // 6a41-6a42 + '12G' // 6a43 #318 + '139Y' // 6a44 #3638 + '23D' // 6a45 #601 + '12K' // 6a46 #322 + '16R' // 6a47 #433 + '34K' // 6a48 #894 + '1Q' // 6a49 #42 + '12K' // 6a4a #322 + '213F' // 6a4b #5543 + '12G' // 6a4c #318 + 'A' // 6a4d + '12K' // 6a4e #322 + 'A' // 6a4f + '16R' // 6a50 #433 + '1Q' // 6a51 #42 + '34K' // 6a52 #894 + '103N' // 6a53 #2691 + '1Q' // 6a54 #42 + 'a25Y' // 6a55-6a56 #674 + '46Q' // 6a57 #1212 + '174D' // 6a58 #4527 + '159M' // 6a59 #4146 + '46Q' // 6a5a #1212 + '16R' // 6a5b #433 + 'aA' // 6a5c-6a5d + '46P' // 6a5e #1211 + '244H' // 6a5f #6351 + 'A' // 6a60 + '157Z' // 6a61 #4107 + '16R' // 6a62 #433 + '12G' // 6a63 #318 + '1Q' // 6a64 #42 + '103O' // 6a65 #2692 + '16R' // 6a66 #433 + '12K' // 6a67 #322 + 'aA' // 6a68-6a69 + '25Y' // 6a6a #674 + '193D' // 6a6b #5021 + 'dA' // 6a6c-6a70 + '103B' // 6a71 #2679 + 'a1Q' // 6a72-6a73 #42 + '12G' // 6a74 #318 + 'bA' // 6a75-6a77 + '1Q' // 6a78 #42 + '11L' // 6a79 #297 + '23D' // 6a7a #601 + 'A' // 6a7b + '11L' // 6a7c #297 + 'A' // 6a7d + 'a16R' // 6a7e-6a7f #433 + '103E' // 6a80 #2682 + '25Y' // 6a81 #674 + '12G' // 6a82 #318 + '1Q' // 6a83 #42 + '34K' // 6a84 #894 + 'A' // 6a85 + '12K' // 6a86 #322 + '25Y' // 6a87 #674 + 'A' // 6a88 + '46N' // 6a89 #1209 + '12G' // 6a8a #318 + '1Q' // 6a8b #42 + 'A' // 6a8c + '46N' // 6a8d #1209 + '59B' // 6a8e #1535 + '12G' // 6a8f #318 + 'a16R' // 6a90-6a91 #433 + '46Q' // 6a92 #1212 + 'A' // 6a93 + '217M' // 6a94 #5654 + 'aA' // 6a95-6a96 + '34K' // 6a97 #894 + 'A' // 6a98 + '12G' // 6a99 #318 + 'A' // 6a9a + '1Q' // 6a9b #42 + '59B' // 6a9c #1535 + '23D' // 6a9d #601 + '16R' // 6a9e #433 + '25Y' // 6a9f #674 + '16R' // 6aa0 #433 + '25Y' // 6aa1 #674 + '225K' // 6aa2 #5860 + '46N' // 6aa3 #1209 + '46P' // 6aa4 #1211 + '25Y' // 6aa5 #674 + 'A' // 6aa6 + '12G' // 6aa7 #318 + '29X' // 6aa8 #777 + '11L' // 6aa9 #297 + '1Q' // 6aaa #42 + '8E' // 6aab #212 + '164P' // 6aac #4279 + 'A' // 6aad + '8E' // 6aae #212 + '66J' // 6aaf #1725 + '12K' // 6ab0 #322 + '46L' // 6ab1 #1207 + '58Y' // 6ab2 #1532 + '146E' // 6ab3 #3800 + '1Q' // 6ab4 #42 + '39A' // 6ab5 #1014 + 'A' // 6ab6 + '11L' // 6ab7 #297 + '66J' // 6ab8 #1725 + 'A' // 6ab9 + '39A' // 6aba #1014 + '133R' // 6abb #3475 + 'A' // 6abc + '12K' // 6abd #322 + '14H' // 6abe #371 + '12K' // 6abf #322 + 'A' // 6ac0 + '1Q' // 6ac1 #42 + '58X' // 6ac2 #1531 + '200J' // 6ac3 #5209 + '11L' // 6ac4 #297 + '29X' // 6ac5 #777 + '8E' // 6ac6 #212 + 'A' // 6ac7 + '8E' // 6ac8 #212 + '14H' // 6ac9 #371 + '29W' // 6aca #776 + 'A' // 6acb + '8E' // 6acc #212 + 'A' // 6acd + '11L' // 6ace #297 + 'A' // 6acf + 'a1Q' // 6ad0-6ad1 #42 + '11L' // 6ad2 #297 + '58X' // 6ad3 #1531 + '46L' // 6ad4 #1207 + 'a1Q' // 6ad5-6ad6 #42 + 'A' // 6ad7 + '58Y' // 6ad8 #1532 + '11L' // 6ad9 #297 + 'a46M' // 6ada-6adb #1208 + '12K' // 6adc #322 + 'a14H' // 6add-6ade #371 + '8E' // 6adf #212 + '11L' // 6ae0 #297 + 'A' // 6ae1 + '1Q' // 6ae2 #42 + 'A' // 6ae3 + '12K' // 6ae4 #322 + '162L' // 6ae5 #4223 + 'A' // 6ae6 + 'a8E' // 6ae7-6ae8 #212 + 'A' // 6ae9 + '14H' // 6aea #371 + '29X' // 6aeb #777 + '14H' // 6aec #371 + 'bA' // 6aed-6aef + '1Q' // 6af0 #42 + '14H' // 6af1 #371 + '1Q' // 6af2 #42 + '14H' // 6af3 #371 + 'aA' // 6af4-6af5 + '102U' // 6af6 #2672 + 'A' // 6af7 + '14H' // 6af8 #371 + 'A' // 6af9 + '14H' // 6afa #371 + '195S' // 6afb #5088 + '14H' // 6afc #371 + '1Q' // 6afd #42 + 'cA' // 6afe-6b01 + 'a1Q' // 6b02-6b03 #42 + '204A' // 6b04 #5304 + '102O' // 6b05 #2666 + 'a1Q' // 6b06-6b07 #42 + 'A' // 6b08 + '14H' // 6b09 #371 + '239V' // 6b0a #6235 + '1Q' // 6b0b #42 + '260X' // 6b0c #6783 + 'a11L' // 6b0d-6b0e #297 + 'b8E' // 6b0f-6b11 #212 + '46M' // 6b12 #1208 + '39A' // 6b13 #1014 + 'aA' // 6b14-6b15 + '65F' // 6b16 #1695 + '8E' // 6b17 #212 + 'A' // 6b18 + '11L' // 6b19 #297 + 'A' // 6b1a + '1Q' // 6b1b #42 + 'A' // 6b1c + 'a14H' // 6b1d-6b1e #371 + '1Q' // 6b1f #42 + '169D' // 6b20 #4397 + '68U' // 6b21 #1788 + '3Q' // 6b22 #94 + '200I' // 6b23 #5208 + '12K' // 6b24 #322 + '39A' // 6b25 #1014 + 'A' // 6b26 + '256B' // 6b27 #6657 + '1Q' // 6b28 #42 + 'aA' // 6b29-6b2a + '1Q' // 6b2b #42 + '8E' // 6b2c #212 + 'aA' // 6b2d-6b2e + '1Q' // 6b2f #42 + 'A' // 6b30 + '29X' // 6b31 #777 + '199E' // 6b32 #5178 + 'aA' // 6b33-6b34 + 'a14H' // 6b35-6b36 #371 + '8E' // 6b37 #212 + '146F' // 6b38 #3801 + '8E' // 6b39 #212 + '182K' // 6b3a #4742 + '8E' // 6b3b #212 + 'A' // 6b3c + '65F' // 6b3d #1695 + '240X' // 6b3e #6263 + '1Q' // 6b3f #42 + 'bA' // 6b40-6b42 + '8E' // 6b43 #212 + 'aA' // 6b44-6b45 + '46M' // 6b46 #1208 + '157Y' // 6b47 #4106 + '29X' // 6b48 #777 + '193E' // 6b49 #5022 + '1Q' // 6b4a #42 + 'A' // 6b4b + '216L' // 6b4c #5627 + '1Q' // 6b4d #42 + '128P' // 6b4e #3343 + 'A' // 6b4f + '217K' // 6b50 #5652 + 'A' // 6b51 + '46L' // 6b52 #1207 + '256T' // 6b53 #6675 + '8E' // 6b54 #212 + '29X' // 6b55 #777 + '1Q' // 6b56 #42 + '29W' // 6b57 #776 + '12K' // 6b58 #322 + '8E' // 6b59 #212 + 'A' // 6b5a + '8E' // 6b5b #212 + 'A' // 6b5c + '1Q' // 6b5d #42 + 'A' // 6b5e + '58W' // 6b5f #1530 + '8E' // 6b60 #212 + '232N' // 6b61 #6045 + '223N' // 6b62 #5811 + '41F' // 6b63 #1071 + '240U' // 6b64 #6260 + '225I' // 6b65 #5858 + '212R' // 6b66 #5529 + '151H' // 6b67 #3933 + 'A' // 6b68 + '259C' // 6b69 #6736 + '154B' // 6b6a #4005 + 'a1Q' // 6b6b-6b6c #42 + '11L' // 6b6d #297 + '1Q' // 6b6e #42 + '102S' // 6b6f #2670 + '12K' // 6b70 #322 + 'A' // 6b71 + '217L' // 6b72 #5653 + '258Z' // 6b73 #6733 + '102T' // 6b74 #2671 + '1Q' // 6b75 #42 + 'A' // 6b76 + '225H' // 6b77 #5857 + '200H' // 6b78 #5207 + '128R' // 6b79 #3345 + '8E' // 6b7a #212 + '68E' // 6b7b #1772 + '3W' // 6b7c #100 + 'a1Q' // 6b7d-6b7e #42 + '58W' // 6b7f #1530 + 'b8E' // 6b80-6b82 #212 + 'a102N' // 6b83-6b84 #2665 + '21K' // 6b85 #556 + '102P' // 6b86 #2667 + '2W' // 6b87 #74 + 'A' // 6b88 + '46J' // 6b89 #1205 + '203H' // 6b8a #5285 + '259H' // 6b8b #6741 + 'A' // 6b8c + '23C' // 6b8d #600 + 'bA' // 6b8e-6b90 + '102W' // 6b91 #2674 + 'a8R' // 6b92-6b93 #225 + 'A' // 6b94 + '21K' // 6b95 #556 + '167H' // 6b96 #4349 + '21K' // 6b97 #556 + '185V' // 6b98 #4831 + 'A' // 6b99 + '8R' // 6b9a #225 + '23C' // 6b9b #600 + 'aA' // 6b9c-6b9d + '46J' // 6b9e #1205 + 'a5L' // 6b9f-6ba0 #141 + '8R' // 6ba1 #225 + '23C' // 6ba2 #600 + '21K' // 6ba3 #556 + '23C' // 6ba4 #600 + 'bA' // 6ba5-6ba7 + '5L' // 6ba8 #141 + '21K' // 6ba9 #556 + '23C' // 6baa #600 + '102M' // 6bab #2664 + '5L' // 6bac #141 + '23C' // 6bad #600 + '102I' // 6bae #2660 + '63F' // 6baf #1643 + '21K' // 6bb0 #556 + '5L' // 6bb1 #141 + '46J' // 6bb2 #1205 + '23C' // 6bb3 #600 + '254A' // 6bb4 #6604 + '230C' // 6bb5 #5982 + 'A' // 6bb6 + '133Q' // 6bb7 #3474 + 'a5L' // 6bb8-6bb9 #141 + '215Q' // 6bba #5606 + '253R' // 6bbb #6595 + '193B' // 6bbc #5019 + '23C' // 6bbd #600 + '5L' // 6bbe #141 + '168Y' // 6bbf #4392 + '185Y' // 6bc0 #4834 + '102V' // 6bc1 #2673 + '8R' // 6bc2 #225 + 'a5L' // 6bc3-6bc4 #141 + '166K' // 6bc5 #4326 + '146C' // 6bc6 #3798 + 'b5L' // 6bc7-6bc9 #141 + '8R' // 6bca #225 + '128N' // 6bcb #3341 + '9U' // 6bcc #254 + '221Y' // 6bcd #5770 + '259B' // 6bce #6735 + '232M' // 6bcf #6044 + '14I' // 6bd0 #372 + '8R' // 6bd1 #225 + '209Y' // 6bd2 #5458 + '133P' // 6bd3 #3473 + '236E' // 6bd4 #6140 + '3Y' // 6bd5 #102 + 'b46I' // 6bd6-6bd8 #1204 + '3W' // 6bd9 #100 + '21K' // 6bda #556 + '221I' // 6bdb #5754 + '29W' // 6bdc #776 + 'A' // 6bdd + '39B' // 6bde #1015 + '5L' // 6bdf #141 + 'A' // 6be0 + '9U' // 6be1 #254 + 'A' // 6be2 + '5L' // 6be3 #141 + 'aA' // 6be4-6be5 + '21K' // 6be6 #556 + '5L' // 6be7 #141 + 'aA' // 6be8-6be9 + '29W' // 6bea #776 + '185X' // 6beb #4833 + '46I' // 6bec #1204 + 'A' // 6bed + '5L' // 6bee #141 + '159E' // 6bef #4138 + 'A' // 6bf0 + '21K' // 6bf1 #556 + 'A' // 6bf2 + '9U' // 6bf3 #254 + 'a8R' // 6bf4-6bf5 #225 + 'A' // 6bf6 + '5L' // 6bf7 #141 + 'A' // 6bf8 + '9U' // 6bf9 #254 + '29W' // 6bfa #776 + 'aA' // 6bfb-6bfc + '14I' // 6bfd #372 + 'A' // 6bfe + '46K' // 6bff #1206 + '39B' // 6c00 #1015 + 'A' // 6c01 + '46K' // 6c02 #1206 + 'A' // 6c03 + '5L' // 6c04 #141 + '9U' // 6c05 #254 + '14I' // 6c06 #372 + '8R' // 6c07 #225 + '63F' // 6c08 #1643 + 'a5L' // 6c09-6c0a #141 + 'A' // 6c0b + '39B' // 6c0c #1015 + '9U' // 6c0d #254 + '5L' // 6c0e #141 + '191P' // 6c0f #4981 + '9U' // 6c10 #254 + '235F' // 6c11 #6115 + '5L' // 6c12 #141 + '102H' // 6c13 #2659 + '139X' // 6c14 #3637 + '8R' // 6c15 #225 + '14I' // 6c16 #372 + '260J' // 6c17 #6769 + '14I' // 6c18 #372 + '9U' // 6c19 #254 + '14I' // 6c1a #372 + '185Z' // 6c1b #4835 + '29W' // 6c1c #776 + 'aA' // 6c1d-6c1e + '102K' // 6c1f #2662 + 'A' // 6c20 + '14I' // 6c21 #372 + '3G' // 6c22 #84 + '240J' // 6c23 #6249 + '9U' // 6c24 #254 + '8R' // 6c25 #225 + '9U' // 6c26 #254 + '171K' // 6c27 #4456 + '102L' // 6c28 #2663 + '8R' // 6c29 #225 + '14I' // 6c2a #372 + '126H' // 6c2b #3283 + '9U' // 6c2c #254 + '8R' // 6c2d #225 + '102J' // 6c2e #2661 + '102X' // 6c2f #2675 + 'b14I' // 6c30-6c32 #372 + '9U' // 6c33 #254 + '244P' // 6c34 #6359 + 'a9U' // 6c35-6c36 #254 + '102R' // 6c37 #2669 + '220K' // 6c38 #5730 + '14I' // 6c39 #372 + '9U' // 6c3a #254 + '5L' // 6c3b #141 + '8R' // 6c3c #225 + '14I' // 6c3d #372 + '46I' // 6c3e #1204 + '9U' // 6c3f #254 + '139W' // 6c40 #3636 + '190C' // 6c41 #4942 + '236L' // 6c42 #6147 + '39B' // 6c43 #1015 + 'aA' // 6c44-6c45 + '14I' // 6c46 #372 + '2C' // 6c47 #54 + 'A' // 6c48 + '102Y' // 6c49 #2676 + 'a9U' // 6c4a-6c4b #254 + '14I' // 6c4c #372 + '46K' // 6c4d #1206 + '102Q' // 6c4e #2668 + '9U' // 6c4f #254 + '159Z' // 6c50 #4159 + 'A' // 6c51 + '5L' // 6c52 #141 + 'A' // 6c53 + '18T' // 6c54 #487 + '58R' // 6c55 #1525 + '8R' // 6c56 #225 + '182R' // 6c57 #4749 + '29V' // 6c58 #775 + '151G' // 6c59 #3932 + '102D' // 6c5a #2655 + 'a18T' // 6c5b-6c5c #487 + '128O' // 6c5d #3342 + '22Z' // 6c5e #597 + '216B' // 6c5f #5617 + '205M' // 6c60 #5342 + '184K' // 6c61 #4794 + '5L' // 6c62 #141 + '8R' // 6c63 #225 + '3N' // 6c64 #91 + 'a29V' // 6c65-6c66 #775 + '18T' // 6c67 #487 + '22Z' // 6c68 #597 + '46H' // 6c69 #1203 + '164N' // 6c6a #4277 + '18T' // 6c6b #487 + 'A' // 6c6c + '22Z' // 6c6d #597 + '29V' // 6c6e #775 + '18T' // 6c6f #487 + '142Q' // 6c70 #3708 + '29V' // 6c71 #775 + '101Z' // 6c72 #2651 + 'a18T' // 6c73-6c74 #487 + '29V' // 6c75 #775 + '139V' // 6c76 #3635 + 'A' // 6c77 + 'a18T' // 6c78-6c79 #487 + '223T' // 6c7a #5817 + '5L' // 6c7b #141 + '8R' // 6c7c #225 + '218E' // 6c7d #5672 + '101V' // 6c7e #2647 + '34J' // 6c7f #893 + 'A' // 6c80 + '123E' // 6c81 #3202 + '58R' // 6c82 #1525 + '151F' // 6c83 #3931 + '18T' // 6c84 #487 + 'b22Z' // 6c85-6c87 #597 + '175H' // 6c88 #4557 + '193C' // 6c89 #5020 + 'A' // 6c8a + '8R' // 6c8b #225 + '22Z' // 6c8c #597 + '46G' // 6c8d #1202 + 'A' // 6c8e + '46H' // 6c8f #1203 + '178S' // 6c90 #4646 + '8R' // 6c91 #225 + '239U' // 6c92 #6234 + 'a22Z' // 6c93-6c94 #597 + '249N' // 6c95 #6487 + '199D' // 6c96 #5177 + '5L' // 6c97 #141 + '18T' // 6c98 #487 + '219J' // 6c99 #5703 + '22Z' // 6c9a #597 + '157X' // 6c9b #4105 + '5L' // 6c9c #141 + '46H' // 6c9d #1203 + 'A' // 6c9e + '58T' // 6c9f #1527 + 'A' // 6ca0 + '254U' // 6ca1 #6624 + '102F' // 6ca2 #2657 + 'a8R' // 6ca3-6ca4 #225 + '3G' // 6ca5 #84 + '2K' // 6ca6 #62 + '3H' // 6ca7 #85 + 'a8R' // 6ca8-6ca9 #225 + '58T' // 6caa #1527 + '146D' // 6cab #3799 + 'a18T' // 6cac-6cad #487 + '22Z' // 6cae #597 + '29V' // 6caf #775 + '46G' // 6cb0 #1202 + 'a23A' // 6cb1-6cb2 #598 + '67X' // 6cb3 #1765 + '23A' // 6cb4 #598 + '5D' // 6cb5 #133 + 'A' // 6cb6 + '5D' // 6cb7 #133 + '143D' // 6cb8 #3721 + '219Z' // 6cb9 #5719 + '23A' // 6cba #598 + '244G' // 6cbb #6350 + '102C' // 6cbc #2654 + '123D' // 6cbd #3201 + '164M' // 6cbe #4276 + '183I' // 6cbf #4766 + 'A' // 6cc0 + '229T' // 6cc1 #5973 + '58Q' // 6cc2 #1524 + '58V' // 6cc3 #1529 + '135Q' // 6cc4 #3526 + 'a23A' // 6cc5-6cc6 #598 + '58V' // 6cc7 #1529 + 'A' // 6cc8 + '198Z' // 6cc9 #5173 + '176R' // 6cca #4593 + '34J' // 6ccb #893 + '153S' // 6ccc #3996 + '5L' // 6ccd #141 + '34J' // 6cce #893 + '5L' // 6ccf #141 + 'b23A' // 6cd0-6cd2 #598 + '133O' // 6cd3 #3472 + '23A' // 6cd4 #598 + '69A' // 6cd5 #1794 + '23A' // 6cd6 #598 + '58Q' // 6cd7 #1524 + 'A' // 6cd8 + 'a23A' // 6cd9-6cda #598 + '171J' // 6cdb #4455 + '46G' // 6cdc #1202 + '25W' // 6cdd #672 + '23B' // 6cde #599 + '58U' // 6cdf #1528 + '6Y' // 6ce0 #180 + '209X' // 6ce1 #5457 + '68E' // 6ce2 #1772 + '143G' // 6ce3 #3724 + '5D' // 6ce4 #133 + '189C' // 6ce5 #4916 + '5D' // 6ce6 #133 + '25W' // 6ce7 #672 + '231Q' // 6ce8 #6022 + '6Y' // 6ce9 #180 + '101X' // 6cea #2649 + '22Y' // 6ceb #596 + '6Y' // 6cec #180 + '49T' // 6ced #1293 + 'a22Y' // 6cee-6cef #596 + '218T' // 6cf0 #5687 + '6Y' // 6cf1 #180 + '49T' // 6cf2 #1293 + '189G' // 6cf3 #4920 + '5L' // 6cf4 #141 + '64C' // 6cf5 #1666 + 'a5D' // 6cf6-6cf7 #133 + '3W' // 6cf8 #100 + 'A' // 6cf9 + '5D' // 6cfa #133 + '249O' // 6cfb #6488 + '2R' // 6cfc #69 + '3N' // 6cfd #91 + '5D' // 6cfe #133 + '34J' // 6cff #893 + '25W' // 6d00 #672 + '101Y' // 6d01 #2650 + '25X' // 6d02 #673 + '5D' // 6d03 #133 + '6Y' // 6d04 #180 + '58U' // 6d05 #1528 + '25X' // 6d06 #673 + '6Y' // 6d07 #180 + '5D' // 6d08 #133 + '25X' // 6d09 #673 + '6Y' // 6d0a #180 + '216I' // 6d0b #5624 + '22Y' // 6d0c #596 + 'A' // 6d0d + 'a6Y' // 6d0e-6d0f #180 + '23B' // 6d10 #599 + '22Y' // 6d11 #596 + '102B' // 6d12 #2653 + '49T' // 6d13 #1293 + '5D' // 6d14 #133 + 'A' // 6d15 + '5D' // 6d16 #133 + '221N' // 6d17 #5759 + '23B' // 6d18 #599 + '22Y' // 6d19 #596 + '6Y' // 6d1a #180 + '194O' // 6d1b #5058 + '5D' // 6d1c #133 + 'A' // 6d1d + '195X' // 6d1e #5093 + '6Y' // 6d1f #180 + 'aA' // 6d20-6d21 + '23B' // 6d22 #599 + '5D' // 6d23 #133 + '25W' // 6d24 #672 + '191G' // 6d25 #4972 + '58S' // 6d26 #1526 + '22Y' // 6d27 #596 + '6Y' // 6d28 #180 + '172V' // 6d29 #4493 + '187W' // 6d2a #4884 + '6Y' // 6d2b #180 + 'A' // 6d2c + '25X' // 6d2d #673 + 'a6Y' // 6d2e-6d2f #180 + '23B' // 6d30 #599 + '101W' // 6d31 #2648 + '218U' // 6d32 #5688 + '6Y' // 6d33 #180 + '25W' // 6d34 #672 + 'a22Y' // 6d35-6d36 #596 + '25X' // 6d37 #673 + 'a22Y' // 6d38-6d39 #596 + '23B' // 6d3a #599 + '68U' // 6d3b #1788 + '6Y' // 6d3c #180 + '185W' // 6d3d #4832 + '67X' // 6d3e #1765 + '6Y' // 6d3f #180 + 'A' // 6d40 + '237I' // 6d41 #6170 + 'a5D' // 6d42-6d43 #133 + '255W' // 6d44 #6652 + '256Q' // 6d45 #6672 + '2Y' // 6d46 #76 + '3X' // 6d47 #101 + 'a5D' // 6d48-6d49 #133 + '3W' // 6d4a #100 + '2D' // 6d4b #55 + 'A' // 6d4c + '5D' // 6d4d #133 + '102G' // 6d4e #2658 + '2C' // 6d4f #54 + '5D' // 6d50 #133 + '3H' // 6d51 #85 + '5D' // 6d52 #133 + '1R' // 6d53 #43 + '5D' // 6d54 #133 + 'A' // 6d55 + '5D' // 6d56 #133 + 'a6Y' // 6d57-6d58 #180 + '164O' // 6d59 #4278 + '58P' // 6d5a #1523 + '58S' // 6d5b #1526 + '102E' // 6d5c #2656 + 'A' // 6d5d + '6Y' // 6d5e #180 + '25W' // 6d5f #672 + 'a6Y' // 6d60-6d61 #180 + '25X' // 6d62 #673 + '102A' // 6d63 #2652 + 'a6Y' // 6d64-6d65 #180 + '176N' // 6d66 #4589 + '6Y' // 6d67 #180 + 'A' // 6d68 + '182F' // 6d69 #4737 + '215A' // 6d6a #5590 + 'A' // 6d6b + '58P' // 6d6c #1523 + '23B' // 6d6d #599 + '190Y' // 6d6e #4964 + '6Y' // 6d6f #180 + '25W' // 6d70 #672 + '34J' // 6d71 #893 + '25X' // 6d72 #673 + 'A' // 6d73 + '205K' // 6d74 #5340 + '23B' // 6d75 #599 + 'A' // 6d76 + '238X' // 6d77 #6211 + '168A' // 6d78 #4368 + '101M' // 6d79 #2638 + 'A' // 6d7a + '5D' // 6d7b #133 + '20N' // 6d7c #533 + '5D' // 6d7d #133 + 'A' // 6d7e + '101R' // 6d7f #2643 + '7D' // 6d80 #185 + '46E' // 6d81 #1200 + '101L' // 6d82 #2637 + 'aA' // 6d83-6d84 + '101F' // 6d85 #2631 + 'A' // 6d86 + '34H' // 6d87 #891 + '235Z' // 6d88 #6135 + '200F' // 6d89 #5205 + '70F' // 6d8a #1825 + 'A' // 6d8b + '146B' // 6d8c #3797 + '70D' // 6d8d #1823 + '34H' // 6d8e #891 + '46F' // 6d8f #1201 + '5D' // 6d90 #133 + '34H' // 6d91 #891 + '20N' // 6d92 #533 + '34H' // 6d93 #891 + '20N' // 6d94 #533 + '34H' // 6d95 #891 + '101I' // 6d96 #2634 + 'a20N' // 6d97-6d98 #533 + '256F' // 6d99 #6661 + '101S' // 6d9a #2644 + '252C' // 6d9b #6554 + '70F' // 6d9c #1825 + 'c5D' // 6d9d-6da0 #133 + '3X' // 6da1 #101 + 'a5D' // 6da2-6da3 #133 + '101U' // 6da4 #2646 + '25V' // 6da5 #671 + '3Y' // 6da6 #102 + '5D' // 6da7 #133 + '1R' // 6da8 #43 + '3H' // 6da9 #85 + 'b20N' // 6daa-6dac #533 + 'A' // 6dad + '133N' // 6dae #3471 + '174K' // 6daf #4534 + 'A' // 6db0 + '25V' // 6db1 #671 + '67S' // 6db2 #1760 + '101T' // 6db3 #2645 + '20N' // 6db4 #533 + '178R' // 6db5 #4645 + '5D' // 6db6 #133 + '34I' // 6db7 #892 + '20N' // 6db8 #533 + '46E' // 6db9 #1200 + 'aA' // 6dba-6dbb + '203P' // 6dbc #5293 + '7D' // 6dbd #185 + '46F' // 6dbe #1201 + '20N' // 6dbf #533 + '101P' // 6dc0 #2641 + 'A' // 6dc1 + '20N' // 6dc2 #533 + '260W' // 6dc3 #6782 + '101G' // 6dc4 #2632 + '58M' // 6dc5 #1520 + '123A' // 6dc6 #3198 + '164L' // 6dc7 #4275 + '34I' // 6dc8 #892 + '58O' // 6dc9 #1522 + '34I' // 6dca #892 + '179Y' // 6dcb #4678 + '58N' // 6dcc #1521 + '46F' // 6dcd #1201 + '7D' // 6dce #185 + '58M' // 6dcf #1520 + '34I' // 6dd0 #892 + '180F' // 6dd1 #4685 + '58N' // 6dd2 #1521 + '58O' // 6dd3 #1522 + 'A' // 6dd4 + '34I' // 6dd5 #892 + '5I' // 6dd6 #138 + 'A' // 6dd7 + '185U' // 6dd8 #4830 + '10V' // 6dd9 #281 + '66W' // 6dda #1738 + '5I' // 6ddb #138 + '18S' // 6ddc #486 + '5I' // 6ddd #138 + '10V' // 6dde #281 + '18R' // 6ddf #485 + '5I' // 6de0 #138 + '202O' // 6de1 #5266 + '18R' // 6de2 #485 + '14G' // 6de3 #370 + '5I' // 6de4 #138 + '18R' // 6de5 #485 + '5I' // 6de6 #138 + 'A' // 6de7 + '207H' // 6de8 #5389 + '5I' // 6de9 #138 + '145Z' // 6dea #3795 + '168K' // 6deb #4378 + '5I' // 6dec #138 + 'A' // 6ded + '128M' // 6dee #3340 + 'a5I' // 6def-6df0 #138 + '229J' // 6df1 #5963 + '18R' // 6df2 #485 + '142S' // 6df3 #3710 + '18R' // 6df4 #485 + '153R' // 6df5 #3995 + '5I' // 6df6 #138 + '67S' // 6df7 #1760 + '249L' // 6df8 #6485 + '123B' // 6df9 #3199 + '66W' // 6dfa #1738 + '197X' // 6dfb #5145 + '5I' // 6dfc #138 + '14G' // 6dfd #370 + '25V' // 6dfe #671 + 'A' // 6dff + '18R' // 6e00 #485 + 'A' // 6e01 + '14G' // 6e02 #370 + '18S' // 6e03 #486 + '101H' // 6e04 #2633 + '235G' // 6e05 #6116 + 'A' // 6e06 + '252G' // 6e07 #6558 + '70Z' // 6e08 #1845 + '255H' // 6e09 #6637 + '101J' // 6e0a #2635 + '256Y' // 6e0b #6680 + '11K' // 6e0c #296 + '2W' // 6e0d #74 + '11K' // 6e0e #296 + '25V' // 6e0f #671 + '3N' // 6e10 #91 + '11K' // 6e11 #296 + 'A' // 6e12 + '253J' // 6e13 #6587 + '2Y' // 6e14 #76 + '101O' // 6e15 #2640 + '11K' // 6e16 #296 + '249M' // 6e17 #6486 + '25V' // 6e18 #671 + '10V' // 6e19 #281 + '101N' // 6e1a #2639 + '215V' // 6e1b #5611 + 'A' // 6e1c + '101K' // 6e1d #2636 + '36D' // 6e1e #939 + '10V' // 6e1f #281 + '139S' // 6e20 #3632 + '183V' // 6e21 #4779 + '5I' // 6e22 #138 + '157W' // 6e23 #4104 + '101E' // 6e24 #2630 + '10V' // 6e25 #281 + '135W' // 6e26 #3532 + '5I' // 6e27 #138 + 'A' // 6e28 + '137K' // 6e29 #3572 + '25V' // 6e2a #671 + '10V' // 6e2b #281 + '220J' // 6e2c #5729 + '101D' // 6e2d #2629 + '18R' // 6e2e #485 + '234O' // 6e2f #6098 + 'a14G' // 6e30-6e31 #370 + '146A' // 6e32 #3796 + 'A' // 6e33 + '157V' // 6e34 #4103 + 'A' // 6e35 + '10V' // 6e36 #281 + '11K' // 6e37 #296 + '200G' // 6e38 #5206 + '5I' // 6e39 #138 + '10V' // 6e3a #281 + '7D' // 6e3b #185 + '10V' // 6e3c #281 + '101Q' // 6e3d #2642 + '147X' // 6e3e #3845 + '11K' // 6e3f #296 + 'a18S' // 6e40-6e41 #486 + '7D' // 6e42 #185 + '139R' // 6e43 #3631 + '10V' // 6e44 #281 + '5I' // 6e45 #138 + 'A' // 6e46 + '18S' // 6e47 #486 + '7D' // 6e48 #185 + '5I' // 6e49 #138 + '160K' // 6e4a #4170 + '5I' // 6e4b #138 + '7D' // 6e4c #185 + '10V' // 6e4d #281 + '5I' // 6e4e #138 + '18R' // 6e4f #485 + '25V' // 6e50 #671 + '5I' // 6e51 #138 + '7D' // 6e52 #185 + 'a5I' // 6e53-6e54 #138 + '11K' // 6e55 #296 + '215D' // 6e56 #5593 + '46E' // 6e57 #1200 + '148X' // 6e58 #3871 + '14G' // 6e59 #370 + 'A' // 6e5a + '133M' // 6e5b #3470 + '10V' // 6e5c #281 + '36D' // 6e5d #939 + 'a10V' // 6e5e-6e5f #281 + '14G' // 6e60 #370 + '18S' // 6e61 #486 + '36D' // 6e62 #939 + '5I' // 6e63 #138 + '14G' // 6e64 #370 + 'a18S' // 6e65-6e66 #486 + '160X' // 6e67 #4183 + '7D' // 6e68 #185 + '14G' // 6e69 #370 + '11K' // 6e6a #296 + '10V' // 6e6b #281 + 'A' // 6e6c + '11K' // 6e6d #296 + '10V' // 6e6e #281 + '204X' // 6e6f #5327 + '11K' // 6e70 #296 + '14G' // 6e71 #370 + 'a10V' // 6e72-6e73 #281 + '18S' // 6e74 #486 + 'A' // 6e75 + '5I' // 6e76 #138 + '11K' // 6e77 #296 + '18S' // 6e78 #486 + 'A' // 6e79 + '260V' // 6e7a #6781 + '7D' // 6e7b #185 + '18S' // 6e7c #486 + '7D' // 6e7d #185 + '256R' // 6e7e #6673 + '255Q' // 6e7f #6646 + '70Z' // 6e80 #1845 + '11K' // 6e81 #296 + '36D' // 6e82 #939 + '2R' // 6e83 #69 + 'A' // 6e84 + '3G' // 6e85 #84 + '18S' // 6e86 #486 + '11K' // 6e87 #296 + '14G' // 6e88 #370 + '5I' // 6e89 #138 + 'A' // 6e8a + '14G' // 6e8b #370 + '7D' // 6e8c #185 + '5I' // 6e8d #138 + '14G' // 6e8e #370 + '5I' // 6e8f #138 + '228F' // 6e90 #5933 + 'aA' // 6e91-6e92 + '18R' // 6e93 #485 + 'aA' // 6e94-6e95 + '230A' // 6e96 #5980 + 'A' // 6e97 + '5I' // 6e98 #138 + '18R' // 6e99 #485 + '14G' // 6e9a #370 + 'A' // 6e9b + '167Z' // 6e9c #4367 + '188Q' // 6e9d #4904 + '11K' // 6e9e #296 + '29U' // 6e9f #774 + '7D' // 6ea0 #185 + '38Z' // 6ea1 #1013 + '161Q' // 6ea2 #4202 + 'A' // 6ea3 + '46D' // 6ea4 #1199 + '29U' // 6ea5 #774 + '38Z' // 6ea6 #1013 + '20M' // 6ea7 #532 + 'aA' // 6ea8-6ea9 + '185T' // 6eaa #4829 + '225G' // 6eab #5856 + 'A' // 6eac + '7D' // 6ead #185 + '58L' // 6eae #1519 + '133L' // 6eaf #3469 + 'A' // 6eb0 + '29U' // 6eb1 #774 + '20M' // 6eb2 #532 + '7D' // 6eb3 #185 + '20M' // 6eb4 #532 + '46D' // 6eb5 #1199 + '161S' // 6eb6 #4204 + '20M' // 6eb7 #532 + '29T' // 6eb8 #773 + 'A' // 6eb9 + '136I' // 6eba #3544 + '100R' // 6ebb #2617 + '139U' // 6ebc #3634 + '20M' // 6ebd #532 + 'A' // 6ebe + 'a36D' // 6ebf-6ec0 #939 + '20M' // 6ec1 #532 + '29U' // 6ec2 #774 + '20M' // 6ec3 #532 + '63W' // 6ec4 #1660 + '190M' // 6ec5 #4952 + '11K' // 6ec6 #296 + '100S' // 6ec7 #2618 + '20M' // 6ec8 #532 + '29U' // 6ec9 #774 + '7D' // 6eca #185 + '190X' // 6ecb #4963 + '63W' // 6ecc #1660 + '58L' // 6ecd #1519 + '100P' // 6ece #2615 + '20M' // 6ecf #532 + '38Z' // 6ed0 #1013 + '209W' // 6ed1 #5456 + 'A' // 6ed2 + 'a29U' // 6ed3-6ed4 #774 + '100T' // 6ed5 #2619 + '46D' // 6ed6 #1199 + '11K' // 6ed7 #296 + '38Z' // 6ed8 #1013 + '123C' // 6ed9 #3200 + '100U' // 6eda #2620 + '18P' // 6edb #483 + 'A' // 6edc + '100Y' // 6edd #2624 + '255P' // 6ede #6645 + 'a11J' // 6edf-6ee0 #295 + '2D' // 6ee1 #55 + '14F' // 6ee2 #369 + 'A' // 6ee3 + '2L' // 6ee4 #63 + '2K' // 6ee5 #62 + '70E' // 6ee6 #1824 + 'A' // 6ee7 + '101C' // 6ee8 #2628 + '101B' // 6ee9 #2627 + '11J' // 6eea #295 + '18Q' // 6eeb #484 + '139T' // 6eec #3633 + '7D' // 6eed #185 + '18P' // 6eee #483 + '145Y' // 6eef #3794 + 'aA' // 6ef0-6ef1 + '147S' // 6ef2 #3840 + '11J' // 6ef3 #295 + '188G' // 6ef4 #4894 + 'aA' // 6ef5-6ef6 + '151D' // 6ef7 #3929 + '46B' // 6ef8 #1197 + '18P' // 6ef9 #483 + '14F' // 6efa #369 + '18Q' // 6efb #484 + 'A' // 6efc + '7D' // 6efd #185 + '67H' // 6efe #1749 + '232L' // 6eff #6043 + '9T' // 6f00 #253 + '174X' // 6f01 #4547 + '202H' // 6f02 #5259 + '11J' // 6f03 #295 + '58J' // 6f04 #1517 + 'A' // 6f05 + '173S' // 6f06 #4516 + 'A' // 6f07 + 'a18P' // 6f08-6f09 #483 + '18Q' // 6f0a #484 + '29T' // 6f0b #773 + '58J' // 6f0c #1517 + '18Q' // 6f0d #484 + '9T' // 6f0e #253 + '189Y' // 6f0f #4938 + '7D' // 6f10 #185 + '70D' // 6f11 #1823 + '9T' // 6f12 #253 + '58K' // 6f13 #1518 + '230B' // 6f14 #5981 + '46B' // 6f15 #1197 + '100Q' // 6f16 #2616 + '29T' // 6f17 #773 + '7D' // 6f18 #185 + '14F' // 6f19 #369 + '18Q' // 6f1a #484 + '7D' // 6f1b #185 + 'cA' // 6f1c-6f1f + '160D' // 6f20 #4163 + 'A' // 6f21 + '210E' // 6f22 #5464 + '100V' // 6f23 #2621 + '46C' // 6f24 #1198 + '18P' // 6f25 #483 + '18Q' // 6f26 #484 + '9T' // 6f27 #253 + '11J' // 6f28 #295 + 'a18P' // 6f29-6f2a #483 + '228E' // 6f2b #5932 + '143B' // 6f2c #3719 + '18P' // 6f2d #483 + '9T' // 6f2e #253 + '18P' // 6f2f #483 + '18Q' // 6f30 #484 + '100N' // 6f31 #2613 + '185S' // 6f32 #4828 + '58K' // 6f33 #1518 + '9T' // 6f34 #253 + '18Q' // 6f35 #484 + '18P' // 6f36 #483 + '11J' // 6f37 #295 + '67H' // 6f38 #1749 + 'A' // 6f39 + '9T' // 6f3a #253 + 'a18Q' // 6f3b-6f3c #484 + '29T' // 6f3d #773 + '151E' // 6f3e #3930 + '66I' // 6f3f #1724 + '9T' // 6f40 #253 + '100O' // 6f41 #2614 + 'A' // 6f42 + 'a14F' // 6f43-6f44 #369 + '70E' // 6f45 #1824 + '11J' // 6f46 #295 + '3H' // 6f47 #85 + '11J' // 6f48 #295 + 'aA' // 6f49-6f4a + '11J' // 6f4b #295 + 'A' // 6f4c + '2K' // 6f4d #62 + '14F' // 6f4e #369 + '18P' // 6f4f #483 + 'A' // 6f50 + '66I' // 6f51 #1724 + '7D' // 6f52 #185 + '18Q' // 6f53 #484 + '209I' // 6f54 #5442 + '11J' // 6f55 #295 + '46C' // 6f56 #1198 + '46B' // 6f57 #1197 + '171G' // 6f58 #4452 + '41P' // 6f59 #1081 + '38Y' // 6f5a #1012 + '192Z' // 6f5b #5017 + '100Z' // 6f5c #2625 + '5K' // 6f5d #140 + '38Y' // 6f5e #1012 + '101A' // 6f5f #2626 + '25U' // 6f60 #670 + '15M' // 6f61 #402 + '171E' // 6f62 #4450 + '9T' // 6f63 #253 + '196O' // 6f64 #5110 + '11J' // 6f65 #295 + '15M' // 6f66 #402 + '14F' // 6f67 #369 + '5K' // 6f68 #140 + 'b14F' // 6f69-6f6b #369 + '15M' // 6f6c #402 + '171F' // 6f6d #4451 + '215G' // 6f6e #5596 + '15M' // 6f6f #402 + '167W' // 6f70 #4364 + '11J' // 6f71 #295 + '14F' // 6f72 #369 + '9T' // 6f73 #253 + '15M' // 6f74 #402 + '11J' // 6f75 #295 + '14F' // 6f76 #369 + '9T' // 6f77 #253 + '15M' // 6f78 #402 + '46C' // 6f79 #1198 + '38Y' // 6f7a #1012 + '14F' // 6f7b #369 + 'b38Y' // 6f7c-6f7e #1012 + '9T' // 6f7f #253 + '151C' // 6f80 #3928 + '100X' // 6f81 #2623 + '15M' // 6f82 #402 + '41P' // 6f83 #1081 + '167G' // 6f84 #4348 + 'A' // 6f85 + '63E' // 6f86 #1642 + '25U' // 6f87 #670 + '128L' // 6f88 #3339 + '14F' // 6f89 #369 + '29T' // 6f8a #773 + 'a15M' // 6f8b-6f8c #402 + '18O' // 6f8d #482 + '192Y' // 6f8e #5016 + 'A' // 6f8f + '18O' // 6f90 #482 + '5K' // 6f91 #140 + '15M' // 6f92 #402 + '5K' // 6f93 #140 + '18O' // 6f94 #482 + '9T' // 6f95 #253 + '25U' // 6f96 #670 + '18O' // 6f97 #482 + '41P' // 6f98 #1081 + 'A' // 6f99 + '5K' // 6f9a #140 + '11J' // 6f9b #295 + '2K' // 6f9c #62 + '58I' // 6f9d #1516 + 'A' // 6f9e + 'a25U' // 6f9f-6fa0 #670 + '171H' // 6fa1 #4453 + '9T' // 6fa2 #253 + '18O' // 6fa3 #482 + '197P' // 6fa4 #5137 + '15M' // 6fa5 #402 + '25U' // 6fa6 #670 + '18O' // 6fa7 #482 + '25U' // 6fa8 #670 + 'A' // 6fa9 + '100W' // 6faa #2622 + '9T' // 6fab #253 + 'aA' // 6fac-6fad + '100M' // 6fae #2612 + '18O' // 6faf #482 + '5K' // 6fb0 #140 + '64T' // 6fb1 #1683 + 'A' // 6fb2 + '217J' // 6fb3 #5651 + '14F' // 6fb4 #369 + '58I' // 6fb5 #1516 + '15M' // 6fb6 #402 + '5K' // 6fb7 #140 + 'A' // 6fb8 + '18O' // 6fb9 #482 + '9T' // 6fba #253 + '29T' // 6fbb #773 + '25U' // 6fbc #670 + 'A' // 6fbd + '100L' // 6fbe #2611 + 'A' // 6fbf + '213E' // 6fc0 #5542 + '125G' // 6fc1 #3256 + '18O' // 6fc2 #482 + '204R' // 6fc3 #5321 + '11J' // 6fc4 #295 + '41P' // 6fc5 #1081 + 'c15M' // 6fc6-6fc9 #402 + '18O' // 6fca #482 + '14F' // 6fcb #369 + 'aA' // 6fcc-6fcd + '9T' // 6fce #253 + 'aA' // 6fcf-6fd0 + 'a11J' // 6fd1-6fd2 #295 + '100F' // 6fd3 #2605 + '46A' // 6fd4 #1196 + '200E' // 6fd5 #5204 + 'aA' // 6fd6-6fd7 + '46A' // 6fd8 #1196 + '13C' // 6fd9 #340 + '99P' // 6fda #2589 + '63E' // 6fdb #1642 + 'aA' // 6fdc-6fdd + '99Q' // 6fde #2590 + '207G' // 6fdf #5388 + '58G' // 6fe0 #1514 + '99Z' // 6fe1 #2599 + '100K' // 6fe2 #2610 + 'A' // 6fe3 + '145W' // 6fe4 #3792 + 'bA' // 6fe5-6fe7 + '46A' // 6fe8 #1196 + '58G' // 6fe9 #1514 + 'A' // 6fea + '157T' // 6feb #4101 + '58F' // 6fec #1513 + 'A' // 6fed + '45Z' // 6fee #1195 + '131C' // 6fef #3408 + '45Z' // 6ff0 #1195 + '174C' // 6ff1 #4526 + 'A' // 6ff2 + '5K' // 6ff3 #140 + 'A' // 6ff4 + '5K' // 6ff5 #140 + '14D' // 6ff6 #367 + 'A' // 6ff7 + '13C' // 6ff8 #340 + '5K' // 6ff9 #140 + '45Z' // 6ffa #1195 + 'A' // 6ffb + '14D' // 6ffc #367 + '5K' // 6ffd #140 + '171D' // 6ffe #4449 + '9S' // 6fff #252 + '14D' // 7000 #367 + '58H' // 7001 #1515 + 'A' // 7002 + '9S' // 7003 #252 + 'A' // 7004 + 'a58F' // 7005-7006 #1513 + '14D' // 7007 #367 + 'A' // 7008 + '64T' // 7009 #1683 + '5K' // 700a #140 + '8D' // 700b #211 + 'A' // 700c + '7O' // 700d #196 + 'A' // 700e + '225F' // 700f #5855 + 'A' // 7010 + '139Q' // 7011 #3630 + 'bA' // 7012-7014 + '8D' // 7015 #211 + 'A' // 7016 + '5K' // 7017 #140 + '8D' // 7018 #211 + 'A' // 7019 + '139P' // 701a #3629 + '8D' // 701b #211 + '50E' // 701c #1304 + '63V' // 701d #1659 + '8D' // 701e #211 + '63D' // 701f #1641 + '14D' // 7020 #367 + '9S' // 7021 #252 + 'A' // 7022 + '8D' // 7023 #211 + 'aA' // 7024-7025 + '7O' // 7026 #196 + '99W' // 7027 #2596 + '133J' // 7028 #3467 + 'bA' // 7029-702b + '100B' // 702c #2601 + 'A' // 702d + '6T' // 702e #175 + '8D' // 702f #211 + '7O' // 7030 #196 + '9S' // 7031 #252 + '14D' // 7032 #367 + 'A' // 7033 + '14D' // 7034 #367 + '14E' // 7035 #368 + 'A' // 7036 + '8D' // 7037 #211 + '9S' // 7038 #252 + 'a7O' // 7039-703a #196 + '9S' // 703b #252 + '7O' // 703c #196 + '6T' // 703d #175 + '63V' // 703e #1659 + 'A' // 703f + '9S' // 7040 #252 + 'A' // 7041 + '9S' // 7042 #252 + '14D' // 7043 #367 + '7O' // 7044 #196 + 'A' // 7045 + '9S' // 7046 #252 + 'a5K' // 7047-7048 #140 + '7O' // 7049 #196 + '36C' // 704a #938 + '7O' // 704b #196 + '164K' // 704c #4274 + '13C' // 704d #340 + '5K' // 704e #140 + '6T' // 704f #175 + '100C' // 7050 #2602 + '164H' // 7051 #4271 + '9S' // 7052 #252 + 'A' // 7053 + 'a14D' // 7054-7055 #367 + 'aA' // 7056-7057 + '173J' // 7058 #4507 + 'A' // 7059 + '6T' // 705a #175 + 'A' // 705b + '14E' // 705c #368 + '8D' // 705d #211 + '7O' // 705e #196 + 'a9S' // 705f-7060 #252 + '14E' // 7061 #368 + 'A' // 7062 + '240S' // 7063 #6258 + '7O' // 7064 #196 + '14D' // 7065 #367 + '14E' // 7066 #368 + 'a9S' // 7067-7068 #252 + '14D' // 7069 #367 + 'A' // 706a + '68M' // 706b #1780 + '7O' // 706c #196 + '1R' // 706d #43 + '45Y' // 706e #1194 + '100A' // 706f #2600 + '196A' // 7070 #5096 + 'bA' // 7071-7073 + '9S' // 7074 #252 + '99U' // 7075 #2594 + '122Z' // 7076 #3197 + '13C' // 7077 #340 + '99X' // 7078 #2597 + '13C' // 7079 #340 + '9S' // 707a #252 + 'A' // 707b + '129X' // 707c #3377 + '191F' // 707d #4971 + '99T' // 707e #2593 + '100I' // 707f #2608 + '6T' // 7080 #175 + '7O' // 7081 #196 + 'bA' // 7082-7084 + '8D' // 7085 #211 + '7O' // 7086 #196 + 'aA' // 7087-7088 + '99Y' // 7089 #2598 + '149A' // 708a #3874 + '13C' // 708b #340 + 'aA' // 708c-708d + '190O' // 708e #4954 + '100E' // 708f #2604 + 'A' // 7090 + '14E' // 7091 #368 + '188F' // 7092 #4893 + 'A' // 7093 + 'a7O' // 7094-7095 #196 + '99S' // 7096 #2592 + '5K' // 7097 #140 + '8D' // 7098 #211 + '130C' // 7099 #3382 + '50E' // 709a #1304 + '5K' // 709b #140 + 'a6T' // 709c-709d #175 + 'A' // 709e + '7O' // 709f #196 + '13C' // 70a0 #340 + '100D' // 70a1 #2603 + 'A' // 70a2 + '13C' // 70a3 #340 + '8D' // 70a4 #211 + 'b13C' // 70a5-70a7 #340 + 'A' // 70a8 + '14E' // 70a9 #368 + '6T' // 70aa #175 + '164J' // 70ab #4273 + '99N' // 70ac #2587 + '168V' // 70ad #4389 + '171I' // 70ae #4454 + '8D' // 70af #211 + 'a7O' // 70b0-70b1 #196 + '6T' // 70b2 #175 + '133K' // 70b3 #3468 + '7O' // 70b4 #196 + '14E' // 70b5 #368 + '6T' // 70b6 #175 + '8D' // 70b7 #211 + '194R' // 70b8 #5061 + '247H' // 70b9 #6429 + '246J' // 70ba #6405 + '7O' // 70bb #196 + '100J' // 70bc #2609 + '100G' // 70bd #2606 + '9S' // 70be #252 + 'A' // 70bf + '14E' // 70c0 #368 + '3W' // 70c1 #100 + '1R' // 70c2 #43 + '6T' // 70c3 #175 + '13C' // 70c4 #340 + 'aA' // 70c5-70c6 + '6T' // 70c7 #175 + '202N' // 70c8 #5265 + 'A' // 70c9 + '99R' // 70ca #2591 + '8D' // 70cb #211 + '13C' // 70cc #340 + 'A' // 70cd + '6T' // 70ce #175 + '195K' // 70cf #5080 + '13C' // 70d0 #340 + '5K' // 70d1 #140 + '9S' // 70d2 #252 + '5K' // 70d3 #140 + '7O' // 70d4 #196 + '14D' // 70d5 #367 + '45Y' // 70d6 #1194 + 'A' // 70d7 + '178Q' // 70d8 #4644 + '8D' // 70d9 #211 + '14E' // 70da #368 + '3X' // 70db #101 + '7O' // 70dc #196 + '8D' // 70dd #211 + 'A' // 70de + '99O' // 70df #2588 + '6T' // 70e0 #175 + 'bA' // 70e1-70e3 + '193A' // 70e4 #5018 + 'A' // 70e5 + '1R' // 70e6 #43 + '3Y' // 70e7 #102 + '2W' // 70e8 #74 + '6T' // 70e9 #175 + 'A' // 70ea + '2R' // 70eb #69 + '36C' // 70ec #938 + '7K' // 70ed #192 + 'A' // 70ee + '100H' // 70ef #2607 + 'A' // 70f0 + '58H' // 70f1 #1515 + 'aA' // 70f2-70f3 + '9S' // 70f4 #252 + '13C' // 70f5 #340 + 'A' // 70f6 + '14E' // 70f7 #368 + 'A' // 70f8 + '166N' // 70f9 #4329 + '7O' // 70fa #196 + 'aA' // 70fb-70fc + '8D' // 70fd #211 + '13C' // 70fe #340 + '14E' // 70ff #368 + 'bA' // 7100-7102 + '5K' // 7103 #140 + '8D' // 7104 #211 + '45Y' // 7105 #1194 + '14D' // 7106 #367 + '5K' // 7107 #140 + '36C' // 7108 #938 + '99V' // 7109 #2595 + '64C' // 710a #1666 + '5K' // 710b #140 + '8D' // 710c #211 + 'aA' // 710d-710e + '5K' // 710f #140 + '14E' // 7110 #368 + 'aA' // 7111-7112 + '14E' // 7113 #368 + '36C' // 7114 #938 + '2K' // 7115 #62 + '6T' // 7116 #175 + '143S' // 7117 #3736 + '6T' // 7118 #175 + '164I' // 7119 #4272 + '136C' // 711a #3538 + 'A' // 711b + '22X' // 711c #595 + '6N' // 711d #169 + '34G' // 711e #890 + 'A' // 711f + '22X' // 7120 #595 + '245P' // 7121 #6385 + '12F' // 7122 #317 + 'bA' // 7123-7125 + '202X' // 7126 #5275 + 'aA' // 7127-7128 + '6N' // 7129 #169 + 'A' // 712a + '58D' // 712b #1511 + '6N' // 712c #169 + '5K' // 712d #140 + 'a22X' // 712e-712f #595 + '145X' // 7130 #3793 + '22X' // 7131 #595 + 'A' // 7132 + '6N' // 7133 #169 + '12F' // 7134 #317 + '6N' // 7135 #169 + '237H' // 7136 #6169 + 'A' // 7137 + '5K' // 7138 #140 + 'aA' // 7139-713a + '6N' // 713b #169 + '258K' // 713c #6718 + 'A' // 713d + '6N' // 713e #169 + 'A' // 713f + '6N' // 7140 #169 + '5K' // 7141 #140 + '6T' // 7142 #175 + '12F' // 7143 #317 + '6T' // 7144 #175 + 'a22X' // 7145-7146 #595 + '34G' // 7147 #890 + 'A' // 7148 + '157S' // 7149 #4100 + '34G' // 714a #890 + '22X' // 714b #595 + '159R' // 714c #4151 + 'A' // 714d + '166O' // 714e #4330 + '6N' // 714f #169 + '34G' // 7150 #890 + '38X' // 7151 #1011 + '22X' // 7152 #595 + '38X' // 7153 #1011 + 'A' // 7154 + '36C' // 7155 #938 + '34G' // 7156 #890 + '58D' // 7157 #1511 + 'A' // 7158 + '197I' // 7159 #5130 + '22X' // 715a #595 + 'A' // 715b + '63D' // 715c #1641 + 'A' // 715d + '157U' // 715e #4102 + 'A' // 715f + '38X' // 7160 #1011 + '6T' // 7161 #175 + '38X' // 7162 #1011 + 'A' // 7163 + '151B' // 7164 #3927 + '145V' // 7165 #3791 + '58B' // 7166 #1509 + '234U' // 7167 #6104 + '38W' // 7168 #1010 + '194T' // 7169 #5063 + 'A' // 716a + '6N' // 716b #169 + '58C' // 716c #1510 + '6T' // 716d #175 + '189F' // 716e #4919 + 'aA' // 716f-7170 + '12F' // 7171 #317 + '149U' // 7172 #3894 + '29S' // 7173 #772 + '58E' // 7174 #1512 + '6N' // 7175 #169 + '58E' // 7176 #1512 + '6N' // 7177 #169 + '29S' // 7178 #772 + '2Z' // 7179 #77 + '29S' // 717a #772 + '12F' // 717b #317 + '6N' // 717c #169 + '99M' // 717d #2586 + '6N' // 717e #169 + '6T' // 717f #175 + '38W' // 7180 #1010 + '12F' // 7181 #317 + 'aA' // 7182-7183 + '139O' // 7184 #3628 + '16Q' // 7185 #432 + '6T' // 7186 #175 + 'a38W' // 7187-7188 #1010 + '50E' // 7189 #1304 + '205J' // 718a #5339 + 'A' // 718b + '34E' // 718c #888 + 'A' // 718d + '6N' // 718e #169 + '99J' // 718f #2583 + '12F' // 7190 #317 + '6N' // 7191 #169 + '58B' // 7192 #1509 + 'A' // 7193 + '122X' // 7194 #3195 + '2Z' // 7195 #77 + '16Q' // 7196 #432 + '12F' // 7197 #317 + '29S' // 7198 #772 + '157R' // 7199 #4099 + 'a16Q' // 719a-719b #432 + '29S' // 719c #772 + 'aA' // 719d-719e + '210W' // 719f #5482 + '38W' // 71a0 #1010 + 'A' // 71a1 + '99I' // 71a2 #2582 + '6N' // 71a3 #169 + '29S' // 71a4 #772 + '6T' // 71a5 #175 + 'aA' // 71a6-71a7 + '7N' // 71a8 #195 + 'bA' // 71a9-71ab + '157Q' // 71ac #4098 + '6N' // 71ad #169 + '2Z' // 71ae #77 + '7N' // 71af #195 + '2Z' // 71b0 #77 + '241O' // 71b1 #6280 + 'a7N' // 71b2-71b3 #195 + '45X' // 71b4 #1193 + '18N' // 71b5 #481 + '11I' // 71b6 #294 + 'a18N' // 71b7-71b8 #481 + 'a18M' // 71b9-71ba #480 + 'a11I' // 71bb-71bc #294 + 'A' // 71bd + '18M' // 71be #480 + 'a2Z' // 71bf-71c0 #77 + '18M' // 71c1 #480 + '11I' // 71c2 #294 + '183A' // 71c3 #4758 + '7N' // 71c4 #195 + 'bA' // 71c5-71c7 + '208L' // 71c8 #5419 + '65E' // 71c9 #1694 + '18N' // 71ca #481 + '7N' // 71cb #195 + '2Z' // 71cc #77 + 'A' // 71cd + '18M' // 71ce #480 + '18N' // 71cf #481 + '18M' // 71d0 #480 + '6N' // 71d1 #169 + '67Z' // 71d2 #1767 + '2Z' // 71d3 #77 + '18M' // 71d4 #480 + '180J' // 71d5 #4689 + 'a2Z' // 71d6-71d7 #77 + '12F' // 71d8 #317 + '164G' // 71d9 #4270 + '7N' // 71da #195 + 'A' // 71db + '7N' // 71dc #195 + '6N' // 71dd #169 + 'A' // 71de + '232K' // 71df #6042 + '7N' // 71e0 #195 + '12F' // 71e1 #317 + 'bA' // 71e2-71e4 + '168Q' // 71e5 #4384 + '164F' // 71e6 #4269 + '18M' // 71e7 #480 + '11I' // 71e8 #294 + 'A' // 71e9 + '11I' // 71ea #294 + '6N' // 71eb #169 + '16Q' // 71ec #432 + '65E' // 71ed #1694 + '18M' // 71ee #480 + 'dA' // 71ef-71f3 + '7N' // 71f4 #195 + '16Q' // 71f5 #432 + '18N' // 71f6 #481 + 'A' // 71f7 + '2Z' // 71f8 #77 + '7N' // 71f9 #195 + 'A' // 71fa + '124Q' // 71fb #3240 + '18M' // 71fc #480 + 'A' // 71fd + '58C' // 71fe #1510 + 'a18M' // 71ff-7200 #480 + '12F' // 7201 #317 + 'A' // 7202 + '12F' // 7203 #317 + 'aA' // 7204-7205 + '220Q' // 7206 #5736 + '7N' // 7207 #195 + '2Z' // 7208 #77 + '16Q' // 7209 #432 + 'aA' // 720a-720b + '18N' // 720c #481 + '122Y' // 720d #3196 + 'a6N' // 720e-720f #169 + '192W' // 7210 #5014 + 'aA' // 7211-7212 + '16Q' // 7213 #432 + '12F' // 7214 #317 + '16Q' // 7215 #432 + '45X' // 7216 #1193 + '16Q' // 7217 #432 + 'aA' // 7218-7219 + '7N' // 721a #195 + '194W' // 721b #5066 + '11I' // 721c #294 + '7N' // 721d #195 + '11I' // 721e #294 + '2Z' // 721f #77 + 'aA' // 7220-7221 + '12F' // 7222 #317 + '18N' // 7223 #481 + '34E' // 7224 #888 + '6N' // 7225 #169 + 'aA' // 7226-7227 + '7N' // 7228 #195 + 'A' // 7229 + '154N' // 722a #4017 + '7N' // 722b #195 + '180Q' // 722c #4696 + '67Z' // 722d #1767 + '45X' // 722e #1193 + '2Z' // 722f #77 + '34F' // 7230 #889 + '3Q' // 7231 #94 + '249J' // 7232 #6483 + 'A' // 7233 + '2Z' // 7234 #77 + '173C' // 7235 #4500 + '212A' // 7236 #5512 + '1R' // 7237 #43 + '192X' // 7238 #5015 + '145U' // 7239 #3790 + '188B' // 723a #4889 + '34F' // 723b #889 + '21J' // 723c #555 + '202M' // 723d #5264 + '217H' // 723e #5649 + '7N' // 723f #195 + '34F' // 7240 #889 + 'a7N' // 7241-7242 #195 + '2Z' // 7243 #77 + 'A' // 7244 + '2Z' // 7245 #77 + '200D' // 7246 #5203 + '241H' // 7247 #6273 + '242E' // 7248 #6296 + 'aA' // 7249-724a + '7N' // 724b #195 + '226F' // 724c #5881 + '11I' // 724d #294 + '21J' // 724e #555 + '2Z' // 724f #77 + '34E' // 7250 #888 + 'A' // 7251 + '34F' // 7252 #889 + '16Q' // 7253 #432 + 'A' // 7254 + '34E' // 7255 #888 + '7N' // 7256 #195 + '34E' // 7257 #888 + '34F' // 7258 #889 + '208U' // 7259 #5428 + '21J' // 725a #555 + '219Q' // 725b #5710 + '99K' // 725c #2584 + '99L' // 725d #2585 + '2Z' // 725e #77 + '130G' // 725f #3386 + '151A' // 7260 #3926 + '148M' // 7261 #3860 + '159G' // 7262 #4140 + '16Q' // 7263 #432 + 'a11I' // 7264-7265 #294 + '18N' // 7266 #481 + '175X' // 7267 #4573 + '2Z' // 7268 #77 + '35V' // 7269 #931 + '18N' // 726a #481 + '2Z' // 726b #77 + '11I' // 726c #294 + 'A' // 726d + 'a7N' // 726e-726f #195 + '12F' // 7270 #317 + '21J' // 7271 #555 + '153L' // 7272 #3989 + '18N' // 7273 #481 + '7N' // 7274 #195 + '4C' // 7275 #106 + 'A' // 7276 + '58A' // 7277 #1508 + '21J' // 7278 #555 + '35V' // 7279 #931 + '3X' // 727a #101 + '57Z' // 727b #1507 + '2Z' // 727c #77 + '180L' // 727d #4691 + '57Z' // 727e #1507 + '58A' // 727f #1508 + '145T' // 7280 #3789 + '45V' // 7281 #1191 + '10U' // 7282 #280 + 'A' // 7283 + '10U' // 7284 #280 + 'aA' // 7285-7286 + '10U' // 7287 #280 + 'A' // 7288 + '2Z' // 7289 #77 + '11I' // 728a #294 + 'aA' // 728b-728c + '10U' // 728d #280 + '2Z' // 728e #77 + '99H' // 728f #2581 + 'aA' // 7290-7291 + '10U' // 7292 #280 + '2Z' // 7293 #77 + '38U' // 7294 #1008 + 'A' // 7295 + '25T' // 7296 #669 + 'A' // 7297 + '20L' // 7298 #531 + 'aA' // 7299-729a + '10U' // 729b #280 + 'bA' // 729c-729e + '34D' // 729f #887 + '253D' // 72a0 #6581 + '20L' // 72a1 #531 + '57X' // 72a2 #1505 + 'cA' // 72a3-72a6 + '145S' // 72a7 #3788 + '2Z' // 72a8 #77 + 'bA' // 72a9-72ab + '183P' // 72ac #4773 + 'a10U' // 72ad-72ae #280 + '204W' // 72af #5326 + '10U' // 72b0 #280 + '21J' // 72b1 #555 + '10U' // 72b2 #280 + '11I' // 72b3 #294 + '10U' // 72b4 #280 + '34D' // 72b5 #887 + '259V' // 72b6 #6755 + 'a11I' // 72b7-72b8 #294 + '249K' // 72b9 #6484 + 'b11I' // 72ba-72bc #294 + '34D' // 72bd #887 + '21J' // 72be #555 + 'A' // 72bf + '225E' // 72c0 #5854 + '10U' // 72c1 #280 + '215E' // 72c2 #5594 + '10U' // 72c3 #280 + '164E' // 72c4 #4268 + '34D' // 72c5 #887 + '10U' // 72c6 #280 + '2Z' // 72c7 #77 + '11I' // 72c8 #294 + '2Z' // 72c9 #77 + 'aA' // 72ca-72cb + '10U' // 72cc #280 + '34D' // 72cd #887 + '45V' // 72ce #1191 + 'A' // 72cf + '166T' // 72d0 #4335 + 'A' // 72d1 + '10U' // 72d2 #280 + 'A' // 72d3 + '20L' // 72d4 #531 + '2Z' // 72d5 #77 + '21J' // 72d6 #555 + '208N' // 72d7 #5421 + '2Z' // 72d8 #77 + '131B' // 72d9 #3407 + 'A' // 72da + '252J' // 72db #6561 + 'A' // 72dc + 'a11I' // 72dd-72de #294 + '10U' // 72df #280 + '66H' // 72e0 #1723 + '45V' // 72e1 #1191 + '98Z' // 72e2 #2573 + 'aA' // 72e3-72e4 + '21J' // 72e5 #555 + '7G' // 72e6 #188 + 'A' // 72e7 + '14C' // 72e8 #366 + '130X' // 72e9 #3403 + 'aA' // 72ea-72eb + '258U' // 72ec #6728 + '255Z' // 72ed #6655 + '2L' // 72ee #63 + 'a7G' // 72ef-72f0 #188 + '2L' // 72f1 #63 + '7G' // 72f2 #188 + 'a12E' // 72f3-72f4 #316 + 'A' // 72f5 + '7G' // 72f6 #188 + '12E' // 72f7 #316 + '148B' // 72f8 #3849 + '139N' // 72f9 #3627 + 'a12E' // 72fa-72fb #316 + '181F' // 72fc #4711 + '20K' // 72fd #530 + '2Z' // 72fe #77 + 'A' // 72ff + 'a14C' // 7300-7301 #366 + '57Y' // 7302 #1506 + '7G' // 7303 #188 + '12E' // 7304 #316 + '2Z' // 7305 #77 + 'A' // 7306 + '12E' // 7307 #316 + 'aA' // 7308-7309 + '20K' // 730a #530 + '12E' // 730b #316 + '7G' // 730c #188 + '2Z' // 730d #77 + '1R' // 730e #43 + 'A' // 730f + '38U' // 7310 #1008 + 'A' // 7311 + '2Z' // 7312 #77 + '12E' // 7313 #316 + 'a7G' // 7314-7315 #188 + '20K' // 7316 #530 + '12E' // 7317 #316 + '2Z' // 7318 #77 + '25T' // 7319 #669 + 'A' // 731a + '189B' // 731b #4915 + '178P' // 731c #4643 + '128K' // 731d #3338 + '12E' // 731e #316 + '252Q' // 731f #6568 + 'a7G' // 7320-7321 #188 + '12E' // 7322 #316 + 'A' // 7323 + '2Z' // 7324 #77 + '130O' // 7325 #3394 + 'A' // 7326 + '36B' // 7327 #937 + '25T' // 7328 #669 + '20K' // 7329 #530 + '99B' // 732a #2575 + '99D' // 732b #2577 + '12E' // 732c #316 + '14C' // 732d #366 + '99C' // 732e #2576 + '2Z' // 732f #77 + '14C' // 7330 #366 + '12E' // 7331 #316 + '2Z' // 7332 #77 + '36B' // 7333 #937 + '66H' // 7334 #1723 + '36B' // 7335 #937 + '178O' // 7336 #4642 + '20K' // 7337 #530 + '38U' // 7338 #1008 + '57Y' // 7339 #1506 + 'a25T' // 733a-733b #669 + '20L' // 733c #531 + '36B' // 733d #937 + '20K' // 733e #530 + '136R' // 733f #3553 + '14C' // 7340 #366 + '20L' // 7341 #531 + '7G' // 7342 #188 + '25T' // 7343 #669 + '182V' // 7344 #4753 + '187U' // 7345 #4882 + 'aA' // 7346-7347 + '38U' // 7348 #1008 + 'A' // 7349 + '7G' // 734a #188 + 'A' // 734b + '20L' // 734c #531 + '12E' // 734d #316 + '217I' // 734e #5650 + '12E' // 734f #316 + '20K' // 7350 #530 + 'A' // 7351 + '20K' // 7352 #530 + 'aA' // 7353-7354 + '7G' // 7355 #188 + '2Z' // 7356 #77 + '20K' // 7357 #530 + '2Z' // 7358 #77 + '14C' // 7359 #366 + '20L' // 735a #531 + 'aA' // 735b-735c + 'a2Z' // 735d-735e #77 + '36B' // 735f #937 + '12E' // 7360 #316 + '20L' // 7361 #531 + '14C' // 7362 #366 + '255O' // 7363 #6644 + 'A' // 7364 + '14C' // 7365 #366 + 'a2Z' // 7366-7367 #77 + '225D' // 7368 #5853 + '25T' // 7369 #669 + '57X' // 736a #1505 + '25T' // 736b #669 + '20J' // 736c #529 + '7G' // 736d #188 + '34C' // 736e #886 + '20J' // 736f #529 + '25S' // 7370 #668 + '98W' // 7371 #2570 + '227R' // 7372 #5919 + '20L' // 7373 #531 + '14C' // 7374 #366 + '66V' // 7375 #1737 + '7G' // 7376 #188 + '34C' // 7377 #886 + '66V' // 7378 #1737 + '9I' // 7379 #242 + '25S' // 737a #668 + '192U' // 737b #5012 + '34C' // 737c #886 + '7G' // 737d #188 + '14C' // 737e #366 + 'A' // 737f + '34C' // 7380 #886 + '9I' // 7381 #242 + '7G' // 7382 #188 + '70C' // 7383 #1822 + '182Z' // 7384 #4757 + '34C' // 7385 #886 + '25S' // 7386 #668 + '222G' // 7387 #5778 + '7G' // 7388 #188 + '213N' // 7389 #5551 + '20J' // 738a #529 + '235J' // 738b #6119 + '99F' // 738c #2579 + '7G' // 738d #188 + '25S' // 738e #668 + '14C' // 738f #366 + '70C' // 7390 #1822 + '7G' // 7391 #188 + '14C' // 7392 #366 + '20J' // 7393 #529 + '98X' // 7394 #2571 + '20J' // 7395 #529 + '122W' // 7396 #3194 + 'a25S' // 7397-7398 #668 + 'a7G' // 7399-739a #188 + '1R' // 739b #43 + '45U' // 739c #1190 + 'A' // 739d + '45U' // 739e #1190 + '128H' // 739f #3335 + '20J' // 73a0 #529 + '14C' // 73a1 #366 + '20J' // 73a2 #529 + '7G' // 73a3 #188 + 'A' // 73a4 + 'a20J' // 73a5-73a6 #529 + '99E' // 73a7 #2578 + '20J' // 73a8 #529 + '233M' // 73a9 #6070 + '45U' // 73aa #1190 + '66T' // 73ab #1735 + 'A' // 73ac + '25S' // 73ad #668 + '3X' // 73ae #101 + '1Z' // 73af #51 + '7K' // 73b0 #192 + '7G' // 73b1 #188 + '189A' // 73b2 #4914 + '25S' // 73b3 #668 + '45W' // 73b4 #1192 + '38V' // 73b5 #1009 + '99G' // 73b6 #2580 + '57W' // 73b7 #1504 + '45W' // 73b8 #1192 + '57V' // 73b9 #1503 + '98Y' // 73ba #2572 + '192V' // 73bb #5013 + '38V' // 73bc #1009 + '9I' // 73bd #242 + 'A' // 73be + '38V' // 73bf #1009 + '135M' // 73c0 #3522 + 'A' // 73c1 + '99A' // 73c2 #2574 + 'A' // 73c3 + '45W' // 73c4 #1192 + 'a57W' // 73c5-73c6 #1504 + '7G' // 73c7 #188 + '148R' // 73c8 #3865 + '57V' // 73c9 #1503 + '171B' // 73ca #4447 + '38V' // 73cb #1009 + '22W' // 73cc #594 + '203R' // 73cd #5295 + '45T' // 73ce #1189 + '22W' // 73cf #594 + '38T' // 73d0 #1007 + '3G' // 73d1 #84 + '45T' // 73d2 #1189 + '9I' // 73d3 #242 + 'A' // 73d4 + '12B' // 73d5 #313 + '22W' // 73d6 #594 + '12D' // 73d7 #315 + 'A' // 73d8 + '22W' // 73d9 #594 + 'A' // 73da + 'a38T' // 73db-73dc #1007 + 'a22W' // 73dd-73de #594 + 'A' // 73df + '209K' // 73e0 #5444 + '25R' // 73e1 #667 + '12B' // 73e2 #313 + '22W' // 73e3 #594 + '98J' // 73e4 #2557 + 'a22W' // 73e5-73e6 #594 + '45T' // 73e7 #1189 + '38T' // 73e8 #1007 + 'a22W' // 73e9-73ea #594 + '12D' // 73eb #315 + 'A' // 73ec + '218I' // 73ed #5676 + '133I' // 73ee #3466 + '38T' // 73ef #1007 + '9N' // 73f0 #247 + '9I' // 73f1 #242 + '9N' // 73f2 #247 + '12B' // 73f3 #313 + '31K' // 73f4 #816 + '7M' // 73f5 #194 + '12D' // 73f6 #315 + '12C' // 73f7 #314 + '9I' // 73f8 #242 + '12C' // 73f9 #314 + '7M' // 73fa #194 + '25R' // 73fb #667 + '18L' // 73fc #479 + '12C' // 73fd #314 + '244F' // 73fe #6349 + '31K' // 73ff #816 + '7M' // 7400 #194 + '12C' // 7401 #314 + '57T' // 7402 #1501 + '235B' // 7403 #6111 + '7M' // 7404 #194 + '98M' // 7405 #2560 + '35U' // 7406 #930 + '12C' // 7407 #314 + '18L' // 7408 #479 + '160J' // 7409 #4169 + '7M' // 740a #194 + 'b18L' // 740b-740d #479 + 'a9N' // 740e-740f #247 + '3X' // 7410 #101 + '25R' // 7411 #667 + '12B' // 7412 #313 + '70B' // 7413 #1821 + 'a12B' // 7414-7415 #313 + '12D' // 7416 #315 + '12B' // 7417 #313 + '9N' // 7418 #247 + '12B' // 7419 #313 + '7M' // 741a #194 + '12C' // 741b #314 + '57T' // 741c #1501 + '12D' // 741d #315 + '18L' // 741e #479 + '12B' // 741f #313 + '98U' // 7420 #2568 + '98N' // 7421 #2561 + '98P' // 7422 #2563 + '12D' // 7423 #315 + '7M' // 7424 #194 + '57R' // 7425 #1499 + '139M' // 7426 #3626 + 'A' // 7427 + '12C' // 7428 #314 + '7M' // 7429 #194 + '171C' // 742a #4448 + '70B' // 742b #1821 + '12C' // 742c #314 + '7M' // 742d #194 + 'b12C' // 742e-7430 #314 + '29R' // 7431 #771 + '98Q' // 7432 #2564 + '178N' // 7433 #4641 + '181U' // 7434 #4726 + 'a57R' // 7435-7436 #1499 + '12B' // 7437 #313 + '57S' // 7438 #1500 + '7M' // 7439 #194 + '12C' // 743a #314 + '9N' // 743b #247 + '98V' // 743c #2569 + 'A' // 743d + '9N' // 743e #247 + 'b12C' // 743f-7441 #314 + '12D' // 7442 #315 + '98K' // 7443 #2558 + '133G' // 7444 #3464 + '18L' // 7445 #479 + '7M' // 7446 #194 + '25R' // 7447 #667 + '18L' // 7448 #479 + '12D' // 7449 #315 + '18L' // 744a #479 + '164C' // 744b #4266 + '12B' // 744c #313 + '9I' // 744d #242 + 'bA' // 744e-7450 + '29R' // 7451 #771 + '7M' // 7452 #194 + '25R' // 7453 #667 + '12D' // 7454 #315 + '164D' // 7455 #4267 + '12B' // 7456 #313 + '12C' // 7457 #314 + 'A' // 7458 + '98L' // 7459 #2559 + '133H' // 745a #3465 + '125D' // 745b #3253 + '178M' // 745c #4640 + '7M' // 745d #194 + '209D' // 745e #5437 + '157P' // 745f #4097 + '98R' // 7460 #2565 + '12D' // 7461 #315 + '12C' // 7462 #314 + '128I' // 7463 #3336 + '145R' // 7464 #3787 + '57S' // 7465 #1500 + '9I' // 7466 #242 + '7M' // 7467 #194 + '12C' // 7468 #314 + '157O' // 7469 #4096 + '192T' // 746a #5011 + '25R' // 746b #667 + '12D' // 746c #315 + '7M' // 746d #194 + '29R' // 746e #771 + '122V' // 746f #3193 + '66T' // 7470 #1735 + '7M' // 7471 #194 + '29R' // 7472 #771 + '7M' // 7473 #194 + '18L' // 7474 #479 + '12D' // 7475 #315 + '57Q' // 7476 #1498 + '9N' // 7477 #247 + 'A' // 7478 + '12D' // 7479 #315 + '18L' // 747a #479 + 'A' // 747b + 'a12D' // 747c-747d #315 + '12C' // 747e #314 + '12D' // 747f #315 + '128J' // 7480 #3337 + '7M' // 7481 #194 + '98T' // 7482 #2567 + '195E' // 7483 #5074 + '9N' // 7484 #247 + '29R' // 7485 #771 + '7M' // 7486 #194 + '133F' // 7487 #3463 + '7M' // 7488 #194 + '57P' // 7489 #1497 + '18L' // 748a #479 + '122U' // 748b #3192 + 'a12B' // 748c-748d #313 + '9N' // 748e #247 + '31K' // 748f #816 + '57Q' // 7490 #1498 + '9I' // 7491 #242 + '29R' // 7492 #771 + '9N' // 7493 #247 + 'bA' // 7494-7496 + '9I' // 7497 #242 + '57P' // 7498 #1497 + '25R' // 7499 #667 + '7M' // 749a #194 + '12B' // 749b #313 + '34A' // 749c #884 + '57U' // 749d #1502 + 'a34A' // 749e-749f #884 + '34B' // 74a0 #885 + '45S' // 74a1 #1188 + '9I' // 74a2 #242 + '34A' // 74a3 #884 + '12B' // 74a4 #313 + '45S' // 74a5 #1188 + '34B' // 74a6 #885 + '98S' // 74a7 #2566 + '128G' // 74a8 #3334 + '34B' // 74a9 #885 + '34A' // 74aa #884 + '98O' // 74ab #2562 + 'aA' // 74ac-74ad + '31K' // 74ae #816 + '9I' // 74af #242 + '229S' // 74b0 #5972 + '34B' // 74b1 #885 + '34A' // 74b2 #884 + 'A' // 74b3 + '12B' // 74b4 #313 + '45S' // 74b5 #1188 + '9N' // 74b6 #247 + 'A' // 74b7 + '57U' // 74b8 #1502 + '98I' // 74b9 #2556 + '34B' // 74ba #885 + '9I' // 74bb #242 + 'A' // 74bc + '64S' // 74bd #1682 + 'A' // 74be + '29Q' // 74bf #770 + 'A' // 74c0 + '9N' // 74c1 #247 + 'A' // 74c2 + '9N' // 74c3 #247 + 'A' // 74c4 + '98H' // 74c5 #2555 + '98E' // 74c6 #2552 + 'A' // 74c7 + '22U' // 74c8 #592 + '9I' // 74c9 #242 + '150Y' // 74ca #3924 + 'A' // 74cb + '22U' // 74cc #592 + 'aA' // 74cd-74ce + '64S' // 74cf #1682 + '33Y' // 74d0 #882 + 'a9N' // 74d1-74d2 #247 + '33Y' // 74d3 #882 + '29Q' // 74d4 #770 + '9N' // 74d5 #247 + '45Q' // 74d6 #1186 + 'A' // 74d7 + '29Q' // 74d8 #770 + '9N' // 74d9 #247 + '29Q' // 74da #770 + '31K' // 74db #816 + '194S' // 74dc #5062 + 'A' // 74dd + 'a22U' // 74de-74df #592 + '29Q' // 74e0 #770 + 'A' // 74e1 + '97T' // 74e2 #2541 + '150Z' // 74e3 #3925 + '22U' // 74e4 #592 + '9N' // 74e5 #247 + '188A' // 74e6 #4888 + '57O' // 74e7 #1496 + 'a22U' // 74e8-74e9 #592 + 'a9I' // 74ea-74eb #242 + 'aA' // 74ec-74ed + '29Q' // 74ee #770 + '31K' // 74ef #816 + 'b33Y' // 74f0-74f2 #882 + 'A' // 74f3 + '22U' // 74f4 #592 + 'A' // 74f5 + '196J' // 74f6 #5105 + '164B' // 74f7 #4265 + '33Y' // 74f8 #882 + 'A' // 74f9 + '9I' // 74fa #242 + '45Q' // 74fb #1186 + '9I' // 74fc #242 + 'aA' // 74fd-74fe + '22U' // 74ff #592 + '22V' // 7500 #593 + '249I' // 7501 #6482 + 'A' // 7502 + '22U' // 7503 #592 + '157N' // 7504 #4095 + '57O' // 7505 #1496 + '9I' // 7506 #242 + '22V' // 7507 #593 + '9N' // 7508 #247 + 'bA' // 7509-750b + '45Q' // 750c #1186 + '97S' // 750d #2540 + '33Y' // 750e #882 + '13N' // 750f #351 + 'A' // 7510 + '9F' // 7511 #239 + '3P' // 7512 #93 + '5A' // 7513 #130 + 'A' // 7514 + '9F' // 7515 #239 + '25Q' // 7516 #666 + '5A' // 7517 #130 + '198M' // 7518 #5160 + '33Z' // 7519 #883 + '208P' // 751a #5423 + '260U' // 751b #6780 + '214N' // 751c #5577 + 'A' // 751d + '25Q' // 751e #666 + '69L' // 751f #1805 + '3P' // 7520 #93 + '5A' // 7521 #130 + '239T' // 7522 #6233 + '259S' // 7523 #6752 + '11C' // 7524 #288 + '97U' // 7525 #2542 + '128D' // 7526 #3331 + '11C' // 7527 #288 + '69K' // 7528 #1804 + '164A' // 7529 #4264 + '5A' // 752a #130 + '139K' // 752b #3624 + '9F' // 752c #239 + '33Z' // 752d #883 + '22V' // 752e #593 + '5A' // 752f #130 + '216S' // 7530 #5634 + '236S' // 7531 #6154 + '211W' // 7532 #5508 + '213Y' // 7533 #5562 + '33Z' // 7534 #883 + '49C' // 7535 #1276 + '11C' // 7536 #288 + '237P' // 7537 #6177 + '150U' // 7538 #3920 + '3P' // 7539 #93 + '162H' // 753a #4219 + '149P' // 753b #3889 + 'a3P' // 753c-753d #93 + '5A' // 753e #130 + '3P' // 753f #93 + '133D' // 7540 #3461 + 'A' // 7541 + '22V' // 7542 #593 + '3P' // 7543 #93 + '11C' // 7544 #288 + '3Y' // 7545 #102 + '45P' // 7546 #1185 + '9F' // 7547 #239 + '5A' // 7548 #130 + '11C' // 7549 #288 + 'a5A' // 754a-754b #130 + '237G' // 754c #6168 + '97R' // 754d #2539 + '5A' // 754e #130 + '65D' // 754f #1693 + '11C' // 7550 #288 + '98D' // 7551 #2551 + '11C' // 7552 #288 + '98F' // 7553 #2553 + '141S' // 7554 #3684 + '57M' // 7555 #1494 + '13N' // 7556 #351 + '11C' // 7557 #288 + 'A' // 7558 + '234E' // 7559 #6088 + '5A' // 755a #130 + '9F' // 755b #239 + '148Z' // 755c #3873 + '9F' // 755d #239 + '11C' // 755e #288 + '3P' // 755f #93 + '97W' // 7560 #2544 + '3P' // 7561 #93 + '207F' // 7562 #5387 + '22V' // 7563 #593 + '5A' // 7564 #130 + '220P' // 7565 #5735 + '9F' // 7566 #239 + '5A' // 7567 #130 + '13N' // 7568 #351 + '3P' // 7569 #93 + '199I' // 756a #5182 + '225C' // 756b #5852 + '5A' // 756c #130 + '25Q' // 756d #666 + '22V' // 756e #593 + '9F' // 756f #239 + '223D' // 7570 #5801 + '11C' // 7571 #288 + '5A' // 7572 #130 + '255B' // 7573 #6631 + '249H' // 7574 #6481 + '249G' // 7575 #6480 + '239S' // 7576 #6232 + '25Q' // 7577 #666 + '97P' // 7578 #2537 + '5A' // 7579 #130 + '9F' // 757a #239 + 'a11C' // 757b-757c #288 + '3P' // 757d #93 + '5A' // 757e #130 + '98C' // 757f #2550 + 'A' // 7580 + 'a11C' // 7581-7582 #288 + 'a33Z' // 7583-7584 #883 + '3P' // 7585 #93 + '150W' // 7586 #3922 + '9F' // 7587 #239 + 'A' // 7588 + '11C' // 7589 #288 + '178L' // 758a #4639 + '9F' // 758b #239 + '5A' // 758c #130 + '33Z' // 758d #883 + '98B' // 758e #2549 + '171A' // 758f #4446 + '5A' // 7590 #130 + '211Y' // 7591 #5510 + '5A' // 7592 #130 + '3P' // 7593 #93 + '5A' // 7594 #130 + '25Q' // 7595 #666 + '13N' // 7596 #351 + '2C' // 7597 #54 + 'A' // 7598 + 'a5A' // 7599-759a #130 + 'A' // 759b + '3P' // 759c #93 + '9F' // 759d #239 + '57M' // 759e #1494 + 'a13N' // 759f-75a0 #351 + '2W' // 75a1 #74 + '25Q' // 75a2 #666 + '5A' // 75a3 #130 + '133E' // 75a4 #3462 + '9F' // 75a5 #239 + 'A' // 75a6 + '22V' // 75a7 #593 + '13N' // 75a8 #351 + 'A' // 75a9 + '22V' // 75aa #593 + '166U' // 75ab #4336 + 'a13N' // 75ac-75ad #351 + '3X' // 75ae #101 + '3N' // 75af #91 + '5A' // 75b0 #130 + '9F' // 75b1 #239 + '169C' // 75b2 #4396 + '9F' // 75b3 #239 + '5A' // 75b4 #130 + '150T' // 75b5 #3919 + 'A' // 75b6 + '3P' // 75b7 #93 + '9F' // 75b8 #239 + '142C' // 75b9 #3694 + '3P' // 75ba #93 + 'A' // 75bb + '172T' // 75bc #4491 + '9F' // 75bd #239 + '188X' // 75be #4911 + '3P' // 75bf #93 + '25Q' // 75c0 #666 + '11C' // 75c1 #288 + '9F' // 75c2 #239 + 'a5A' // 75c3-75c4 #130 + '212Y' // 75c5 #5536 + '3P' // 75c6 #93 + '198U' // 75c7 #5168 + '98G' // 75c8 #2554 + '13N' // 75c9 #351 + '5A' // 75ca #130 + 'A' // 75cb + '5A' // 75cc #130 + '33X' // 75cd #881 + 'a3P' // 75ce-75cf #93 + 'aA' // 75d0-75d1 + '97V' // 75d2 #2543 + '3P' // 75d3 #93 + '33X' // 75d4 #881 + '180P' // 75d5 #4695 + '13N' // 75d6 #351 + '3P' // 75d7 #93 + '65D' // 75d8 #1693 + '98A' // 75d9 #2548 + 'A' // 75da + '211X' // 75db #5509 + '45P' // 75dc #1185 + '3P' // 75dd #93 + '185R' // 75de #4827 + '57N' // 75df #1495 + '128F' // 75e0 #3333 + '3P' // 75e1 #93 + '97X' // 75e2 #2545 + 'a20I' // 75e3-75e4 #528 + 'A' // 75e5 + '45R' // 75e6 #1187 + '20I' // 75e7 #528 + '13N' // 75e8 #351 + '254Q' // 75e9 #6620 + '13N' // 75ea #351 + '3G' // 75eb #84 + '3P' // 75ec #93 + 'A' // 75ed + 'a3P' // 75ee-75ef #93 + '97O' // 75f0 #2536 + '20I' // 75f1 #528 + '33X' // 75f2 #881 + '20I' // 75f3 #528 + '161V' // 75f4 #4207 + 'aA' // 75f5-75f6 + '45R' // 75f7 #1187 + 'A' // 75f8 + '20I' // 75f9 #528 + '97Y' // 75fa #2546 + 'A' // 75fb + '33X' // 75fc #881 + 'A' // 75fd + 'a20I' // 75fe-75ff #528 + '33X' // 7600 #881 + '20I' // 7601 #528 + '57N' // 7602 #1495 + '11C' // 7603 #288 + '3P' // 7604 #93 + 'a13N' // 7605-7606 #351 + '45P' // 7607 #1185 + 'b20I' // 7608-760a #528 + '200C' // 760b #5202 + '20I' // 760c #528 + '97Z' // 760d #2547 + '13N' // 760e #351 + '3P' // 760f #93 + '45R' // 7610 #1187 + '13N' // 7611 #351 + '11C' // 7612 #288 + '128E' // 7613 #3332 + '6I' // 7614 #164 + '97Q' // 7615 #2538 + '6X' // 7616 #179 + '6I' // 7617 #164 + '41O' // 7618 #1080 + '25O' // 7619 #664 + '38S' // 761a #1006 + 'b6X' // 761b-761d #179 + '29O' // 761e #768 + 'a25O' // 761f-7620 #664 + '64R' // 7621 #1681 + '25O' // 7622 #664 + '29O' // 7623 #768 + '150V' // 7624 #3921 + '6X' // 7625 #179 + '200B' // 7626 #5201 + '6X' // 7627 #179 + '41O' // 7628 #1080 + '6X' // 7629 #179 + '6I' // 762a #164 + '3X' // 762b #101 + '33V' // 762c #879 + '3P' // 762d #93 + 'a6I' // 762e-762f #164 + '6X' // 7630 #179 + 'A' // 7631 + 'c6X' // 7632-7635 #179 + 'aA' // 7636-7637 + '6X' // 7638 #179 + '41O' // 7639 #1080 + '6X' // 763a #179 + '45O' // 763b #1184 + '6X' // 763c #179 + '6I' // 763d #164 + '2R' // 763e #69 + '6I' // 763f #164 + '6X' // 7640 #179 + '3P' // 7641 #93 + '216A' // 7642 #5616 + '6X' // 7643 #179 + 'a3P' // 7644-7645 #93 + '29O' // 7646 #768 + '122T' // 7647 #3191 + '6X' // 7648 #179 + '29O' // 7649 #768 + 'a3P' // 764a-764b #93 + '187T' // 764c #4881 + '25P' // 764d #665 + '25O' // 764e #664 + '33V' // 764f #879 + 'A' // 7650 + '33V' // 7651 #879 + '183E' // 7652 #4762 + 'A' // 7653 + '25P' // 7654 #665 + '3P' // 7655 #93 + '130V' // 7656 #3401 + 'A' // 7657 + '6X' // 7658 #179 + '3P' // 7659 #93 + '6I' // 765a #164 + 'A' // 765b + '6X' // 765c #179 + 'A' // 765d + '6I' // 765e #164 + '6X' // 765f #179 + 'A' // 7660 + '150S' // 7661 #3918 + '150X' // 7662 #3923 + '6I' // 7663 #164 + '45O' // 7664 #1184 + '6X' // 7665 #179 + '38S' // 7666 #1006 + '29O' // 7667 #768 + '3P' // 7668 #93 + '25O' // 7669 #664 + '3P' // 766a #93 + '3X' // 766b #101 + '25O' // 766c #664 + '29O' // 766d #768 + '163Z' // 766e #4263 + '6X' // 766f #179 + '45O' // 7670 #1184 + '139L' // 7671 #3625 + '64R' // 7672 #1681 + '97M' // 7673 #2534 + '97G' // 7674 #2528 + '6I' // 7675 #164 + '6X' // 7676 #179 + 'A' // 7677 + '25O' // 7678 #664 + '25P' // 7679 #665 + '97L' // 767a #2533 + '35U' // 767b #930 + '246A' // 767c #6396 + '238B' // 767d #6189 + '234R' // 767e #6101 + '25P' // 767f #665 + '3P' // 7680 #93 + '6X' // 7681 #179 + '157M' // 7682 #4094 + '41O' // 7683 #1080 + '69I' // 7684 #1802 + '3P' // 7685 #93 + '213D' // 7686 #5541 + '202W' // 7687 #5274 + '6X' // 7688 #179 + 'A' // 7689 + '25P' // 768a #665 + '6X' // 768b #179 + '21I' // 768c #554 + '3J' // 768d #87 + '45N' // 768e #1183 + 'A' // 768f + '45N' // 7690 #1183 + 'a6I' // 7691-7692 #164 + '139H' // 7693 #3621 + 'A' // 7694 + '14B' // 7695 #365 + '97F' // 7696 #2527 + 'A' // 7697 + '6I' // 7698 #164 + '122S' // 7699 #3190 + 'a14B' // 769a-769b #365 + '38R' // 769c #1005 + 'a14B' // 769d-769e #365 + 'a21I' // 769f-76a0 #554 + '45M' // 76a1 #1182 + '21I' // 76a2 #554 + '3J' // 76a3 #87 + '14B' // 76a4 #365 + '57J' // 76a5 #1491 + 'a21I' // 76a6-76a7 #554 + '3J' // 76a8 #87 + 'A' // 76a9 + '38R' // 76aa #1005 + 'a6I' // 76ab-76ac #164 + '3J' // 76ad #87 + '227Q' // 76ae #5918 + '21I' // 76af #554 + '14B' // 76b0 #365 + '3H' // 76b1 #85 + '6I' // 76b2 #164 + 'A' // 76b3 + '14B' // 76b4 #365 + '6I' // 76b5 #164 + '3J' // 76b6 #87 + 'a38R' // 76b7-76b8 #1005 + '3J' // 76b9 #87 + '157K' // 76ba #4092 + '6I' // 76bb #164 + 'A' // 76bc + '3J' // 76bd #87 + 'A' // 76be + '125S' // 76bf #3268 + 'A' // 76c0 + '3J' // 76c1 #87 + '45N' // 76c2 #1183 + '163X' // 76c3 #4261 + 'A' // 76c4 + '14B' // 76c5 #365 + '174W' // 76c6 #4546 + 'A' // 76c7 + '178J' // 76c8 #4637 + '14B' // 76c9 #365 + '219L' // 76ca #5705 + '3J' // 76cb #87 + 'a14B' // 76cc-76cd #365 + '122Q' // 76ce #3188 + '2W' // 76cf #74 + '4C' // 76d0 #106 + '2C' // 76d1 #54 + '207E' // 76d2 #5386 + 'A' // 76d3 + '63U' // 76d4 #1658 + 'A' // 76d5 + '97E' // 76d6 #2526 + '256L' // 76d7 #6667 + '2D' // 76d8 #55 + '45M' // 76d9 #1182 + 'A' // 76da + '212M' // 76db #5524 + '185Q' // 76dc #4826 + '6I' // 76dd #164 + '122P' // 76de #3187 + '210B' // 76df #5461 + '3J' // 76e0 #87 + '214M' // 76e1 #5576 + '6I' // 76e2 #164 + '211Q' // 76e3 #5502 + '220I' // 76e4 #5728 + 'a14B' // 76e5-76e6 #365 + '178I' // 76e7 #4636 + '21I' // 76e8 #554 + '25P' // 76e9 #665 + '145O' // 76ea #3784 + '3J' // 76eb #87 + '38R' // 76ec #1005 + 'A' // 76ed + '245G' // 76ee #6376 + '143V' // 76ef #3739 + '3J' // 76f0 #87 + '14B' // 76f1 #365 + '159T' // 76f2 #4153 + 'A' // 76f3 + '237N' // 76f4 #6175 + 'A' // 76f5 + '3J' // 76f6 #87 + '38S' // 76f7 #1006 + '243N' // 76f8 #6331 + '14B' // 76f9 #365 + '25P' // 76fa #665 + '14B' // 76fb #365 + '163Y' // 76fc #4262 + '6I' // 76fd #164 + '167A' // 76fe #4342 + '6I' // 76ff #164 + '21I' // 7700 #554 + '211R' // 7701 #5503 + 'A' // 7702 + '6I' // 7703 #164 + '97C' // 7704 #2524 + '38S' // 7705 #1006 + '3J' // 7706 #87 + '57K' // 7707 #1492 + '97B' // 7708 #2523 + '180W' // 7709 #4702 + '38Q' // 770a #1004 + '241D' // 770b #6269 + '97K' // 770c #2532 + '6I' // 770d #164 + '57J' // 770e #1491 + '97N' // 770f #2535 + 'aA' // 7710-7711 + '21I' // 7712 #554 + 'A' // 7713 + '21I' // 7714 #554 + '38Q' // 7715 #1004 + '6I' // 7716 #164 + '3J' // 7717 #87 + 'A' // 7718 + 'b57K' // 7719-771b #1492 + '3J' // 771c #87 + '29P' // 771d #769 + '97J' // 771e #2531 + '244S' // 771f #6362 + '183J' // 7720 #4767 + '6I' // 7721 #164 + '9R' // 7722 #251 + 'A' // 7723 + '45M' // 7724 #1182 + '38Q' // 7725 #1004 + '9R' // 7726 #251 + 'A' // 7727 + '9R' // 7728 #251 + '135V' // 7729 #3531 + 'A' // 772a + '57L' // 772b #1493 + '8Q' // 772c #224 + '9R' // 772d #251 + '23Y' // 772e #622 + '9R' // 772f #251 + '8Q' // 7730 #224 + 'A' // 7731 + '8Q' // 7732 #224 + '33W' // 7733 #880 + 'b9R' // 7734-7736 #251 + '139E' // 7737 #3618 + '97D' // 7738 #2525 + '23Y' // 7739 #622 + '130U' // 773a #3400 + '29P' // 773b #769 + '227U' // 773c #5922 + '9R' // 773d #251 + '217G' // 773e #5648 + '8Q' // 773f #224 + '176Z' // 7740 #4601 + '3G' // 7741 #84 + '3J' // 7742 #87 + '57L' // 7743 #1493 + '29P' // 7744 #769 + '3J' // 7745 #87 + '9R' // 7746 #251 + '178K' // 7747 #4638 + 'aA' // 7748-7749 + '3J' // 774a #87 + 'A' // 774b + '33W' // 774c #880 + '249E' // 774d #6478 + 'a9R' // 774e-774f #251 + '3X' // 7750 #101 + '8Q' // 7751 #224 + '9R' // 7752 #251 + 'aA' // 7753-7754 + '29P' // 7755 #769 + 'a3J' // 7756-7757 #87 + '57I' // 7758 #1490 + '33W' // 7759 #880 + '9R' // 775a #251 + '192S' // 775b #5010 + '63T' // 775c #1657 + '8Q' // 775d #224 + '63T' // 775e #1657 + 'a38Q' // 775f-7760 #1004 + '209N' // 7761 #5447 + '9R' // 7762 #251 + '183H' // 7763 #4765 + '3J' // 7764 #87 + '9R' // 7765 #251 + '97I' // 7766 #2530 + '3J' // 7767 #87 + '97H' // 7768 #2529 + '33W' // 7769 #880 + '9R' // 776a #251 + '145M' // 776b #3782 + '9R' // 776c #251 + '33W' // 776d #880 + '29P' // 776e #769 + 'A' // 776f + '3J' // 7770 #87 + '8Q' // 7771 #224 + '57I' // 7772 #1490 + 'a3J' // 7773-7774 #87 + 'aA' // 7775-7776 + '33V' // 7777 #879 + '29P' // 7778 #769 + '65C' // 7779 #1692 + '9R' // 777a #251 + '33V' // 777b #879 + '23Y' // 777c #622 + '9R' // 777d #251 + '22S' // 777e #590 + '145N' // 777f #3783 + '6W' // 7780 #178 + 'bA' // 7781-7783 + '145P' // 7784 #3785 + '12A' // 7785 #312 + 'A' // 7786 + '12A' // 7787 #312 + 'A' // 7788 + '29N' // 7789 #767 + 'A' // 778a + '22S' // 778b #590 + 'a6W' // 778c-778d #178 + '145Q' // 778e #3786 + 'a8Q' // 778f-7790 #224 + '57H' // 7791 #1489 + '3G' // 7792 #84 + '12A' // 7793 #312 + 'b3J' // 7794-7796 #87 + 'A' // 7797 + '38O' // 7798 #1002 + 'A' // 7799 + '3J' // 779a #87 + 'A' // 779b + '29N' // 779c #767 + 'A' // 779d + '133C' // 779e #3460 + 'a6W' // 779f-77a0 #178 + 'A' // 77a1 + '6W' // 77a2 #178 + 'A' // 77a3 + '3J' // 77a4 #87 + '22S' // 77a5 #590 + 'A' // 77a6 + '139J' // 77a7 #3623 + 'A' // 77a8 + '249F' // 77a9 #6479 + '122R' // 77aa #3189 + 'A' // 77ab + '183K' // 77ac #4768 + '180E' // 77ad #4684 + '3J' // 77ae #87 + '57F' // 77af #1487 + '57D' // 77b0 #1485 + '29M' // 77b1 #766 + 'A' // 77b2 + '142V' // 77b3 #3713 + '12A' // 77b4 #312 + '23Y' // 77b5 #622 + 'a6W' // 77b6-77b7 #178 + 'A' // 77b8 + '29M' // 77b9 #766 + 'A' // 77ba + '139F' // 77bb #3619 + '96X' // 77bc #2519 + 'a6W' // 77bd-77be #178 + '22S' // 77bf #590 + '8Q' // 77c0 #224 + 'A' // 77c1 + '8Q' // 77c2 #224 + '45K' // 77c3 #1180 + 'A' // 77c4 + '12A' // 77c5 #312 + 'A' // 77c6 + '6W' // 77c7 #178 + 'A' // 77c8 + '3J' // 77c9 #87 + 'A' // 77ca + '38O' // 77cb #1002 + '29N' // 77cc #767 + '6W' // 77cd #178 + 'bA' // 77ce-77d0 + 'a3J' // 77d1-77d2 #87 + '29N' // 77d3 #767 + 'A' // 77d4 + '3J' // 77d5 #87 + '8Q' // 77d6 #224 + '22S' // 77d7 #590 + 'A' // 77d8 + '23Y' // 77d9 #622 + '139I' // 77da #3622 + '148E' // 77db #3852 + '96V' // 77dc #2517 + '38P' // 77dd #1003 + '6W' // 77de #178 + 'a3J' // 77df-77e0 #87 + 'A' // 77e1 + '143I' // 77e2 #3726 + '57D' // 77e3 #1485 + '3J' // 77e4 #87 + '35U' // 77e5 #930 + '45K' // 77e6 #1180 + '6W' // 77e7 #178 + 'A' // 77e8 + '139G' // 77e9 #3620 + '23Y' // 77ea #622 + '3X' // 77eb #101 + '6W' // 77ec #178 + '222F' // 77ed #5777 + '65C' // 77ee #1692 + '143A' // 77ef #3718 + '29M' // 77f0 #766 + '3J' // 77f1 #87 + '29N' // 77f2 #767 + '223Q' // 77f3 #5814 + '45K' // 77f4 #1180 + 'A' // 77f5 + '2K' // 77f6 #62 + 'A' // 77f7 + '6W' // 77f8 #178 + 'A' // 77f9 + '29N' // 77fa #767 + '6W' // 77fb #178 + '29M' // 77fc #766 + '162K' // 77fd #4222 + '12A' // 77fe #312 + '1R' // 77ff #43 + '8Q' // 7800 #224 + '3Q' // 7801 #94 + '183D' // 7802 #4761 + '12A' // 7803 #312 + 'A' // 7804 + '29M' // 7805 #766 + '6W' // 7806 #178 + 'A' // 7807 + '38O' // 7808 #1002 + '6W' // 7809 #178 + 'aA' // 780a-780b + '63U' // 780c #1658 + '157L' // 780d #4093 + '3J' // 780e #87 + '8Q' // 780f #224 + '12A' // 7810 #312 + '6W' // 7811 #178 + '22S' // 7812 #590 + 'A' // 7813 + '222Z' // 7814 #5797 + '253T' // 7815 #6597 + '2L' // 7816 #63 + '8Q' // 7817 #224 + '38P' // 7818 #1003 + '3J' // 7819 #87 + 'a8Q' // 781a-781b #224 + '12A' // 781c #312 + '6W' // 781d #178 + '38P' // 781e #1003 + '12A' // 781f #312 + '29M' // 7820 #766 + 'b6W' // 7821-7823 #178 + 'A' // 7824 + '57H' // 7825 #1489 + 'a22S' // 7826-7827 #590 + 'A' // 7828 + '12A' // 7829 #312 + 'A' // 782a + '8Q' // 782b #224 + '22S' // 782c #590 + 'a6W' // 782d-782e #178 + '12A' // 782f #312 + '6W' // 7830 #178 + 'A' // 7831 + '161D' // 7832 #4189 + '12A' // 7833 #312 + '221H' // 7834 #5753 + '6W' // 7835 #178 + 'A' // 7836 + '6W' // 7837 #178 + '155R' // 7838 #4047 + '12A' // 7839 #312 + '23Y' // 783a #622 + '8Q' // 783b #224 + '12A' // 783c #312 + '38O' // 783d #1002 + '8Q' // 783e #224 + '3J' // 783f #87 + '3I' // 7840 #86 + '8Q' // 7841 #224 + '97A' // 7842 #2522 + '57G' // 7843 #1488 + '57F' // 7844 #1487 + '96W' // 7845 #2518 + 'A' // 7846 + '57G' // 7847 #1488 + '23Y' // 7848 #622 + '8Q' // 7849 #224 + '25N' // 784a #663 + '38P' // 784b #1003 + '57E' // 784c #1486 + '25N' // 784d #663 + '57E' // 784e #1486 + '249D' // 784f #6477 + '22T' // 7850 #591 + '57C' // 7851 #1484 + '15L' // 7852 #401 + '22T' // 7853 #591 + '45L' // 7854 #1181 + '4C' // 7855 #106 + 'a6H' // 7856-7857 #163 + 'A' // 7858 + 'a6H' // 7859-785a #163 + 'A' // 785b + '22R' // 785c #589 + '96Y' // 785d #2520 + '3T' // 785e #97 + 'A' // 785f + 'a3T' // 7860-7861 #97 + 'A' // 7862 + '3T' // 7863 #97 + '22R' // 7864 #589 + '6H' // 7865 #163 + '25N' // 7866 #663 + 'A' // 7867 + '22R' // 7868 #589 + '6H' // 7869 #163 + '15L' // 786a #401 + '124X' // 786b #3247 + '209V' // 786c #5455 + '6H' // 786d #163 + '150R' // 786e #3917 + '33U' // 786f #878 + 'aA' // 7870-7871 + '3T' // 7872 #97 + 'A' // 7873 + '3T' // 7874 #97 + 'A' // 7875 + 'a6H' // 7876-7877 #163 + 'aA' // 7878-7879 + '33T' // 787a #877 + 'A' // 787b + '33U' // 787c #878 + 'A' // 787d + '16B' // 787e #417 + '22T' // 787f #591 + 'A' // 7880 + '125L' // 7881 #3261 + 'cA' // 7882-7885 + '22R' // 7886 #589 + '33U' // 7887 #878 + '45L' // 7888 #1181 + '22T' // 7889 #591 + '3T' // 788a #97 + 'A' // 788b + '150Q' // 788c #3916 + '96Q' // 788d #2512 + '185N' // 788e #4823 + '22R' // 788f #589 + 'A' // 7890 + '166I' // 7891 #4324 + 'A' // 7892 + '15L' // 7893 #401 + '16B' // 7894 #417 + '22R' // 7895 #589 + '25N' // 7896 #663 + '187K' // 7897 #4872 + '15L' // 7898 #401 + '6H' // 7899 #163 + '15L' // 789a #401 + 'a6H' // 789b-789c #163 + '16B' // 789d #417 + '15L' // 789e #401 + '185O' // 789f #4824 + 'A' // 78a0 + '15L' // 78a1 #401 + 'A' // 78a2 + '33U' // 78a3 #878 + '3T' // 78a4 #97 + '22T' // 78a5 #591 + 'A' // 78a6 + '180V' // 78a7 #4701 + '3T' // 78a8 #97 + '178G' // 78a9 #4634 + '22R' // 78aa #589 + 'A' // 78ab + '3T' // 78ac #97 + '15L' // 78ad #401 + 'A' // 78ae + '33T' // 78af #877 + '185P' // 78b0 #4825 + '96U' // 78b1 #2516 + '15L' // 78b2 #401 + '178H' // 78b3 #4635 + '22T' // 78b4 #591 + '3T' // 78b5 #97 + '22T' // 78b6 #591 + 'A' // 78b7 + '25N' // 78b8 #663 + '45L' // 78b9 #1181 + '231L' // 78ba #6017 + '249B' // 78bb #6475 + '225B' // 78bc #5851 + '3T' // 78bd #97 + '96T' // 78be #2515 + '16B' // 78bf #417 + 'A' // 78c0 + '181P' // 78c1 #4721 + 'A' // 78c2 + '6H' // 78c3 #163 + 'A' // 78c4 + '157J' // 78c5 #4091 + '3T' // 78c6 #97 + '33T' // 78c7 #877 + '22R' // 78c8 #589 + '15L' // 78c9 #401 + '122N' // 78ca #3185 + '33U' // 78cb #878 + '3T' // 78cc #97 + 'A' // 78cd + '96R' // 78ce #2513 + 'A' // 78cf + '96Z' // 78d0 #2521 + '15L' // 78d1 #401 + '57C' // 78d2 #1484 + '33T' // 78d3 #877 + '15L' // 78d4 #401 + '96S' // 78d5 #2514 + '3T' // 78d6 #97 + 'a25N' // 78d7-78d8 #663 + '6H' // 78d9 #163 + '66S' // 78da #1734 + '3T' // 78db #97 + '6H' // 78dc #163 + 'A' // 78dd + '22T' // 78de #591 + 'a3T' // 78df-78e0 #97 + '128B' // 78e1 #3329 + 'A' // 78e2 + '25N' // 78e3 #663 + '33T' // 78e4 #877 + '6H' // 78e5 #163 + '16B' // 78e6 #417 + '18K' // 78e7 #478 + '197O' // 78e8 #5136 + 'A' // 78e9 + '5R' // 78ea #147 + 'A' // 78eb + '29L' // 78ec #765 + 'A' // 78ed + '20H' // 78ee #527 + '160Z' // 78ef #4185 + '20H' // 78f0 #527 + '57B' // 78f1 #1483 + '5R' // 78f2 #147 + '18K' // 78f3 #478 + '5R' // 78f4 #147 + '96P' // 78f5 #2511 + '3T' // 78f6 #97 + '128C' // 78f7 #3330 + 'A' // 78f8 + '3T' // 78f9 #97 + '5R' // 78fa #147 + '29L' // 78fb #765 + 'A' // 78fc + '18K' // 78fd #478 + '5R' // 78fe #147 + '18K' // 78ff #478 + '3T' // 7900 #97 + '157G' // 7901 #4088 + '6H' // 7902 #163 + 'A' // 7903 + '33S' // 7904 #876 + '25M' // 7905 #662 + '18K' // 7906 #478 + '16B' // 7907 #417 + 'A' // 7908 + '6H' // 7909 #163 + 'A' // 790a + '6H' // 790b #163 + '5R' // 790c #147 + 'A' // 790d + '190G' // 790e #4946 + 'A' // 790f + '5R' // 7910 #147 + 'a18K' // 7911-7912 #478 + '6H' // 7913 #163 + 'aA' // 7914-7915 + '260S' // 7916 #6778 + 'aA' // 7917-7918 + '192R' // 7919 #5009 + 'a16B' // 791a-791b #417 + '18K' // 791c #478 + 'A' // 791d + '5R' // 791e #147 + '16B' // 791f #417 + '3T' // 7920 #97 + '25M' // 7921 #662 + 'aA' // 7922-7923 + '6H' // 7924 #163 + '3T' // 7925 #97 + '66S' // 7926 #1734 + 'b3T' // 7927-7929 #97 + 'a29L' // 792a-792b #765 + '96H' // 792c #2503 + '3T' // 792d #97 + '18K' // 792e #478 + 'A' // 792f + '3T' // 7930 #97 + '18K' // 7931 #478 + 'a20H' // 7932-7933 #527 + '5R' // 7934 #147 + '3T' // 7935 #97 + '57B' // 7936 #1483 + 'A' // 7937 + '33S' // 7938 #876 + '6H' // 7939 #163 + '239A' // 793a #6214 + '5R' // 793b #147 + '125Z' // 793c #3275 + '5R' // 793d #147 + '49H' // 793e #1281 + '18K' // 793f #478 + '141Q' // 7940 #3682 + '96F' // 7941 #2501 + '5R' // 7942 #147 + '6H' // 7943 #163 + '3T' // 7944 #97 + 'a5R' // 7945-7946 #147 + '96K' // 7947 #2506 + '168C' // 7948 #4370 + '96L' // 7949 #2507 + '16B' // 794a #417 + '3T' // 794b #97 + '25M' // 794c #662 + 'A' // 794d + '6H' // 794e #163 + '16B' // 794f #417 + '154X' // 7950 #4027 + '16B' // 7951 #417 + 'A' // 7952 + '96J' // 7953 #2505 + '5R' // 7954 #147 + '170Z' // 7955 #4445 + '196T' // 7956 #5115 + '29L' // 7957 #765 + '5R' // 7958 #147 + '25M' // 7959 #662 + '29L' // 795a #765 + '96G' // 795b #2502 + '29L' // 795c #765 + '199C' // 795d #5176 + '238O' // 795e #6202 + '5R' // 795f #147 + '128A' // 7960 #3328 + '33S' // 7961 #876 + '5R' // 7962 #147 + 'A' // 7963 + '25M' // 7964 #662 + '189P' // 7965 #4929 + '6H' // 7966 #163 + '5R' // 7967 #147 + '220C' // 7968 #5722 + '5R' // 7969 #147 + 'A' // 796a + '5R' // 796b #147 + 'A' // 796c + '183W' // 796d #4780 + 'A' // 796e + '6H' // 796f #163 + 'A' // 7970 + '20H' // 7971 #527 + '5R' // 7972 #147 + '33S' // 7973 #876 + '6H' // 7974 #163 + 'aA' // 7975-7976 + '249C' // 7977 #6476 + '2Y' // 7978 #76 + '5R' // 7979 #147 + '122L' // 797a #3183 + '16B' // 797b #417 + '5R' // 797c #147 + 'A' // 797d + '5R' // 797e #147 + '139B' // 797f #3615 + '5R' // 7980 #147 + '213H' // 7981 #5545 + '25M' // 7982 #662 + '20H' // 7983 #527 + '252H' // 7984 #6559 + '253G' // 7985 #6584 + 'a25M' // 7986-7987 #662 + '33S' // 7988 #876 + '6H' // 7989 #163 + '96I' // 798a #2504 + '9E' // 798b #238 + '31J' // 798c #815 + '178F' // 798d #4633 + '133A' // 798e #3458 + '230Z' // 798f #6005 + 'A' // 7990 + '56Y' // 7991 #1480 + '9M' // 7992 #246 + '25K' // 7993 #660 + 'a9E' // 7994-7995 #238 + '25K' // 7996 #660 + '9M' // 7997 #246 + '9E' // 7998 #238 + '20H' // 7999 #527 + '25L' // 799a #661 + '9E' // 799b #238 + '31J' // 799c #815 + '29K' // 799d #764 + 'A' // 799e + '25L' // 799f #661 + '33R' // 79a0 #875 + '25K' // 79a1 #660 + '33R' // 79a2 #875 + '9M' // 79a3 #246 + '25L' // 79a4 #661 + '57A' // 79a5 #1482 + '65S' // 79a6 #1708 + '139C' // 79a7 #3616 + 'a9E' // 79a8-79a9 #238 + '65S' // 79aa #1708 + '31J' // 79ab #815 + '9M' // 79ac #246 + 'A' // 79ad + '225A' // 79ae #5850 + '31J' // 79af #815 + '9E' // 79b0 #238 + '139A' // 79b1 #3614 + 'A' // 79b2 + '45J' // 79b3 #1179 + '31J' // 79b4 #815 + '9M' // 79b5 #246 + 'aA' // 79b6-79b7 + '9E' // 79b8 #238 + '122M' // 79b9 #3184 + '9E' // 79ba #238 + '122O' // 79bb #3186 + 'A' // 79bc + '139D' // 79bd #3617 + '157H' // 79be #4089 + '124P' // 79bf #3239 + '215I' // 79c0 #5598 + '237F' // 79c1 #6167 + '31J' // 79c2 #815 + '2W' // 79c3 #74 + '29K' // 79c4 #764 + 'A' // 79c5 + '25L' // 79c6 #661 + '3T' // 79c7 #97 + '9E' // 79c8 #238 + '157I' // 79c9 #4090 + '96E' // 79ca #2500 + '213L' // 79cb #5549 + '29K' // 79cc #764 + '133B' // 79cd #3459 + 'A' // 79ce + '9E' // 79cf #238 + '20H' // 79d0 #527 + '235X' // 79d1 #6133 + '210V' // 79d2 #5481 + 'A' // 79d3 + '29K' // 79d4 #764 + '45J' // 79d5 #1179 + '9E' // 79d6 #238 + 'A' // 79d7 + '221A' // 79d8 #5746 + 'A' // 79d9 + '3T' // 79da #97 + 'A' // 79db + '33R' // 79dc #875 + 'a9E' // 79dd-79de #238 + '200A' // 79df #5200 + 'a3T' // 79e0-79e1 #97 + '29K' // 79e2 #764 + '9E' // 79e3 #238 + '148Y' // 79e4 #3872 + '3T' // 79e5 #97 + '160B' // 79e6 #4161 + '45J' // 79e7 #1179 + '9M' // 79e8 #246 + '136H' // 79e9 #3543 + '25K' // 79ea #660 + '9E' // 79eb #238 + '25K' // 79ec #660 + '9E' // 79ed #238 + 'A' // 79ee + '2D' // 79ef #55 + '257J' // 79f0 #6691 + '29K' // 79f1 #764 + 'aA' // 79f2-79f3 + '25L' // 79f4 #661 + 'A' // 79f5 + '33R' // 79f6 #875 + '25L' // 79f7 #661 + '9E' // 79f8 #238 + 'A' // 79f9 + '9M' // 79fa #246 + '213M' // 79fb #5550 + '3T' // 79fc #97 + '2Y' // 79fd #76 + '9M' // 79fe #246 + 'A' // 79ff + '174J' // 7a00 #4533 + 'A' // 7a01 + 'a9E' // 7a02-7a03 #238 + 'A' // 7a04 + '199Z' // 7a05 #5199 + '20H' // 7a06 #527 + '3T' // 7a07 #97 + '56Y' // 7a08 #1480 + '3T' // 7a09 #97 + '9E' // 7a0a #238 + '241V' // 7a0b #6287 + '25K' // 7a0c #660 + '192Q' // 7a0d #5008 + '96O' // 7a0e #2510 + 'A' // 7a0f + '33R' // 7a10 #875 + '25K' // 7a11 #660 + 'aA' // 7a12-7a13 + '56Z' // 7a14 #1481 + '3T' // 7a15 #97 + 'A' // 7a16 + '38N' // 7a17 #1001 + '56X' // 7a18 #1479 + '38N' // 7a19 #1001 + '161Y' // 7a1a #4210 + '2O' // 7a1b #66 + '56Z' // 7a1c #1481 + 'A' // 7a1d + '56W' // 7a1e #1478 + '38N' // 7a1f #1001 + '127X' // 7a20 #3325 + '2O' // 7a21 #66 + 'A' // 7a22 + '9M' // 7a23 #246 + 'A' // 7a24 + '9M' // 7a25 #246 + '25L' // 7a26 #661 + '2O' // 7a27 #66 + 'aA' // 7a28-7a29 + '20H' // 7a2a #527 + '2O' // 7a2b #66 + '57A' // 7a2c #1482 + '56X' // 7a2d #1479 + '237V' // 7a2e #6183 + '2O' // 7a2f #66 + '19H' // 7a30 #501 + '232J' // 7a31 #6041 + '96N' // 7a32 #2509 + '3I' // 7a33 #86 + 'a2O' // 7a34-7a35 #66 + '260T' // 7a36 #6779 + '38N' // 7a37 #1001 + '2O' // 7a38 #66 + '56W' // 7a39 #1478 + '56V' // 7a3a #1477 + '170Y' // 7a3b #4444 + '96M' // 7a3c #2508 + '136G' // 7a3d #3542 + '56V' // 7a3e #1477 + '206K' // 7a3f #5366 + '153Q' // 7a40 #3994 + 'A' // 7a41 + '255N' // 7a42 #6643 + '18H' // 7a43 #475 + '2O' // 7a44 #66 + '18J' // 7a45 #477 + '145L' // 7a46 #3781 + '19H' // 7a47 #501 + '2O' // 7a48 #66 + '29J' // 7a49 #763 + 'aA' // 7a4a-7a4b + '132Y' // 7a4c #3456 + '228X' // 7a4d #5951 + '66U' // 7a4e #1736 + '253Y' // 7a4f #6602 + '2O' // 7a50 #66 + '9M' // 7a51 #246 + 'bA' // 7a52-7a54 + '19H' // 7a55 #501 + '18J' // 7a56 #477 + '145J' // 7a57 #3779 + 'A' // 7a58 + '2O' // 7a59 #66 + '38M' // 7a5a #1000 + '9M' // 7a5b #246 + '18J' // 7a5c #477 + '19H' // 7a5d #501 + '9M' // 7a5e #246 + '2O' // 7a5f #66 + '18J' // 7a60 #477 + '56T' // 7a61 #1475 + '150P' // 7a62 #3915 + '19H' // 7a63 #501 + 'A' // 7a64 + '18H' // 7a65 #475 + '9M' // 7a66 #246 + '2O' // 7a67 #66 + '38L' // 7a68 #999 + '199Y' // 7a69 #5198 + '2O' // 7a6a #66 + '142N' // 7a6b #3705 + 'A' // 7a6c + '18J' // 7a6d #477 + '38M' // 7a6e #1000 + 'A' // 7a6f + '29J' // 7a70 #763 + '38M' // 7a71 #1000 + '45G' // 7a72 #1176 + 'A' // 7a73 + '169F' // 7a74 #4399 + '2O' // 7a75 #66 + '222M' // 7a76 #5784 + '4C' // 7a77 #106 + '11Z' // 7a78 #311 + '95P' // 7a79 #2485 + '237Z' // 7a7a #6187 + 'aA' // 7a7b-7a7c + '95M' // 7a7d #2482 + '2O' // 7a7e #66 + '218F' // 7a7f #5673 + '11Z' // 7a80 #311 + '213C' // 7a81 #5540 + '2O' // 7a82 #66 + '95V' // 7a83 #2491 + '157F' // 7a84 #4087 + 'a11Z' // 7a85-7a86 #311 + '9M' // 7a87 #246 + '29J' // 7a88 #763 + 'A' // 7a89 + '19H' // 7a8a #501 + '2O' // 7a8b #66 + 'A' // 7a8c + '2K' // 7a8d #62 + 'aA' // 7a8e-7a8f + '11Z' // 7a90 #311 + '95R' // 7a91 #2487 + '125A' // 7a92 #3250 + '96D' // 7a93 #2499 + '11Z' // 7a94 #311 + '29J' // 7a95 #763 + '11Z' // 7a96 #311 + '207D' // 7a97 #5385 + '29J' // 7a98 #763 + 'aA' // 7a99-7a9a + 'a9M' // 7a9b-7a9c #246 + '2L' // 7a9d #63 + '19H' // 7a9e #501 + '136F' // 7a9f #3541 + '11Z' // 7aa0 #311 + '9M' // 7aa1 #246 + 'A' // 7aa2 + '11Z' // 7aa3 #311 + 'A' // 7aa4 + '3H' // 7aa5 #85 + '3G' // 7aa6 #84 + 'A' // 7aa7 + '38L' // 7aa8 #999 + '66U' // 7aa9 #1736 + '95Z' // 7aaa #2495 + 'A' // 7aab + '11Z' // 7aac #311 + '9M' // 7aad #246 + '180O' // 7aae #4694 + '130L' // 7aaf #3391 + '11Z' // 7ab0 #311 + 'aA' // 7ab1-7ab2 + '11Z' // 7ab3 #311 + 'A' // 7ab4 + '2O' // 7ab5 #66 + '18J' // 7ab6 #477 + 'A' // 7ab7 + '38L' // 7ab8 #999 + '2O' // 7ab9 #66 + '145I' // 7aba #3778 + '18J' // 7abb #477 + '18H' // 7abc #475 + '19H' // 7abd #501 + 'a11Z' // 7abe-7abf #311 + 'aA' // 7ac0-7ac1 + '38M' // 7ac2 #1000 + '18H' // 7ac3 #475 + '135J' // 7ac4 #3519 + '138Z' // 7ac5 #3613 + '2O' // 7ac6 #66 + '122K' // 7ac7 #3182 + '11Z' // 7ac8 #311 + '18J' // 7ac9 #477 + '157E' // 7aca #4086 + '68W' // 7acb #1790 + 'b2O' // 7acc-7ace #66 + '18H' // 7acf #475 + 'A' // 7ad0 + '11Z' // 7ad1 #311 + '19H' // 7ad2 #501 + '18H' // 7ad3 #475 + 'A' // 7ad4 + '2O' // 7ad5 #66 + '3H' // 7ad6 #85 + '71D' // 7ad7 #1849 + 'A' // 7ad8 + '240W' // 7ad9 #6262 + '11Z' // 7ada #311 + '95Q' // 7adb #2486 + '96C' // 7adc #2498 + '29J' // 7add #763 + '3I' // 7ade #86 + '214L' // 7adf #5575 + '234C' // 7ae0 #6086 + '19H' // 7ae1 #501 + '18H' // 7ae2 #475 + '124W' // 7ae3 #3246 + '38L' // 7ae4 #999 + '227W' // 7ae5 #5924 + '56U' // 7ae6 #1476 + '18H' // 7ae7 #475 + '2O' // 7ae8 #66 + '18J' // 7ae9 #477 + '56T' // 7aea #1475 + '18J' // 7aeb #477 + '2O' // 7aec #66 + '145K' // 7aed #3780 + 'A' // 7aee + '212Q' // 7aef #5528 + 'a2O' // 7af0-7af1 #66 + 'aA' // 7af2-7af3 + '14Q' // 7af4 #380 + '7W' // 7af5 #204 + '204V' // 7af6 #5325 + '7W' // 7af7 #204 + '2O' // 7af8 #66 + '215K' // 7af9 #5600 + '95O' // 7afa #2484 + '56U' // 7afb #1476 + '45G' // 7afc #1176 + 'a5H' // 7afd-7afe #137 + '135U' // 7aff #3530 + '7W' // 7b00 #204 + '14A' // 7b01 #364 + '14Q' // 7b02 #380 + '7W' // 7b03 #204 + '5H' // 7b04 #137 + '14A' // 7b05 #364 + '5H' // 7b06 #137 + '14Q' // 7b07 #380 + '127Y' // 7b08 #3326 + '14A' // 7b09 #364 + '5H' // 7b0a #137 + '95S' // 7b0b #2488 + '38K' // 7b0c #998 + 'A' // 7b0d + '14A' // 7b0e #364 + '18I' // 7b0f #476 + '45I' // 7b10 #1178 + '223C' // 7b11 #5800 + '2O' // 7b12 #66 + '7W' // 7b13 #204 + '95T' // 7b14 #2489 + 'a7W' // 7b15-7b16 #204 + 'A' // 7b17 + '5H' // 7b18 #137 + '122J' // 7b19 #3181 + '45I' // 7b1a #1178 + '142J' // 7b1b #3701 + 'aA' // 7b1c-7b1d + '18I' // 7b1e #476 + '5H' // 7b1f #137 + '125Q' // 7b20 #3266 + 'A' // 7b21 + '14A' // 7b22 #364 + '5H' // 7b23 #137 + '14A' // 7b24 #364 + '5H' // 7b25 #137 + '209A' // 7b26 #5434 + '18H' // 7b27 #475 + '163W' // 7b28 #4260 + 'b5H' // 7b29-7b2b #137 + '243A' // 7b2c #6318 + '18I' // 7b2d #476 + '5H' // 7b2e #137 + '2O' // 7b2f #66 + '14Q' // 7b30 #380 + '5H' // 7b31 #137 + '14A' // 7b32 #364 + 'b5H' // 7b33-7b35 #137 + '14Q' // 7b36 #380 + '7W' // 7b37 #204 + '14A' // 7b38 #364 + '96A' // 7b39 #2496 + '7W' // 7b3a #204 + '5H' // 7b3b #137 + '2R' // 7b3c #69 + '2O' // 7b3d #66 + '7W' // 7b3e #204 + '14Q' // 7b3f #380 + '2O' // 7b40 #66 + '14Q' // 7b41 #380 + '38K' // 7b42 #998 + '45G' // 7b43 #1176 + '7W' // 7b44 #204 + '5H' // 7b45 #137 + '221G' // 7b46 #5752 + '5H' // 7b47 #137 + '95X' // 7b48 #2493 + '242V' // 7b49 #6313 + '14A' // 7b4a #364 + '176Q' // 7b4b #4592 + '18I' // 7b4c #476 + '145H' // 7b4d #3777 + '5H' // 7b4e #137 + 'a18I' // 7b4f-7b50 #476 + '125P' // 7b51 #3265 + '182E' // 7b52 #4736 + '2O' // 7b53 #66 + '222E' // 7b54 #5776 + '18H' // 7b55 #475 + '68J' // 7b56 #1777 + 'A' // 7b57 + '14A' // 7b58 #364 + 'A' // 7b59 + '7W' // 7b5a #204 + '2Y' // 7b5b #76 + '7W' // 7b5c #204 + '248Z' // 7b5d #6473 + 'aA' // 7b5e-7b5f + '18I' // 7b60 #476 + 'A' // 7b61 + '45I' // 7b62 #1178 + 'A' // 7b63 + '2O' // 7b64 #66 + '45H' // 7b65 #1177 + '5H' // 7b66 #137 + '95U' // 7b67 #2490 + 'A' // 7b68 + '5H' // 7b69 #137 + '2O' // 7b6a #66 + 'A' // 7b6b + '95N' // 7b6c #2483 + '5H' // 7b6d #137 + '18I' // 7b6e #476 + '5H' // 7b6f #137 + '2O' // 7b70 #66 + '127Z' // 7b71 #3327 + 'b5H' // 7b72-7b74 #137 + '18I' // 7b75 #476 + '14A' // 7b76 #364 + '132Z' // 7b77 #3457 + 'A' // 7b78 + '249A' // 7b79 #6474 + '2O' // 7b7a #66 + '38K' // 7b7b #998 + '7W' // 7b7c #204 + '260R' // 7b7d #6777 + '1Z' // 7b7e #51 + '14Q' // 7b7f #380 + '3Q' // 7b80 #94 + 'A' // 7b81 + '14A' // 7b82 #364 + 'A' // 7b83 + '45H' // 7b84 #1177 + '14A' // 7b85 #364 + '2O' // 7b86 #66 + '96B' // 7b87 #2497 + 'A' // 7b88 + '2O' // 7b89 #66 + 'A' // 7b8a + '95W' // 7b8b #2492 + '7W' // 7b8c #204 + 'a5H' // 7b8d-7b8e #137 + '18I' // 7b8f #476 + 'b5H' // 7b90-7b92 #137 + '7W' // 7b93 #204 + '124V' // 7b94 #3245 + '95Y' // 7b95 #2494 + '5H' // 7b96 #137 + '68J' // 7b97 #1777 + '5H' // 7b98 #137 + '14Q' // 7b99 #380 + '248Y' // 7b9a #6472 + '14Q' // 7b9b #380 + '5H' // 7b9c #137 + '18I' // 7b9d #476 + 'a14Q' // 7b9e-7b9f #380 + '45H' // 7ba0 #1177 + '237E' // 7ba1 #6166 + '38K' // 7ba2 #998 + '56R' // 7ba3 #1473 + '7W' // 7ba4 #204 + '2O' // 7ba5 #66 + 'c7W' // 7ba6-7ba9 #204 + '252F' // 7baa #6557 + '7W' // 7bab #204 + '95D' // 7bac #2473 + '170X' // 7bad #4443 + '56R' // 7bae #1473 + 'a14Q' // 7baf-7bb0 #380 + '228D' // 7bb1 #5931 + '56P' // 7bb2 #1471 + 'A' // 7bb3 + '45D' // 7bb4 #1173 + 'a2O' // 7bb5-7bb6 #66 + '7W' // 7bb7 #204 + '95H' // 7bb8 #2477 + '95K' // 7bb9 #2480 + '2O' // 7bba #66 + '14Q' // 7bbb #380 + 'a2O' // 7bbc-7bbd #66 + 'aA' // 7bbe-7bbf + '235O' // 7bc0 #6124 + '45D' // 7bc1 #1173 + '2O' // 7bc2 #66 + '7W' // 7bc3 #204 + '219P' // 7bc4 #5709 + '56P' // 7bc5 #1471 + '45D' // 7bc6 #1173 + '226U' // 7bc7 #5896 + '70A' // 7bc8 #1820 + '198W' // 7bc9 #5170 + '70A' // 7bca #1820 + 'a95C' // 7bcb-7bcc #2472 + 'aA' // 7bcd-7bce + '33P' // 7bcf #873 + '29I' // 7bd0 #762 + '7W' // 7bd1 #204 + '71D' // 7bd2 #1849 + '5Y' // 7bd3 #154 + '13Z' // 7bd4 #363 + 'A' // 7bd5 + 'a3B' // 7bd6-7bd7 #79 + 'A' // 7bd8 + 'a13Z' // 7bd9-7bda #363 + '33P' // 7bdb #873 + 'A' // 7bdc + '13Z' // 7bdd #363 + 'aA' // 7bde-7bdf + '95I' // 7be0 #2478 + '45E' // 7be1 #1174 + 'aA' // 7be2-7be3 + '130N' // 7be4 #3393 + 'a13Z' // 7be5-7be6 #363 + 'A' // 7be7 + '3B' // 7be8 #79 + '66N' // 7be9 #1729 + '13Z' // 7bea #363 + 'A' // 7beb + '29I' // 7bec #762 + '251X' // 7bed #6549 + '1R' // 7bee #43 + '5Y' // 7bef #154 + '3B' // 7bf0 #79 + '95L' // 7bf1 #2481 + 'a33P' // 7bf2-7bf3 #873 + '10F' // 7bf4 #265 + '3B' // 7bf5 #79 + '10F' // 7bf6 #265 + '150O' // 7bf7 #3914 + 'a33P' // 7bf8-7bf9 #873 + '18G' // 7bfa #474 + 'A' // 7bfb + '13Z' // 7bfc #363 + '56S' // 7bfd #1474 + '13Z' // 7bfe #363 + '56S' // 7bff #1474 + '33P' // 7c00 #873 + '13Z' // 7c01 #363 + '3B' // 7c02 #79 + '13Z' // 7c03 #363 + '3B' // 7c04 #79 + 'A' // 7c05 + '3B' // 7c06 #79 + '56N' // 7c07 #1469 + '5Y' // 7c08 #154 + '3B' // 7c09 #79 + '45E' // 7c0a #1174 + 'b13Z' // 7c0b-7c0d #363 + '3B' // 7c0e #79 + '13Z' // 7c0f #363 + 'A' // 7c10 + '13Z' // 7c11 #363 + '95A' // 7c12 #2470 + 'a3B' // 7c13-7c14 #79 + '45E' // 7c15 #1174 + '5Y' // 7c16 #154 + '3B' // 7c17 #79 + 'A' // 7c18 + '10F' // 7c19 #265 + 'A' // 7c1a + '18G' // 7c1b #474 + 'aA' // 7c1c-7c1d + '56N' // 7c1e #1469 + 'a13Z' // 7c1f-7c20 #363 + '236J' // 7c21 #6145 + 'A' // 7c22 + '5G' // 7c23 #136 + '5Y' // 7c24 #154 + '13B' // 7c25 #339 + '5G' // 7c26 #136 + '132W' // 7c27 #3454 + '10F' // 7c28 #265 + '5Y' // 7c29 #154 + 'a38I' // 7c2a-7c2b #996 + '10F' // 7c2c #265 + 'a5Y' // 7c2d-7c2e #154 + '10F' // 7c2f #265 + '5Y' // 7c30 #154 + '10F' // 7c31 #265 + '5Y' // 7c32 #154 + '10F' // 7c33 #265 + '3B' // 7c34 #79 + '38J' // 7c35 #997 + '3B' // 7c36 #79 + 'b5G' // 7c37-7c39 #136 + '10F' // 7c3a #265 + '5Y' // 7c3b #154 + 'A' // 7c3c + '199X' // 7c3d #5197 + '150M' // 7c3e #3912 + '201X' // 7c3f #5249 + '5G' // 7c40 #136 + '5Y' // 7c41 #154 + '18G' // 7c42 #474 + '185M' // 7c43 #4822 + '29I' // 7c44 #762 + 'a3B' // 7c45-7c46 #79 + '5Y' // 7c47 #154 + '33Q' // 7c48 #874 + '45F' // 7c49 #1175 + '3B' // 7c4a #79 + 'A' // 7c4b + '66N' // 7c4c #1729 + '205S' // 7c4d #5348 + 'A' // 7c4e + '3B' // 7c4f #79 + '5G' // 7c50 #136 + '18G' // 7c51 #474 + '3B' // 7c52 #79 + 'a5G' // 7c53-7c54 #136 + '3B' // 7c55 #79 + 'a13B' // 7c56-7c57 #339 + '3B' // 7c58 #79 + '5G' // 7c59 #136 + 'b13B' // 7c5a-7c5c #339 + '18G' // 7c5d #474 + '3B' // 7c5e #79 + '5G' // 7c5f #136 + '167V' // 7c60 #4363 + '3B' // 7c61 #79 + 'A' // 7c62 + '5G' // 7c63 #136 + '207C' // 7c64 #5384 + '5G' // 7c65 #136 + 'A' // 7c66 + '3B' // 7c67 #79 + 'A' // 7c68 + '13B' // 7c69 #339 + 'aA' // 7c6a-7c6b + '38I' // 7c6c #996 + '13B' // 7c6d #339 + '5G' // 7c6e #136 + '3B' // 7c6f #79 + '18G' // 7c70 #474 + 'A' // 7c71 + '163V' // 7c72 #4259 + '229I' // 7c73 #5962 + '33Q' // 7c74 #874 + '13B' // 7c75 #339 + 'bA' // 7c76-7c78 + '5G' // 7c79 #136 + '5Y' // 7c7a #154 + '145G' // 7c7b #3776 + '5G' // 7c7c #136 + '145F' // 7c7d #3775 + '13B' // 7c7e #339 + 'aA' // 7c7f-7c80 + 'a10F' // 7c81-7c82 #265 + '94X' // 7c83 #2467 + '33Q' // 7c84 #874 + '5Y' // 7c85 #154 + '18G' // 7c86 #474 + '3B' // 7c87 #79 + '5Y' // 7c88 #154 + '227K' // 7c89 #5912 + '5Y' // 7c8a #154 + '254P' // 7c8b #6619 + '5Y' // 7c8c #154 + '5G' // 7c8d #136 + '38J' // 7c8e #997 + 'a10F' // 7c8f-7c90 #265 + '33Q' // 7c91 #874 + '181Z' // 7c92 #4731 + '5Y' // 7c93 #154 + '5G' // 7c94 #136 + '95E' // 7c95 #2474 + '5Y' // 7c96 #154 + '195N' // 7c97 #5083 + '154Q' // 7c98 #4020 + '5Y' // 7c99 #154 + 'A' // 7c9a + '252I' // 7c9b #6560 + '38J' // 7c9c #997 + '5Y' // 7c9d #154 + '10F' // 7c9e #265 + '95G' // 7c9f #2476 + 'a10F' // 7ca0-7ca1 #265 + '5G' // 7ca2 #136 + 'A' // 7ca3 + '248X' // 7ca4 #6471 + '150N' // 7ca5 #3913 + '5G' // 7ca6 #136 + '136Z' // 7ca7 #3561 + '5G' // 7ca8 #136 + '5Y' // 7ca9 #154 + '3G' // 7caa #84 + '3B' // 7cab #79 + '38J' // 7cac #997 + '3B' // 7cad #79 + '94Y' // 7cae #2468 + '5Y' // 7caf #154 + '10F' // 7cb0 #265 + 'b38I' // 7cb1-7cb3 #996 + 'A' // 7cb4 + '162J' // 7cb5 #4221 + 'a10F' // 7cb6-7cb7 #265 + '29I' // 7cb8 #762 + '66C' // 7cb9 #1718 + '5G' // 7cba #136 + '10F' // 7cbb #265 + '5G' // 7cbc #136 + '122H' // 7cbd #3179 + '241N' // 7cbe #6279 + '5G' // 7cbf #136 + '10F' // 7cc0 #265 + '5Y' // 7cc1 #154 + '56O' // 7cc2 #1470 + '29I' // 7cc3 #762 + '3B' // 7cc4 #79 + '5G' // 7cc5 #136 + 'A' // 7cc6 + '56O' // 7cc7 #1470 + 'a5G' // 7cc8-7cc9 #136 + '163U' // 7cca #4258 + 'A' // 7ccb + '33Q' // 7ccc #874 + '5G' // 7ccd #136 + '13B' // 7cce #339 + '3B' // 7ccf #79 + 'aA' // 7cd0-7cd1 + '3B' // 7cd2 #79 + '18G' // 7cd3 #474 + '3B' // 7cd4 #79 + '192P' // 7cd5 #5007 + '203D' // 7cd6 #5281 + '5G' // 7cd7 #136 + '3B' // 7cd8 #79 + '132X' // 7cd9 #3455 + '18G' // 7cda #474 + 'A' // 7cdb + '5G' // 7cdc #136 + '13B' // 7cdd #339 + '125O' // 7cde #3264 + '66C' // 7cdf #1718 + '38I' // 7ce0 #996 + 'A' // 7ce1 + '13B' // 7ce2 #339 + 'A' // 7ce3 + 'a5Y' // 7ce4-7ce5 #154 + '18G' // 7ce6 #474 + '159Q' // 7ce7 #4150 + '56Q' // 7ce8 #1472 + '3B' // 7ce9 #79 + '45F' // 7cea #1175 + '3B' // 7ceb #79 + '64B' // 7cec #1665 + '45F' // 7ced #1175 + 'A' // 7cee + '122I' // 7cef #3180 + '64B' // 7cf0 #1665 + 'A' // 7cf1 + '13B' // 7cf2 #339 + '29I' // 7cf3 #762 + '13B' // 7cf4 #339 + '95B' // 7cf5 #2471 + '33O' // 7cf6 #872 + '11H' // 7cf7 #293 + '95J' // 7cf8 #2479 + '33O' // 7cf9 #872 + '3B' // 7cfa #79 + '236K' // 7cfb #6146 + '56Q' // 7cfc #1472 + 'A' // 7cfd + '170W' // 7cfe #4442 + 'A' // 7cff + '227Y' // 7d00 #5926 + 'A' // 7d01 + '94Z' // 7d02 #2469 + '3B' // 7d03 #79 + '239F' // 7d04 #6219 + '233W' // 7d05 #6080 + 'b56M' // 7d06-7d08 #1468 + '33O' // 7d09 #872 + '56M' // 7d0a #1468 + '202E' // 7d0b #5256 + 'A' // 7d0c + '213K' // 7d0d #5548 + 'A' // 7d0e + '13B' // 7d0f #339 + '195R' // 7d10 #5087 + 'a33O' // 7d11-7d12 #872 + '145E' // 7d13 #3774 + '212D' // 7d14 #5515 + '13B' // 7d15 #339 + '3B' // 7d16 #79 + '189O' // 7d17 #4928 + '95F' // 7d18 #2475 + '222D' // 7d19 #5775 + '235P' // 7d1a #6125 + '196D' // 7d1b #5099 + '33O' // 7d1c #872 + 'a33M' // 7d1d-7d1e #870 + '11H' // 7d1f #293 + '223S' // 7d20 #5816 + '153E' // 7d21 #3982 + '224F' // 7d22 #5829 + '3B' // 7d23 #79 + '11H' // 7d24 #293 + '33N' // 7d25 #871 + '36A' // 7d26 #936 + '2C' // 7d27 #54 + '11H' // 7d28 #293 + '33N' // 7d29 #871 + '36A' // 7d2a #936 + '203V' // 7d2b #5299 + '94P' // 7d2c #2459 + '36A' // 7d2d #936 + '150L' // 7d2e #3911 + '208Z' // 7d2f #5433 + '68N' // 7d30 #1781 + '33M' // 7d31 #870 + '29G' // 7d32 #760 + '148T' // 7d33 #3867 + 'A' // 7d34 + '56K' // 7d35 #1466 + '11H' // 7d36 #293 + 'A' // 7d37 + '33N' // 7d38 #871 + '68N' // 7d39 #1781 + '94S' // 7d3a #2462 + 'A' // 7d3b + '29G' // 7d3c #760 + '3B' // 7d3d #79 + 'a29G' // 7d3e-7d3f #760 + '33M' // 7d40 #870 + '29G' // 7d41 #760 + '223R' // 7d42 #5815 + '56K' // 7d43 #1466 + '237D' // 7d44 #6165 + '94O' // 7d45 #2458 + '125N' // 7d46 #3263 + 'a3B' // 7d47-7d48 #79 + 'aA' // 7d49-7d4a + '36A' // 7d4b #936 + '259P' // 7d4c #6749 + '94N' // 7d4d #2457 + '29G' // 7d4e #760 + '33M' // 7d4f #870 + '243T' // 7d50 #6337 + '36A' // 7d51 #936 + 'A' // 7d52 + '29G' // 7d53 #760 + '33N' // 7d54 #871 + '217F' // 7d55 #5647 + '33M' // 7d56 #870 + '4H' // 7d57 #111 + '11H' // 7d58 #293 + '4H' // 7d59 #111 + '38H' // 7d5a #995 + 'a29E' // 7d5b-7d5c #758 + '29F' // 7d5d #759 + '131D' // 7d5e #3409 + '45C' // 7d5f #1172 + 'A' // 7d60 + '241U' // 7d61 #6286 + '130M' // 7d62 #3392 + '29E' // 7d63 #758 + 'A' // 7d64 + '4H' // 7d65 #111 + '68P' // 7d66 #1783 + '29F' // 7d67 #759 + '179X' // 7d68 #4677 + 'A' // 7d69 + '56J' // 7d6a #1465 + 'a11H' // 7d6b-7d6c #293 + '45C' // 7d6d #1172 + '66B' // 7d6e #1717 + '11H' // 7d6f #293 + '29F' // 7d70 #759 + '68P' // 7d71 #1783 + '224Z' // 7d72 #5849 + '56J' // 7d73 #1465 + 'A' // 7d74 + '258G' // 7d75 #6714 + '258W' // 7d76 #6730 + '11H' // 7d77 #293 + '4H' // 7d78 #111 + '94R' // 7d79 #2461 + '29E' // 7d7a #758 + '29F' // 7d7b #759 + 'A' // 7d7c + '29F' // 7d7d #759 + '11H' // 7d7e #293 + '69Z' // 7d7f #1819 + '45C' // 7d80 #1172 + '178E' // 7d81 #4632 + '4H' // 7d82 #111 + '29F' // 7d83 #759 + '33N' // 7d84 #871 + '49S' // 7d85 #1292 + '29E' // 7d86 #758 + '11H' // 7d87 #293 + 'a29E' // 7d88-7d89 #758 + '11H' // 7d8a #293 + '29E' // 7d8b #758 + '9D' // 7d8c #237 + '18F' // 7d8d #473 + '94V' // 7d8e #2465 + '29D' // 7d8f #757 + 'A' // 7d90 + '18F' // 7d91 #473 + 'A' // 7d92 + '239R' // 7d93 #6231 + '11H' // 7d94 #293 + '22Q' // 7d95 #588 + '18F' // 7d96 #473 + '38H' // 7d97 #995 + '11H' // 7d98 #293 + '257M' // 7d99 #6694 + '259X' // 7d9a #6757 + '4H' // 7d9b #111 + '67Y' // 7d9c #1766 + 'a18F' // 7d9d-7d9e #473 + '49S' // 7d9f #1292 + '67Y' // 7da0 #1766 + 'A' // 7da1 + '29D' // 7da2 #757 + '9D' // 7da3 #237 + '29B' // 7da4 #755 + 'A' // 7da5 + '18F' // 7da6 #473 + '4H' // 7da7 #111 + '29B' // 7da8 #755 + 'A' // 7da9 + '18F' // 7daa #473 + '138Y' // 7dab #3612 + '29D' // 7dac #757 + '227G' // 7dad #5908 + 'b18F' // 7dae-7db0 #473 + '160W' // 7db1 #4182 + '246F' // 7db2 #6401 + '9D' // 7db3 #237 + '142U' // 7db4 #3712 + '29D' // 7db5 #757 + '49S' // 7db6 #1292 + '18F' // 7db7 #473 + '138X' // 7db8 #3611 + '9D' // 7db9 #237 + '155H' // 7dba #4037 + '148A' // 7dbb #3848 + '11H' // 7dbc #293 + '29D' // 7dbd #757 + '130Y' // 7dbe #3404 + '175M' // 7dbf #4562 + '4H' // 7dc0 #111 + '94W' // 7dc1 #2466 + 'a4H' // 7dc2-7dc3 #111 + '18F' // 7dc4 #473 + 'a9D' // 7dc5-7dc6 #237 + '29D' // 7dc7 #757 + '11H' // 7dc8 #293 + 'A' // 7dc9 + '210U' // 7dca #5480 + '141Z' // 7dcb #3691 + 'a18F' // 7dcc-7dcd #473 + '9D' // 7dce #237 + '94U' // 7dcf #2464 + '38H' // 7dd0 #995 + '256X' // 7dd1 #6679 + '191R' // 7dd2 #4983 + '29B' // 7dd3 #755 + '56L' // 7dd4 #1467 + '4H' // 7dd5 #111 + '248W' // 7dd6 #6470 + '13Y' // 7dd7 #362 + '29C' // 7dd8 #756 + '9D' // 7dd9 #237 + '242L' // 7dda #6303 + '29H' // 7ddb #761 + '13Y' // 7ddc #362 + '64Q' // 7ddd #1680 + '122G' // 7dde #3178 + '5C' // 7ddf #132 + '143L' // 7de0 #3729 + '33L' // 7de1 #869 + '19G' // 7de2 #500 + '199W' // 7de3 #5196 + 'a45B' // 7de4-7de5 #1171 + '13Y' // 7de6 #362 + 'A' // 7de7 + '237C' // 7de8 #6164 + '196N' // 7de9 #5109 + 'a19G' // 7dea-7deb #500 + '65R' // 7dec #1707 + '19G' // 7ded #500 + 'A' // 7dee + '160R' // 7def #4177 + '22Q' // 7df0 #588 + 'a13Y' // 7df1-7df2 #362 + '29H' // 7df3 #761 + '212C' // 7df4 #5514 + '45B' // 7df5 #1171 + '9D' // 7df6 #237 + '5C' // 7df7 #132 + 'A' // 7df8 + '145D' // 7df9 #3773 + '4H' // 7dfa #111 + '194N' // 7dfb #5057 + '56L' // 7dfc #1467 + '29B' // 7dfd #755 + '29H' // 7dfe #761 + '5C' // 7dff #132 + '19G' // 7e00 #500 + '256N' // 7e01 #6669 + '5C' // 7e02 #132 + 'A' // 7e03 + '257X' // 7e04 #6705 + '4H' // 7e05 #111 + 'A' // 7e06 + '29B' // 7e07 #755 + '13Y' // 7e08 #362 + 'a29C' // 7e09-7e0a #756 + '13Y' // 7e0b #362 + 'cA' // 7e0c-7e0f + 'a9D' // 7e10-7e11 #237 + '19G' // 7e12 #500 + '22Q' // 7e13 #588 + 'A' // 7e14 + '33L' // 7e15 #869 + 'A' // 7e16 + '4H' // 7e17 #111 + 'bA' // 7e18-7e1a + '130T' // 7e1b #3399 + '4H' // 7e1c #111 + '33L' // 7e1d #869 + '94Q' // 7e1e #2460 + '33L' // 7e1f #869 + '13Y' // 7e20 #362 + '69Z' // 7e21 #1819 + '13Y' // 7e22 #362 + '214K' // 7e23 #5574 + 'A' // 7e24 + '22Q' // 7e25 #588 + '255V' // 7e26 #6651 + '9D' // 7e27 #237 + '4H' // 7e28 #111 + '29H' // 7e29 #761 + 'A' // 7e2a + '175B' // 7e2b #4551 + '4H' // 7e2c #111 + '9D' // 7e2d #237 + '204H' // 7e2e #5311 + '29C' // 7e2f #756 + '22Q' // 7e30 #588 + '170V' // 7e31 #4441 + 'a9D' // 7e32-7e33 #237 + '22Q' // 7e34 #588 + 'a9D' // 7e35-7e36 #237 + '29C' // 7e37 #756 + 'A' // 7e38 + '13Y' // 7e39 #362 + '19G' // 7e3a #500 + '13Y' // 7e3b #362 + '5C' // 7e3c #132 + '232I' // 7e3d #6040 + '198K' // 7e3e #5158 + '4H' // 7e3f #111 + '5C' // 7e40 #132 + '215W' // 7e41 #5612 + 'A' // 7e42 + '132V' // 7e43 #3453 + '13Y' // 7e44 #362 + '9D' // 7e45 #237 + '145C' // 7e46 #3772 + '29C' // 7e47 #756 + '9D' // 7e48 #237 + 'A' // 7e49 + '254W' // 7e4a #6626 + '256E' // 7e4b #6660 + 'A' // 7e4c + '254C' // 7e4d #6606 + '19G' // 7e4e #500 + 'A' // 7e4f + '9D' // 7e50 #237 + '29H' // 7e51 #761 + '33L' // 7e52 #869 + 'A' // 7e53 + '211P' // 7e54 #5501 + '142B' // 7e55 #3693 + '13Y' // 7e56 #362 + 'A' // 7e57 + 'b13Y' // 7e58-7e5a #362 + '29H' // 7e5b #761 + 'A' // 7e5c + '19G' // 7e5d #500 + '66R' // 7e5e #1733 + '4H' // 7e5f #111 + 'A' // 7e60 + '157C' // 7e61 #4084 + '9D' // 7e62 #237 + 'aA' // 7e63-7e64 + '38H' // 7e65 #995 + '19G' // 7e66 #500 + '45B' // 7e67 #1171 + '22Q' // 7e68 #588 + '66R' // 7e69 #1733 + '192O' // 7e6a #5006 + '207A' // 7e6b #5382 + '29B' // 7e6c #755 + '29C' // 7e6d #756 + 'a9D' // 7e6e-7e6f #237 + '94T' // 7e70 #2463 + 'aA' // 7e71-7e72 + '178D' // 7e73 #4631 + 'A' // 7e74 + '4H' // 7e75 #111 + '22Q' // 7e76 #588 + '5C' // 7e77 #132 + '33K' // 7e78 #868 + '65R' // 7e79 #1707 + 'A' // 7e7a + '29A' // 7e7b #754 + '217E' // 7e7c #5646 + '157D' // 7e7d #4085 + '33K' // 7e7e #868 + '56H' // 7e7f #1463 + 'A' // 7e80 + '29A' // 7e81 #754 + '38G' // 7e82 #994 + '4H' // 7e83 #111 + 'aA' // 7e84-7e85 + 'b33K' // 7e86-7e88 #868 + '4H' // 7e89 #111 + '33K' // 7e8a #868 + 'A' // 7e8b + '224Y' // 7e8c #5848 + '29A' // 7e8d #754 + '33K' // 7e8e #868 + '159Y' // 7e8f #4158 + 'a4H' // 7e90-7e91 #111 + '29A' // 7e92 #754 + '38G' // 7e93 #994 + '29A' // 7e94 #754 + '4H' // 7e95 #111 + '185K' // 7e96 #4820 + 'A' // 7e97 + '38G' // 7e98 #994 + '94M' // 7e99 #2456 + '29A' // 7e9a #754 + '38G' // 7e9b #994 + '64Q' // 7e9c #1680 + '4H' // 7e9d #111 + '19G' // 7e9e #500 + '94E' // 7e9f #2448 + '3Y' // 7ea0 #102 + '5C' // 7ea1 #132 + '1Z' // 7ea2 #51 + '5C' // 7ea3 #132 + '94K' // 7ea4 #2454 + '5C' // 7ea5 #132 + '1Z' // 7ea6 #51 + '3Q' // 7ea7 #94 + 'a5C' // 7ea8-7ea9 #132 + '2D' // 7eaa #55 + '5C' // 7eab #132 + '94H' // 7eac #2451 + 'a5C' // 7ead-7eae #132 + '2C' // 7eaf #54 + '5C' // 7eb0 #132 + '1R' // 7eb1 #43 + '2R' // 7eb2 #69 + '2C' // 7eb3 #54 + 'A' // 7eb4 + '4C' // 7eb5 #106 + '2W' // 7eb6 #74 + '1R' // 7eb7 #43 + '2C' // 7eb8 #54 + '3Y' // 7eb9 #102 + '94J' // 7eba #2453 + '5C' // 7ebb #132 + 'A' // 7ebc + '4C' // 7ebd #106 + '5C' // 7ebe #132 + '7K' // 7ebf #192 + 'b5C' // 7ec0-7ec2 #132 + '3I' // 7ec3 #86 + '1Z' // 7ec4 #51 + '3X' // 7ec5 #101 + '1Z' // 7ec6 #51 + '94L' // 7ec7 #2455 + '2D' // 7ec8 #55 + '5C' // 7ec9 #132 + '2W' // 7eca #74 + 'a5C' // 7ecb-7ecc #132 + '1Z' // 7ecd #51 + '2Y' // 7ece #76 + '35T' // 7ecf #929 + '5C' // 7ed0 #132 + '1R' // 7ed1 #43 + '2Y' // 7ed2 #76 + '3Q' // 7ed3 #94 + '5C' // 7ed4 #132 + '1R' // 7ed5 #43 + 'A' // 7ed6 + '5C' // 7ed7 #132 + '3Y' // 7ed8 #102 + '3Q' // 7ed9 #94 + '3G' // 7eda #84 + '5C' // 7edb #132 + '1Z' // 7edc #51 + '2D' // 7edd #55 + '3G' // 7ede #84 + '155T' // 7edf #4049 + 'b5C' // 7ee0-7ee2 #132 + '2Y' // 7ee3 #76 + 'A' // 7ee4 + 'a5C' // 7ee5-7ee6 #132 + '2C' // 7ee7 #54 + '5C' // 7ee8 #132 + '3N' // 7ee9 #91 + '2L' // 7eea #63 + '5C' // 7eeb #132 + 'A' // 7eec + '2D' // 7eed #55 + '3X' // 7eee #101 + '2K' // 7eef #62 + 'b5X' // 7ef0-7ef2 #153 + '2Y' // 7ef3 #76 + '1Z' // 7ef4 #51 + '2L' // 7ef5 #63 + '5X' // 7ef6 #153 + '2W' // 7ef7 #74 + '2K' // 7ef8 #62 + 'A' // 7ef9 + 'a5X' // 7efa-7efb #153 + '2D' // 7efc #55 + '2K' // 7efd #62 + '5X' // 7efe #153 + '2C' // 7eff #54 + '2K' // 7f00 #62 + 'c5X' // 7f01-7f04 #153 + '2R' // 7f05 #69 + '94I' // 7f06 #2452 + '2W' // 7f07 #74 + '5X' // 7f08 #153 + '2W' // 7f09 #74 + 'h5X' // 7f0a-7f12 #153 + '3N' // 7f13 #91 + '3X' // 7f14 #101 + '2W' // 7f15 #74 + '3Q' // 7f16 #94 + '5X' // 7f17 #153 + '3Y' // 7f18 #102 + '5X' // 7f19 #153 + '3X' // 7f1a #101 + 'a5X' // 7f1b-7f1c #153 + '2L' // 7f1d #63 + 'A' // 7f1e + '5X' // 7f1f #153 + '2R' // 7f20 #69 + 'b5X' // 7f21-7f23 #153 + '3G' // 7f24 #84 + 'c5X' // 7f25-7f28 #153 + '3Y' // 7f29 #102 + 'i5X' // 7f2a-7f33 #153 + '2R' // 7f34 #69 + '5X' // 7f35 #153 + '94G' // 7f36 #2450 + '44Z' // 7f37 #1169 + '66B' // 7f38 #1717 + 'A' // 7f39 + '207B' // 7f3a #5383 + 'a4H' // 7f3b-7f3c #111 + '16P' // 7f3d #431 + 'a4H' // 7f3e-7f3f #111 + 'a93Z' // 7f40-7f41 #2443 + '5X' // 7f42 #153 + '44Y' // 7f43 #1168 + 'a16P' // 7f44-7f45 #431 + 'A' // 7f46 + '44Y' // 7f47 #1168 + 'c45A' // 7f48-7f4b #1170 + '56I' // 7f4c #1464 + '16P' // 7f4d #431 + '44Y' // 7f4e #1168 + '4H' // 7f4f #111 + '178C' // 7f50 #4630 + '187Z' // 7f51 #4887 + '16P' // 7f52 #431 + '56H' // 7f53 #1463 + '56I' // 7f54 #1464 + '163T' // 7f55 #4257 + 'A' // 7f56 + '2C' // 7f57 #54 + '16P' // 7f58 #431 + '5X' // 7f59 #153 + '4C' // 7f5a #106 + 'a4H' // 7f5b-7f5c #111 + '16P' // 7f5d #431 + 'A' // 7f5e + '16P' // 7f5f #431 + '94F' // 7f60 #2449 + '16P' // 7f61 #431 + '2R' // 7f62 #69 + '16P' // 7f63 #431 + '4H' // 7f64 #111 + '16P' // 7f65 #431 + 'a4H' // 7f66-7f67 #111 + '16P' // 7f68 #431 + '185L' // 7f69 #4821 + '197R' // 7f6a #5139 + '94C' // 7f6b #2446 + 'A' // 7f6c + '4H' // 7f6d #111 + '230V' // 7f6e #6001 + 'A' // 7f6f + '181N' // 7f70 #4719 + '94D' // 7f71 #2447 + '188Z' // 7f72 #4913 + 'a5X' // 7f73-7f74 #153 + '180N' // 7f75 #4693 + 'A' // 7f76 + '66A' // 7f77 #1716 + '28Z' // 7f78 #753 + '150K' // 7f79 #3910 + 'bA' // 7f7a-7f7c + 'a25J' // 7f7d-7f7e #659 + 'a1V' // 7f7f-7f80 #47 + '2W' // 7f81 #74 + '1V' // 7f82 #47 + '28Z' // 7f83 #753 + 'A' // 7f84 + '219C' // 7f85 #5696 + '25J' // 7f86 #659 + '28Z' // 7f87 #753 + '122F' // 7f88 #3177 + '5X' // 7f89 #153 + '196S' // 7f8a #5114 + '25J' // 7f8b #659 + '44X' // 7f8c #1167 + '28Z' // 7f8d #753 + '244R' // 7f8e #6361 + '94A' // 7f8f #2444 + '41N' // 7f90 #1079 + '25J' // 7f91 #659 + 'A' // 7f92 + '45A' // 7f93 #1170 + '44X' // 7f94 #1167 + '44Z' // 7f95 #1169 + '41N' // 7f96 #1079 + '28Z' // 7f97 #753 + 'a5X' // 7f98-7f99 #153 + '44X' // 7f9a #1167 + '5X' // 7f9b #153 + '1V' // 7f9c #47 + '25J' // 7f9d #659 + '173M' // 7f9e #4510 + '5X' // 7f9f #153 + 'A' // 7fa0 + '94B' // 7fa1 #2445 + '28Z' // 7fa2 #753 + '25J' // 7fa3 #659 + '229E' // 7fa4 #5958 + '45A' // 7fa5 #1170 + '1V' // 7fa6 #47 + '44Z' // 7fa7 #1169 + '160Q' // 7fa8 #4176 + '228W' // 7fa9 #5950 + '1V' // 7faa #47 + 'A' // 7fab + '5X' // 7fac #153 + 'b25J' // 7fad-7faf #659 + 'a93X' // 7fb0-7fb1 #2441 + '93J' // 7fb2 #2427 + 'A' // 7fb3 + '56F' // 7fb4 #1461 + 'A' // 7fb5 + '56F' // 7fb6 #1461 + 'A' // 7fb7 + '25H' // 7fb8 #657 + '127W' // 7fb9 #3324 + 'aA' // 7fba-7fbb + '11Y' // 7fbc #310 + '198T' // 7fbd #5167 + 'A' // 7fbe + 'a11Y' // 7fbf-7fc0 #310 + '172U' // 7fc1 #4492 + 'A' // 7fc2 + '11Y' // 7fc3 #310 + 'A' // 7fc4 + '157A' // 7fc5 #4082 + '1V' // 7fc6 #47 + 'A' // 7fc7 + '1V' // 7fc8 #47 + 'A' // 7fc9 + '127V' // 7fca #3323 + '25I' // 7fcb #658 + '93U' // 7fcc #2438 + 'A' // 7fcd + '25H' // 7fce #657 + '13X' // 7fcf #361 + 'A' // 7fd0 + '25I' // 7fd1 #658 + '229R' // 7fd2 #5971 + 'A' // 7fd3 + '190B' // 7fd4 #4941 + '25H' // 7fd5 #657 + 'aA' // 7fd6-7fd7 + '2R' // 7fd8 #69 + 'a5X' // 7fd9-7fda #153 + '11Y' // 7fdb #310 + 'A' // 7fdc + '33J' // 7fdd #867 + '25I' // 7fde #658 + '25H' // 7fdf #657 + '187X' // 7fe0 #4885 + '156Z' // 7fe1 #4081 + 'A' // 7fe2 + '41N' // 7fe3 #1079 + 'A' // 7fe4 + 'a11Y' // 7fe5-7fe6 #310 + '93W' // 7fe7 #2440 + '1V' // 7fe8 #47 + '25H' // 7fe9 #657 + 'A' // 7fea + '25H' // 7feb #657 + '11Y' // 7fec #310 + 'A' // 7fed + '11Y' // 7fee #310 + '41N' // 7fef #1079 + '163S' // 7ff0 #4256 + '137M' // 7ff1 #3574 + '13X' // 7ff2 #361 + '11Y' // 7ff3 #310 + '5X' // 7ff4 #153 + 'cA' // 7ff5-7ff8 + '66A' // 7ff9 #1716 + '11Y' // 7ffa #310 + '218Z' // 7ffb #5693 + '175P' // 7ffc #4565 + '13X' // 7ffd #361 + '11Y' // 7ffe #310 + '1V' // 7fff #47 + '194K' // 8000 #5054 + '234T' // 8001 #6103 + '13X' // 8002 #361 + '231F' // 8003 #6011 + '11Y' // 8004 #310 + '35V' // 8005 #931 + '25H' // 8006 #657 + '1V' // 8007 #47 + '22P' // 8008 #587 + '71C' // 8009 #1848 + '1V' // 800a #47 + '11Y' // 800b #310 + '232H' // 800c #6039 + '157B' // 800d #4083 + '11Y' // 800e #310 + '1V' // 800f #47 + '204I' // 8010 #5312 + 'a11Y' // 8011-8012 #310 + '1V' // 8013 #47 + '11Y' // 8014 #310 + '160I' // 8015 #4168 + '4Z' // 8016 #129 + '188I' // 8017 #4896 + '93I' // 8018 #2426 + '4Z' // 8019 #129 + 'aA' // 801a-801b + '4Z' // 801c #129 + '22P' // 801d #587 + '26Z' // 801e #701 + '1V' // 801f #47 + '22P' // 8020 #587 + '1V' // 8021 #47 + 'aA' // 8022-8023 + '13X' // 8024 #361 + '33J' // 8025 #867 + '4Z' // 8026 #129 + '6G' // 8027 #162 + '4Z' // 8028 #129 + 'a6G' // 8029-802a #162 + 'A' // 802b + '13X' // 802c #361 + '71C' // 802d #1848 + '22P' // 802e #587 + '33J' // 802f #867 + '13X' // 8030 #361 + '28Y' // 8031 #752 + 'A' // 8032 + '210R' // 8033 #5477 + '26Z' // 8034 #701 + '4Z' // 8035 #129 + '195G' // 8036 #5076 + '4Z' // 8037 #129 + '2W' // 8038 #74 + '13X' // 8039 #361 + '1V' // 803a #47 + '93L' // 803b #2429 + '22P' // 803c #587 + '127U' // 803d #3322 + '1V' // 803e #47 + '64P' // 803f #1679 + '1V' // 8040 #47 + 'A' // 8041 + '3W' // 8042 #100 + '18E' // 8043 #472 + '1V' // 8044 #47 + 'A' // 8045 + '64P' // 8046 #1679 + 'bA' // 8047-8049 + '206Y' // 804a #5380 + '6G' // 804b #162 + '2D' // 804c #55 + '6G' // 804d #162 + 'cA' // 804e-8051 + '4Z' // 8052 #129 + 'A' // 8053 + '49C' // 8054 #1276 + 'A' // 8055 + '215J' // 8056 #5599 + 'A' // 8057 + '178B' // 8058 #4629 + '6G' // 8059 #162 + '206Z' // 805a #5381 + '33J' // 805b #867 + 'aA' // 805c-805d + '236I' // 805e #6144 + 'a1V' // 805f-8060 #47 + '93R' // 8061 #2435 + '22P' // 8062 #587 + '33J' // 8063 #867 + '1V' // 8064 #47 + 'A' // 8065 + '22P' // 8066 #587 + 'A' // 8067 + '26Z' // 8068 #701 + '6G' // 8069 #162 + '2L' // 806a #63 + 'aA' // 806b-806c + '1V' // 806d #47 + '6G' // 806e #162 + '239Q' // 806f #6230 + '178A' // 8070 #4628 + '4Z' // 8071 #129 + '232G' // 8072 #6038 + '18E' // 8073 #472 + '257Q' // 8074 #6698 + '13X' // 8075 #361 + '4Z' // 8076 #129 + '223B' // 8077 #5799 + '6G' // 8078 #162 + '13X' // 8079 #361 + 'A' // 807a + '1V' // 807b #47 + '6G' // 807c #162 + '224X' // 807d #5847 + 'a18E' // 807e-807f #472 + '93K' // 8080 #2428 + '1V' // 8081 #47 + '6G' // 8082 #162 + '4C' // 8083 #106 + '18E' // 8084 #472 + '145B' // 8085 #3771 + '150I' // 8086 #3908 + '138V' // 8087 #3609 + '26Z' // 8088 #701 + '222B' // 8089 #5773 + 'A' // 808a + '132T' // 808b #3451 + '204Q' // 808c #5320 + 'A' // 808d + '1V' // 808e #47 + '20G' // 808f #526 + 'bA' // 8090-8092 + '4Z' // 8093 #129 + 'A' // 8094 + '25I' // 8095 #658 + '173R' // 8096 #4515 + 'A' // 8097 + '93O' // 8098 #2432 + '1V' // 8099 #47 + '192N' // 809a #5005 + '136M' // 809b #3548 + '4Z' // 809c #129 + '175G' // 809d #4556 + '1V' // 809e #47 + '20G' // 809f #526 + '4C' // 80a0 #106 + '227D' // 80a1 #5905 + '161P' // 80a2 #4201 + 'A' // 80a3 + '248V' // 80a4 #6469 + '202G' // 80a5 #5258 + '1V' // 80a6 #47 + '13X' // 80a7 #361 + 'A' // 80a8 + '190L' // 80a9 #4951 + '161C' // 80aa #4188 + '4Z' // 80ab #129 + '1V' // 80ac #47 + '4Z' // 80ad #129 + '20G' // 80ae #526 + '201H' // 80af #5233 + 'A' // 80b0 + '18E' // 80b1 #472 + '230O' // 80b2 #5994 + 'A' // 80b3 + '93Q' // 80b4 #2434 + '25I' // 80b5 #658 + '20G' // 80b6 #526 + '28Y' // 80b7 #752 + '4Z' // 80b8 #129 + '1V' // 80b9 #47 + '160A' // 80ba #4160 + 'A' // 80bb + 'a20G' // 80bc-80bd #526 + '2R' // 80be #69 + '2L' // 80bf #63 + 'a2R' // 80c0-80c1 #69 + '20G' // 80c2 #526 + '182G' // 80c3 #4738 + '18E' // 80c4 #472 + '1V' // 80c5 #47 + '93S' // 80c6 #2436 + '25I' // 80c7 #658 + '1V' // 80c8 #47 + 'A' // 80c9 + '26Z' // 80ca #701 + 'A' // 80cb + '220Z' // 80cc #5745 + '4Z' // 80cd #129 + '194X' // 80ce #5067 + '13X' // 80cf #361 + 'A' // 80d0 + '6G' // 80d1 #162 + '1V' // 80d2 #47 + 'A' // 80d3 + '4Z' // 80d4 #129 + '1V' // 80d5 #47 + '185J' // 80d6 #4819 + '4Z' // 80d7 #129 + '1V' // 80d8 #47 + '4Z' // 80d9 #129 + '122E' // 80da #3176 + '18E' // 80db #472 + '93Y' // 80dc #2442 + '4Z' // 80dd #129 + '174S' // 80de #4542 + 'A' // 80df + '4Z' // 80e0 #129 + '195J' // 80e1 #5079 + 'A' // 80e2 + '25I' // 80e3 #658 + 'a18E' // 80e4-80e5 #472 + '1V' // 80e6 #47 + 'a6G' // 80e7-80e8 #162 + '28Y' // 80e9 #752 + 'a6G' // 80ea-80eb #162 + '20G' // 80ec #526 + '4Z' // 80ed #129 + '1V' // 80ee #47 + 'a4Z' // 80ef-80f0 #129 + '18E' // 80f1 #472 + '1V' // 80f2 #47 + '4Z' // 80f3 #129 + '93P' // 80f4 #2433 + '1V' // 80f5 #47 + '93N' // 80f6 #2431 + '1V' // 80f7 #47 + '198D' // 80f8 #5151 + '1V' // 80f9 #47 + '132U' // 80fa #3452 + '1V' // 80fb #47 + '4Z' // 80fc #129 + '244W' // 80fd #6366 + '13X' // 80fe #361 + 'aA' // 80ff-8100 + '4Z' // 8101 #129 + '190F' // 8102 #4945 + '22P' // 8103 #587 + '6G' // 8104 #162 + '181M' // 8105 #4718 + '180S' // 8106 #4698 + '93T' // 8107 #2437 + '175F' // 8108 #4555 + '93M' // 8109 #2430 + '152Y' // 810a #3976 + '1V' // 810b #47 + '28Y' // 810c #752 + '26Z' // 810d #701 + '28Y' // 810e #752 + '4C' // 810f #106 + '2W' // 8110 #74 + '2D' // 8111 #55 + '20G' // 8112 #526 + '6G' // 8113 #162 + '28Y' // 8114 #752 + '20G' // 8115 #526 + '138W' // 8116 #3610 + '4Z' // 8117 #129 + '18E' // 8118 #472 + '6G' // 8119 #162 + '93V' // 811a #2439 + '56D' // 811b #1459 + '26Z' // 811c #701 + '6G' // 811d #162 + '56E' // 811e #1460 + '6G' // 811f #162 + '1V' // 8120 #47 + 'a56G' // 8121-8122 #1462 + '56D' // 8123 #1459 + '56E' // 8124 #1460 + '56G' // 8125 #1462 + '6G' // 8126 #162 + '38E' // 8127 #992 + '6G' // 8128 #162 + '56A' // 8129 #1456 + '44W' // 812a #1166 + '199V' // 812b #5195 + '38E' // 812c #992 + 'a6G' // 812d-812e #162 + '56A' // 812f #1456 + '38E' // 8130 #992 + '93F' // 8131 #2423 + '28X' // 8132 #751 + '256H' // 8133 #6663 + '28X' // 8134 #751 + '1V' // 8135 #47 + 'A' // 8136 + '28X' // 8137 #751 + '3I' // 8138 #86 + '150H' // 8139 #3907 + '56B' // 813a #1457 + 'A' // 813b + '1V' // 813c #47 + '56B' // 813d #1457 + '150J' // 813e #3909 + 'a6G' // 813f-8140 #162 + '1V' // 8141 #47 + '44W' // 8142 #1166 + 'A' // 8143 + '25G' // 8144 #656 + '1V' // 8145 #47 + '38E' // 8146 #992 + '1V' // 8147 #47 + '28X' // 8148 #751 + 'A' // 8149 + '92X' // 814a #2415 + '124O' // 814b #3238 + '92W' // 814c #2414 + '28X' // 814d #751 + '159X' // 814e #4157 + 'A' // 814f + '190A' // 8150 #4940 + '92T' // 8151 #2411 + '2J' // 8152 #61 + '55Z' // 8153 #1455 + '166S' // 8154 #4334 + '176S' // 8155 #4594 + '25G' // 8156 #656 + '2J' // 8157 #61 + '6G' // 8158 #162 + '44W' // 8159 #1166 + '28X' // 815a #751 + 'bA' // 815b-815d + '6G' // 815e #162 + '2J' // 815f #61 + '55Z' // 8160 #1455 + '2J' // 8161 #61 + 'bA' // 8162-8164 + '144Z' // 8165 #3769 + '224V' // 8166 #5845 + '4O' // 8167 #118 + '21H' // 8168 #553 + '4O' // 8169 #118 + 'A' // 816a + '174O' // 816b #4538 + '38D' // 816c #991 + '4O' // 816d #118 + '127T' // 816e #3321 + '11X' // 816f #309 + '197N' // 8170 #5135 + '16O' // 8171 #430 + 'A' // 8172 + '214J' // 8173 #5573 + '4O' // 8174 #118 + 'aA' // 8175-8176 + '2J' // 8177 #61 + '189W' // 8178 #4936 + '183O' // 8179 #4772 + '153W' // 817a #4000 + '2Y' // 817b #76 + '28W' // 817c #750 + '38F' // 817d #993 + '2D' // 817e #55 + '201D' // 817f #5229 + '156X' // 8180 #4079 + '2J' // 8181 #61 + '4O' // 8182 #118 + '2J' // 8183 #61 + '20F' // 8184 #525 + 'a2J' // 8185-8186 #61 + 'A' // 8187 + '16O' // 8188 #430 + 'A' // 8189 + '55Y' // 818a #1454 + '2J' // 818b #61 + 'aA' // 818c-818d + '2J' // 818e #61 + '180K' // 818f #4690 + '2J' // 8190 #61 + '16G' // 8191 #422 + 'A' // 8192 + '20F' // 8193 #525 + 'A' // 8194 + '11X' // 8195 #309 + '2J' // 8196 #61 + 'A' // 8197 + '4O' // 8198 #118 + '16G' // 8199 #422 + '202L' // 819a #5263 + '4O' // 819b #118 + '195Q' // 819c #5086 + '161B' // 819d #4187 + '11X' // 819e #309 + 'A' // 819f + '206W' // 81a0 #5378 + 'A' // 81a1 + '2J' // 81a2 #61 + '93C' // 81a3 #2420 + '2J' // 81a4 #61 + '38F' // 81a5 #993 + '28W' // 81a6 #750 + '25G' // 81a7 #656 + '142Z' // 81a8 #3717 + '177Z' // 81a9 #4627 + '38D' // 81aa #991 + '25G' // 81ab #656 + 'aA' // 81ac-81ad + '2J' // 81ae #61 + 'A' // 81af + '11X' // 81b0 #309 + 'A' // 81b1 + '2J' // 81b2 #61 + '154A' // 81b3 #4004 + '2J' // 81b4 #61 + '33I' // 81b5 #866 + '28W' // 81b6 #750 + 'A' // 81b7 + '2J' // 81b8 #61 + 'A' // 81b9 + '16O' // 81ba #430 + '4O' // 81bb #118 + 'A' // 81bc + '185I' // 81bd #4818 + '33I' // 81be #866 + '92Z' // 81bf #2417 + '156Y' // 81c0 #4080 + '92V' // 81c1 #2413 + '163Q' // 81c2 #4254 + '4O' // 81c3 #118 + 'A' // 81c4 + '2J' // 81c5 #61 + '16O' // 81c6 #430 + 'A' // 81c7 + '20F' // 81c8 #525 + '217D' // 81c9 #5645 + '4O' // 81ca #118 + '2J' // 81cb #61 + '28W' // 81cc #750 + '122B' // 81cd #3173 + '2J' // 81ce #61 + '11X' // 81cf #309 + 'A' // 81d0 + '4O' // 81d1 #118 + '25G' // 81d2 #656 + '254Z' // 81d3 #6629 + '16G' // 81d4 #422 + 'a2J' // 81d5-81d6 #61 + '11X' // 81d7 #309 + '66G' // 81d8 #1722 + 'a11X' // 81d9-81da #309 + '21H' // 81db #553 + '16G' // 81dc #422 + '11X' // 81dd #309 + '4O' // 81de #118 + '170T' // 81df #4439 + 'a11X' // 81e0-81e1 #309 + '25G' // 81e2 #656 + '167U' // 81e3 #4362 + '20F' // 81e4 #525 + '66G' // 81e5 #1722 + 'A' // 81e6 + '16O' // 81e7 #430 + '210I' // 81e8 #5468 + '16G' // 81e9 #422 + '68Z' // 81ea #1793 + '21H' // 81eb #553 + '4O' // 81ec #118 + '182Y' // 81ed #4756 + '16G' // 81ee #422 + '4O' // 81ef #118 + 'b2J' // 81f0-81f2 #61 + '233V' // 81f3 #6079 + '222C' // 81f4 #5774 + '21H' // 81f5 #553 + '20F' // 81f6 #525 + 'A' // 81f7 + '21H' // 81f8 #553 + '2J' // 81f9 #61 + '68B' // 81fa #1769 + '144Y' // 81fb #3768 + '56C' // 81fc #1458 + '2J' // 81fd #61 + '16O' // 81fe #430 + '2J' // 81ff #61 + 'b4O' // 8200-8202 #118 + '2J' // 8203 #61 + '4O' // 8204 #118 + '55Y' // 8205 #1454 + '2R' // 8206 #69 + '240I' // 8207 #6248 + '229D' // 8208 #5957 + '224W' // 8209 #5846 + '68B' // 820a #1769 + '11X' // 820b #309 + '181T' // 820c #4725 + '177Y' // 820d #4626 + '256D' // 820e #6659 + '21H' // 820f #553 + '93E' // 8210 #2422 + 'A' // 8211 + '206X' // 8212 #5379 + '2J' // 8213 #61 + '138U' // 8214 #3608 + '25G' // 8215 #656 + '192M' // 8216 #5004 + '258Y' // 8217 #6732 + '93A' // 8218 #2418 + '2J' // 8219 #61 + '20F' // 821a #525 + '16O' // 821b #430 + '138T' // 821c #3607 + '11X' // 821d #309 + '212L' // 821e #5523 + '154D' // 821f #4007 + 'A' // 8220 + '16O' // 8221 #430 + '4O' // 8222 #118 + 'a16G' // 8223-8224 #422 + 'A' // 8225 + '38F' // 8226 #993 + '16G' // 8227 #422 + '4O' // 8228 #118 + '11X' // 8229 #309 + '215P' // 822a #5605 + '16O' // 822b #430 + '222R' // 822c #5789 + '38F' // 822d #993 + '2J' // 822e #61 + '28W' // 822f #750 + '1R' // 8230 #43 + '3X' // 8231 #101 + 'b4O' // 8232-8234 #118 + '122C' // 8235 #3174 + '93B' // 8236 #2419 + '16O' // 8237 #430 + '4O' // 8238 #118 + '198J' // 8239 #5157 + '4O' // 823a #118 + '16G' // 823b #422 + '2J' // 823c #61 + 'A' // 823d + '28W' // 823e #750 + 'A' // 823f + '33I' // 8240 #866 + 'aA' // 8241-8242 + '2J' // 8243 #61 + '4O' // 8244 #118 + '33I' // 8245 #866 + '2J' // 8246 #61 + '153G' // 8247 #3984 + 'A' // 8248 + '4O' // 8249 #118 + 'A' // 824a + '4O' // 824b #118 + 'aA' // 824c-824d + '11X' // 824e #309 + '4O' // 824f #118 + 'A' // 8250 + '2J' // 8251 #61 + 'aA' // 8252-8253 + '38D' // 8254 #991 + 'A' // 8255 + '2J' // 8256 #61 + '11X' // 8257 #309 + '122D' // 8258 #3175 + '65Q' // 8259 #1706 + '4O' // 825a #118 + 'A' // 825b + 'a2J' // 825c-825d #61 + 'A' // 825e + '4O' // 825f #118 + '2J' // 8260 #61 + 'A' // 8261 + '20F' // 8262 #525 + '2J' // 8263 #61 + '33I' // 8264 #866 + '38D' // 8265 #991 + '196W' // 8266 #5118 + '2J' // 8267 #61 + '4O' // 8268 #118 + 'A' // 8269 + '2J' // 826a #61 + '11X' // 826b #309 + 'A' // 826c + '2J' // 826d #61 + '16O' // 826e #430 + '223Z' // 826f #5823 + '2R' // 8270 #69 + '65Q' // 8271 #1706 + '243F' // 8272 #6323 + '3N' // 8273 #91 + '2J' // 8274 #61 + 'A' // 8275 + '93D' // 8276 #2421 + '170U' // 8277 #4440 + '56C' // 8278 #1458 + '4O' // 8279 #118 + '64L' // 827a #1675 + '20F' // 827b #525 + 'A' // 827c + '25F' // 827d #655 + '192L' // 827e #5003 + '25F' // 827f #655 + 'a2J' // 8280-8281 #61 + '3Q' // 8282 #94 + 'a25F' // 8283-8284 #655 + 'aA' // 8285-8286 + '20F' // 8287 #525 + '16G' // 8288 #422 + '21H' // 8289 #553 + '25F' // 828a #655 + '160U' // 828b #4180 + 'A' // 828c + 'a92S' // 828d-828e #2410 + 'a93G' // 828f-8290 #2424 + '25F' // 8291 #655 + '163R' // 8292 #4255 + 'a25F' // 8293-8294 #655 + 'A' // 8295 + '21H' // 8296 #553 + '16G' // 8297 #422 + '25F' // 8298 #655 + '163P' // 8299 #4253 + '92U' // 829a #2412 + '92Y' // 829b #2416 + '2K' // 829c #62 + '189X' // 829d #4937 + '93H' // 829e #2425 + '92H' // 829f #2399 + 'a28U' // 82a0-82a1 #748 + '28V' // 82a2 #749 + 'a28U' // 82a3-82a4 #748 + '129W' // 82a5 #3376 + '92O' // 82a6 #2406 + 'a28U' // 82a7-82a8 #748 + '55U' // 82a9 #1450 + 'a28U' // 82aa-82ab #748 + '185H' // 82ac #4817 + '159L' // 82ad #4145 + '55U' // 82ae #1450 + '160N' // 82af #4173 + '28U' // 82b0 #748 + '68S' // 82b1 #1786 + '21H' // 82b2 #553 + '196G' // 82b3 #5102 + '28U' // 82b4 #748 + '16G' // 82b5 #422 + '92Q' // 82b6 #2408 + '127S' // 82b7 #3320 + '149N' // 82b8 #3887 + '125F' // 82b9 #3255 + '92J' // 82ba #2401 + '92G' // 82bb #2398 + '28S' // 82bc #746 + '175E' // 82bd #4554 + '28T' // 82be #747 + '92E' // 82bf #2396 + 'b5B' // 82c0-82c2 #131 + 'A' // 82c3 + '18D' // 82c4 #471 + 'a248U' // 82c5-82c6 #6468 + 'a5B' // 82c7-82c8 #131 + 'A' // 82c9 + '33H' // 82ca #865 + 'a5B' // 82cb-82cc #131 + '2L' // 82cd #63 + '5B' // 82ce #131 + '55X' // 82cf #1453 + '28T' // 82d0 #747 + '181E' // 82d1 #4710 + '28S' // 82d2 #746 + '145A' // 82d3 #3770 + '142G' // 82d4 #3698 + '28S' // 82d5 #746 + '18D' // 82d6 #471 + '202D' // 82d7 #5255 + '33H' // 82d8 #865 + '28T' // 82d9 #747 + '2J' // 82da #61 + '92K' // 82db #2402 + '28T' // 82dc #747 + '5B' // 82dd #131 + '28S' // 82de #746 + '92F' // 82df #2397 + '28T' // 82e0 #747 + '28S' // 82e1 #746 + '92I' // 82e2 #2400 + 'a28T' // 82e3-82e4 #747 + '229H' // 82e5 #5961 + '212H' // 82e6 #5519 + '28S' // 82e7 #746 + '3A' // 82e8 #78 + '5B' // 82e9 #131 + '8W' // 82ea #230 + '92L' // 82eb #2403 + 'A' // 82ec + '41M' // 82ed #1078 + '33H' // 82ee #865 + '92B' // 82ef #2393 + '28V' // 82f0 #749 + '235K' // 82f1 #6120 + 'A' // 82f2 + 'a8W' // 82f3-82f4 #230 + 'A' // 82f5 + '22O' // 82f6 #586 + '8W' // 82f7 #230 + '33H' // 82f8 #865 + '92C' // 82f9 #2394 + '92M' // 82fa #2404 + '8W' // 82fb #230 + '18D' // 82fc #471 + '91Z' // 82fd #2391 + '18C' // 82fe #470 + '18D' // 82ff #471 + '8W' // 8300 #230 + '18C' // 8301 #470 + '168G' // 8302 #4374 + '185F' // 8303 #4815 + '166F' // 8304 #4321 + '142P' // 8305 #3707 + 'b8W' // 8306-8308 #230 + '136O' // 8309 #3550 + '3A' // 830a #78 + '22O' // 830b #586 + '8W' // 830c #230 + '18D' // 830d #471 + '253Q' // 830e #6594 + '5B' // 830f #131 + 'A' // 8310 + '5B' // 8311 #131 + 'A' // 8312 + 'b5B' // 8313-8315 #131 + '22O' // 8316 #586 + '127Q' // 8317 #3318 + '8W' // 8318 #230 + 'A' // 8319 + '18D' // 831a #471 + '8W' // 831b #230 + '142H' // 831c #3699 + '8W' // 831d #230 + '22O' // 831e #586 + '3A' // 831f #78 + 'A' // 8320 + 'b3A' // 8321-8323 #78 + '5B' // 8324 #131 + 'aA' // 8325-8326 + '18D' // 8327 #471 + '137E' // 8328 #3566 + 'A' // 8329 + '18D' // 832a #471 + '132R' // 832b #3449 + 'a8W' // 832c-832d #230 + '3A' // 832e #78 + '18C' // 832f #470 + '3A' // 8330 #78 + '18C' // 8331 #470 + '163N' // 8332 #4251 + '8W' // 8333 #230 + '18C' // 8334 #470 + '138Q' // 8335 #3604 + '212X' // 8336 #5535 + '22O' // 8337 #586 + '130K' // 8338 #3390 + '141Y' // 8339 #3690 + '8W' // 833a #230 + '18D' // 833b #471 + '8W' // 833c #230 + '44T' // 833d #1163 + 'A' // 833e + '28V' // 833f #749 + '18C' // 8340 #470 + 'A' // 8341 + '22O' // 8342 #586 + '144X' // 8343 #3767 + 'a8W' // 8344-8345 #230 + '248R' // 8346 #6465 + '18C' // 8347 #470 + '5B' // 8348 #131 + '215X' // 8349 #5613 + '127P' // 834a #3317 + '28V' // 834b #749 + '18D' // 834c #471 + 'a3A' // 834d-834e #78 + '18C' // 834f #470 + '138S' // 8350 #3606 + '18C' // 8351 #470 + '176J' // 8352 #4585 + '3A' // 8353 #78 + '138R' // 8354 #3605 + '3A' // 8355 #78 + '8W' // 8356 #230 + '22O' // 8357 #586 + '254V' // 8358 #6625 + '5B' // 8359 #131 + '41M' // 835a #1078 + 'a5B' // 835b-835c #131 + 'A' // 835d + '5B' // 835e #131 + '3X' // 835f #101 + '5B' // 8360 #131 + '4C' // 8361 #106 + '44T' // 8362 #1163 + '92D' // 8363 #2395 + 'a5B' // 8364-8365 #131 + '33H' // 8366 #865 + '2K' // 8367 #62 + 'b5B' // 8368-836a #131 + '3W' // 836b #100 + 'b5B' // 836c-836e #131 + '55X' // 836f #1453 + '3A' // 8370 #78 + 'aA' // 8371-8372 + '18C' // 8373 #470 + 'A' // 8374 + '22O' // 8375 #586 + 'A' // 8376 + '199A' // 8377 #5174 + '8W' // 8378 #230 + 'A' // 8379 + '28V' // 837a #749 + '92N' // 837b #2405 + 'a8W' // 837c-837d #230 + '28V' // 837e #749 + '8W' // 837f #230 + '41M' // 8380 #1078 + 'A' // 8381 + '41M' // 8382 #1078 + '44V' // 8383 #1165 + '3A' // 8384 #78 + 'a92A' // 8385-8386 #2392 + '3A' // 8387 #78 + '5B' // 8388 #131 + '202Q' // 8389 #5268 + '199T' // 838a #5193 + '5B' // 838b #131 + 'A' // 838c + '3A' // 838d #78 + '177W' // 838e #4624 + 'aA' // 838f-8390 + '55T' // 8391 #1449 + '25E' // 8392 #654 + '170S' // 8393 #4438 + '25E' // 8394 #654 + '44U' // 8395 #1164 + '132Q' // 8396 #3448 + 'A' // 8397 + '38C' // 8398 #990 + '25E' // 8399 #654 + '3A' // 839a #78 + 'a25E' // 839b-839c #654 + '3A' // 839d #78 + '144W' // 839e #3766 + '3A' // 839f #78 + '25E' // 83a0 #654 + 'A' // 83a1 + '38C' // 83a2 #990 + '5B' // 83a3 #131 + '44V' // 83a4 #1165 + 'A' // 83a5 + '3A' // 83a6 #78 + '44U' // 83a7 #1164 + '25E' // 83a8 #654 + 'a38C' // 83a9-83aa #990 + '192K' // 83ab #5002 + '44T' // 83ac #1163 + '3A' // 83ad #78 + '5B' // 83ae #131 + 'a55W' // 83af-83b0 #1452 + '248T' // 83b1 #6467 + '1R' // 83b2 #43 + 'a5B' // 83b3-83b4 #131 + '3A' // 83b5 #78 + '5B' // 83b6 #131 + '1Z' // 83b7 #51 + '5B' // 83b8 #131 + '92R' // 83b9 #2409 + '5B' // 83ba #131 + 'A' // 83bb + '5B' // 83bc #131 + '38C' // 83bd #990 + '44U' // 83be #1164 + 'a25E' // 83bf-83c0 #654 + '163M' // 83c1 #4250 + '55W' // 83c2 #1452 + 'A' // 83c3 + '44V' // 83c4 #1165 + '92P' // 83c5 #2407 + 'A' // 83c6 + '163O' // 83c7 #4252 + '55V' // 83c8 #1451 + '91Y' // 83c9 #2390 + '168J' // 83ca #4377 + '55V' // 83cb #1451 + '182J' // 83cc #4741 + '55T' // 83cd #1449 + '18B' // 83ce #469 + '91S' // 83cf #2384 + '3A' // 83d0 #78 + '3Z' // 83d1 #103 + 'A' // 83d2 + '131F' // 83d3 #3411 + '3Z' // 83d4 #103 + '25D' // 83d5 #653 + '16N' // 83d6 #429 + 'A' // 83d7 + '3Z' // 83d8 #103 + 'aA' // 83d9-83da + '11G' // 83db #292 + '216D' // 83dc #5619 + '3Z' // 83dd #103 + 'A' // 83de + '3Z' // 83df #103 + '132S' // 83e0 #3450 + '3Z' // 83e1 #103 + '15K' // 83e2 #400 + 'aA' // 83e3-83e4 + '3Z' // 83e5 #103 + 'aA' // 83e6-83e7 + '3A' // 83e8 #78 + '147R' // 83e9 #3839 + '3Z' // 83ea #103 + '16N' // 83eb #429 + 'A' // 83ec + '10T' // 83ed #279 + 'A' // 83ee + '234Y' // 83ef #6108 + '16N' // 83f0 #429 + '161U' // 83f1 #4206 + '199U' // 83f2 #5194 + '15K' // 83f3 #400 + '16N' // 83f4 #429 + 'A' // 83f5 + 'a3A' // 83f6-83f7 #78 + '156W' // 83f8 #4078 + '16N' // 83f9 #429 + 'A' // 83fa + '3Z' // 83fb #103 + '18B' // 83fc #469 + '16N' // 83fd #429 + '15K' // 83fe #400 + '11G' // 83ff #292 + 'A' // 8400 + '63S' // 8401 #1656 + 'A' // 8402 + '156V' // 8403 #4077 + '170R' // 8404 #4437 + '10T' // 8405 #279 + '3Z' // 8406 #103 + '18B' // 8407 #469 + 'aA' // 8408-8409 + '199S' // 840a #5192 + '3Z' // 840b #103 + '189Q' // 840c #4930 + '144V' // 840d #3765 + '135T' // 840e #3529 + '3Z' // 840f #103 + 'A' // 8410 + '3Z' // 8411 #103 + 'A' // 8412 + '18B' // 8413 #469 + '10T' // 8414 #279 + '3A' // 8415 #78 + '10T' // 8416 #279 + '3A' // 8417 #78 + '25D' // 8418 #653 + '3A' // 8419 #78 + 'A' // 841a + '15K' // 841b #400 + '25D' // 841c #653 + '4C' // 841d #106 + 'aA' // 841e-841f + '3Z' // 8420 #103 + '15K' // 8421 #400 + '49R' // 8422 #1291 + 'a25D' // 8423-8424 #653 + '1Z' // 8425 #51 + '25D' // 8426 #653 + '2R' // 8427 #69 + '1R' // 8428 #43 + '91W' // 8429 #2388 + '3A' // 842a #78 + '15K' // 842b #400 + '233E' // 842c #6062 + 'a15K' // 842d-842e #400 + '3A' // 842f #78 + 'A' // 8430 + '150F' // 8431 #3905 + 'a15K' // 8432-8433 #400 + 'A' // 8434 + '18B' // 8435 #469 + 'A' // 8436 + '15K' // 8437 #400 + '16N' // 8438 #429 + '3Z' // 8439 #103 + 'A' // 843a + '25D' // 843b #653 + '3Z' // 843c #103 + '230N' // 843d #5993 + '15K' // 843e #400 + '11G' // 843f #292 + 'dA' // 8440-8444 + '18B' // 8445 #469 + 'a3Z' // 8446-8447 #103 + '28R' // 8448 #745 + '213X' // 8449 #5561 + '28R' // 844a #745 + 'aA' // 844b-844c + '3A' // 844d #78 + '3Z' // 844e #103 + '3A' // 844f #78 + 'A' // 8450 + 'a3Z' // 8451-8452 #103 + '10T' // 8453 #279 + 'A' // 8454 + '10T' // 8455 #279 + '3Z' // 8456 #103 + '235A' // 8457 #6110 + '28R' // 8458 #745 + 'a3Z' // 8459-845a #103 + '168S' // 845b #4386 + '3Z' // 845c #103 + 'aA' // 845d-845e + '18B' // 845f #469 + '3A' // 8460 #78 + '177X' // 8461 #4625 + '3Z' // 8462 #103 + '201G' // 8463 #5232 + '28R' // 8464 #745 + '3A' // 8465 #78 + '16N' // 8466 #429 + '18B' // 8467 #469 + 'A' // 8468 + '127R' // 8469 #3319 + '3A' // 846a #78 + '122A' // 846b #3172 + '154V' // 846c #4025 + '3Z' // 846d #103 + '3A' // 846e #78 + '16N' // 846f #429 + '3Z' // 8470 #103 + '91T' // 8471 #2385 + '10T' // 8472 #279 + '3Z' // 8473 #103 + '18B' // 8474 #469 + '161E' // 8475 #4190 + 'b3Z' // 8476-8478 #103 + '3A' // 8479 #78 + '16N' // 847a #429 + 'A' // 847b + '3A' // 847c #78 + '49R' // 847d #1291 + 'A' // 847e + 'a10T' // 847f-8480 #279 + '3A' // 8481 #78 + '185G' // 8482 #4816 + 'A' // 8483 + '3Z' // 8484 #103 + '49R' // 8485 #1291 + 'A' // 8486 + '11G' // 8487 #292 + '10T' // 8488 #279 + '11G' // 8489 #292 + 'A' // 848a + '248S' // 848b #6466 + '11G' // 848c #292 + '15K' // 848d #400 + '25D' // 848e #653 + 'A' // 848f + '163L' // 8490 #4249 + 'A' // 8491 + '28R' // 8492 #745 + '3Z' // 8493 #103 + '91U' // 8494 #2386 + '3A' // 8495 #78 + '10T' // 8496 #279 + '3Z' // 8497 #103 + 'A' // 8498 + '187J' // 8499 #4871 + 'A' // 849a + '11G' // 849b #292 + '150G' // 849c #3906 + '15K' // 849d #400 + '63S' // 849e #1656 + '3Z' // 849f #103 + 'A' // 84a0 + '16N' // 84a1 #429 + 'A' // 84a2 + '10T' // 84a3 #279 + 'A' // 84a4 + '11G' // 84a5 #292 + '3A' // 84a6 #78 + 'A' // 84a7 + '3Z' // 84a8 #103 + 'a3A' // 84a9-84aa #78 + 'aA' // 84ab-84ac + '28R' // 84ad #745 + 'A' // 84ae + '3Z' // 84af #103 + 'A' // 84b0 + '18B' // 84b1 #469 + '154K' // 84b2 #4014 + 'A' // 84b3 + '44R' // 84b4 #1161 + 'bA' // 84b5-84b7 + '181Y' // 84b8 #4730 + 'a44R' // 84b9-84ba #1161 + '55R' // 84bb #1447 + '161L' // 84bc #4197 + 'a44R' // 84bd-84be #1161 + '55R' // 84bf #1447 + '38B' // 84c0 #989 + '63C' // 84c1 #1640 + '28Q' // 84c2 #744 + 'A' // 84c3 + '154T' // 84c4 #4023 + '11G' // 84c5 #292 + '28Q' // 84c6 #744 + '15J' // 84c7 #399 + '2H' // 84c8 #59 + '156U' // 84c9 #4076 + '15J' // 84ca #399 + '208X' // 84cb #5431 + '2H' // 84cc #59 + '28Q' // 84cd #744 + 'a25C' // 84ce-84cf #652 + '15J' // 84d0 #399 + '28Q' // 84d1 #744 + '44S' // 84d2 #1162 + '63C' // 84d3 #1640 + 'aA' // 84d4-84d5 + '15J' // 84d6 #399 + 'aA' // 84d7-84d8 + '2H' // 84d9 #59 + '91R' // 84da #2383 + 'A' // 84db + '2H' // 84dc #59 + '2C' // 84dd #54 + '10T' // 84de #279 + 'a11G' // 84df-84e0 #292 + '10T' // 84e1 #279 + '55S' // 84e2 #1448 + '11G' // 84e3 #292 + '10T' // 84e4 #279 + '91X' // 84e5 #2389 + '11G' // 84e6 #292 + '15J' // 84e7 #399 + '44S' // 84e8 #1162 + 'A' // 84e9 + '25C' // 84ea #652 + 'A' // 84eb + '156T' // 84ec #4075 + 'A' // 84ed + '209M' // 84ee #5446 + 'a25C' // 84ef-84f0 #652 + 'a2H' // 84f1-84f2 #59 + '55S' // 84f3 #1448 + '38B' // 84f4 #989 + 'A' // 84f5 + '11G' // 84f6 #292 + '25C' // 84f7 #652 + '10T' // 84f8 #279 + 'A' // 84f9 + '15J' // 84fa #399 + '2H' // 84fb #59 + '28Q' // 84fc #744 + '25C' // 84fd #652 + 'A' // 84fe + 'a15J' // 84ff-8500 #399 + 'A' // 8501 + '2H' // 8502 #59 + '44Q' // 8503 #1160 + 'a10T' // 8504-8505 #279 + '25C' // 8506 #652 + '2H' // 8507 #59 + 'cA' // 8508-850b + '15J' // 850c #399 + 'A' // 850d + '2H' // 850e #59 + 'A' // 850f + '44Q' // 8510 #1160 + '91V' // 8511 #2387 + 'A' // 8512 + '152N' // 8513 #3965 + '150E' // 8514 #3904 + '15J' // 8515 #399 + 'A' // 8516 + '28Q' // 8517 #744 + '121Y' // 8518 #3170 + 'A' // 8519 + '144U' // 851a #3764 + 'a2H' // 851b-851c #59 + '44S' // 851d #1162 + '38B' // 851e #989 + '15J' // 851f #399 + 'A' // 8520 + '199Q' // 8521 #5190 + '2H' // 8522 #59 + '156S' // 8523 #4074 + '25C' // 8524 #652 + '170Q' // 8525 #4436 + '141R' // 8526 #3683 + '2H' // 8527 #59 + 'A' // 8528 + '11G' // 8529 #292 + '2H' // 852a #59 + '15J' // 852b #399 + '177U' // 852c #4622 + '135S' // 852d #3528 + 'A' // 852e + '38B' // 852f #989 + 'aA' // 8530-8531 + '248Q' // 8532 #6464 + '44Q' // 8533 #1160 + '15J' // 8534 #399 + '257W' // 8535 #6704 + '2H' // 8536 #59 + '2W' // 8537 #74 + '55Q' // 8538 #1446 + 'a11G' // 8539-853a #292 + '137Q' // 853b #3578 + '11G' // 853c #292 + '147T' // 853d #3841 + '6M' // 853e #168 + '248P' // 853f #6463 + '2H' // 8540 #59 + '55P' // 8541 #1445 + '28P' // 8542 #743 + '144T' // 8543 #3763 + 'A' // 8544 + '55Q' // 8545 #1446 + '2H' // 8546 #59 + 'A' // 8547 + '6B' // 8548 #157 + '152S' // 8549 #3970 + '127O' // 854a #3316 + '6M' // 854b #168 + '16M' // 854c #428 + '28P' // 854d #743 + '91M' // 854e #2378 + 'b2H' // 854f-8551 #59 + '6M' // 8552 #168 + '55O' // 8553 #1444 + 'A' // 8554 + '6M' // 8555 #168 + 'a6B' // 8556-8557 #157 + '6M' // 8558 #168 + '127N' // 8559 #3315 + '6M' // 855a #168 + 'A' // 855b + 'a2H' // 855c-855d #59 + '6B' // 855e #157 + '28L' // 855f #739 + '2H' // 8560 #59 + '6B' // 8561 #157 + '6M' // 8562 #168 + '55P' // 8563 #1445 + '6B' // 8564 #157 + '91P' // 8565 #2381 + 'aA' // 8566-8567 + '91J' // 8568 #2375 + '156R' // 8569 #4073 + '55O' // 856a #1444 + '6M' // 856b #168 + '28P' // 856c #743 + '170P' // 856d #4435 + 'A' // 856e + '6B' // 856f #157 + '91O' // 8570 #2380 + 'A' // 8571 + '16F' // 8572 #421 + '16M' // 8573 #428 + '2K' // 8574 #62 + 'A' // 8575 + '16F' // 8576 #421 + '6M' // 8577 #168 + '28P' // 8578 #743 + 'b6B' // 8579-857b #157 + 'A' // 857c + '2H' // 857d #59 + '177V' // 857e #4623 + '2H' // 857f #59 + '6B' // 8580 #157 + '6M' // 8581 #168 + 'aA' // 8582-8583 + '204P' // 8584 #5319 + 'a6B' // 8585-8586 #157 + '173Q' // 8587 #4514 + '132P' // 8588 #3447 + '2H' // 8589 #59 + '6B' // 858a #157 + '2H' // 858b #59 + '6M' // 858c #168 + 'aA' // 858d-858e + '20E' // 858f #524 + '6M' // 8590 #168 + '156P' // 8591 #4071 + 'A' // 8592 + '28L' // 8593 #739 + '91L' // 8594 #2377 + 'aA' // 8595-8596 + 'a6M' // 8597-8598 #168 + '91H' // 8599 #2373 + 'A' // 859a + '144S' // 859b #3762 + '6B' // 859c #157 + '2H' // 859d #59 + 'A' // 859e + '6M' // 859f #168 + '2H' // 85a0 #59 + '28P' // 85a1 #743 + '6B' // 85a2 #157 + 'A' // 85a3 + '6B' // 85a4 #157 + '2H' // 85a5 #59 + '233N' // 85a6 #6071 + '2H' // 85a7 #59 + '20E' // 85a8 #524 + '187S' // 85a9 #4880 + '187N' // 85aa #4875 + '253M' // 85ab #6590 + '258F' // 85ac #6713 + '2H' // 85ad #59 + '35Z' // 85ae #935 + '163K' // 85af #4248 + '156Q' // 85b0 #4072 + 'aA' // 85b1-85b2 + '28P' // 85b3 #743 + '6M' // 85b4 #168 + 'A' // 85b5 + '2H' // 85b6 #59 + '6B' // 85b7 #157 + '35Z' // 85b8 #935 + '6B' // 85b9 #157 + '33G' // 85ba #864 + 'A' // 85bb + '2H' // 85bc #59 + '6M' // 85bd #168 + '6B' // 85be #157 + '2H' // 85bf #59 + 'A' // 85c0 + '20E' // 85c1 #524 + '6M' // 85c2 #168 + 'cA' // 85c3-85c6 + '35Z' // 85c7 #935 + 'A' // 85c8 + '192J' // 85c9 #5001 + '2H' // 85ca #59 + '6M' // 85cb #168 + 'A' // 85cc + '218O' // 85cd #5682 + '33G' // 85ce #864 + '224U' // 85cf #5844 + '6B' // 85d0 #157 + 'aA' // 85d1-85d2 + '16F' // 85d3 #421 + 'A' // 85d4 + '20E' // 85d5 #524 + '16M' // 85d6 #428 + 'A' // 85d7 + 'b2H' // 85d8-85da #59 + 'A' // 85db + '20E' // 85dc #524 + '226M' // 85dd #5888 + 'A' // 85de + '35Z' // 85df #935 + '6B' // 85e0 #157 + '2H' // 85e1 #59 + 'aA' // 85e2-85e3 + '184C' // 85e4 #4786 + '206V' // 85e5 #5377 + '6B' // 85e6 #157 + 'A' // 85e7 + '6B' // 85e8 #157 + '135P' // 85e9 #3525 + '91I' // 85ea #2374 + 'aA' // 85eb-85ec + '6M' // 85ed #168 + '16M' // 85ee #428 + 'cA' // 85ef-85f2 + '2H' // 85f3 #59 + '6B' // 85f4 #157 + 'A' // 85f5 + '6M' // 85f6 #168 + '33G' // 85f7 #864 + 'A' // 85f8 + '6B' // 85f9 #157 + '33G' // 85fa #864 + '148G' // 85fb #3854 + '28L' // 85fc #739 + 'A' // 85fd + '35Z' // 85fe #935 + '20E' // 85ff #524 + '2H' // 8600 #59 + 'A' // 8601 + '20E' // 8602 #524 + 'A' // 8603 + '6M' // 8604 #168 + '6B' // 8605 #157 + '170O' // 8606 #4434 + '208W' // 8607 #5430 + 'aA' // 8608-8609 + '138P' // 860a #3603 + '199R' // 860b #5191 + 'A' // 860c + '28L' // 860d #739 + '2H' // 860e #59 + '16M' // 860f #428 + '6M' // 8610 #168 + '121Z' // 8611 #3171 + '2H' // 8612 #59 + '28L' // 8613 #739 + '16M' // 8614 #428 + 'A' // 8615 + '20E' // 8616 #524 + '33G' // 8617 #864 + '6B' // 8618 #157 + '2H' // 8619 #59 + '20E' // 861a #524 + '2H' // 861b #59 + 'aA' // 861c-861d + '6M' // 861e #168 + 'aA' // 861f-8620 + 'a28N' // 8621-8622 #741 + 'A' // 8623 + '2H' // 8624 #59 + 'aA' // 8625-8626 + '28M' // 8627 #740 + '16M' // 8628 #428 + '28M' // 8629 #740 + '28O' // 862a #742 + 'aA' // 862b-862c + '226W' // 862d #5898 + 'A' // 862e + '28L' // 862f #739 + '2H' // 8630 #59 + 'bA' // 8631-8633 + 'a28O' // 8634-8635 #742 + '28N' // 8636 #741 + 'A' // 8637 + '28M' // 8638 #740 + '2H' // 8639 #59 + '28N' // 863a #741 + 'A' // 863b + '28M' // 863c #740 + '2H' // 863d #59 + 'A' // 863e + '177T' // 863f #4621 + '28N' // 8640 #741 + '2H' // 8641 #59 + '28N' // 8642 #741 + 'aA' // 8643-8644 + '16M' // 8645 #428 + '28N' // 8646 #741 + 'dA' // 8647-864b + '28O' // 864c #742 + '28M' // 864d #740 + '202K' // 864e #5262 + '16F' // 864f #421 + '167T' // 8650 #4361 + '3Y' // 8651 #102 + 'a28M' // 8652-8653 #740 + '91G' // 8654 #2372 + '232F' // 8655 #6037 + 'b1O' // 8656-8658 #40 + '33F' // 8659 #863 + '254H' // 865a #6611 + '192I' // 865b #5000 + '91K' // 865c #2376 + '1O' // 865d #40 + '127K' // 865e #3312 + '239P' // 865f #6229 + 'a1O' // 8660-8661 #40 + '20D' // 8662 #523 + 'a1O' // 8663-8664 #40 + 'aA' // 8665-8666 + '163J' // 8667 #4247 + 'A' // 8668 + '1O' // 8669 #40 + 'A' // 866a + '91N' // 866b #2379 + '20D' // 866c #523 + 'A' // 866d + '16F' // 866e #421 + '33F' // 866f #863 + '28O' // 8670 #742 + '20D' // 8671 #523 + '16M' // 8672 #428 + '28O' // 8673 #742 + 'A' // 8674 + '41L' // 8675 #1077 + '1O' // 8676 #40 + '33F' // 8677 #863 + 'A' // 8678 + '181L' // 8679 #4717 + 'a20D' // 867a-867b #523 + '16F' // 867c #421 + '248O' // 867d #6462 + '91Q' // 867e #2382 + '16F' // 867f #421 + '3H' // 8680 #85 + 'a2R' // 8681-8682 #69 + 'cA' // 8683-8686 + 'b1O' // 8687-8689 #40 + '172Y' // 868a #4496 + '20D' // 868b #523 + '91F' // 868c #2371 + '20D' // 868d #523 + 'bA' // 868e-8690 + '1O' // 8691 #40 + '16M' // 8692 #428 + '44P' // 8693 #1159 + '28O' // 8694 #742 + '55N' // 8695 #1443 + '33F' // 8696 #863 + 'A' // 8697 + '1O' // 8698 #40 + 'A' // 8699 + '33F' // 869a #863 + 'A' // 869b + 'a20D' // 869c-869d #523 + 'aA' // 869e-869f + '16M' // 86a0 #428 + '20D' // 86a1 #523 + 'A' // 86a2 + 'a44P' // 86a3-86a4 #1159 + 'A' // 86a5 + '1O' // 86a6 #40 + 'a20D' // 86a7-86a8 #523 + '44P' // 86a9 #1159 + '55N' // 86aa #1443 + '1O' // 86ab #40 + '16F' // 86ac #421 + '91E' // 86ad #2370 + 'A' // 86ae + 'b18A' // 86af-86b1 #468 + '44M' // 86b2 #1156 + '22N' // 86b3 #585 + '18A' // 86b4 #468 + '132O' // 86b5 #3446 + '18A' // 86b6 #468 + 'a1O' // 86b7-86b8 #40 + '22N' // 86b9 #585 + '55M' // 86ba #1442 + 'cA' // 86bb-86be + '1O' // 86bf #40 + '18A' // 86c0 #468 + '22N' // 86c1 #585 + '38A' // 86c2 #988 + '1O' // 86c3 #40 + '18A' // 86c4 #468 + '1O' // 86c5 #40 + '18A' // 86c6 #468 + '181D' // 86c7 #4709 + 'A' // 86c8 + '18A' // 86c9 #468 + '16F' // 86ca #421 + '217C' // 86cb #5644 + '38A' // 86cc #988 + '253X' // 86cd #6601 + '41L' // 86ce #1077 + '16F' // 86cf #421 + '55M' // 86d0 #1442 + '41L' // 86d1 #1077 + '1O' // 86d2 #40 + '38A' // 86d3 #988 + '44O' // 86d4 #1158 + '1O' // 86d5 #40 + 'A' // 86d6 + '1O' // 86d7 #40 + '16F' // 86d8 #421 + '159F' // 86d9 #4139 + '1O' // 86da #40 + '141N' // 86db #3679 + '1O' // 86dc #40 + 'A' // 86dd + '18A' // 86de #468 + '44O' // 86df #1158 + '1O' // 86e0 #40 + 'aA' // 86e1-86e2 + '1O' // 86e3 #40 + '127J' // 86e4 #3311 + '1O' // 86e5 #40 + '41L' // 86e6 #1077 + '1O' // 86e7 #40 + 'A' // 86e8 + '18A' // 86e9 #468 + 'aA' // 86ea-86eb + '1O' // 86ec #40 + '44O' // 86ed #1158 + '252L' // 86ee #6563 + '18A' // 86ef #468 + 'c16F' // 86f0-86f3 #421 + '24G' // 86f4 #630 + 'bA' // 86f5-86f7 + 'a8V' // 86f8-86f9 #229 + '22N' // 86fa #585 + '8V' // 86fb #229 + 'a1O' // 86fc-86fd #40 + '28K' // 86fe #738 + 'A' // 86ff + '127L' // 8700 #3313 + 'A' // 8701 + '180U' // 8702 #4700 + '28K' // 8703 #738 + 'a1O' // 8704-8705 #40 + 'a8V' // 8706-8707 #229 + '28K' // 8708 #738 + 'a8V' // 8709-870a #229 + '1O' // 870b #40 + 'A' // 870c + '8V' // 870d #229 + '22N' // 870e #585 + 'a1O' // 870f-8710 #40 + 'a8V' // 8711-8712 #229 + '64O' // 8713 #1678 + '1O' // 8714 #40 + '91D' // 8715 #2369 + 'A' // 8716 + '3G' // 8717 #84 + '135I' // 8718 #3518 + '22N' // 8719 #585 + '28K' // 871a #738 + 'A' // 871b + '201W' // 871c #5248 + 'A' // 871d + '8V' // 871e #229 + '1O' // 871f #40 + 'A' // 8720 + '91C' // 8721 #2368 + 'a8V' // 8722-8723 #229 + 'A' // 8724 + '8V' // 8725 #229 + 'aA' // 8726-8727 + '22N' // 8728 #585 + '8V' // 8729 #229 + 'cA' // 872a-872d + '8V' // 872e #229 + '1O' // 872f #40 + 'A' // 8730 + '8V' // 8731 #229 + '1O' // 8732 #40 + 'A' // 8733 + '8V' // 8734 #229 + 'aA' // 8735-8736 + '8V' // 8737 #229 + 'A' // 8738 + '1O' // 8739 #40 + '8V' // 873a #229 + '64O' // 873b #1678 + 'a1O' // 873c-873d #40 + 'b8V' // 873e-8740 #229 + 'A' // 8741 + '55L' // 8742 #1441 + '1O' // 8743 #40 + 'A' // 8744 + '1O' // 8745 #40 + 'A' // 8746 + 'a24G' // 8747-8748 #630 + '248N' // 8749 #6461 + 'A' // 874a + '1O' // 874b #40 + '91B' // 874c #2367 + '1O' // 874d #40 + '91A' // 874e #2366 + '38A' // 874f #988 + 'A' // 8750 + '1O' // 8751 #40 + 'A' // 8752 + '8V' // 8753 #229 + 'A' // 8754 + '150D' // 8755 #3903 + 'A' // 8756 + '28K' // 8757 #738 + '8V' // 8758 #229 + '63R' // 8759 #1655 + 'bA' // 875a-875c + '8V' // 875d #229 + 'A' // 875e + '28K' // 875f #738 + '63R' // 8760 #1655 + '22N' // 8761 #585 + '55L' // 8762 #1441 + 'b9C' // 8763-8765 #236 + '67G' // 8766 #1748 + 'A' // 8767 + '33D' // 8768 #861 + 'A' // 8769 + '22M' // 876a #584 + 'A' // 876b + 'a16L' // 876c-876d #427 + '9C' // 876e #236 + '22M' // 876f #584 + '33E' // 8770 #862 + '25B' // 8771 #651 + '49Q' // 8772 #1290 + 'A' // 8773 + '156O' // 8774 #4070 + '24G' // 8775 #630 + '174N' // 8776 #4537 + '16L' // 8777 #427 + '63Q' // 8778 #1654 + 'A' // 8779 + '16L' // 877a #427 + '9C' // 877b #236 + '49Q' // 877c #1290 + '33E' // 877d #862 + '24G' // 877e #630 + '1O' // 877f #40 + 'A' // 8780 + '16L' // 8781 #427 + '63Q' // 8782 #1654 + '121X' // 8783 #3169 + '22M' // 8784 #584 + '9C' // 8785 #236 + '25B' // 8786 #651 + '1O' // 8787 #40 + '9C' // 8788 #236 + '1O' // 8789 #40 + 'A' // 878a + '9C' // 878b #236 + '25B' // 878c #651 + '203Z' // 878d #5303 + '1O' // 878e #40 + 'A' // 878f + '1O' // 8790 #40 + 'aA' // 8791-8792 + '9C' // 8793 #236 + 'A' // 8794 + '1O' // 8795 #40 + 'A' // 8796 + '9C' // 8797 #236 + '22M' // 8798 #584 + '1O' // 8799 #40 + '24G' // 879a #630 + 'bA' // 879b-879d + '127M' // 879e #3314 + '33D' // 879f #861 + '49Q' // 87a0 #1290 + 'A' // 87a1 + '67G' // 87a2 #1748 + '9C' // 87a3 #236 + 'A' // 87a4 + '44M' // 87a5 #1156 + 'A' // 87a6 + '1O' // 87a7 #40 + '24G' // 87a8 #630 + '44M' // 87a9 #1156 + 'A' // 87aa + 'b9C' // 87ab-87ad #236 + '1O' // 87ae #40 + '9C' // 87af #236 + 'A' // 87b0 + '25B' // 87b1 #651 + 'A' // 87b2 + '33D' // 87b3 #861 + 'A' // 87b4 + '9C' // 87b5 #236 + 'bA' // 87b6-87b8 + '16L' // 87b9 #427 + '172S' // 87ba #4490 + '22M' // 87bb #584 + 'A' // 87bc + '9C' // 87bd #236 + 'a22M' // 87be-87bf #584 + '9C' // 87c0 #236 + '25B' // 87c1 #651 + 'aA' // 87c2-87c3 + '33D' // 87c4 #861 + '16L' // 87c5 #427 + '9C' // 87c6 #236 + '1O' // 87c7 #40 + '22M' // 87c8 #584 + '1O' // 87c9 #40 + 'a9C' // 87ca-87cb #236 + '16L' // 87cc #427 + 'A' // 87cd + '22M' // 87ce #584 + 'A' // 87cf + '1O' // 87d0 #40 + '131J' // 87d1 #3415 + '9C' // 87d2 #236 + 'aA' // 87d3-87d4 + '1O' // 87d5 #40 + '25B' // 87d6 #651 + 'aA' // 87d7-87d8 + '1O' // 87d9 #40 + '25B' // 87da #651 + '33E' // 87db #862 + '9C' // 87dc #236 + 'A' // 87dd + '24G' // 87de #630 + '1O' // 87df #40 + '33D' // 87e0 #861 + '16L' // 87e1 #427 + '1L' // 87e2 #37 + '33C' // 87e3 #860 + '1L' // 87e4 #37 + '25A' // 87e5 #650 + '1L' // 87e6 #37 + '16L' // 87e7 #427 + 'aA' // 87e8-87e9 + '25A' // 87ea #650 + '33C' // 87eb #860 + '132N' // 87ec #3445 + '1L' // 87ed #37 + '33E' // 87ee #862 + '90Z' // 87ef #2365 + 'A' // 87f0 + '1L' // 87f1 #37 + '194J' // 87f2 #5053 + '25A' // 87f3 #650 + '16L' // 87f4 #427 + '44N' // 87f5 #1157 + 'a33C' // 87f6-87f7 #860 + '1L' // 87f8 #37 + '173L' // 87f9 #4509 + '1L' // 87fa #37 + '144Q' // 87fb #3760 + 'A' // 87fc + '24G' // 87fd #630 + '55K' // 87fe #1440 + '1L' // 87ff #37 + 'A' // 8800 + '1L' // 8801 #37 + '33E' // 8802 #862 + '25A' // 8803 #650 + '16L' // 8804 #427 + '55K' // 8805 #1440 + '33C' // 8806 #860 + '1L' // 8807 #37 + 'A' // 8808 + '1L' // 8809 #37 + 'a25A' // 880a-880b #650 + 'A' // 880c + '127I' // 880d #3310 + '1L' // 880e #37 + '44N' // 880f #1157 + 'a33C' // 8810-8811 #860 + '1L' // 8812 #37 + '25A' // 8813 #650 + '121W' // 8814 #3168 + 'a25A' // 8815-8816 #650 + 'A' // 8817 + '44N' // 8818 #1157 + '69Y' // 8819 #1818 + '1L' // 881a #37 + '10S' // 881b #278 + '1L' // 881c #37 + 'A' // 881d + '1L' // 881e #37 + '156N' // 881f #4069 + 'A' // 8820 + '10S' // 8821 #278 + '138O' // 8822 #3602 + '90O' // 8823 #2354 + 'bA' // 8824-8826 + '24Z' // 8827 #649 + '1L' // 8828 #37 + 'cA' // 8829-882c + '28I' // 882d #736 + '1L' // 882e #37 + 'A' // 882f + '1L' // 8830 #37 + '28J' // 8831 #737 + '10S' // 8832 #278 + 'aA' // 8833-8834 + '10S' // 8835 #278 + '127H' // 8836 #3309 + 'aA' // 8837-8838 + '10S' // 8839 #278 + '1L' // 883a #37 + '185D' // 883b #4813 + '10S' // 883c #278 + 'bA' // 883d-883f + '215R' // 8840 #5607 + '1L' // 8841 #37 + '28I' // 8842 #736 + '1L' // 8843 #37 + '10S' // 8844 #278 + '90K' // 8845 #2350 + '90V' // 8846 #2361 + 'A' // 8847 + 'a1L' // 8848-8849 #37 + '10S' // 884a #278 + '1L' // 884b #37 + '247A' // 884c #6422 + '65Z' // 884d #1715 + '10S' // 884e #278 + '22L' // 884f #583 + 'A' // 8850 + '1L' // 8851 #37 + '28J' // 8852 #737 + '236H' // 8853 #6143 + '2R' // 8854 #69 + '10S' // 8855 #278 + '24Z' // 8856 #649 + '228U' // 8857 #5948 + '1L' // 8858 #37 + '28J' // 8859 #737 + '24Z' // 885a #649 + '215T' // 885b #5609 + '1L' // 885c #37 + '204J' // 885d #5313 + '10S' // 885e #278 + '1L' // 885f #37 + '28I' // 8860 #736 + '187E' // 8861 #4866 + '28J' // 8862 #737 + '228V' // 8863 #5949 + '10S' // 8864 #278 + '90Y' // 8865 #2364 + 'aA' // 8866-8867 + '244V' // 8868 #6365 + '10S' // 8869 #278 + '90X' // 886a #2363 + '170M' // 886b #4432 + '2L' // 886c #63 + '55J' // 886d #1439 + '10S' // 886e #278 + '69Y' // 886f #1818 + '173X' // 8870 #4521 + '24Z' // 8871 #649 + '28J' // 8872 #737 + 'aA' // 8873-8874 + '24Z' // 8875 #649 + 'A' // 8876 + '150A' // 8877 #3900 + 'A' // 8878 + '10S' // 8879 #278 + 'A' // 887a + '1L' // 887b #37 + 'A' // 887c + '10S' // 887d #278 + '28J' // 887e #737 + '90N' // 887f #2353 + '1L' // 8880 #37 + '150B' // 8881 #3901 + '44L' // 8882 #1155 + 'A' // 8883 + '55I' // 8884 #1438 + 'a10J' // 8885-8886 #269 + '22L' // 8887 #583 + '44L' // 8888 #1155 + 'aA' // 8889-888a + '213B' // 888b #5539 + 'A' // 888c + '144R' // 888d #3761 + 'A' // 888e + '90W' // 888f #2362 + '55J' // 8890 #1439 + 'A' // 8891 + '44L' // 8892 #1155 + '55I' // 8893 #1438 + 'aA' // 8894-8895 + '182P' // 8896 #4747 + '55H' // 8897 #1437 + '24Z' // 8898 #649 + '1L' // 8899 #37 + '24Z' // 889a #649 + '24Y' // 889b #648 + '90L' // 889c #2351 + '22L' // 889d #583 + '55H' // 889e #1437 + '1L' // 889f #37 + '28I' // 88a0 #736 + 'A' // 88a1 + '24Y' // 88a2 #648 + 'A' // 88a3 + '24Y' // 88a4 #648 + 'A' // 88a5 + '10J' // 88a6 #269 + 'A' // 88a7 + '24Y' // 88a8 #648 + 'A' // 88a9 + '24Y' // 88aa #648 + '233Y' // 88ab #6082 + 'A' // 88ac + '3Y' // 88ad #102 + '24Y' // 88ae #648 + 'A' // 88af + '1L' // 88b0 #37 + '24Y' // 88b1 #648 + 'aA' // 88b2-88b3 + '90P' // 88b4 #2355 + '28I' // 88b5 #736 + 'A' // 88b6 + '6L' // 88b7 #167 + '17Z' // 88b8 #467 + 'A' // 88b9 + '1L' // 88ba #37 + 'A' // 88bb + '6L' // 88bc #167 + 'a20C' // 88bd-88be #522 + '28I' // 88bf #736 + '6L' // 88c0 #167 + '190E' // 88c1 #4944 + '181X' // 88c2 #4729 + 'a1L' // 88c3-88c4 #37 + '149O' // 88c5 #3888 + '41K' // 88c6 #1076 + '17Y' // 88c7 #466 + '10J' // 88c8 #269 + '17Y' // 88c9 #466 + 'b20C' // 88ca-88cc #522 + '1L' // 88cd #37 + '6L' // 88ce #167 + '191M' // 88cf #4978 + 'A' // 88d0 + '41K' // 88d1 #1076 + '6L' // 88d2 #167 + '41K' // 88d3 #1076 + '65Z' // 88d4 #1715 + '176K' // 88d5 #4586 + '17Z' // 88d6 #467 + 'A' // 88d7 + '6L' // 88d8 #167 + '185E' // 88d9 #4814 + 'A' // 88da + '6L' // 88db #167 + '221V' // 88dc #5767 + '232E' // 88dd #6036 + '1L' // 88de #37 + '24X' // 88df #647 + '1L' // 88e0 #37 + '224T' // 88e1 #5843 + 'a10J' // 88e2-88e3 #269 + '3N' // 88e4 #91 + '10J' // 88e5 #269 + '22L' // 88e6 #583 + '1L' // 88e7 #37 + '24X' // 88e8 #647 + 'bA' // 88e9-88eb + '17Y' // 88ec #466 + 'aA' // 88ed-88ee + '20C' // 88ef #522 + 'a6L' // 88f0-88f1 #167 + '1L' // 88f2 #37 + '90M' // 88f3 #2352 + '24X' // 88f4 #647 + '55G' // 88f5 #1436 + 'A' // 88f6 + '1L' // 88f7 #37 + '183C' // 88f8 #4760 + '150C' // 88f9 #3902 + 'aA' // 88fa-88fb + '6L' // 88fc #167 + '236R' // 88fd #6153 + '90R' // 88fe #2357 + '22L' // 88ff #583 + '17Y' // 8900 #466 + '1L' // 8901 #37 + '6L' // 8902 #167 + 'A' // 8903 + '1L' // 8904 #37 + 'A' // 8905 + '6L' // 8906 #167 + '216C' // 8907 #5618 + 'A' // 8908 + '10J' // 8909 #269 + '6L' // 890a #167 + '17Y' // 890b #466 + '6L' // 890c #167 + 'b1L' // 890d-890f #37 + '136B' // 8910 #3537 + 'A' // 8911 + '90T' // 8912 #2359 + '24X' // 8913 #647 + '17Y' // 8914 #466 + '6L' // 8915 #167 + '1L' // 8916 #37 + 'A' // 8917 + 'a24X' // 8918-8919 #647 + '6L' // 891a #167 + '10J' // 891b #269 + 'b1L' // 891c-891e #37 + '17Z' // 891f #467 + '1L' // 8920 #37 + '17Y' // 8921 #466 + 'A' // 8922 + '17Z' // 8923 #467 + '22L' // 8924 #583 + '24X' // 8925 #647 + 'b1L' // 8926-8928 #37 + 'A' // 8929 + '90J' // 892a #2349 + '6L' // 892b #167 + 'A' // 892c + '17Z' // 892d #467 + 'aA' // 892e-892f + '6L' // 8930 #167 + '1L' // 8931 #37 + '199P' // 8932 #5189 + '17Z' // 8933 #467 + '10J' // 8934 #269 + '20C' // 8935 #522 + '24X' // 8936 #647 + '1L' // 8937 #37 + '55G' // 8938 #1436 + 'a1L' // 8939-893a #37 + '132M' // 893b #3444 + 'A' // 893c + '17Y' // 893d #466 + '1L' // 893e #37 + 'A' // 893f + '1L' // 8940 #37 + '90H' // 8941 #2347 + 'a20C' // 8942-8943 #522 + '127G' // 8944 #3308 + '1L' // 8945 #37 + '20C' // 8946 #522 + '17Z' // 8947 #467 + 'A' // 8948 + '20C' // 8949 #522 + 'aA' // 894a-894b + '6L' // 894c #167 + '20C' // 894d #522 + 'A' // 894e + '1L' // 894f #37 + 'aA' // 8950-8951 + '1L' // 8952 #37 + 'A' // 8953 + '22L' // 8954 #583 + '10J' // 8955 #269 + '6L' // 8956 #167 + '20C' // 8957 #522 + 'A' // 8958 + '17Y' // 8959 #466 + 'a1L' // 895a-895b #37 + '6L' // 895c #167 + 'A' // 895d + '6L' // 895e #167 + '90Q' // 895f #2356 + '6L' // 8960 #167 + 'b1L' // 8961-8963 #37 + '90I' // 8964 #2348 + '22L' // 8965 #583 + '6L' // 8966 #167 + 'bA' // 8967-8969 + '170L' // 896a #4431 + '1L' // 896b #37 + '17Y' // 896c #466 + 'a1L' // 896d-896e #37 + '170N' // 896f #4433 + '41K' // 8970 #1076 + '17Z' // 8971 #467 + '197A' // 8972 #5122 + '1T' // 8973 #45 + '33B' // 8974 #859 + '1T' // 8975 #45 + 'A' // 8976 + '55F' // 8977 #1435 + 'aA' // 8978-8979 + '1T' // 897a #45 + '37Z' // 897b #987 + 'a1T' // 897c-897d #45 + '37Z' // 897e #987 + '68S' // 897f #1786 + '37Z' // 8980 #987 + '244U' // 8981 #6364 + '17Z' // 8982 #467 + '90G' // 8983 #2346 + 'A' // 8984 + '10J' // 8985 #269 + '218R' // 8986 #5685 + '90U' // 8987 #2360 + '37Z' // 8988 #987 + '55F' // 8989 #1435 + '33B' // 898a #859 + '245H' // 898b #6377 + '10J' // 898c #269 + '1T' // 898d #45 + 'A' // 898e + '238Z' // 898f #6213 + '1T' // 8990 #45 + '17Z' // 8991 #467 + 'A' // 8992 + '138M' // 8993 #3600 + 'a33B' // 8994-8995 #859 + '241T' // 8996 #6285 + '90S' // 8997 #2358 + '33B' // 8998 #859 + 'A' // 8999 + '258E' // 899a #6712 + '1T' // 899b #45 + '33B' // 899c #859 + 'aA' // 899d-899e + 'a1T' // 899f-89a0 #45 + '37Y' // 89a1 #986 + 'aA' // 89a2-89a3 + '90F' // 89a4 #2345 + 'a20B' // 89a5-89a6 #521 + '90B' // 89a7 #2341 + 'A' // 89a8 + '37Y' // 89a9 #986 + '235N' // 89aa #6123 + 'A' // 89ab + '20B' // 89ac #521 + 'aA' // 89ad-89ae + '20B' // 89af #521 + '1T' // 89b0 #45 + 'A' // 89b1 + '37Y' // 89b2 #986 + '259L' // 89b3 #6745 + 'b1T' // 89b4-89b6 #45 + '17X' // 89b7 #465 + 'aA' // 89b8-89b9 + '224S' // 89ba #5842 + 'A' // 89bb + '28H' // 89bc #735 + '232C' // 89bd #6034 + 'A' // 89be + '20B' // 89bf #521 + '232B' // 89c0 #6033 + '132L' // 89c1 #3443 + '1Z' // 89c2 #51 + 'A' // 89c3 + '3Q' // 89c4 #94 + '3G' // 89c5 #84 + '49C' // 89c6 #1276 + '10J' // 89c7 #269 + '1Z' // 89c8 #51 + '2D' // 89c9 #55 + 'b10J' // 89ca-89cc #269 + 'A' // 89cd + 'c10J' // 89ce-89d1 #269 + '228T' // 89d2 #5947 + 'A' // 89d3 + 'a20B' // 89d4-89d5 #521 + '49P' // 89d6 #1289 + 'a1T' // 89d7-89d8 #45 + 'A' // 89d9 + '17X' // 89da #465 + 'A' // 89db + '17X' // 89dc #465 + '20B' // 89dd #521 + '10J' // 89de #269 + 'cA' // 89df-89e2 + '238H' // 89e3 #6195 + 'A' // 89e4 + '17X' // 89e5 #465 + '90A' // 89e6 #2340 + '17X' // 89e7 #465 + 'A' // 89e8 + '1T' // 89e9 #45 + 'A' // 89ea + '49P' // 89eb #1289 + 'A' // 89ec + '20B' // 89ed #521 + 'A' // 89ee + '10J' // 89ef #269 + 'A' // 89f0 + '17X' // 89f1 #465 + 'A' // 89f2 + '17X' // 89f3 #465 + '37Y' // 89f4 #986 + 'A' // 89f5 + '20B' // 89f6 #521 + 'A' // 89f7 + '199O' // 89f8 #5188 + '1T' // 89f9 #45 + 'bA' // 89fa-89fc + '1T' // 89fd #45 + 'A' // 89fe + '17X' // 89ff #465 + '243L' // 8a00 #6329 + '49P' // 8a01 #1289 + '233L' // 8a02 #6069 + '89U' // 8a03 #2334 + 'a1T' // 8a04-8a05 #45 + 'A' // 8a06 + '17X' // 8a07 #465 + '242U' // 8a08 #6312 + '10J' // 8a09 #269 + '240O' // 8a0a #6254 + 'A' // 8a0b + '89Q' // 8a0c #2330 + 'A' // 8a0d + '220B' // 8a0e #5721 + '17X' // 8a0f #465 + 'b20B' // 8a10-8a12 #521 + '209P' // 8a13 #5449 + '1T' // 8a14 #45 + '121V' // 8a15 #3167 + '89P' // 8a16 #2329 + '197M' // 8a17 #5134 + '245J' // 8a18 #6379 + 'A' // 8a19 + '27G' // 8a1a #708 + '8U' // 8a1b #228 + '33A' // 8a1c #858 + '41B' // 8a1d #1067 + '1T' // 8a1e #45 + '142F' // 8a1f #3697 + 'a1T' // 8a20-8a21 #45 + '10R' // 8a22 #277 + '66O' // 8a23 #1730 + '1T' // 8a24 #45 + '8U' // 8a25 #228 + '1T' // 8a26 #45 + '24W' // 8a27 #646 + 'A' // 8a28 + '33A' // 8a29 #858 + '221F' // 8a2a #5751 + '10R' // 8a2b #277 + '1T' // 8a2c #45 + '244E' // 8a2d #6348 + 'A' // 8a2e + '1T' // 8a2f #45 + 'A' // 8a30 + '68K' // 8a31 #1778 + 'A' // 8a32 + '258C' // 8a33 #6710 + '215C' // 8a34 #5592 + '1T' // 8a35 #45 + '8U' // 8a36 #228 + '41J' // 8a37 #1075 + '90E' // 8a38 #2344 + 'A' // 8a39 + '67C' // 8a3a #1744 + '232D' // 8a3b #6035 + '155O' // 8a3c #4044 + '4G' // 8a3d #110 + '10R' // 8a3e #277 + 'A' // 8a3f + '41J' // 8a40 #1075 + '10R' // 8a41 #277 + 'A' // 8a42 + '1T' // 8a43 #45 + 'A' // 8a44 + '4G' // 8a45 #110 + '10R' // 8a46 #277 + '1T' // 8a47 #45 + '10R' // 8a48 #277 + '28H' // 8a49 #735 + 'bA' // 8a4a-8a4c + '1T' // 8a4d #45 + '4G' // 8a4e #110 + 'A' // 8a4f + '196Z' // 8a50 #5121 + '4G' // 8a51 #110 + '10R' // 8a52 #277 + '1T' // 8a53 #45 + '8U' // 8a54 #228 + '68R' // 8a55 #1785 + 'a4G' // 8a56-8a57 #110 + '10R' // 8a58 #277 + 'aA' // 8a59-8a5a + '8U' // 8a5b #228 + '1T' // 8a5c #45 + '41J' // 8a5d #1075 + '210H' // 8a5e #5467 + '27G' // 8a5f #708 + '159P' // 8a60 #4149 + '10R' // 8a61 #277 + '224R' // 8a62 #5841 + '89T' // 8a63 #2333 + 'A' // 8a64 + '1T' // 8a65 #45 + '230M' // 8a66 #5992 + '4G' // 8a67 #110 + 'A' // 8a68 + '202T' // 8a69 #5271 + '27G' // 8a6a #708 + '89X' // 8a6b #2337 + '4G' // 8a6c #110 + '41B' // 8a6d #1067 + '152W' // 8a6e #3974 + 'A' // 8a6f + '89Z' // 8a70 #2339 + '244K' // 8a71 #6354 + '227T' // 8a72 #5921 + '223W' // 8a73 #5820 + '24W' // 8a74 #646 + '17W' // 8a75 #464 + '4G' // 8a76 #110 + '1T' // 8a77 #45 + 'A' // 8a78 + '156M' // 8a79 #4068 + '10R' // 8a7a #277 + 'a4G' // 8a7b-8a7c #110 + 'A' // 8a7d + '28H' // 8a7e #735 + 'a1T' // 8a7f-8a80 #45 + 'A' // 8a81 + '4G' // 8a82 #110 + '1T' // 8a83 #45 + '4G' // 8a84 #110 + '8U' // 8a85 #228 + '4G' // 8a86 #110 + '175W' // 8a87 #4572 + 'A' // 8a88 + '254E' // 8a89 #6608 + '27G' // 8a8a #708 + '1T' // 8a8b #45 + '222L' // 8a8c #5783 + '238G' // 8a8d #6194 + 'A' // 8a8e + '4G' // 8a8f #110 + 'b10R' // 8a90-8a92 #277 + '153V' // 8a93 #3999 + '24W' // 8a94 #646 + '198V' // 8a95 #5169 + 'a1T' // 8a96-8a97 #45 + '67C' // 8a98 #1744 + '1T' // 8a99 #45 + '4G' // 8a9a #110 + 'A' // 8a9b + '33A' // 8a9c #858 + 'A' // 8a9d + '238P' // 8a9e #6203 + '1T' // 8a9f #45 + '210A' // 8aa0 #5460 + '8U' // 8aa1 #228 + 'A' // 8aa2 + '8U' // 8aa3 #228 + '210T' // 8aa4 #5479 + '8U' // 8aa5 #228 + '63B' // 8aa6 #1639 + '4G' // 8aa7 #110 + '8U' // 8aa8 #228 + '89R' // 8aa9 #2331 + '239O' // 8aaa #6228 + 'A' // 8aab + '131H' // 8aac #3413 + '259W' // 8aad #6756 + '41J' // 8aae #1075 + '28H' // 8aaf #735 + '213A' // 8ab0 #5538 + 'A' // 8ab1 + '212B' // 8ab2 #5513 + '1T' // 8ab3 #45 + '33A' // 8ab4 #858 + 'A' // 8ab5 + '4G' // 8ab6 #110 + '1T' // 8ab7 #45 + '27G' // 8ab8 #708 + '142K' // 8ab9 #3702 + 'A' // 8aba + '1T' // 8abb #45 + '163I' // 8abc #4246 + 'A' // 8abd + '8U' // 8abe #228 + '231K' // 8abf #6016 + 'aA' // 8ac0-8ac1 + '17W' // 8ac2 #464 + '1T' // 8ac3 #45 + '17W' // 8ac4 #464 + 'A' // 8ac5 + '4G' // 8ac6 #110 + '213W' // 8ac7 #5560 + '1T' // 8ac8 #45 + '4G' // 8ac9 #110 + '1T' // 8aca #45 + '241M' // 8acb #6278 + '4G' // 8acc #110 + '17W' // 8acd #464 + 'A' // 8ace + '89V' // 8acf #2335 + '1T' // 8ad0 #45 + '4G' // 8ad1 #110 + '172N' // 8ad2 #4485 + 'b1T' // 8ad3-8ad5 #45 + '234I' // 8ad6 #6092 + '138N' // 8ad7 #3601 + 'aA' // 8ad8-8ad9 + '28H' // 8ada #735 + '8U' // 8adb #228 + '144O' // 8adc #3758 + 'a4G' // 8add-8ade #110 + '10R' // 8adf #277 + '4G' // 8ae0 #110 + '8U' // 8ae1 #228 + '4G' // 8ae2 #110 + 'A' // 8ae3 + '4G' // 8ae4 #110 + 'A' // 8ae5 + '89Y' // 8ae6 #2338 + '41B' // 8ae7 #1067 + '27G' // 8ae8 #708 + 'A' // 8ae9 + '90C' // 8aea #2342 + '8U' // 8aeb #228 + '1T' // 8aec #45 + '89W' // 8aed #2336 + '185C' // 8aee #4812 + 'A' // 8aef + '1T' // 8af0 #45 + '8U' // 8af1 #228 + '24W' // 8af2 #646 + 'a10R' // 8af3-8af4 #277 + '4G' // 8af5 #110 + '17W' // 8af6 #464 + '41B' // 8af7 #1067 + '182Q' // 8af8 #4748 + '33A' // 8af9 #858 + '8U' // 8afa #228 + 'A' // 8afb + '4G' // 8afc #110 + 'A' // 8afd + '203U' // 8afe #5298 + '1T' // 8aff #45 + '66O' // 8b00 #1730 + '8U' // 8b01 #228 + '192H' // 8b02 #4999 + 'A' // 8b03 + '17W' // 8b04 #464 + '4G' // 8b05 #110 + '1T' // 8b06 #45 + '10R' // 8b07 #277 + 'aA' // 8b08-8b09 + '144P' // 8b0a #3759 + '4G' // 8b0b #110 + '10R' // 8b0c #277 + '4G' // 8b0d #110 + '168X' // 8b0e #4391 + '4G' // 8b0f #110 + '8U' // 8b10 #228 + '1T' // 8b11 #45 + 'A' // 8b12 + '24W' // 8b13 #646 + '17W' // 8b14 #464 + 'A' // 8b15 + '17W' // 8b16 #464 + '153P' // 8b17 #3993 + 'A' // 8b18 + '160H' // 8b19 #4167 + '17W' // 8b1a #464 + '221M' // 8b1b #5758 + '4G' // 8b1c #110 + '228C' // 8b1d #5930 + '1T' // 8b1e #45 + '28H' // 8b1f #735 + '170K' // 8b20 #4430 + '253L' // 8b21 #6589 + '24W' // 8b22 #646 + 'bA' // 8b23-8b25 + '10R' // 8b26 #277 + 'A' // 8b27 + '8U' // 8b28 #228 + 'aA' // 8b29-8b2a + '17W' // 8b2b #464 + '63B' // 8b2c #1639 + '55D' // 8b2d #1433 + '24W' // 8b2e #646 + 'A' // 8b2f + '1K' // 8b30 #36 + 'aA' // 8b31-8b32 + '89S' // 8b33 #2332 + 'bA' // 8b34-8b36 + '1K' // 8b37 #36 + 'A' // 8b38 + '166L' // 8b39 #4327 + 'aA' // 8b3a-8b3b + '1K' // 8b3c #36 + 'A' // 8b3d + '132K' // 8b3e #3442 + '90D' // 8b3f #2343 + 'A' // 8b40 + '89M' // 8b41 #2326 + '1K' // 8b42 #36 + '55D' // 8b43 #1433 + '1K' // 8b44 #36 + '248M' // 8b45 #6460 + '55E' // 8b46 #1434 + 'A' // 8b47 + '1K' // 8b48 #36 + '232A' // 8b49 #6032 + 'aA' // 8b4a-8b4b + '89O' // 8b4c #2328 + '55E' // 8b4d #1434 + '89N' // 8b4e #2327 + '54Z' // 8b4f #1429 + 'A' // 8b50 + 'a1K' // 8b51-8b52 #36 + 'a20A' // 8b53-8b54 #520 + 'A' // 8b55 + '20A' // 8b56 #520 + '27G' // 8b57 #708 + '68K' // 8b58 #1778 + '20A' // 8b59 #520 + '153D' // 8b5a #3981 + '1K' // 8b5b #36 + '202C' // 8b5c #5254 + 'A' // 8b5d + '89D' // 8b5e #2317 + '20A' // 8b5f #520 + '89K' // 8b60 #2324 + 'A' // 8b61 + '54Y' // 8b62 #1428 + '1K' // 8b63 #36 + 'aA' // 8b64-8b65 + '211G' // 8b66 #5492 + 'aA' // 8b67-8b68 + '28G' // 8b69 #734 + '89L' // 8b6a #2325 + '20A' // 8b6b #520 + '54Z' // 8b6c #1429 + '20A' // 8b6d #520 + 'A' // 8b6e + '192F' // 8b6f #4997 + '228S' // 8b70 #5946 + '1K' // 8b71 #36 + '255U' // 8b72 #6650 + 'A' // 8b73 + '127E' // 8b74 #3306 + 'A' // 8b75 + '1K' // 8b76 #36 + '68R' // 8b77 #1785 + 'a1K' // 8b78-8b79 #36 + 'aA' // 8b7a-8b7b + '1K' // 8b7c #36 + '177S' // 8b7d #4620 + '20A' // 8b7e #520 + '1K' // 8b7f #36 + '224Q' // 8b80 #5840 + '28G' // 8b81 #734 + 'A' // 8b82 + '89F' // 8b83 #2319 + 'a1K' // 8b84-8b85 #36 + 'cA' // 8b86-8b89 + '68O' // 8b8a #1782 + '1K' // 8b8b #36 + '55B' // 8b8c #1431 + '1K' // 8b8d #36 + '20A' // 8b8e #520 + '28G' // 8b8f #734 + '89E' // 8b90 #2318 + 'A' // 8b91 + '55A' // 8b92 #1430 + '68O' // 8b93 #1782 + '1K' // 8b94 #36 + '20A' // 8b95 #520 + '55A' // 8b96 #1430 + 'aA' // 8b97-8b98 + '55B' // 8b99 #1431 + '217A' // 8b9a #5642 + '54Y' // 8b9b #1428 + '11W' // 8b9c #308 + '1K' // 8b9d #36 + 'a11W' // 8b9e-8b9f #308 + '89C' // 8ba0 #2316 + '3Q' // 8ba1 #94 + '2D' // 8ba2 #55 + '3D' // 8ba3 #81 + '3Q' // 8ba4 #94 + 'b3D' // 8ba5-8ba7 #81 + '3I' // 8ba8 #86 + '1Z' // 8ba9 #51 + 'b3D' // 8baa-8bac #81 + '3I' // 8bad #86 + 'b3Q' // 8bae-8bb0 #94 + 'A' // 8bb1 + '2C' // 8bb2 #54 + '2W' // 8bb3 #74 + 'a3D' // 8bb4-8bb5 #81 + '3X' // 8bb6 #101 + '3D' // 8bb7 #81 + '1Z' // 8bb8 #51 + '3D' // 8bb9 #81 + '7K' // 8bba #192 + 'A' // 8bbb + '3X' // 8bbc #101 + '2K' // 8bbd #62 + '65M' // 8bbe #1702 + '2D' // 8bbf #55 + '2R' // 8bc0 #69 + '3Q' // 8bc1 #94 + 'a3D' // 8bc2-8bc3 #81 + '3Q' // 8bc4 #94 + '3D' // 8bc5 #81 + '1Z' // 8bc6 #51 + 'A' // 8bc7 + '4C' // 8bc8 #106 + '2D' // 8bc9 #55 + '1R' // 8bca #43 + 'a3D' // 8bcb-8bcc #81 + '2D' // 8bcd #55 + 'b3D' // 8bce-8bd0 #81 + '3N' // 8bd1 #91 + 'b3D' // 8bd2-8bd4 #81 + '1Z' // 8bd5 #51 + '3D' // 8bd6 #81 + '3Y' // 8bd7 #102 + 'a3D' // 8bd8-8bd9 #81 + '2C' // 8bda #54 + '3G' // 8bdb #84 + '3D' // 8bdc #81 + '7K' // 8bdd #192 + '1R' // 8bde #43 + '3D' // 8bdf #81 + 'a2K' // 8be0-8be1 #62 + '63N' // 8be2 #1651 + 'a3D' // 8be3-8be4 #81 + '1Z' // 8be5 #51 + '2D' // 8be6 #55 + 'b3D' // 8be7-8be9 #81 + 'A' // 8bea + 'a3D' // 8beb-8bec #81 + '1Z' // 8bed #51 + '3D' // 8bee #81 + '3I' // 8bef #86 + '3D' // 8bf0 #81 + '3N' // 8bf1 #91 + 'a3D' // 8bf2-8bf3 #81 + '7K' // 8bf4 #192 + '3G' // 8bf5 #84 + '3D' // 8bf6 #81 + '7K' // 8bf7 #192 + '4C' // 8bf8 #106 + '3D' // 8bf9 #81 + '3Y' // 8bfa #102 + '3Q' // 8bfb #94 + 'a3D' // 8bfc-8bfd #81 + '3I' // 8bfe #86 + 'a3D' // 8bff-8c00 #81 + '2C' // 8c01 #54 + '3D' // 8c02 #81 + '1Z' // 8c03 #51 + '3D' // 8c04 #81 + '3H' // 8c05 #85 + 'a3D' // 8c06-8c07 #81 + '2C' // 8c08 #54 + 'A' // 8c09 + '2R' // 8c0a #69 + '4C' // 8c0b #106 + '3D' // 8c0c #81 + '3H' // 8c0d #85 + '2K' // 8c0e #62 + '3D' // 8c0f #81 + '2L' // 8c10 #63 + 'a3D' // 8c11-8c12 #81 + '1R' // 8c13 #43 + 'g3D' // 8c14-8c1b #81 + '2Y' // 8c1c #76 + 'd3D' // 8c1d-8c21 #81 + '2D' // 8c22 #55 + '2L' // 8c23 #63 + 'a3D' // 8c24-8c25 #81 + '3H' // 8c26 #85 + '3D' // 8c27 #81 + '4C' // 8c28 #106 + '2R' // 8c29 #69 + 'b3D' // 8c2a-8c2c #81 + '2R' // 8c2d #69 + 'b3D' // 8c2e-8c30 #81 + '3N' // 8c31 #91 + 'd3D' // 8c32-8c36 #81 + '206F' // 8c37 #5361 + '1K' // 8c38 #36 + '11W' // 8c39 #308 + '1K' // 8c3a #36 + 'aA' // 8c3b-8c3c + 'a1K' // 8c3d-8c3e #36 + '44K' // 8c3f #1154 + 'A' // 8c40 + '132J' // 8c41 #3441 + 'bA' // 8c42-8c44 + '1K' // 8c45 #36 + '204O' // 8c46 #5318 + '11V' // 8c47 #307 + '132I' // 8c48 #3440 + '11V' // 8c49 #307 + '89J' // 8c4a #2323 + '11V' // 8c4b #307 + '44K' // 8c4c #1154 + 'A' // 8c4d + '127F' // 8c4e #3307 + '11V' // 8c4f #307 + '217B' // 8c50 #5643 + '11W' // 8c51 #308 + 'A' // 8c52 + '1K' // 8c53 #36 + '163G' // 8c54 #4244 + '44K' // 8c55 #1154 + '3D' // 8c56 #81 + 'b1K' // 8c57-8c59 #36 + '149D' // 8c5a #3877 + '1K' // 8c5b #36 + 'A' // 8c5c + '1K' // 8c5d #36 + 'bA' // 8c5e-8c60 + '223A' // 8c61 #5798 + '11V' // 8c62 #307 + 'a1K' // 8c63-8c64 #36 + 'A' // 8c65 + '1K' // 8c66 #36 + 'A' // 8c67 + '11V' // 8c68 #307 + '1K' // 8c69 #36 + '210Q' // 8c6a #5476 + '163F' // 8c6b #4243 + '192G' // 8c6c #4998 + '1K' // 8c6d #36 + 'dA' // 8c6e-8c72 + '11V' // 8c73 #307 + 'A' // 8c74 + 'a1K' // 8c75-8c76 #36 + 'A' // 8c77 + '11V' // 8c78 #307 + '159O' // 8c79 #4148 + '44J' // 8c7a #1153 + '11W' // 8c7b #308 + '1K' // 8c7c #36 + 'A' // 8c7d + '1K' // 8c7e #36 + 'bA' // 8c7f-8c81 + '44J' // 8c82 #1153 + 'aA' // 8c83-8c84 + '11V' // 8c85 #307 + 'a1K' // 8c86-8c87 #36 + 'A' // 8c88 + '11V' // 8c89 #307 + '44J' // 8c8a #1153 + '1K' // 8c8b #36 + '188L' // 8c8c #4899 + '11V' // 8c8d #307 + '248L' // 8c8e #6459 + 'A' // 8c8f + '11V' // 8c90 #307 + 'A' // 8c91 + '1K' // 8c92 #36 + '206U' // 8c93 #5376 + '11V' // 8c94 #307 + 'bA' // 8c95-8c97 + '11V' // 8c98 #307 + '11W' // 8c99 #308 + 'A' // 8c9a + '28G' // 8c9b #734 + '1K' // 8c9c #36 + '209B' // 8c9d #5435 + '168B' // 8c9e #4369 + '28G' // 8c9f #734 + '222A' // 8ca0 #5772 + '228H' // 8ca1 #5935 + '182H' // 8ca2 #4739 + '55C' // 8ca3 #1432 + '11W' // 8ca4 #308 + 'aA' // 8ca5-8ca6 + '168W' // 8ca7 #4390 + '229C' // 8ca8 #5956 + '199H' // 8ca9 #5181 + '166G' // 8caa #4322 + '168P' // 8cab #4383 + '229Q' // 8cac #5970 + '11W' // 8cad #308 + '1K' // 8cae #36 + '89I' // 8caf #2322 + '89H' // 8cb0 #2321 + 'A' // 8cb1 + '11W' // 8cb2 #308 + '63P' // 8cb3 #1653 + '215Z' // 8cb4 #5615 + 'A' // 8cb5 + '63P' // 8cb6 #1653 + '237U' // 8cb7 #6182 + '176O' // 8cb8 #4590 + '11V' // 8cb9 #307 + '11W' // 8cba #308 + '241R' // 8cbb #6283 + '227P' // 8cbc #5917 + '44I' // 8cbd #1152 + 'A' // 8cbe + '201O' // 8cbf #5240 + '183X' // 8cc0 #4781 + 'a44I' // 8cc1-8cc2 #1152 + '149G' // 8cc3 #3880 + '44I' // 8cc4 #1152 + '11W' // 8cc5 #308 + '1K' // 8cc6 #36 + '241Z' // 8cc7 #6291 + '156K' // 8cc8 #4066 + '1K' // 8cc9 #36 + '174I' // 8cca #4532 + '1K' // 8ccb #36 + 'A' // 8ccc + '89B' // 8ccd #2315 + '1K' // 8cce #36 + '11W' // 8ccf #308 + 'A' // 8cd0 + '89G' // 8cd1 #2320 + '11W' // 8cd2 #308 + '201A' // 8cd3 #5226 + '55C' // 8cd4 #1432 + '11W' // 8cd5 #308 + '28G' // 8cd6 #734 + 'aA' // 8cd7-8cd8 + '11W' // 8cd9 #308 + '89A' // 8cda #2314 + '88W' // 8cdb #2310 + '153C' // 8cdc #3980 + '1K' // 8cdd #36 + '221L' // 8cde #5757 + 'A' // 8cdf + '174B' // 8ce0 #4525 + '13A' // 8ce1 #338 + '189V' // 8ce2 #4935 + '231Z' // 8ce3 #6031 + '144N' // 8ce4 #3757 + 'A' // 8ce5 + '163E' // 8ce6 #4242 + '4R' // 8ce7 #121 + '16K' // 8ce8 #426 + '24U' // 8ce9 #644 + '238F' // 8cea #6193 + '19Z' // 8ceb #519 + '163H' // 8cec #4245 + '166R' // 8ced #4333 + 'A' // 8cee + '1K' // 8cef #36 + '13A' // 8cf0 #338 + '1K' // 8cf1 #36 + '32Z' // 8cf2 #857 + 'A' // 8cf3 + '67K' // 8cf4 #1752 + '1K' // 8cf5 #36 + 'A' // 8cf6 + '32Z' // 8cf7 #857 + '16K' // 8cf8 #426 + 'A' // 8cf9 + '67F' // 8cfa #1747 + '88Q' // 8cfb #2304 + '237B' // 8cfc #6163 + '216Z' // 8cfd #5641 + '13A' // 8cfe #338 + '1K' // 8cff #36 + 'A' // 8d00 + '1K' // 8d01 #36 + 'A' // 8d02 + '32Z' // 8d03 #857 + '28F' // 8d04 #733 + '125R' // 8d05 #3267 + 'A' // 8d06 + '28F' // 8d07 #733 + '203Y' // 8d08 #5302 + '1K' // 8d09 #36 + '67K' // 8d0a #1752 + '13A' // 8d0b #338 + '24U' // 8d0c #644 + '28F' // 8d0d #733 + '1K' // 8d0e #36 + '185B' // 8d0f #4811 + '13A' // 8d10 #338 + '24U' // 8d11 #644 + '13A' // 8d12 #338 + '28F' // 8d13 #733 + '16K' // 8d14 #426 + 'A' // 8d15 + '127D' // 8d16 #3305 + '13A' // 8d17 #338 + '24U' // 8d18 #644 + 'aA' // 8d19-8d1a + '16K' // 8d1b #426 + '13A' // 8d1c #338 + '88S' // 8d1d #2306 + '3H' // 8d1e #85 + '2C' // 8d1f #54 + '4R' // 8d20 #121 + '1R' // 8d21 #43 + '2D' // 8d22 #55 + '1Z' // 8d23 #51 + '1R' // 8d24 #43 + '3Y' // 8d25 #102 + 'a2D' // 8d26-8d27 #55 + '3Q' // 8d28 #94 + '2K' // 8d29 #62 + '2Y' // 8d2a #76 + '4C' // 8d2b #106 + '3W' // 8d2c #100 + '1Z' // 8d2d #51 + '2W' // 8d2e #74 + '4C' // 8d2f #106 + '4R' // 8d30 #121 + '3H' // 8d31 #85 + 'a4R' // 8d32-8d33 #121 + '1Z' // 8d34 #51 + '2C' // 8d35 #54 + '4R' // 8d36 #121 + '4C' // 8d37 #106 + '3Y' // 8d38 #102 + '3Q' // 8d39 #94 + '4C' // 8d3a #106 + '4R' // 8d3b #121 + '2Y' // 8d3c #76 + '4R' // 8d3d #121 + '2L' // 8d3e #63 + '3G' // 8d3f #84 + '4R' // 8d40 #121 + '2R' // 8d41 #69 + 'a4R' // 8d42-8d43 #121 + '7K' // 8d44 #192 + 'a4R' // 8d45-8d46 #121 + 'A' // 8d47 + 'b4R' // 8d48-8d4a #121 + 'a2L' // 8d4b-8d4c #63 + '4R' // 8d4d #121 + '3W' // 8d4e #100 + '3I' // 8d4f #86 + '2K' // 8d50 #62 + '4R' // 8d51 #121 + 'A' // 8d52 + '4R' // 8d53 #121 + '4C' // 8d54 #106 + '4R' // 8d55 #121 + '1R' // 8d56 #43 + 'A' // 8d57 + '2W' // 8d58 #74 + '4R' // 8d59 #121 + '3N' // 8d5a #91 + '2C' // 8d5b #54 + 'a4R' // 8d5c-8d5d #121 + '2C' // 8d5e #54 + '4R' // 8d5f #121 + '1R' // 8d60 #43 + '4R' // 8d61 #121 + '3I' // 8d62 #86 + '2K' // 8d63 #62 + '184B' // 8d64 #4785 + '1K' // 8d65 #36 + '88U' // 8d66 #2308 + '16K' // 8d67 #426 + '19Z' // 8d68 #519 + '13A' // 8d69 #338 + '4R' // 8d6a #121 + '170I' // 8d6b #4428 + '13A' // 8d6c #338 + '16K' // 8d6d #426 + '13A' // 8d6e #338 + 'A' // 8d6f + '228M' // 8d70 #5940 + '248K' // 8d71 #6458 + 'A' // 8d72 + '28F' // 8d73 #733 + '180M' // 8d74 #4692 + '3N' // 8d75 #91 + '88R' // 8d76 #2305 + '241S' // 8d77 #6284 + 'aA' // 8d78-8d79 + '24U' // 8d7a #644 + '19Z' // 8d7b #519 + 'A' // 8d7c + '19Z' // 8d7d #519 + 'A' // 8d7e + '1K' // 8d7f #36 + 'A' // 8d80 + '170J' // 8d81 #4429 + '32Z' // 8d82 #857 + 'A' // 8d83 + '16K' // 8d84 #426 + '242A' // 8d85 #6292 + 'aA' // 8d86-8d87 + '1K' // 8d88 #36 + 'A' // 8d89 + '223H' // 8d8a #5805 + '1R' // 8d8b #43 + 'A' // 8d8c + '1K' // 8d8d #36 + 'aA' // 8d8e-8d8f + 'a16K' // 8d90-8d91 #426 + 'aA' // 8d92-8d93 + '24V' // 8d94 #645 + '67F' // 8d95 #1747 + '19Z' // 8d96 #519 + 'aA' // 8d97-8d98 + '177R' // 8d99 #4619 + 'aA' // 8d9a-8d9b + '19Z' // 8d9c #519 + 'A' // 8d9d + '1K' // 8d9e #36 + '156L' // 8d9f #4067 + '1K' // 8da0 #36 + 'aA' // 8da1-8da2 + '228L' // 8da3 #5939 + 'aA' // 8da4-8da5 + '32Z' // 8da6 #857 + 'A' // 8da7 + '185A' // 8da8 #4810 + '24U' // 8da9 #644 + 'A' // 8daa + '13A' // 8dab #338 + '1K' // 8dac #36 + 'aA' // 8dad-8dae + '16K' // 8daf #426 + 'A' // 8db0 + '4R' // 8db1 #121 + '13A' // 8db2 #338 + '230U' // 8db3 #6000 + '66P' // 8db4 #1731 + '16K' // 8db5 #426 + 'A' // 8db6 + '13A' // 8db7 #338 + '4R' // 8db8 #121 + '1K' // 8db9 #36 + '28F' // 8dba #733 + '1K' // 8dbb #36 + '16K' // 8dbc #426 + 'A' // 8dbd + '121U' // 8dbe #3166 + '24V' // 8dbf #645 + '44F' // 8dc0 #1149 + 'A' // 8dc1 + '11U' // 8dc2 #306 + '88Y' // 8dc3 #2312 + '4R' // 8dc4 #121 + '1N' // 8dc5 #39 + '28E' // 8dc6 #732 + 'a1N' // 8dc7-8dc8 #39 + 'A' // 8dc9 + '1N' // 8dca #39 + '28E' // 8dcb #732 + '184Y' // 8dcc #4808 + 'A' // 8dcd + '11U' // 8dce #306 + '28E' // 8dcf #732 + '19Z' // 8dd0 #519 + '214I' // 8dd1 #5572 + 'aA' // 8dd2-8dd3 + '44F' // 8dd4 #1149 + '1N' // 8dd5 #39 + 'a11U' // 8dd6-8dd7 #306 + 'A' // 8dd8 + '1N' // 8dd9 #39 + '11U' // 8dda #306 + '28E' // 8ddb #732 + 'A' // 8ddc + '204L' // 8ddd #5315 + '4R' // 8dde #121 + '224P' // 8ddf #5839 + 'A' // 8de0 + '198L' // 8de1 #5159 + 'A' // 8de2 + '28E' // 8de3 #732 + '11U' // 8de4 #306 + '1N' // 8de5 #39 + 'A' // 8de6 + '1N' // 8de7 #39 + '208K' // 8de8 #5418 + '24V' // 8de9 #645 + '149Z' // 8dea #3899 + 'a11U' // 8deb-8dec #306 + 'aA' // 8ded-8dee + '241W' // 8def #6288 + '69X' // 8df0 #1817 + '11U' // 8df1 #306 + '1N' // 8df2 #39 + '218M' // 8df3 #5680 + '1N' // 8df4 #39 + '255C' // 8df5 #6632 + 'c4R' // 8df6-8df9 #121 + '24V' // 8dfa #645 + '4R' // 8dfb #121 + 'a11U' // 8dfc-8dfd #306 + 'A' // 8dfe + '1N' // 8dff #39 + 'A' // 8e00 + '44F' // 8e01 #1149 + 'aA' // 8e02-8e03 + '1N' // 8e04 #39 + '11U' // 8e05 #306 + '1N' // 8e06 #39 + '4R' // 8e07 #121 + '1N' // 8e08 #39 + '11U' // 8e09 #306 + '88V' // 8e0a #2309 + '1N' // 8e0b #39 + '69X' // 8e0c #1817 + 'A' // 8e0d + '24V' // 8e0e #645 + '190D' // 8e0f #4943 + '156I' // 8e10 #4064 + '1N' // 8e11 #39 + 'aA' // 8e12-8e13 + '11U' // 8e14 #306 + 'A' // 8e15 + '44H' // 8e16 #1151 + 'A' // 8e17 + '19Z' // 8e18 #519 + 'cA' // 8e19-8e1c + '11U' // 8e1d #306 + '28E' // 8e1e #732 + '11U' // 8e1f #306 + 'a44H' // 8e20-8e21 #1151 + '184Z' // 8e22 #4809 + '11U' // 8e23 #306 + 'aA' // 8e24-8e25 + '11U' // 8e26 #306 + '44H' // 8e27 #1151 + '19Z' // 8e28 #519 + '177C' // 8e29 #4604 + '88T' // 8e2a #2307 + '24V' // 8e2b #645 + '4R' // 8e2c #121 + 'a24V' // 8e2d-8e2e #645 + '4R' // 8e2f #121 + '54W' // 8e30 #1426 + '37X' // 8e31 #985 + 'A' // 8e32 + '1N' // 8e33 #39 + '121T' // 8e34 #3165 + '54W' // 8e35 #1426 + 'b1N' // 8e36-8e38 #39 + '37X' // 8e39 #985 + '88X' // 8e3a #2311 + 'A' // 8e3b + '88Z' // 8e3c #2313 + '37X' // 8e3d #985 + 'aA' // 8e3e-8e3f + '37X' // 8e40 #985 + '54X' // 8e41 #1427 + '44G' // 8e42 #1150 + 'A' // 8e43 + '127C' // 8e44 #3304 + 'A' // 8e45 + '24U' // 8e46 #644 + '44G' // 8e47 #1150 + '156J' // 8e48 #4065 + 'a44G' // 8e49-8e4a #1150 + '54X' // 8e4b #1427 + 'b88P' // 8e4c-8e4e #2303 + '22K' // 8e4f #582 + '13W' // 8e50 #360 + 'a8P' // 8e51-8e52 #223 + '37W' // 8e53 #984 + 'a5Q' // 8e54-8e55 #146 + 'bA' // 8e56-8e58 + '12Z' // 8e59 #337 + '37W' // 8e5a #984 + '5Q' // 8e5b #146 + 'b1N' // 8e5c-8e5e #39 + '177Q' // 8e5f #4618 + '12Z' // 8e60 #337 + '1N' // 8e61 #39 + 'a5Q' // 8e62-8e63 #146 + '216Y' // 8e64 #5640 + 'A' // 8e65 + '126I' // 8e66 #3284 + '19Y' // 8e67 #518 + '32X' // 8e68 #855 + '13W' // 8e69 #360 + 'aA' // 8e6a-8e6b + '13W' // 8e6c #360 + '88B' // 8e6d #2289 + 'A' // 8e6e + '5Q' // 8e6f #146 + '49O' // 8e70 #1288 + '22K' // 8e71 #582 + '138L' // 8e72 #3599 + 'A' // 8e73 + '88F' // 8e74 #2293 + '22K' // 8e75 #582 + '12Z' // 8e76 #337 + '22K' // 8e77 #582 + 'A' // 8e78 + '1N' // 8e79 #39 + 'b13W' // 8e7a-8e7c #360 + 'A' // 8e7d + '32X' // 8e7e #855 + '8P' // 8e7f #223 + '32X' // 8e80 #855 + '144M' // 8e81 #3756 + '5Q' // 8e82 #146 + '1N' // 8e83 #39 + 'a13W' // 8e84-8e85 #360 + 'A' // 8e86 + '88C' // 8e87 #2290 + 'A' // 8e88 + '13W' // 8e89 #360 + '88D' // 8e8a #2291 + '13W' // 8e8b #360 + 'A' // 8e8c + '183R' // 8e8d #4775 + 'A' // 8e8e + '8P' // 8e8f #223 + '13W' // 8e90 #360 + 'b5Q' // 8e91-8e93 #146 + '13W' // 8e94 #360 + '5Q' // 8e95 #146 + 'aA' // 8e96-8e97 + '1N' // 8e98 #39 + '49O' // 8e99 #1288 + '5Q' // 8e9a #146 + '1N' // 8e9b #39 + '8P' // 8e9c #223 + '5Q' // 8e9d #146 + '13W' // 8e9e #360 + 'aA' // 8e9f-8ea0 + '5Q' // 8ea1 #146 + '1N' // 8ea2 #39 + '19Y' // 8ea3 #518 + 'A' // 8ea4 + 'a19Y' // 8ea5-8ea6 #518 + '22K' // 8ea7 #582 + 'A' // 8ea8 + '1N' // 8ea9 #39 + '12Z' // 8eaa #337 + '242T' // 8eab #6311 + '12Z' // 8eac #337 + '5Q' // 8ead #146 + '1N' // 8eae #39 + '248J' // 8eaf #6457 + '13W' // 8eb0 #360 + '1N' // 8eb1 #39 + '169U' // 8eb2 #4414 + '1N' // 8eb3 #39 + 'A' // 8eb4 + '1N' // 8eb5 #39 + '88A' // 8eb6 #2288 + 'aA' // 8eb7-8eb8 + '19Y' // 8eb9 #518 + '163D' // 8eba #4241 + '1N' // 8ebb #39 + '19Y' // 8ebc #518 + 'A' // 8ebd + '1N' // 8ebe #39 + 'A' // 8ebf + '121S' // 8ec0 #3164 + '1N' // 8ec1 #39 + 'A' // 8ec2 + '22K' // 8ec3 #582 + 'a1N' // 8ec4-8ec5 #39 + '49O' // 8ec6 #1288 + 'a1N' // 8ec7-8ec8 #39 + 'A' // 8ec9 + '243Q' // 8eca #6334 + '12Z' // 8ecb #337 + '181I' // 8ecc #4714 + '220A' // 8ecd #5720 + '88K' // 8ece #2298 + '5Q' // 8ecf #146 + 'A' // 8ed0 + '5Q' // 8ed1 #146 + '182O' // 8ed2 #4746 + 'A' // 8ed3 + '5Q' // 8ed4 #146 + 'aA' // 8ed5-8ed6 + '19Y' // 8ed7 #518 + 'aA' // 8ed8-8ed9 + '37W' // 8eda #984 + '5Q' // 8edb #146 + '1N' // 8edc #39 + 'aA' // 8edd-8ede + '218Y' // 8edf #5692 + 'aA' // 8ee0-8ee1 + '88I' // 8ee2 #2296 + '1N' // 8ee3 #39 + '32X' // 8ee4 #855 + 'bA' // 8ee5-8ee7 + '5Q' // 8ee8 #146 + 'aA' // 8ee9-8eea + '12Z' // 8eeb #337 + 'A' // 8eec + '22K' // 8eed #582 + '1N' // 8eee #39 + 'A' // 8eef + 'a1N' // 8ef0-8ef1 #39 + '19Y' // 8ef2 #518 + 'cA' // 8ef3-8ef6 + '1N' // 8ef7 #39 + '175D' // 8ef8 #4553 + 'a5Q' // 8ef9-8efa #146 + '12Z' // 8efb #337 + '13W' // 8efc #360 + '259K' // 8efd #6744 + '12Z' // 8efe #337 + 'A' // 8eff + '1N' // 8f00 #39 + 'A' // 8f01 + '1N' // 8f02 #39 + '228R' // 8f03 #5945 + 'A' // 8f04 + '32Y' // 8f05 #856 + 'A' // 8f06 + 'a5Q' // 8f07-8f08 #146 + '238R' // 8f09 #6205 + '5Q' // 8f0a #146 + '37W' // 8f0b #984 + '251Z' // 8f0c #6551 + 'aA' // 8f0d-8f0e + 'a1N' // 8f0f-8f10 #39 + 'A' // 8f11 + 'a12Z' // 8f12-8f13 #337 + '195Z' // 8f14 #5095 + '68H' // 8f15 #1775 + '1N' // 8f16 #39 + '5Q' // 8f17 #146 + '1N' // 8f18 #39 + '5Q' // 8f19 #146 + '19Y' // 8f1a #518 + '170H' // 8f1b #4427 + '32Y' // 8f1c #856 + '190N' // 8f1d #4953 + '32Y' // 8f1e #856 + '12Z' // 8f1f #337 + 'a1N' // 8f20-8f21 #39 + 'A' // 8f22 + '1N' // 8f23 #39 + 'A' // 8f24 + '5Q' // 8f25 #146 + '32Y' // 8f26 #856 + '69W' // 8f27 #1816 + '1N' // 8f28 #39 + '190W' // 8f29 #4962 + '212K' // 8f2a #5522 + 'a1N' // 8f2b-8f2c #39 + '5Q' // 8f2d #146 + '1N' // 8f2e #39 + '68H' // 8f2f #1775 + '19Y' // 8f30 #518 + 'aA' // 8f31-8f32 + '32Y' // 8f33 #856 + 'a1N' // 8f34-8f35 #39 + '13W' // 8f36 #360 + '1N' // 8f37 #39 + '219Y' // 8f38 #5718 + '69W' // 8f39 #1816 + '1N' // 8f3a #39 + '127B' // 8f3b #3303 + '32X' // 8f3c #855 + 'A' // 8f3d + '12Z' // 8f3e #337 + '129V' // 8f3f #3375 + 'b5Q' // 8f40-8f42 #146 + '1N' // 8f43 #39 + '147Z' // 8f44 #3847 + '12Z' // 8f45 #337 + 'a5Q' // 8f46-8f47 #146 + 'A' // 8f48 + '239N' // 8f49 #6227 + '22K' // 8f4a #582 + 'A' // 8f4b + '1N' // 8f4c #39 + '12Z' // 8f4d #337 + '138K' // 8f4e #3598 + '1N' // 8f4f #39 + 'A' // 8f50 + '1N' // 8f51 #39 + 'a2G' // 8f52-8f53 #58 + 'a44E' // 8f54-8f55 #1148 + 'A' // 8f56 + 'a2G' // 8f57-8f58 #58 + 'bA' // 8f59-8f5b + '37V' // 8f5c #983 + '54U' // 8f5d #1424 + '2G' // 8f5e #58 + '173B' // 8f5f #4499 + 'A' // 8f60 + '44E' // 8f61 #1148 + '54U' // 8f62 #1424 + '2G' // 8f63 #58 + '44E' // 8f64 #1148 + '2G' // 8f65 #58 + '144L' // 8f66 #3755 + '88L' // 8f67 #2299 + '1R' // 8f68 #43 + '2L' // 8f69 #63 + 'A' // 8f6a + '8P' // 8f6b #223 + '3Q' // 8f6c #94 + '8P' // 8f6d #223 + '88M' // 8f6e #2300 + '2D' // 8f6f #55 + '2Y' // 8f70 #76 + 'b8P' // 8f71-8f73 #223 + '2L' // 8f74 #63 + 'a8P' // 8f75-8f76 #223 + 'A' // 8f77 + 'b8P' // 8f78-8f7a #223 + '2D' // 8f7b #55 + '8P' // 8f7c #223 + '3Q' // 8f7d #94 + '8P' // 8f7e #223 + '2K' // 8f7f #62 + 'A' // 8f80 + 'a8P' // 8f81-8f82 #223 + '2D' // 8f83 #55 + '8P' // 8f84 #223 + '3N' // 8f85 #91 + '4C' // 8f86 #106 + '8P' // 8f87 #223 + '4C' // 8f88 #106 + '1R' // 8f89 #43 + 'a8P' // 8f8a-8f8b #223 + 'A' // 8f8c + 'b8P' // 8f8d-8f8f #223 + '3H' // 8f90 #85 + '2D' // 8f91 #55 + 'A' // 8f92 + '88N' // 8f93 #2301 + 'a8P' // 8f94-8f95 #223 + '3H' // 8f96 #85 + 'c8P' // 8f97-8f9a #223 + '197W' // 8f9b #5144 + '138I' // 8f9c #3596 + '2G' // 8f9d #58 + '256W' // 8f9e #6678 + '87X' // 8f9f #2285 + '37V' // 8fa0 #983 + 'a2G' // 8fa1-8fa2 #58 + '194I' // 8fa3 #5052 + '2G' // 8fa4 #58 + '37V' // 8fa5 #983 + '224O' // 8fa6 #5838 + '87Z' // 8fa7 #2287 + '170G' // 8fa8 #4426 + '2Y' // 8fa9 #76 + 'A' // 8faa + '2W' // 8fab #74 + 'A' // 8fac + '184V' // 8fad #4805 + '54V' // 8fae #1425 + '156H' // 8faf #4063 + '166X' // 8fb0 #4339 + '181O' // 8fb1 #4720 + '210O' // 8fb2 #5474 + '88O' // 8fb3 #2302 + '2G' // 8fb4 #58 + 'a54V' // 8fb5-8fb6 #1425 + '87W' // 8fb7 #2284 + '37V' // 8fb8 #983 + '131M' // 8fb9 #3418 + '88H' // 8fba #2295 + '88G' // 8fbb #2294 + '88J' // 8fbc #2297 + '1R' // 8fbd #43 + '132H' // 8fbe #3439 + '88E' // 8fbf #2292 + '2G' // 8fc0 #58 + '87Y' // 8fc1 #2286 + '87V' // 8fc2 #2283 + 'A' // 8fc3 + '130Q' // 8fc4 #3396 + '181K' // 8fc5 #4716 + '22J' // 8fc6 #581 + '7K' // 8fc7 #192 + '248I' // 8fc8 #6456 + 'A' // 8fc9 + '28C' // 8fca #730 + '69V' // 8fcb #1815 + '32W' // 8fcc #854 + '22I' // 8fcd #580 + '228K' // 8fce #5938 + '44B' // 8fcf #1145 + '138J' // 8fd0 #3597 + '239E' // 8fd1 #6218 + '22J' // 8fd2 #581 + '22I' // 8fd3 #580 + '216Q' // 8fd4 #5632 + '22I' // 8fd5 #580 + 'A' // 8fd6 + '8P' // 8fd7 #223 + 'a7K' // 8fd8-8fd9 #192 + '87N' // 8fda #2275 + '7K' // 8fdb #192 + '2D' // 8fdc #55 + '2C' // 8fdd #54 + '1Z' // 8fde #51 + '2L' // 8fdf #63 + '2G' // 8fe0 #58 + 'A' // 8fe1 + 'c22I' // 8fe2-8fe5 #580 + '141P' // 8fe6 #3681 + 'A' // 8fe7 + '22I' // 8fe8 #580 + '69V' // 8fe9 #1815 + '199N' // 8fea #5187 + '198G' // 8feb #5154 + 'A' // 8fec + '54S' // 8fed #1422 + '22I' // 8fee #580 + '2G' // 8fef #58 + '215B' // 8ff0 #5591 + '2G' // 8ff1 #58 + '260Q' // 8ff2 #6776 + '8P' // 8ff3 #223 + '192D' // 8ff4 #4995 + '22J' // 8ff5 #581 + '2G' // 8ff6 #58 + '220O' // 8ff7 #5734 + '22I' // 8ff8 #580 + '126Z' // 8ff9 #3301 + '22I' // 8ffa #580 + '22J' // 8ffb #581 + '32W' // 8ffc #854 + '231J' // 8ffd #6015 + '22J' // 8ffe #581 + 'A' // 8fff + '220Y' // 9000 #5744 + '238Q' // 9001 #6204 + '87L' // 9002 #2273 + '191B' // 9003 #4967 + '4Y' // 9004 #128 + '63A' // 9005 #1638 + '191E' // 9006 #4970 + 'A' // 9007 + '87M' // 9008 #2274 + '3Q' // 9009 #94 + '2Y' // 900a #76 + '12Y' // 900b #336 + '2G' // 900c #58 + '144K' // 900d #3754 + '2G' // 900e #58 + '227O' // 900f #5916 + '194Q' // 9010 #5060 + '12Y' // 9011 #336 + '87U' // 9012 #2282 + '2G' // 9013 #58 + '205W' // 9014 #5352 + '64E' // 9015 #1668 + '4Y' // 9016 #128 + '159K' // 9017 #4144 + '2G' // 9018 #58 + '240Y' // 9019 #6264 + '35U' // 901a #930 + '192E' // 901b #4996 + 'A' // 901c + '166W' // 901d #4338 + '12Y' // 901e #336 + '235Y' // 901f #6134 + '235M' // 9020 #6122 + '12Y' // 9021 #336 + '181H' // 9022 #4713 + '244T' // 9023 #6363 + '32W' // 9024 #854 + 'A' // 9025 + '14X' // 9026 #387 + 'c2G' // 9027-902a #58 + 'A' // 902b + '2G' // 902c #58 + '4Y' // 902d #128 + '154S' // 902e #4022 + '4Y' // 902f #128 + 'A' // 9030 + '230T' // 9031 #5999 + '242D' // 9032 #6295 + '28C' // 9033 #730 + '22J' // 9034 #581 + '12Y' // 9035 #336 + '4Y' // 9036 #128 + '28C' // 9037 #730 + '182D' // 9038 #4735 + '69U' // 9039 #1814 + '14X' // 903a #387 + '2R' // 903b #69 + '184W' // 903c #4806 + 'A' // 903d + '177P' // 903e #4617 + '2G' // 903f #58 + 'A' // 9040 + '12Y' // 9041 #336 + '143F' // 9042 #3723 + '2G' // 9043 #58 + '4Y' // 9044 #128 + '257L' // 9045 #6693 + '87S' // 9046 #2280 + '219B' // 9047 #5695 + 'A' // 9048 + '2G' // 9049 #58 + '241P' // 904a #6281 + '243I' // 904b #6326 + '28C' // 904c #730 + '187M' // 904d #4874 + '242K' // 904e #6302 + '4Y' // 904f #128 + 'a12Y' // 9050-9051 #336 + '4Y' // 9052 #128 + '244B' // 9053 #6345 + '237S' // 9054 #6180 + '206J' // 9055 #5365 + '28C' // 9056 #730 + '3I' // 9057 #86 + '4Y' // 9058 #128 + '187D' // 9059 #4865 + 'A' // 905a + '4Y' // 905b #128 + '49D' // 905c #1277 + '12Y' // 905d #336 + '67E' // 905e #1746 + 'A' // 905f + '222K' // 9060 #5782 + '87P' // 9061 #2277 + '4Y' // 9062 #128 + '149I' // 9063 #3882 + '44B' // 9064 #1145 + '254S' // 9065 #6622 + 'a2G' // 9066-9067 #58 + '4Y' // 9068 #128 + '230L' // 9069 #5991 + 'aA' // 906a-906b + '28C' // 906c #730 + '209E' // 906d #5438 + '173W' // 906e #4520 + '12Y' // 906f #336 + '2G' // 9070 #58 + 'A' // 9071 + '49D' // 9072 #1277 + 'A' // 9073 + '4Y' // 9074 #128 + '187H' // 9075 #4869 + '127A' // 9076 #3302 + '174H' // 9077 #4531 + '244D' // 9078 #6347 + '4Y' // 9079 #128 + '203X' // 907a #5301 + 'A' // 907b + '141U' // 907c #3686 + '87Q' // 907d #2278 + 'A' // 907e + '210P' // 907f #5475 + '192C' // 9080 #4994 + '49D' // 9081 #1277 + '63A' // 9082 #1638 + '12Y' // 9083 #336 + '241C' // 9084 #6268 + '4Y' // 9085 #128 + 'A' // 9086 + 'a12Y' // 9087-9088 #336 + '252N' // 9089 #6565 + '226L' // 908a #5887 + '4Y' // 908b #128 + '2G' // 908c #58 + 'A' // 908d + '2G' // 908e #58 + '66F' // 908f #1721 + '22J' // 9090 #581 + '124T' // 9091 #3243 + 'A' // 9092 + '2L' // 9093 #63 + 'A' // 9094 + '12Y' // 9095 #336 + 'A' // 9096 + '4Y' // 9097 #128 + '2G' // 9098 #58 + '12Y' // 9099 #336 + '14X' // 909a #387 + '4Y' // 909b #128 + 'A' // 909c + '14X' // 909d #387 + '32W' // 909e #854 + 'A' // 909f + 'a4Y' // 90a0-90a1 #128 + '87J' // 90a2 #2271 + '234B' // 90a3 #6085 + 'A' // 90a4 + '2G' // 90a5 #58 + '209U' // 90a6 #5454 + 'A' // 90a7 + '64E' // 90a8 #1668 + '14X' // 90a9 #387 + '176E' // 90aa #4580 + 'A' // 90ab + '14X' // 90ac #387 + 'A' // 90ad + '131O' // 90ae #3420 + '54S' // 90af #1422 + '12Y' // 90b0 #336 + '170F' // 90b1 #4425 + 'b4Y' // 90b2-90b4 #128 + '132G' // 90b5 #3438 + '4Y' // 90b6 #128 + 'A' // 90b7 + '130R' // 90b8 #3397 + '3X' // 90b9 #101 + '14X' // 90ba #387 + '87T' // 90bb #2281 + 'A' // 90bc + 'a4Y' // 90bd-90be #128 + 'aA' // 90bf-90c0 + '173K' // 90c1 #4508 + 'A' // 90c2 + 'b4Y' // 90c3-90c5 #128 + 'A' // 90c6 + '4Y' // 90c7 #128 + '69U' // 90c8 #1814 + '2G' // 90c9 #58 + '154C' // 90ca #4006 + 'A' // 90cb + '2G' // 90cc #58 + 'A' // 90cd + '191O' // 90ce #4980 + 'a14X' // 90cf-90d0 #387 + '3Y' // 90d1 #102 + '2G' // 90d2 #58 + '14X' // 90d3 #387 + '32W' // 90d4 #854 + '4Y' // 90d5 #128 + 'A' // 90d6 + '4Y' // 90d7 #128 + 'a2G' // 90d8-90d9 #58 + '14X' // 90da #387 + 'a4Y' // 90db-90dc #128 + '87O' // 90dd #2276 + '69T' // 90de #1813 + '22J' // 90df #581 + 'A' // 90e0 + '137A' // 90e1 #3562 + '10Q' // 90e2 #276 + '44D' // 90e3 #1147 + '10Q' // 90e4 #276 + '2G' // 90e5 #58 + 'a14X' // 90e6-90e7 #387 + '35U' // 90e8 #930 + 'A' // 90e9 + '54T' // 90ea #1423 + '10Q' // 90eb #276 + 'A' // 90ec + '187R' // 90ed #4879 + 'A' // 90ee + '10Q' // 90ef #276 + '2G' // 90f0 #58 + 'A' // 90f1 + '2G' // 90f2 #58 + 'A' // 90f3 + '10Q' // 90f4 #276 + '219X' // 90f5 #5717 + '2G' // 90f6 #58 + '256M' // 90f7 #6668 + '3G' // 90f8 #84 + 'bA' // 90f9-90fb + '44D' // 90fc #1147 + '243E' // 90fd #6322 + 'b10Q' // 90fe-9100 #276 + 'A' // 9101 + '87K' // 9102 #2272 + 'A' // 9103 + '10Q' // 9104 #276 + '2G' // 9105 #58 + '28D' // 9106 #731 + 'A' // 9107 + '2G' // 9108 #58 + '214G' // 9109 #5570 + 'aA' // 910a-910b + '14X' // 910c #387 + '2G' // 910d #58 + 'aA' // 910e-910f + '2G' // 9110 #58 + 'A' // 9111 + '54R' // 9112 #1421 + 'A' // 9113 + '10Q' // 9114 #276 + '69T' // 9115 #1813 + 'a28D' // 9116-9117 #731 + '10Q' // 9118 #276 + '87I' // 9119 #2270 + '28D' // 911a #731 + 'A' // 911b + '10Q' // 911c #276 + 'A' // 911d + '10Q' // 911e #276 + 'A' // 911f + '10Q' // 9120 #276 + 'A' // 9121 + 'a10Q' // 9122-9123 #276 + 'A' // 9124 + '2G' // 9125 #58 + 'A' // 9126 + '66F' // 9127 #1721 + 'A' // 9128 + '28D' // 9129 #731 + 'A' // 912a + '44D' // 912b #1147 + 'A' // 912c + '67E' // 912d #1746 + '2G' // 912e #58 + '10Q' // 912f #276 + '184X' // 9130 #4807 + '10Q' // 9131 #276 + '54R' // 9132 #1421 + 'A' // 9133 + '28D' // 9134 #731 + 'A' // 9135 + '28D' // 9136 #731 + '2G' // 9137 #58 + 'A' // 9138 + 'a10Q' // 9139-913a #276 + 'A' // 913b + 'a2G' // 913c-913d #58 + 'dA' // 913e-9142 + '32V' // 9143 #853 + 'aA' // 9144-9145 + '32V' // 9146 #853 + '2T' // 9147 #71 + '44C' // 9148 #1146 + 'a87H' // 9149-914a #2269 + '121R' // 914b #3163 + '144J' // 914c #3753 + '237T' // 914d #6181 + '87R' // 914e #2279 + '32V' // 914f #853 + '54T' // 9150 #1423 + '44B' // 9151 #1145 + '222Q' // 9152 #5788 + '2T' // 9153 #71 + '255G' // 9154 #6636 + 'A' // 9155 + '44C' // 9156 #1146 + '32V' // 9157 #853 + '2T' // 9158 #71 + '44C' // 9159 #1146 + '32V' // 915a #853 + '2T' // 915b #71 + '24T' // 915c #643 + '14X' // 915d #387 + '87F' // 915e #2267 + 'aA' // 915f-9160 + '86X' // 9161 #2259 + '87C' // 9162 #2264 + 'a9Q' // 9163-9164 #250 + '170E' // 9165 #4424 + 'A' // 9166 + '13V' // 9167 #359 + 'A' // 9168 + '64D' // 9169 #1667 + '152Q' // 916a #3968 + 'A' // 916b + '154M' // 916c #4016 + '2T' // 916d #71 + '54Q' // 916e #1420 + '126J' // 916f #3285 + '32U' // 9170 #852 + '2L' // 9171 #63 + '9Q' // 9172 #250 + '2T' // 9173 #71 + '9Q' // 9174 #250 + '160T' // 9175 #4179 + '87G' // 9176 #2268 + '197C' // 9177 #5124 + '203Q' // 9178 #5294 + 'a9Q' // 9179-917a #250 + '2T' // 917b #71 + '24T' // 917c #643 + 'a50J' // 917d-917e #1309 + '3H' // 917f #85 + 'A' // 9180 + '7L' // 9181 #193 + '9Q' // 9182 #250 + '132F' // 9183 #3437 + '22H' // 9184 #579 + '9Q' // 9185 #250 + '7L' // 9186 #193 + '65Y' // 9187 #1714 + 'A' // 9188 + '177O' // 9189 #4616 + '2T' // 918a #71 + '65Y' // 918b #1714 + '32U' // 918c #852 + '86Y' // 918d #2260 + '7L' // 918e #193 + 'A' // 918f + '86Z' // 9190 #2261 + '9Q' // 9191 #250 + '208Y' // 9192 #5432 + 'b2T' // 9193-9195 #71 + '22H' // 9196 #579 + 'a2T' // 9197-9198 #71 + 'A' // 9199 + '32U' // 919a #852 + '54Q' // 919b #1420 + '173D' // 919c #4501 + 'A' // 919d + '7L' // 919e #193 + 'aA' // 919f-91a0 + 'a9Q' // 91a1-91a2 #250 + '32U' // 91a3 #852 + '254B' // 91a4 #6605 + 'A' // 91a5 + '2T' // 91a6 #71 + '22H' // 91a7 #579 + '35Y' // 91a8 #934 + '24T' // 91a9 #643 + '9Q' // 91aa #250 + '224N' // 91ab #5837 + '184U' // 91ac #4804 + '35Y' // 91ad #934 + 'a37U' // 91ae-91af #982 + '7L' // 91b0 #193 + '86V' // 91b1 #2257 + '7L' // 91b2 #193 + '2T' // 91b3 #71 + 'a37U' // 91b4-91b5 #982 + '13V' // 91b6 #359 + 'A' // 91b7 + '253K' // 91b8 #6588 + 'A' // 91b9 + '138H' // 91ba #3595 + '13V' // 91bb #359 + '2T' // 91bc #71 + '7L' // 91bd #193 + '32U' // 91be #852 + '2T' // 91bf #71 + '163B' // 91c0 #4239 + '9Q' // 91c1 #250 + '2T' // 91c2 #71 + '7L' // 91c3 #193 + '13V' // 91c4 #359 + '7L' // 91c5 #193 + '9Q' // 91c6 #250 + '194P' // 91c7 #5059 + '254M' // 91c8 #6616 + '86U' // 91c9 #2256 + '3Y' // 91ca #102 + '206T' // 91cb #5375 + '228P' // 91cc #5943 + '243C' // 91cd #6320 + '224B' // 91ce #5825 + '236G' // 91cf #6142 + '64D' // 91d0 #1667 + '69B' // 91d1 #1795 + '50J' // 91d2 #1309 + 'a7L' // 91d3-91d4 #193 + '22H' // 91d5 #579 + '13V' // 91d6 #359 + '37U' // 91d7 #982 + '160C' // 91d8 #4162 + '7L' // 91d9 #193 + 'a2T' // 91da-91db #71 + '136P' // 91dc #3551 + '212P' // 91dd #5527 + '2T' // 91de #71 + '13V' // 91df #359 + 'A' // 91e0 + '2T' // 91e1 #71 + 'A' // 91e2 + '190Q' // 91e3 #4956 + '7L' // 91e4 #193 + '13V' // 91e5 #359 + '9Q' // 91e6 #250 + '87A' // 91e7 #2262 + '22H' // 91e8 #579 + '7L' // 91e9 #193 + '248H' // 91ea #6455 + 'A' // 91eb + '7L' // 91ec #193 + '9Q' // 91ed #250 + 'a2T' // 91ee-91ef #71 + '35Y' // 91f0 #934 + '7L' // 91f1 #193 + 'bA' // 91f2-91f4 + '37U' // 91f5 #982 + 'a7L' // 91f6-91f7 #193 + '22H' // 91f8 #579 + '9Q' // 91f9 #250 + '22H' // 91fa #579 + 'a2T' // 91fb-91fc #71 + '7L' // 91fd #193 + '24T' // 91fe #643 + '7L' // 91ff #193 + '9Q' // 9200 #250 + '7L' // 9201 #193 + 'A' // 9202 + '22H' // 9203 #579 + '7L' // 9204 #193 + '9Q' // 9205 #250 + '7L' // 9206 #193 + '9Q' // 9207 #250 + '22F' // 9208 #577 + 'a12X' // 9209-920a #335 + 'A' // 920b + '2T' // 920c #71 + '87B' // 920d #2263 + '12X' // 920e #335 + 'A' // 920f + '32T' // 9210 #851 + '54P' // 9211 #1419 + '248G' // 9212 #6454 + '13V' // 9213 #359 + '156G' // 9214 #4062 + '163C' // 9215 #4240 + '2T' // 9216 #71 + '54P' // 9217 #1419 + '13V' // 9218 #359 + '22G' // 9219 #578 + 'aA' // 921a-921b + '12X' // 921c #335 + '2T' // 921d #71 + '65P' // 921e #1705 + 'aA' // 921f-9220 + '22F' // 9221 #577 + 'A' // 9222 + '65B' // 9223 #1691 + 'a12X' // 9224-9225 #335 + '65B' // 9226 #1691 + '22F' // 9227 #577 + '13V' // 9228 #359 + '35Y' // 9229 #934 + '22F' // 922a #577 + '24T' // 922b #643 + '2T' // 922c #71 + '22F' // 922d #577 + '11T' // 922e #305 + '2T' // 922f #71 + '11T' // 9230 #305 + '22G' // 9231 #578 + 'A' // 9232 + '11T' // 9233 #305 + '183N' // 9234 #4771 + '12X' // 9235 #335 + '11T' // 9236 #305 + '12X' // 9237 #335 + '11T' // 9238 #305 + '12X' // 9239 #335 + '41A' // 923a #1066 + 'A' // 923b + '86W' // 923c #2258 + '22F' // 923d #577 + '12X' // 923e #335 + 'a32T' // 923f-9240 #851 + '22G' // 9241 #578 + 'a2T' // 9242-9243 #71 + '87E' // 9244 #2266 + '65P' // 9245 #1705 + '12X' // 9246 #335 + '2T' // 9247 #71 + '11T' // 9248 #305 + '32T' // 9249 #851 + '2T' // 924a #71 + '11T' // 924b #305 + '22G' // 924c #578 + '11T' // 924d #305 + 'a12X' // 924e-924f #335 + '11T' // 9250 #305 + '12X' // 9251 #335 + 'A' // 9252 + '22F' // 9253 #577 + 'A' // 9254 + '24T' // 9255 #643 + '2T' // 9256 #71 + '126Y' // 9257 #3300 + '13V' // 9258 #359 + '2T' // 9259 #71 + '11T' // 925a #305 + '148S' // 925b #3866 + '2T' // 925c #71 + '11T' // 925d #305 + '32T' // 925e #851 + '24T' // 925f #643 + 'a2T' // 9260-9261 #71 + '87D' // 9262 #2265 + 'A' // 9263 + '41A' // 9264 #1066 + 'a32T' // 9265-9266 #851 + '11T' // 9267 #305 + '35Y' // 9268 #934 + '2T' // 9269 #71 + 'A' // 926a + '22G' // 926b #578 + '22F' // 926c #577 + '22G' // 926d #578 + '13V' // 926e #359 + '12X' // 926f #335 + '2T' // 9270 #71 + '253W' // 9271 #6600 + '22G' // 9272 #578 + 'A' // 9273 + '3N' // 9274 #91 + '2T' // 9275 #71 + '11T' // 9276 #305 + '13V' // 9277 #359 + '11T' // 9278 #305 + '2T' // 9279 #71 + '22G' // 927a #578 + '12X' // 927b #335 + '11T' // 927c #305 + '2T' // 927d #71 + '12X' // 927e #335 + '17V' // 927f #463 + '222P' // 9280 #5787 + '9P' // 9281 #249 + '15I' // 9282 #398 + '86T' // 9283 #2255 + '9P' // 9284 #249 + '188M' // 9285 #4900 + '37T' // 9286 #981 + '50J' // 9287 #1309 + '32S' // 9288 #850 + '86L' // 9289 #2247 + '32S' // 928a #850 + 'aA' // 928b-928c + '17V' // 928d #463 + '32S' // 928e #850 + '9P' // 928f #249 + 'A' // 9290 + '44A' // 9291 #1144 + '2T' // 9292 #71 + '132E' // 9293 #3436 + 'A' // 9294 + '17V' // 9295 #463 + '44A' // 9296 #1144 + '2T' // 9297 #71 + '188V' // 9298 #4909 + '17V' // 9299 #463 + '86M' // 929a #2248 + '17V' // 929b #463 + '41A' // 929c #1066 + '37T' // 929d #981 + 'A' // 929e + '2T' // 929f #71 + '17V' // 92a0 #463 + 'b15I' // 92a1-92a3 #398 + 'a17V' // 92a4-92a5 #463 + '15I' // 92a6 #398 + '17V' // 92a7 #463 + '32S' // 92a8 #850 + 'a15I' // 92a9-92aa #398 + '32S' // 92ab #850 + '37T' // 92ac #981 + '256A' // 92ad #6656 + '37T' // 92ae #981 + '2T' // 92af #71 + 'A' // 92b0 + '9P' // 92b1 #249 + '17V' // 92b2 #463 + '163A' // 92b3 #4238 + 'A' // 92b4 + '15I' // 92b5 #398 + '44A' // 92b6 #1144 + '224M' // 92b7 #5836 + '2T' // 92b8 #71 + '41A' // 92b9 #1066 + '86K' // 92ba #2246 + '17V' // 92bb #463 + '9B' // 92bc #235 + '2X' // 92bd #75 + '9P' // 92be #249 + '28A' // 92bf #728 + '2X' // 92c0 #75 + '170D' // 92c1 #4423 + 'a9B' // 92c2-92c3 #235 + 'A' // 92c4 + '121Q' // 92c5 #3162 + '43Z' // 92c6 #1143 + '9B' // 92c7 #235 + '43Z' // 92c8 #1143 + 'a15I' // 92c9-92ca #398 + '9B' // 92cb #235 + '54M' // 92cc #1416 + '9B' // 92cd #235 + '2X' // 92ce #75 + '86J' // 92cf #2245 + '43Z' // 92d0 #1143 + '15I' // 92d1 #398 + '177N' // 92d2 #4615 + '2X' // 92d3 #75 + '9P' // 92d4 #249 + '9B' // 92d5 #235 + 'A' // 92d6 + '9B' // 92d7 #235 + '2X' // 92d8 #75 + '9B' // 92d9 #235 + 'A' // 92da + '9P' // 92db #249 + '2X' // 92dc #75 + '9B' // 92dd #235 + 'A' // 92de + '9B' // 92df #235 + 'a2X' // 92e0-92e1 #75 + 'A' // 92e2 + '28A' // 92e3 #728 + '54M' // 92e4 #1416 + '9B' // 92e5 #235 + '15I' // 92e6 #398 + '2X' // 92e7 #75 + 'a9B' // 92e8-92e9 #235 + '184S' // 92ea #4802 + '9P' // 92eb #249 + '54N' // 92ec #1417 + '86S' // 92ed #2254 + '9B' // 92ee #235 + '15I' // 92ef #398 + '40Z' // 92f0 #1065 + '15I' // 92f1 #398 + '9B' // 92f2 #235 + '86O' // 92f3 #2250 + '9P' // 92f4 #249 + 'A' // 92f5 + '15I' // 92f6 #398 + '2X' // 92f7 #75 + '132D' // 92f8 #3435 + '9B' // 92f9 #235 + '2X' // 92fa #75 + '9B' // 92fb #235 + '67P' // 92fc #1757 + '9P' // 92fd #249 + 'A' // 92fe + '2X' // 92ff #75 + '9B' // 9300 #235 + '15I' // 9301 #398 + '9B' // 9302 #235 + '9P' // 9303 #249 + '231Y' // 9304 #6030 + 'A' // 9305 + '86P' // 9306 #2251 + '9P' // 9307 #249 + '2X' // 9308 #75 + 'aA' // 9309-930a + '19X' // 930b #517 + 'A' // 930c + '2X' // 930d #75 + 'A' // 930e + '11S' // 930f #304 + '126W' // 9310 #3298 + '2X' // 9311 #75 + '22E' // 9312 #576 + 'A' // 9313 + '2X' // 9314 #75 + '13U' // 9315 #358 + 'aA' // 9316-9317 + '121P' // 9318 #3161 + '11S' // 9319 #304 + '28B' // 931a #729 + '19X' // 931b #517 + '2X' // 931c #75 + '11S' // 931d #304 + '28B' // 931e #729 + '43Y' // 931f #1142 + '148K' // 9320 #3858 + '28B' // 9321 #729 + '224L' // 9322 #5835 + '11S' // 9323 #304 + '28B' // 9324 #729 + '11S' // 9325 #304 + '188Y' // 9326 #4912 + '13U' // 9327 #358 + '28B' // 9328 #729 + '13U' // 9329 #358 + '11S' // 932a #304 + '162Y' // 932b #4236 + '86R' // 932c #2253 + '19X' // 932d #517 + '43Y' // 932e #1142 + '226S' // 932f #5894 + '9P' // 9330 #249 + '22E' // 9331 #576 + '260G' // 9332 #6766 + '11S' // 9333 #304 + '2X' // 9334 #75 + '13U' // 9335 #358 + '192B' // 9336 #4993 + '2X' // 9337 #75 + '19X' // 9338 #517 + 'A' // 9339 + 'a2X' // 933a-933b #75 + '19X' // 933c #517 + 'A' // 933d + '19O' // 933e #508 + 'A' // 933f + 'a22E' // 9340-9341 #576 + 'a9P' // 9342-9343 #249 + '28A' // 9344 #728 + 'a19X' // 9345-9346 #517 + '11S' // 9347 #304 + '43Y' // 9348 #1142 + '11S' // 9349 #304 + '177M' // 934a #4614 + '203M' // 934b #5290 + '19O' // 934c #508 + '144I' // 934d #3752 + '19O' // 934e #508 + '22E' // 934f #576 + 'b13U' // 9350-9352 #358 + '19O' // 9353 #508 + '28B' // 9354 #729 + '21G' // 9355 #552 + 'b13U' // 9356-9358 #358 + '22E' // 9359 #576 + '13U' // 935a #358 + '149C' // 935b #3876 + '13U' // 935c #358 + '19O' // 935d #508 + '13U' // 935e #358 + '22E' // 935f #576 + '13U' // 9360 #358 + 'A' // 9361 + '54O' // 9362 #1418 + '22E' // 9363 #576 + '11S' // 9364 #304 + '13U' // 9365 #358 + '22E' // 9366 #576 + '13U' // 9367 #358 + '54O' // 9368 #1418 + 'a13U' // 9369-936a #358 + '54N' // 936b #1417 + '11S' // 936c #304 + '2X' // 936d #75 + '86N' // 936e #2249 + '2X' // 936f #75 + 'a11S' // 9370-9371 #304 + 'A' // 9372 + '11S' // 9373 #304 + '28A' // 9374 #728 + '227B' // 9375 #5903 + '11S' // 9376 #304 + 'A' // 9377 + '9P' // 9378 #249 + '19X' // 9379 #517 + '11S' // 937a #304 + 'A' // 937b + '86Q' // 937c #2252 + '28A' // 937d #728 + '184R' // 937e #4801 + 'a2X' // 937f-9380 #75 + '28A' // 9381 #728 + '40Z' // 9382 #1065 + 'A' // 9383 + '9P' // 9384 #249 + '19X' // 9385 #517 + '9P' // 9386 #249 + '19X' // 9387 #517 + '2X' // 9388 #75 + 'A' // 9389 + '40Z' // 938a #1065 + '2X' // 938b #75 + '86E' // 938c #2240 + '2X' // 938d #75 + 'A' // 938e + '11R' // 938f #303 + '16J' // 9390 #425 + 'A' // 9391 + '2X' // 9392 #75 + '19O' // 9393 #508 + '22D' // 9394 #575 + '2X' // 9395 #75 + '67P' // 9396 #1757 + '11R' // 9397 #303 + '13T' // 9398 #357 + 'A' // 9399 + '22D' // 939a #575 + '13T' // 939b #357 + '16J' // 939c #425 + '32R' // 939d #849 + '13T' // 939e #357 + 'A' // 939f + '16J' // 93a0 #425 + '13T' // 93a1 #357 + '24S' // 93a2 #642 + '86B' // 93a3 #2237 + '2X' // 93a4 #75 + 'A' // 93a5 + '13T' // 93a6 #357 + '86D' // 93a7 #2239 + '2X' // 93a8 #75 + '13T' // 93a9 #357 + '32R' // 93aa #849 + '2X' // 93ab #75 + 'a22D' // 93ac-93ad #575 + '201T' // 93ae #5245 + '24S' // 93af #642 + '22D' // 93b0 #575 + 'a19O' // 93b1-93b2 #508 + '24S' // 93b3 #642 + 'a11R' // 93b4-93b5 #303 + '21G' // 93b6 #552 + 'a24S' // 93b7-93b8 #642 + 'a21G' // 93b9-93ba #552 + '11R' // 93bb #303 + '19O' // 93bc #508 + '24S' // 93bd #642 + '19O' // 93be #508 + '86G' // 93bf #2242 + '24S' // 93c0 #642 + '21G' // 93c1 #552 + '24S' // 93c2 #642 + '22D' // 93c3 #575 + '11R' // 93c4 #303 + '21G' // 93c5 #552 + '86C' // 93c6 #2238 + '11R' // 93c7 #303 + '184T' // 93c8 #4803 + '21G' // 93c9 #552 + 'c11R' // 93ca-93cd #303 + 'aA' // 93ce-93cf + '11R' // 93d0 #303 + '22D' // 93d1 #575 + 'A' // 93d2 + '27Y' // 93d3 #726 + 'aA' // 93d4-93d5 + 'b11R' // 93d6-93d8 #303 + '2X' // 93d9 #75 + 'A' // 93da + '16J' // 93db #425 + 'a13T' // 93dc-93dd #357 + '22D' // 93de #575 + '126X' // 93df #3299 + '32R' // 93e0 #849 + '215L' // 93e1 #5601 + '40Z' // 93e2 #1065 + 'A' // 93e3 + '22D' // 93e4 #575 + 'b2X' // 93e5-93e7 #75 + '13T' // 93e8 #357 + 'dA' // 93e9-93ed + '32R' // 93ee #849 + 'A' // 93ef + '32R' // 93f0 #849 + '27Y' // 93f1 #726 + 'A' // 93f2 + 'a16J' // 93f3-93f4 #425 + '13T' // 93f5 #357 + '260P' // 93f6 #6775 + '13T' // 93f7 #357 + '11R' // 93f8 #303 + '13T' // 93f9 #357 + '2X' // 93fa #75 + '11R' // 93fb #303 + 'A' // 93fc + '162Z' // 93fd #4237 + 'bA' // 93fe-9400 + '27Y' // 9401 #726 + '2X' // 9402 #75 + '13T' // 9403 #357 + '86A' // 9404 #2236 + 'aA' // 9405-9406 + '13T' // 9407 #357 + '27Y' // 9408 #726 + '2X' // 9409 #75 + 'bA' // 940a-940c + '2X' // 940d #75 + '21G' // 940e #552 + 'a11R' // 940f-9410 #303 + 'a19O' // 9411-9412 #508 + 'a11R' // 9413-9414 #303 + 'a21G' // 9415-9416 #552 + '11R' // 9417 #303 + '218H' // 9418 #5675 + '11R' // 9419 #303 + '21G' // 941a #552 + '16J' // 941b #425 + '5W' // 941c #152 + '27Z' // 941d #727 + '5W' // 941e #152 + '69S' // 941f #1812 + '24R' // 9420 #641 + '69S' // 9421 #1812 + 'a5W' // 9422-9423 #152 + '24R' // 9424 #641 + '86F' // 9425 #2241 + '27Z' // 9426 #727 + 'c24R' // 9427-942a #641 + '54K' // 942b #1414 + 'A' // 942c + '27Z' // 942d #727 + '43X' // 942e #1141 + '27Y' // 942f #726 + 'A' // 9430 + '2X' // 9431 #75 + 'a43X' // 9432-9433 #1141 + '2X' // 9434 #75 + '218C' // 9435 #5670 + '37S' // 9436 #980 + 'A' // 9437 + '54K' // 9438 #1414 + 'A' // 9439 + '43X' // 943a #1141 + '2X' // 943b #75 + 'A' // 943c + '37S' // 943d #980 + '27Z' // 943e #727 + '37S' // 943f #980 + '54L' // 9440 #1415 + '2X' // 9441 #75 + '16J' // 9442 #425 + '27Y' // 9443 #726 + '144H' // 9444 #3751 + '37S' // 9445 #980 + 'aA' // 9446-9447 + '2X' // 9448 #75 + 'A' // 9449 + '24Q' // 944a #640 + 'A' // 944b + '22C' // 944c #574 + '16J' // 944d #425 + 'bA' // 944e-9450 + '198C' // 9451 #5150 + '138D' // 9452 #3591 + '2N' // 9453 #65 + '54L' // 9454 #1415 + '22C' // 9455 #574 + 'aA' // 9456-9457 + '16J' // 9458 #425 + '2N' // 9459 #65 + '69R' // 945a #1811 + '85Y' // 945b #2234 + '2N' // 945c #65 + 'A' // 945d + '22C' // 945e #574 + '2N' // 945f #65 + '22C' // 9460 #574 + '2N' // 9461 #65 + 'a24Q' // 9462-9463 #640 + 'A' // 9464 + '27Z' // 9465 #727 + 'A' // 9466 + '16J' // 9467 #425 + '22C' // 9468 #574 + 'A' // 9469 + '22C' // 946a #574 + '156F' // 946b #4061 + '16J' // 946c #425 + '22C' // 946d #574 + '2N' // 946e #65 + '22C' // 946f #574 + '156E' // 9470 #4060 + '24Q' // 9471 #640 + '132C' // 9472 #3434 + '5W' // 9473 #152 + '24R' // 9474 #641 + '24Q' // 9475 #640 + '24R' // 9476 #641 + '24Q' // 9477 #640 + '5W' // 9478 #152 + '24R' // 9479 #641 + '5W' // 947a #152 + '27Z' // 947b #727 + '149Y' // 947c #3898 + '191Z' // 947d #4991 + '24Q' // 947e #640 + '85X' // 947f #2233 + '24R' // 9480 #641 + '24Q' // 9481 #640 + '5W' // 9482 #152 + 'a69R' // 9483-9484 #1811 + '85Z' // 9485 #2235 + 'a5W' // 9486-9487 #152 + '3I' // 9488 #86 + '2L' // 9489 #63 + 'h5W' // 948a-9492 #152 + '2R' // 9493 #69 + 'd5W' // 9494-9498 #152 + '3H' // 9499 #85 + '5W' // 949a #152 + '3W' // 949b #100 + 'a5W' // 949c-949d #152 + '3G' // 949e #84 + '86I' // 949f #2244 + '3W' // 94a0 #100 + '5W' // 94a1 #152 + '86H' // 94a2 #2243 + 'a5W' // 94a3-94a4 #152 + '2R' // 94a5 #69 + '2K' // 94a6 #62 + '3G' // 94a7 #84 + '5W' // 94a8 #152 + '3H' // 94a9 #85 + 'c5W' // 94aa-94ad #152 + '2L' // 94ae #63 + '5W' // 94af #152 + '3W' // 94b0 #100 + '2D' // 94b1 #55 + '5W' // 94b2 #152 + '2W' // 94b3 #74 + 'f5W' // 94b4-94ba #152 + '1R' // 94bb #43 + 'd5W' // 94bc-94c0 #152 + '54J' // 94c1 #1413 + '3G' // 94c2 #84 + '85T' // 94c3 #2229 + '5W' // 94c4 #152 + '2K' // 94c5 #62 + 'u5W' // 94c6-94db #152 + '85U' // 94dc #2230 + '2Y' // 94dd #76 + 'n5W' // 94de-94ec #152 + '2Y' // 94ed #76 + 'c5W' // 94ee-94f1 #152 + '3X' // 94f2 #101 + '5W' // 94f3 #152 + 'A' // 94f4 + '5V' // 94f5 #151 + '54J' // 94f6 #1413 + '5V' // 94f7 #151 + '2R' // 94f8 #69 + '5V' // 94f9 #151 + '3Y' // 94fa #102 + 'b5V' // 94fb-94fd #151 + '1Z' // 94fe #51 + '5V' // 94ff #151 + '1Z' // 9500 #51 + '3Y' // 9501 #102 + '2W' // 9502 #74 + 'a5V' // 9503-9504 #151 + '1R' // 9505 #43 + 'a5V' // 9506-9507 #151 + '2L' // 9508 #63 + 'a5V' // 9509-950a #151 + '3N' // 950b #91 + '2K' // 950c #62 + 'b5V' // 950d-950f #151 + '2L' // 9510 #63 + 'g5V' // 9511-9518 #151 + '2D' // 9519 #55 + 'f5V' // 951a-9520 #151 + '4C' // 9521 #106 + '5V' // 9522 #151 + '2W' // 9523 #74 + '3H' // 9524 #85 + '3G' // 9525 #84 + '3N' // 9526 #91 + 'A' // 9527 + 'd5V' // 9528-952c #151 + '85S' // 952d #2228 + '1Z' // 952e #51 + '3X' // 952f #101 + 'j5V' // 9530-953a #151 + '2Y' // 953b #76 + 'c5V' // 953c-953f #151 + '3H' // 9540 #85 + 'a3W' // 9541-9542 #100 + 'c5V' // 9543-9546 #151 + '85V' // 9547 #2231 + 'h5V' // 9548-9550 #151 + '2W' // 9551 #74 + 'c5V' // 9552-9555 #151 + '3W' // 9556 #100 + 'd5V' // 9557-955b #151 + '3I' // 955c #86 + 'k5V' // 955d-9568 #151 + 'A' // 9569 + 'c5V' // 956a-956d #151 + 'A' // 956e + '2W' // 956f #74 + 'd5V' // 9570-9574 #151 + 'A' // 9575 + '3X' // 9576 #101 + '244M' // 9577 #6356 + '13S' // 9578 #356 + '2N' // 9579 #65 + 'cA' // 957a-957d + '2N' // 957e #65 + '144G' // 957f #3750 + '242H' // 9580 #6299 + 'A' // 9581 + '13S' // 9582 #356 + '201K' // 9583 #5236 + '2N' // 9584 #65 + '54I' // 9585 #1412 + '19W' // 9586 #516 + '2N' // 9587 #65 + '13S' // 9588 #356 + '223M' // 9589 #5810 + '2N' // 958a #65 + '246R' // 958b #6413 + 'a2N' // 958c-958d #65 + '19W' // 958e #516 + '37R' // 958f #979 + 'A' // 9590 + '142I' // 9591 #3700 + '216X' // 9592 #5639 + '245K' // 9593 #6380 + '65O' // 9594 #1704 + 'A' // 9595 + '85M' // 9596 #2222 + '54I' // 9597 #1412 + '132B' // 9598 #3433 + '19W' // 9599 #516 + 'aA' // 959a-959b + '85W' // 959c #2232 + '2N' // 959d #65 + 'a19W' // 959e-959f #516 + '85N' // 95a0 #2223 + '13S' // 95a1 #356 + '85R' // 95a2 #2227 + '182I' // 95a3 #4740 + '37R' // 95a4 #979 + '129T' // 95a5 #3373 + '19W' // 95a6 #516 + '13S' // 95a7 #356 + '65O' // 95a8 #1704 + '126U' // 95a9 #3296 + '43W' // 95aa #1140 + 'a19W' // 95ab-95ac #516 + '37R' // 95ad #979 + '43W' // 95ae #1140 + '5V' // 95af #151 + '43W' // 95b0 #1140 + '231X' // 95b1 #6029 + '85Q' // 95b2 #2226 + 'A' // 95b3 + '248E' // 95b4 #6452 + 'A' // 95b5 + '13S' // 95b6 #356 + 'A' // 95b7 + '5V' // 95b8 #151 + 'a19W' // 95b9-95ba #516 + 'a37R' // 95bb-95bc #979 + 'b19W' // 95bd-95bf #516 + 'b5V' // 95c0-95c2 #151 + '19W' // 95c3 #516 + 'a8O' // 95c4-95c5 #222 + '184Q' // 95c6 #4800 + '85O' // 95c7 #2224 + 'a43V' // 95c8-95c9 #1139 + '162V' // 95ca #4233 + '13S' // 95cb #356 + 'a43V' // 95cc-95cd #1139 + 'aA' // 95ce-95cf + '13S' // 95d0 #356 + 'a2N' // 95d1-95d2 #65 + '13S' // 95d3 #356 + 'a43U' // 95d4-95d5 #1138 + '170B' // 95d6 #4421 + 'A' // 95d7 + '257C' // 95d8 #6684 + '2N' // 95d9 #65 + '13S' // 95da #356 + 'A' // 95db + '245X' // 95dc #6393 + '2N' // 95dd #65 + '13S' // 95de #356 + '2N' // 95df #65 + '13S' // 95e0 #356 + '43U' // 95e1 #1138 + '138C' // 95e2 #3590 + 'A' // 95e3 + 'a13S' // 95e4-95e5 #356 + '2N' // 95e6 #65 + 'A' // 95e7 + '170C' // 95e8 #4422 + '8O' // 95e9 #222 + '3N' // 95ea #91 + '3W' // 95eb #100 + 'A' // 95ec + '2D' // 95ed #55 + '7K' // 95ee #192 + '2Y' // 95ef #76 + 'a8O' // 95f0-95f1 #222 + '3I' // 95f2 #86 + '8O' // 95f3 #222 + '35T' // 95f4 #929 + '3W' // 95f5 #100 + '8O' // 95f6 #222 + '2R' // 95f7 #69 + '3X' // 95f8 #101 + '1R' // 95f9 #43 + '2Y' // 95fa #76 + '3Q' // 95fb #94 + '8O' // 95fc #222 + '2Y' // 95fd #76 + 'a8O' // 95fe-95ff #222 + '3H' // 9600 #85 + '2L' // 9601 #63 + 'b8O' // 9602-9604 #222 + '1Z' // 9605 #51 + 'g8O' // 9606-960d #222 + '2W' // 960e #74 + '8O' // 960f #222 + '2K' // 9610 #62 + 'b8O' // 9611-9613 #222 + '2L' // 9614 #63 + 'b8O' // 9615-9617 #222 + 'A' // 9618 + 'b8O' // 9619-961b #222 + '85P' // 961c #2225 + '43V' // 961d #1139 + '2N' // 961e #65 + '2D' // 961f #55 + 'A' // 9620 + '43U' // 9621 #1138 + '2N' // 9622 #65 + 'A' // 9623 + '43S' // 9624 #1136 + 'a2N' // 9625-9626 #65 + 'A' // 9627 + '24O' // 9628 #638 + 'A' // 9629 + '184F' // 962a #4789 + 'A' // 962b + '2N' // 962c #65 + 'A' // 962d + '126T' // 962e #3295 + '24O' // 962f #638 + 'A' // 9630 + '138F' // 9631 #3593 + '229Z' // 9632 #5979 + '85A' // 9633 #2210 + '248F' // 9634 #6453 + '3Y' // 9635 #102 + '3N' // 9636 #91 + '2N' // 9637 #65 + '22B' // 9638 #573 + 'a2N' // 9639-963a #65 + '195C' // 963b #5072 + '24O' // 963c #638 + '69Q' // 963d #1810 + '8O' // 963e #222 + '227F' // 963f #5907 + '159I' // 9640 #4142 + '24O' // 9641 #638 + '54G' // 9642 #1410 + '37Q' // 9643 #978 + '226T' // 9644 #5895 + '85L' // 9645 #2221 + '2C' // 9646 #54 + '2W' // 9647 #74 + '2C' // 9648 #54 + '8O' // 9649 #222 + 'A' // 964a + '121O' // 964b #3160 + '162W' // 964c #4234 + '212W' // 964d #5534 + '37Q' // 964e #978 + '43S' // 964f #1136 + '238E' // 9650 #6192 + '37Q' // 9651 #978 + '2N' // 9652 #65 + '37Q' // 9653 #978 + '24O' // 9654 #638 + '1R' // 9655 #43 + '22B' // 9656 #573 + '2N' // 9657 #65 + '43S' // 9658 #1136 + 'aA' // 9659-965a + '85D' // 965b #2213 + 'c54G' // 965c-965f #1410 + 'A' // 9660 + '24O' // 9661 #638 + '229P' // 9662 #5969 + '203C' // 9663 #5280 + '237A' // 9664 #6162 + '254I' // 9665 #6612 + '2N' // 9666 #65 + 'a8O' // 9667-9668 #222 + '85K' // 9669 #2220 + '192A' // 966a #4992 + 'A' // 966b + '24O' // 966c #638 + 'A' // 966d + '2N' // 966e #65 + 'A' // 966f + '197H' // 9670 #5129 + 'A' // 9671 + '12W' // 9672 #334 + '226Q' // 9673 #5892 + '12W' // 9674 #334 + '160Y' // 9675 #4184 + '181W' // 9676 #4728 + '184P' // 9677 #4799 + '220X' // 9678 #5743 + 'A' // 9679 + '258J' // 967a #6717 + '22B' // 967b #573 + '2N' // 967c #65 + '221E' // 967d #5750 + 'a2N' // 967e-967f #65 + 'A' // 9680 + '22B' // 9681 #573 + '69Q' // 9682 #1810 + 'a24P' // 9683-9684 #639 + '85H' // 9685 #2217 + '209T' // 9686 #5453 + 'A' // 9687 + '85G' // 9688 #2216 + '24P' // 9689 #639 + '219W' // 968a #5716 + '84Y' // 968b #2208 + 'A' // 968c + '43R' // 968d #1135 + '212J' // 968e #5521 + '131E' // 968f #3410 + '2D' // 9690 #55 + '2N' // 9691 #65 + 'aA' // 9692-9693 + '209H' // 9694 #5441 + '43R' // 9695 #1135 + '22B' // 9696 #573 + '12W' // 9697 #334 + '43R' // 9698 #1135 + '142X' // 9699 #3715 + '2N' // 969a #65 + '237R' // 969b #6179 + '211O' // 969c #5500 + '2N' // 969d #65 + '54H' // 969e #1411 + '2N' // 969f #65 + '257K' // 96a0 #6692 + 'a17U' // 96a1-96a2 #462 + '85J' // 96a3 #2219 + '12W' // 96a4 #334 + '22B' // 96a5 #573 + '2N' // 96a6 #65 + '144F' // 96a7 #3749 + '68G' // 96a8 #1774 + '24P' // 96a9 #639 + '49F' // 96aa #1279 + 'A' // 96ab + '17U' // 96ac #462 + 'A' // 96ad + '24P' // 96ae #639 + '2N' // 96af #65 + '12W' // 96b0 #334 + '231W' // 96b1 #6028 + '2N' // 96b2 #65 + 'a12W' // 96b3-96b4 #334 + 'A' // 96b5 + '84Z' // 96b6 #2209 + '254F' // 96b7 #6609 + '138E' // 96b8 #3592 + '12W' // 96b9 #334 + '2N' // 96ba #65 + '199M' // 96bb #5186 + '85E' // 96bc #2214 + '12W' // 96bd #334 + '2D' // 96be #55 + 'A' // 96bf + '167K' // 96c0 #4352 + '121N' // 96c1 #3159 + 'A' // 96c2 + '17U' // 96c3 #462 + '227N' // 96c4 #5915 + '219I' // 96c5 #5702 + '239H' // 96c6 #6221 + '136X' // 96c7 #3559 + 'A' // 96c8 + '24N' // 96c9 #637 + '24P' // 96ca #639 + '24N' // 96cb #637 + '125E' // 96cc #3254 + '121M' // 96cd #3158 + '24N' // 96ce #637 + '3W' // 96cf #100 + 'A' // 96d0 + '259J' // 96d1 #6743 + '12W' // 96d2 #334 + 'aA' // 96d3-96d4 + '177L' // 96d5 #4613 + '49F' // 96d6 #1279 + 'A' // 96d7 + '24P' // 96d8 #639 + '68G' // 96d9 #1774 + '2N' // 96da #65 + '125I' // 96db #3258 + '49F' // 96dc #1279 + '24P' // 96dd #639 + '206S' // 96de #5374 + '2N' // 96df #65 + '8O' // 96e0 #222 + 'A' // 96e1 + '229Y' // 96e2 #5978 + '229O' // 96e3 #5968 + 'cA' // 96e4-96e7 + '211N' // 96e8 #5499 + '24N' // 96e9 #637 + '211J' // 96ea #5495 + '252P' // 96eb #6567 + 'bA' // 96ec-96ee + '156D' // 96ef #4059 + '85I' // 96f0 #2218 + '12W' // 96f1 #334 + '227J' // 96f2 #5911 + '2W' // 96f3 #74 + '17U' // 96f4 #462 + 'A' // 96f5 + '218K' // 96f6 #5678 + '209G' // 96f7 #5440 + 'A' // 96f8 + '24N' // 96f9 #637 + '2N' // 96fa #65 + '246P' // 96fb #6411 + 'aA' // 96fc-96fd + '1R' // 96fe #43 + '54H' // 96ff #1411 + '233I' // 9700 #6066 + '8O' // 9701 #222 + '12W' // 9702 #334 + '22B' // 9703 #573 + '126V' // 9704 #3297 + '12W' // 9705 #334 + '149X' // 9706 #3897 + '204G' // 9707 #5310 + '12W' // 9708 #334 + '138G' // 9709 #3594 + '255M' // 970a #6642 + 'aA' // 970b-970c + '162X' // 970d #4235 + 'a12W' // 970e-970f #334 + '17U' // 9710 #462 + '24N' // 9711 #637 + 'A' // 9712 + '126S' // 9713 #3294 + '2N' // 9714 #65 + 'A' // 9715 + '156C' // 9716 #4058 + 'aA' // 9717-9718 + '24N' // 9719 #637 + '2N' // 971a #65 + '22B' // 971b #573 + '180I' // 971c #4688 + '22A' // 971d #572 + '153Z' // 971e #4003 + 'a17U' // 971f-9720 #462 + '17T' // 9721 #461 + 'b1U' // 9722-9724 #46 + 'aA' // 9725-9726 + '188W' // 9727 #4910 + '22A' // 9728 #572 + 'A' // 9729 + '54F' // 972a #1409 + 'aA' // 972b-972c + '8O' // 972d #222 + 'aA' // 972e-972f + '43Q' // 9730 #1134 + '17T' // 9731 #461 + '220N' // 9732 #5733 + '1U' // 9733 #46 + '32Q' // 9734 #848 + 'A' // 9735 + '17T' // 9736 #461 + 'A' // 9737 + '199L' // 9738 #5185 + '131Z' // 9739 #3431 + '17U' // 973a #462 + '1U' // 973b #46 + 'A' // 973c + '43Q' // 973d #1134 + '121L' // 973e #3157 + 'A' // 973f + '32Q' // 9740 #848 + '17T' // 9741 #461 + '131Y' // 9742 #3430 + '1U' // 9743 #46 + '43Q' // 9744 #1134 + 'A' // 9745 + 'a22A' // 9746-9747 #572 + '216W' // 9748 #5638 + '54F' // 9749 #1409 + '17T' // 974a #461 + 'aA' // 974b-974c + 'b1U' // 974d-974f #46 + '8O' // 9750 #222 + '84X' // 9751 #2207 + '230S' // 9752 #5998 + '2Y' // 9753 #76 + 'A' // 9754 + '17T' // 9755 #461 + '153K' // 9756 #3988 + '17T' // 9757 #461 + '22A' // 9758 #572 + '258P' // 9759 #6723 + '170A' // 975a #4420 + '16I' // 975b #424 + '206P' // 975c #5371 + '17U' // 975d #462 + '236P' // 975e #6151 + '32Q' // 975f #848 + '206R' // 9760 #5373 + '132A' // 9761 #3432 + '49G' // 9762 #1280 + '41I' // 9763 #1074 + '1U' // 9764 #46 + '8N' // 9765 #221 + '16I' // 9766 #424 + '1U' // 9767 #46 + '22A' // 9768 #572 + '198I' // 9769 #5156 + 'a1U' // 976a-976b #46 + '43T' // 976c #1137 + '85C' // 976d #2212 + '1U' // 976e #46 + 'aA' // 976f-9770 + '22A' // 9771 #572 + 'A' // 9772 + '16I' // 9773 #424 + '169E' // 9774 #4398 + 'A' // 9775 + '16I' // 9776 #424 + '248C' // 9777 #6450 + 'a1U' // 9778-9779 #46 + '37P' // 977a #977 + '1U' // 977b #46 + '16I' // 977c #424 + '1U' // 977d #46 + 'A' // 977e + '1U' // 977f #46 + '22A' // 9780 #572 + '1U' // 9781 #46 + 'aA' // 9782-9783 + '85F' // 9784 #2215 + '16I' // 9785 #424 + '1U' // 9786 #46 + '32Q' // 9787 #848 + '17U' // 9788 #462 + '17T' // 9789 #461 + 'A' // 978a + '206Q' // 978b #5372 + 'A' // 978c + '141T' // 978d #3685 + '43T' // 978e #1137 + '37P' // 978f #977 + '1U' // 9790 #46 + 'a8N' // 9791-9792 #221 + 'A' // 9793 + '8N' // 9794 #221 + '41I' // 9795 #1074 + 'a1U' // 9796-9797 #46 + '85B' // 9798 #2211 + '1U' // 9799 #46 + '41I' // 979a #1074 + '32Q' // 979b #848 + '1U' // 979c #46 + '17U' // 979d #462 + '16I' // 979e #424 + '17T' // 979f #461 + '121K' // 97a0 #3156 + 'A' // 97a1 + '1U' // 97a2 #46 + '16I' // 97a3 #424 + 'A' // 97a4 + '43T' // 97a5 #1137 + '16I' // 97a6 #424 + 'A' // 97a7 + '37P' // 97a8 #977 + 'aA' // 97a9-97aa + '37P' // 97ab #977 + '16I' // 97ac #424 + '147Q' // 97ad #3838 + '16I' // 97ae #424 + '8N' // 97af #221 + 'A' // 97b0 + '17T' // 97b1 #461 + '54E' // 97b2 #1408 + '1U' // 97b3 #46 + '54E' // 97b4 #1408 + '1U' // 97b5 #46 + '22A' // 97b6 #572 + '43P' // 97b7 #1133 + '12V' // 97b8 #333 + '19V' // 97b9 #515 + '12V' // 97ba #333 + 'A' // 97bb + '1U' // 97bc #46 + '17S' // 97bd #460 + '12V' // 97be #333 + '19V' // 97bf #515 + '17S' // 97c0 #460 + '19V' // 97c1 #515 + '17S' // 97c2 #460 + '19V' // 97c3 #515 + 'a1U' // 97c4-97c5 #46 + '54B' // 97c6 #1405 + '1U' // 97c7 #46 + '12V' // 97c8 #333 + '19V' // 97c9 #515 + '1U' // 97ca #46 + '156A' // 97cb #4056 + '144E' // 97cc #3748 + '19V' // 97cd #515 + '1U' // 97ce #46 + 'A' // 97cf + 'a1U' // 97d0-97d1 #46 + '17S' // 97d2 #460 + '228I' // 97d3 #5936 + '1U' // 97d4 #46 + 'A' // 97d5 + '43P' // 97d6 #1133 + '1U' // 97d7 #46 + '43N' // 97d8 #1131 + '19V' // 97d9 #515 + 'A' // 97da + '1U' // 97db #46 + '54B' // 97dc #1405 + 'a19V' // 97dd-97de #515 + 'A' // 97df + '12V' // 97e0 #333 + '43N' // 97e1 #1131 + 'aA' // 97e2-97e3 + '1U' // 97e4 #46 + 'A' // 97e5 + '84P' // 97e6 #2199 + '3X' // 97e7 #101 + 'A' // 97e8 + '2C' // 97e9 #54 + 'a8N' // 97ea-97eb #221 + '2K' // 97ec #62 + '84O' // 97ed #2198 + '43N' // 97ee #1131 + '1U' // 97ef #46 + '43P' // 97f0 #1133 + '19V' // 97f1 #515 + '12V' // 97f2 #333 + '238C' // 97f3 #6190 + '1U' // 97f4 #46 + '84Q' // 97f5 #2200 + '126Q' // 97f6 #3292 + 'a1U' // 97f7-97f8 #46 + '27X' // 97f9 #725 + '6V' // 97fa #177 + '166H' // 97fb #4323 + 'aA' // 97fc-97fd + '43O' // 97fe #1132 + '228J' // 97ff #5937 + '260O' // 9800 #6774 + '246D' // 9801 #6399 + '68F' // 9802 #1773 + '126D' // 9803 #3279 + '6V' // 9804 #177 + '229X' // 9805 #5977 + '68F' // 9806 #1773 + '6V' // 9807 #177 + '220W' // 9808 #5742 + 'A' // 9809 + '54C' // 980a #1406 + 'A' // 980b + '65N' // 980c #1703 + '1U' // 980d #46 + 'a6V' // 980e-980f #177 + '227C' // 9810 #5904 + '143K' // 9811 #3728 + '166Z' // 9812 #4341 + '195B' // 9813 #5071 + '15H' // 9814 #397 + '17S' // 9815 #460 + '6V' // 9816 #177 + '162U' // 9817 #4232 + '219O' // 9818 #5708 + 'a1U' // 9819-981a #46 + 'A' // 981b + '6V' // 981c #177 + 'A' // 981d + '15H' // 981e #397 + '17S' // 981f #460 + 'a15H' // 9820-9821 #397 + 'A' // 9822 + '6V' // 9823 #177 + '126R' // 9824 #3293 + '1U' // 9825 #46 + '6V' // 9826 #177 + 'A' // 9827 + '43O' // 9828 #1132 + 'aA' // 9829-982a + '15H' // 982b #397 + '253H' // 982c #6585 + '242J' // 982d #6301 + '12V' // 982e #333 + '1U' // 982f #46 + '138A' // 9830 #3588 + 'A' // 9831 + '6V' // 9832 #177 + '12V' // 9833 #333 + '15H' // 9834 #397 + '6V' // 9835 #177 + 'A' // 9836 + '6V' // 9837 #177 + '180D' // 9838 #4683 + '43M' // 9839 #1130 + 'A' // 983a + '218V' // 983b #5689 + '258B' // 983c #6709 + 'a1U' // 983d-983e #46 + 'dA' // 983f-9843 + '1U' // 9844 #46 + '27X' // 9845 #725 + '199K' // 9846 #5184 + '12V' // 9847 #333 + 'a27X' // 9848-9849 #725 + '1U' // 984a #46 + '12V' // 984b #333 + '242S' // 984c #6310 + '222J' // 984d #5781 + '84U' // 984e #2204 + '214H' // 984f #5571 + 'A' // 9850 + '41I' // 9851 #1074 + '15H' // 9852 #397 + '6V' // 9853 #177 + '258V' // 9854 #6729 + '253V' // 9855 #6599 + '6V' // 9856 #177 + '15H' // 9857 #397 + '213Z' // 9858 #5563 + '6V' // 9859 #177 + '248D' // 985a #6451 + '156B' // 985b #4057 + 'aA' // 985c-985d + '242C' // 985e #6294 + 'bA' // 985f-9861 + 'a15H' // 9862-9863 #397 + 'A' // 9864 + '43M' // 9865 #1130 + '12V' // 9866 #333 + '218X' // 9867 #5691 + '17S' // 9868 #460 + 'A' // 9869 + '1U' // 986a #46 + '126P' // 986b #3291 + '12V' // 986c #333 + 'aA' // 986d-986e + '224K' // 986f #5834 + 'a15H' // 9870-9871 #397 + 'A' // 9872 + 'a6V' // 9873-9874 #177 + '177K' // 9875 #4612 + '2D' // 9876 #55 + 'a8N' // 9877-9878 #221 + '1Z' // 9879 #51 + '3I' // 987a #86 + '2C' // 987b #54 + '8N' // 987c #221 + '2K' // 987d #62 + '2C' // 987e #54 + '3N' // 987f #91 + '8N' // 9880 #221 + '2Y' // 9881 #76 + '2K' // 9882 #62 + '8N' // 9883 #221 + '2D' // 9884 #55 + '8N' // 9885 #221 + '1Z' // 9886 #51 + '2Y' // 9887 #76 + '2L' // 9888 #63 + '8N' // 9889 #221 + '2W' // 988a #74 + 'd8N' // 988b-988f #221 + '3W' // 9890 #100 + '3Q' // 9891 #94 + 'A' // 9892 + 'b8N' // 9893-9895 #221 + '1R' // 9896 #43 + '4C' // 9897 #106 + '7K' // 9898 #192 + 'b8N' // 9899-989b #221 + '3I' // 989c #86 + '2C' // 989d #54 + 'a8N' // 989e-989f #221 + '2R' // 98a0 #69 + 'a8N' // 98a1-98a2 #221 + 'A' // 98a3 + '3X' // 98a4 #101 + 'b8N' // 98a5-98a7 #221 + '237O' // 98a8 #6176 + '43O' // 98a9 #1132 + 'a1U' // 98aa-98ab #46 + 'A' // 98ac + '1U' // 98ad #46 + '6V' // 98ae #177 + '84S' // 98af #2202 + '1U' // 98b0 #46 + '65N' // 98b1 #1703 + 'a27X' // 98b2-98b3 #725 + '12V' // 98b4 #333 + 'A' // 98b5 + '15H' // 98b6 #397 + 'a6V' // 98b7-98b8 #177 + '17S' // 98b9 #460 + '15H' // 98ba #397 + 'a6V' // 98bb-98bc #177 + '27X' // 98bd #725 + '8N' // 98be #221 + '6V' // 98bf #177 + 'aA' // 98c0-98c1 + '1U' // 98c2 #46 + '84N' // 98c3 #2197 + '177J' // 98c4 #4611 + '1U' // 98c5 #46 + '169Z' // 98c6 #4419 + '43M' // 98c7 #1130 + '15H' // 98c8 #397 + 'A' // 98c9 + '27X' // 98ca #725 + 'a1U' // 98cb-98cc #46 + 'A' // 98cd + '138B' // 98ce #3589 + 'g8N' // 98cf-98d6 #221 + 'A' // 98d7 + '2L' // 98d8 #63 + '3H' // 98d9 #85 + '8N' // 98da #221 + '221Z' // 98db #5771 + '54C' // 98dc #1406 + '7V' // 98dd #203 + '84R' // 98de #2201 + '238V' // 98df #6209 + '6V' // 98e0 #177 + '84M' // 98e1 #2196 + '130A' // 98e2 #3380 + '6V' // 98e3 #177 + 'A' // 98e4 + '6V' // 98e5 #177 + '12V' // 98e6 #333 + '54D' // 98e7 #1407 + '7V' // 98e8 #203 + '54D' // 98e9 #1407 + '144D' // 98ea #3747 + '37O' // 98eb #976 + '84W' // 98ec #2206 + '53Z' // 98ed #1403 + '248B' // 98ee #6449 + '215U' // 98ef #5610 + '1M' // 98f0 #38 + '37O' // 98f1 #976 + '223G' // 98f2 #5804 + '1M' // 98f3 #38 + '84T' // 98f4 #2203 + '17S' // 98f5 #460 + '1M' // 98f6 #38 + 'dA' // 98f7-98fb + '154Z' // 98fc #4029 + '196K' // 98fd #5106 + '219V' // 98fe #5715 + 'bA' // 98ff-9901 + '1M' // 9902 #38 + '154H' // 9903 #4011 + 'A' // 9904 + '201V' // 9905 #5247 + 'A' // 9906 + '1M' // 9907 #38 + '54A' // 9908 #1404 + '53Z' // 9909 #1403 + '228B' // 990a #5929 + 'A' // 990b + '84V' // 990c #2205 + '7V' // 990d #203 + '17S' // 990e #460 + 'A' // 990f + '226E' // 9910 #5880 + 'a37O' // 9911-9912 #976 + '166E' // 9913 #4320 + '54A' // 9914 #1404 + '1M' // 9915 #38 + 'a37O' // 9916-9917 #976 + '206O' // 9918 #5370 + '12U' // 9919 #332 + '126O' // 991a #3290 + '43L' // 991b #1129 + '32P' // 991c #847 + '1M' // 991d #38 + '84G' // 991e #2190 + '1M' // 991f #38 + '69O' // 9920 #1808 + '144C' // 9921 #3746 + '1M' // 9922 #38 + 'A' // 9923 + '1M' // 9924 #38 + 'A' // 9925 + '1M' // 9926 #38 + '32P' // 9927 #847 + '230H' // 9928 #5987 + 'aA' // 9929-992a + '32P' // 992b #847 + '43L' // 992c #1129 + 'A' // 992d + '43L' // 992e #1129 + 'aA' // 992f-9930 + 'b32P' // 9931-9933 #847 + '1M' // 9934 #38 + '162T' // 9935 #4231 + 'A' // 9936 + '12U' // 9937 #332 + '84L' // 9938 #2195 + '43K' // 9939 #1128 + '32P' // 993a #847 + '27W' // 993b #724 + '4K' // 993c #114 + 'a17R' // 993d-993e #459 + '13R' // 993f #355 + '4K' // 9940 #114 + '1M' // 9941 #38 + '27W' // 9942 #724 + '13R' // 9943 #355 + 'A' // 9944 + '137Z' // 9945 #3587 + 'a1M' // 9946-9947 #38 + '4K' // 9948 #114 + '32O' // 9949 #846 + '13R' // 994a #355 + '184N' // 994b #4797 + '37N' // 994c #975 + '32O' // 994d #846 + '4K' // 994e #114 + 'A' // 994f + '1M' // 9950 #38 + '37N' // 9951 #975 + '144B' // 9952 #3745 + 'A' // 9953 + '37N' // 9954 #975 + '121I' // 9955 #3154 + 'A' // 9956 + '152M' // 9957 #3964 + 'a1M' // 9958-9959 #38 + 'A' // 995a + '1M' // 995b #38 + '4K' // 995c #114 + '12U' // 995d #332 + '17R' // 995e #459 + '4K' // 995f #114 + '1M' // 9960 #38 + 'a13R' // 9961-9962 #355 + '43K' // 9963 #1128 + 'A' // 9964 + '2K' // 9965 #62 + 'c7V' // 9966-9969 #203 + '2K' // 996a #62 + 'a7V' // 996b-996c #203 + 'a3I' // 996d-996e #86 + '7V' // 996f #203 + '2C' // 9970 #54 + '2L' // 9971 #63 + '2K' // 9972 #62 + 'A' // 9973 + 'a7V' // 9974-9975 #203 + '3H' // 9976 #85 + 'b7V' // 9977-9979 #203 + '3X' // 997a #101 + 'A' // 997b + '4C' // 997c #106 + 'a7V' // 997d-997e #203 + '3H' // 997f #85 + 'a7V' // 9980-9981 #203 + 'A' // 9982 + 'a7V' // 9983-9984 #203 + '3W' // 9985 #100 + '2C' // 9986 #54 + '7V' // 9987 #203 + '2D' // 9988 #55 + 'A' // 9989 + 'a7V' // 998a-998b #203 + 'A' // 998c + 'd7V' // 998d-9991 #203 + '3W' // 9992 #100 + 'b7V' // 9993-9995 #203 + '241J' // 9996 #6275 + 'a17R' // 9997-9998 #459 + '236Q' // 9999 #6152 + 'A' // 999a + '27W' // 999b #724 + '13R' // 999c #355 + '69O' // 999d #1808 + '4K' // 999e #114 + '1M' // 999f #38 + 'A' // 99a0 + '13R' // 99a1 #355 + 'A' // 99a2 + '1M' // 99a3 #38 + '12U' // 99a4 #332 + '63O' // 99a5 #1652 + '1M' // 99a6 #38 + 'A' // 99a7 + '184O' // 99a8 #4798 + 'A' // 99a9 + '12U' // 99aa #332 + '53Y' // 99ab #1402 + '235T' // 99ac #6129 + '131X' // 99ad #3429 + '149W' // 99ae #3896 + '13R' // 99af #355 + '1M' // 99b0 #38 + '37N' // 99b1 #975 + '1M' // 99b2 #38 + '159W' // 99b3 #4156 + '84I' // 99b4 #2192 + '1M' // 99b5 #38 + 'aA' // 99b6-99b7 + '12U' // 99b8 #332 + '32O' // 99b9 #846 + '1M' // 99ba #38 + 'A' // 99bb + '43K' // 99bc #1128 + '1M' // 99bd #38 + 'A' // 99be + '69P' // 99bf #1809 + 'A' // 99c0 + '162S' // 99c1 #4230 + 'A' // 99c2 + '69P' // 99c3 #1809 + '84J' // 99c4 #2193 + '84K' // 99c5 #2194 + '256S' // 99c6 #6674 + 'A' // 99c7 + 'a1M' // 99c8-99c9 #38 + 'aA' // 99ca-99cb + '7V' // 99cc #203 + 'aA' // 99cd-99ce + '13R' // 99cf #355 + '191A' // 99d0 #4966 + '32O' // 99d1 #846 + '84H' // 99d2 #2191 + '1M' // 99d3 #38 + '4K' // 99d4 #114 + '191Y' // 99d5 #4990 + '13R' // 99d6 #355 + 'A' // 99d7 + '4K' // 99d8 #114 + '32O' // 99d9 #846 + '27W' // 99da #724 + '177H' // 99db #4609 + '1M' // 99dc #38 + '63O' // 99dd #1652 + '1M' // 99de #38 + '37M' // 99df #974 + '12U' // 99e0 #332 + '17R' // 99e1 #459 + '4K' // 99e2 #114 + 'bA' // 99e3-99e5 + '12U' // 99e6 #332 + '1M' // 99e7 #38 + 'A' // 99e8 + '13R' // 99e9 #355 + 'b1M' // 99ea-99ec #38 + '137X' // 99ed #3585 + '4K' // 99ee #114 + 'A' // 99ef + '4K' // 99f0 #114 + '62Z' // 99f1 #1637 + '1M' // 99f2 #38 + 'A' // 99f3 + '1M' // 99f4 #38 + '27W' // 99f5 #724 + 'aA' // 99f6-99f7 + '4K' // 99f8 #114 + '1M' // 99f9 #38 + 'A' // 99fa + '4K' // 99fb #114 + 'b1M' // 99fc-99fe #38 + '154J' // 99ff #4013 + 'A' // 9a00 + '21Z' // 9a01 #571 + '4K' // 9a02 #114 + 'a17R' // 9a03-9a04 #459 + '4K' // 9a05 #114 + 'aA' // 9a06-9a07 + '248A' // 9a08 #6448 + 'A' // 9a09 + 'a1M' // 9a0a-9a0b #38 + '4K' // 9a0c #114 + 'A' // 9a0d + '203L' // 9a0e #5289 + '21Z' // 9a0f #571 + '4K' // 9a10 #114 + '17R' // 9a11 #459 + '255T' // 9a12 #6649 + '259O' // 9a13 #6748 + 'aA' // 9a14-9a15 + '4K' // 9a16 #114 + 'aA' // 9a17-9a18 + '195W' // 9a19 #5092 + '1M' // 9a1a #38 + '53Y' // 9a1b #1402 + '13R' // 9a1c #355 + 'A' // 9a1d + '1M' // 9a1e #38 + '12U' // 9a1f #332 + '4K' // 9a20 #114 + '12U' // 9a21 #332 + 'a1M' // 9a22-9a23 #38 + '4K' // 9a24 #114 + 'A' // 9a25 + '12U' // 9a26 #332 + '1M' // 9a27 #38 + '252E' // 9a28 #6556 + 'aA' // 9a29-9a2a + '21Z' // 9a2b #571 + 'A' // 9a2c + 'a4K' // 9a2d-9a2e #114 + '12U' // 9a2f #332 + '188U' // 9a30 #4908 + '1M' // 9a31 #38 + '7V' // 9a32 #203 + '1M' // 9a33 #38 + '13R' // 9a34 #355 + '4K' // 9a35 #114 + '37M' // 9a36 #974 + '184M' // 9a37 #4796 + '4K' // 9a38 #114 + 'aA' // 9a39-9a3a + 'a12U' // 9a3b-9a3c #332 + 'A' // 9a3d + '4K' // 9a3e #114 + 'A' // 9a3f + '21Z' // 9a40 #571 + 'a4K' // 9a41-9a42 #114 + '37M' // 9a43 #974 + '4K' // 9a44 #114 + '66Q' // 9a45 #1732 + 'A' // 9a46 + '1M' // 9a47 #38 + 'aA' // 9a48-9a49 + '17R' // 9a4a #459 + '1M' // 9a4b #38 + '4K' // 9a4c #114 + '21Z' // 9a4d #571 + '17R' // 9a4e #459 + 'aA' // 9a4f-9a50 + '1M' // 9a51 #38 + '17R' // 9a52 #459 + 'A' // 9a53 + '1M' // 9a54 #38 + '144A' // 9a55 #3744 + '4K' // 9a56 #114 + '231V' // 9a57 #6027 + '27W' // 9a58 #724 + 'A' // 9a59 + '220V' // 9a5a #5741 + '62Z' // 9a5b #1637 + '12U' // 9a5c #332 + '1M' // 9a5d #38 + 'A' // 9a5e + '66Q' // 9a5f #1732 + 'aA' // 9a60-9a61 + '21Z' // 9a62 #571 + '13R' // 9a63 #355 + '4K' // 9a64 #114 + '21Z' // 9a65 #571 + 'bA' // 9a66-9a68 + '21Z' // 9a69 #571 + '37M' // 9a6a #974 + '17R' // 9a6b #459 + '177I' // 9a6c #4610 + '3W' // 9a6d #100 + '7V' // 9a6e #203 + '2W' // 9a6f #74 + '4C' // 9a70 #106 + '1R' // 9a71 #43 + 'A' // 9a72 + '3X' // 9a73 #101 + '3H' // 9a74 #85 + '7V' // 9a75 #203 + '4C' // 9a76 #106 + 'c7V' // 9a77-9a7a #203 + '3Y' // 9a7b #102 + '3G' // 9a7c #84 + '7V' // 9a7d #203 + '3N' // 9a7e #91 + '3G' // 9a7f #84 + '8M' // 9a80 #220 + '3W' // 9a81 #100 + '1R' // 9a82 #43 + '8M' // 9a83 #220 + '3H' // 9a84 #85 + '8M' // 9a85 #220 + '3W' // 9a86 #100 + '2W' // 9a87 #74 + 'b8M' // 9a88-9a8a #220 + '2W' // 9a8b #74 + '1Z' // 9a8c #51 + 'a8M' // 9a8d-9a8e #220 + '84F' // 9a8f #2189 + '8M' // 9a90 #220 + '1R' // 9a91 #43 + 'a8M' // 9a92-9a93 #220 + 'A' // 9a94 + 'a8M' // 9a95-9a96 #220 + '3Y' // 9a97 #102 + 'a8M' // 9a98-9a99 #220 + '4C' // 9a9a #106 + 'g8M' // 9a9b-9aa2 #220 + 'A' // 9aa3 + '2L' // 9aa4 #63 + '8M' // 9aa5 #220 + 'A' // 9aa6 + '8M' // 9aa7 #220 + '204N' // 9aa8 #5317 + 'A' // 9aa9 + '1M' // 9aaa #38 + 'A' // 9aab + 'b1M' // 9aac-9aae #38 + 'a21X' // 9aaf-9ab0 #569 + '21Y' // 9ab1 #570 + '37L' // 9ab2 #973 + 'A' // 9ab3 + '1M' // 9ab4 #38 + '41H' // 9ab5 #1073 + '21X' // 9ab6 #569 + '83X' // 9ab7 #2181 + '130E' // 9ab8 #3384 + '21X' // 9ab9 #569 + '21Y' // 9aba #570 + '1M' // 9abb #38 + '121J' // 9abc #3155 + '37L' // 9abd #973 + '53X' // 9abe #1401 + '1M' // 9abf #38 + 'a21X' // 9ac0-9ac1 #569 + '21Y' // 9ac2 #570 + '41H' // 9ac3 #1073 + '252V' // 9ac4 #6573 + '2W' // 9ac5 #74 + '1M' // 9ac6 #38 + 'A' // 9ac7 + '41H' // 9ac8 #1073 + 'aA' // 9ac9-9aca + 'a8M' // 9acb-9acc #220 + 'A' // 9acd + '41H' // 9ace #1073 + '21X' // 9acf #569 + '1M' // 9ad0 #38 + '21X' // 9ad1 #569 + '177G' // 9ad2 #4608 + '137Y' // 9ad3 #3586 + '241E' // 9ad4 #6270 + 'b53X' // 9ad5-9ad7 #1401 + '69A' // 9ad8 #1794 + '252M' // 9ad9 #6564 + 'A' // 9ada + 'a1M' // 9adb-9adc #38 + 'A' // 9add + '1M' // 9ade #38 + '21X' // 9adf #569 + '37L' // 9ae0 #973 + '21Y' // 9ae1 #570 + '37L' // 9ae2 #973 + '21X' // 9ae3 #569 + '1M' // 9ae4 #38 + '247Z' // 9ae5 #6447 + '131W' // 9ae6 #3428 + '1H' // 9ae7 #33 + 'A' // 9ae8 + '1H' // 9ae9 #33 + '257G' // 9aea #6688 + '6K' // 9aeb #166 + '1H' // 9aec #33 + '84B' // 9aed #2185 + '206N' // 9aee #5369 + '6K' // 9aef #166 + 'A' // 9af0 + '1H' // 9af1 #33 + '6A' // 9af2 #156 + '1H' // 9af3 #33 + '6K' // 9af4 #166 + '1H' // 9af5 #33 + 'A' // 9af6 + '1H' // 9af7 #33 + 'A' // 9af8 + '6K' // 9af9 #166 + '1H' // 9afa #33 + '6K' // 9afb #166 + 'A' // 9afc + '6A' // 9afd #156 + 'A' // 9afe + '19U' // 9aff #514 + 'a1H' // 9b00-9b01 #33 + '19U' // 9b02 #514 + 'a6K' // 9b03-9b04 #166 + '1H' // 9b05 #33 + '216V' // 9b06 #5637 + 'A' // 9b07 + '6K' // 9b08 #166 + '19U' // 9b09 #514 + 'A' // 9b0a + 'a1H' // 9b0b-9b0c #33 + '65A' // 9b0d #1690 + '1H' // 9b0e #33 + '84E' // 9b0f #2188 + '41G' // 9b10 #1072 + 'A' // 9b11 + '1H' // 9b12 #33 + '8M' // 9b13 #220 + '32M' // 9b14 #844 + 'A' // 9b15 + '1H' // 9b16 #33 + 'A' // 9b17 + '6K' // 9b18 #166 + '1H' // 9b19 #33 + '131U' // 9b1a #3426 + 'b1H' // 9b1b-9b1d #33 + 'A' // 9b1e + '6K' // 9b1f #166 + '1H' // 9b20 #33 + 'A' // 9b21 + 'a6K' // 9b22-9b23 #166 + 'A' // 9b24 + '199J' // 9b25 #5183 + '1H' // 9b26 #33 + '67D' // 9b27 #1745 + '6K' // 9b28 #166 + '6A' // 9b29 #156 + '43J' // 9b2a #1127 + 'a1H' // 9b2b-9b2c #33 + 'a6A' // 9b2d-9b2e #156 + '6K' // 9b2f #166 + 'A' // 9b30 + '167F' // 9b31 #4347 + '6K' // 9b32 #166 + '1H' // 9b33 #33 + '19U' // 9b34 #514 + '1H' // 9b35 #33 + 'A' // 9b36 + '1H' // 9b37 #33 + 'A' // 9b38 + '19U' // 9b39 #514 + '1H' // 9b3a #33 + '6K' // 9b3b #166 + '204F' // 9b3c #5309 + '1H' // 9b3d #33 + 'aA' // 9b3e-9b3f + '32M' // 9b40 #844 + '130B' // 9b41 #3381 + '189Z' // 9b42 #4939 + '43J' // 9b43 #1127 + '131V' // 9b44 #3427 + '191D' // 9b45 #4969 + '21Y' // 9b46 #570 + '8M' // 9b47 #220 + '6K' // 9b48 #166 + '8M' // 9b49 #220 + 'A' // 9b4a + 'a6A' // 9b4b-9b4c #156 + 'a6K' // 9b4d-9b4e #166 + '155Z' // 9b4f #4055 + '32M' // 9b50 #844 + '6K' // 9b51 #166 + 'aA' // 9b52-9b53 + '215Y' // 9b54 #5614 + '6A' // 9b55 #156 + '41G' // 9b56 #1072 + '1H' // 9b57 #33 + '6K' // 9b58 #166 + 'A' // 9b59 + '220H' // 9b5a #5727 + '6A' // 9b5b #156 + '8M' // 9b5c #220 + 'A' // 9b5d + '1H' // 9b5e #33 + '21Y' // 9b5f #570 + '17Q' // 9b60 #458 + '41G' // 9b61 #1072 + '8M' // 9b62 #220 + '1H' // 9b63 #33 + 'A' // 9b64 + 'a1H' // 9b65-9b66 #33 + 'A' // 9b67 + '6A' // 9b68 #156 + '17Q' // 9b69 #458 + 'd1H' // 9b6a-9b6e #33 + '67D' // 9b6f #1745 + 'aA' // 9b70-9b71 + 'a1H' // 9b72-9b73 #33 + '6A' // 9b74 #156 + 'a1H' // 9b75-9b76 #33 + '126N' // 9b77 #3289 + 'a1H' // 9b78-9b79 #33 + 'bA' // 9b7a-9b7c + '17Q' // 9b7d #458 + 'A' // 9b7e + '19U' // 9b7f #514 + '6K' // 9b80 #166 + '17Q' // 9b81 #458 + 'A' // 9b82 + '6A' // 9b83 #156 + 'b1H' // 9b84-9b86 #33 + '6A' // 9b87 #156 + '21Y' // 9b88 #570 + 'a1H' // 9b89-9b8a #33 + '6K' // 9b8b #166 + 'A' // 9b8c + '6A' // 9b8d #156 + '84A' // 9b8e #2184 + '19U' // 9b8f #514 + '6A' // 9b90 #156 + '143Z' // 9b91 #3743 + 'a6A' // 9b92-9b93 #156 + '1H' // 9b94 #33 + '17Q' // 9b95 #458 + '1H' // 9b96 #33 + '6A' // 9b97 #156 + 'aA' // 9b98-9b99 + '1H' // 9b9a #33 + 'aA' // 9b9b-9b9c + '19U' // 9b9d #514 + '1H' // 9b9e #33 + '43J' // 9b9f #1127 + '6K' // 9ba0 #166 + 'A' // 9ba1 + '17Q' // 9ba2 #458 + 'bA' // 9ba3-9ba5 + 'a1H' // 9ba6-9ba7 #33 + '83Z' // 9ba8 #2183 + '1H' // 9ba9 #33 + '137W' // 9baa #3584 + '83Y' // 9bab #2182 + '1H' // 9bac #33 + '130F' // 9bad #3385 + '220F' // 9bae #5725 + 'A' // 9baf + '6K' // 9bb0 #166 + '41G' // 9bb1 #1072 + '1H' // 9bb2 #33 + 'A' // 9bb3 + '1H' // 9bb4 #33 + 'aA' // 9bb5-9bb6 + '1H' // 9bb7 #33 + '6K' // 9bb8 #166 + '1H' // 9bb9 #33 + 'A' // 9bba + 'a1H' // 9bbb-9bbc #33 + 'A' // 9bbd + 'a1H' // 9bbe-9bbf #33 + 'a6A' // 9bc0-9bc1 #156 + 'A' // 9bc2 + '17Q' // 9bc3 #458 + 'aA' // 9bc4-9bc5 + 'b6A' // 9bc6-9bc8 #156 + '136E' // 9bc9 #3540 + '65A' // 9bca #1690 + 'bA' // 9bcb-9bcd + '1H' // 9bce #33 + '19U' // 9bcf #514 + 'b1H' // 9bd0-9bd2 #33 + '21Y' // 9bd3 #570 + '6A' // 9bd4 #156 + '17Q' // 9bd5 #458 + '84D' // 9bd6 #2187 + '6A' // 9bd7 #156 + '1H' // 9bd8 #33 + '17Q' // 9bd9 #458 + 'A' // 9bda + '84C' // 9bdb #2186 + 'A' // 9bdc + '6A' // 9bdd #156 + 'A' // 9bde + '1H' // 9bdf #33 + 'A' // 9be0 + 'a6A' // 9be1-9be2 #156 + '1H' // 9be3 #33 + '83W' // 9be4 #2180 + '6A' // 9be5 #156 + 'A' // 9be6 + '6A' // 9be7 #156 + '142E' // 9be8 #3696 + '32M' // 9be9 #844 + '6A' // 9bea #156 + '1H' // 9beb #33 + 'A' // 9bec + '17Q' // 9bed #458 + 'a1H' // 9bee-9bef #33 + 'a53W' // 9bf0-9bf1 #1400 + 'a1H' // 9bf2-9bf3 #33 + '32M' // 9bf4 #844 + '1H' // 9bf5 #33 + 'A' // 9bf6 + '32N' // 9bf7 #845 + 'b1H' // 9bf8-9bfa #33 + 'aA' // 9bfb-9bfc + '32N' // 9bfd #845 + 'A' // 9bfe + '32N' // 9bff #845 + '1H' // 9c00 #33 + 'A' // 9c01 + '53W' // 9c02 #1400 + 'A' // 9c03 + '1H' // 9c04 #33 + 'A' // 9c05 + '32N' // 9c06 #845 + 'A' // 9c07 + 'b32N' // 9c08-9c0a #845 + '1H' // 9c0b #33 + '37J' // 9c0c #971 + '43H' // 9c0d #1125 + 'A' // 9c0e + '1H' // 9c0f #33 + '43G' // 9c10 #1124 + '1H' // 9c11 #33 + '43H' // 9c12 #1125 + '19T' // 9c13 #513 + '1H' // 9c14 #33 + '43G' // 9c15 #1124 + '1H' // 9c16 #33 + 'A' // 9c17 + 'b1H' // 9c18-9c1a #33 + '37J' // 9c1b #971 + '19T' // 9c1c #513 + 'a1H' // 9c1d-9c1e #33 + '37K' // 9c1f #972 + '21W' // 9c20 #568 + '19T' // 9c21 #513 + '1H' // 9c22 #33 + '19T' // 9c23 #513 + '43I' // 9c24 #1126 + '43H' // 9c25 #1125 + '37J' // 9c26 #971 + '1H' // 9c27 #33 + 'a19T' // 9c28-9c29 #513 + '1H' // 9c2a #33 + 'aA' // 9c2b-9c2c + '43I' // 9c2d #1126 + '1H' // 9c2e #33 + '37J' // 9c2f #971 + '1H' // 9c30 #33 + '19T' // 9c31 #513 + '43G' // 9c32 #1124 + '37K' // 9c33 #972 + 'A' // 9c34 + 'b19T' // 9c35-9c37 #513 + 'A' // 9c38 + '43I' // 9c39 #1126 + '19T' // 9c3a #513 + '83S' // 9c3b #2176 + 'A' // 9c3c + '19T' // 9c3d #513 + '4N' // 9c3e #117 + 'A' // 9c3f + '83V' // 9c40 #2179 + '1I' // 9c41 #34 + 'A' // 9c42 + 'a1I' // 9c43-9c44 #34 + 'a4N' // 9c45-9c46 #117 + '53T' // 9c47 #1397 + '37I' // 9c48 #970 + '53T' // 9c49 #1397 + '1I' // 9c4a #34 + 'bA' // 9c4b-9c4d + '1I' // 9c4e #34 + '32L' // 9c4f #843 + '1I' // 9c50 #34 + 'A' // 9c51 + '4N' // 9c52 #117 + '83Q' // 9c53 #2174 + '4N' // 9c54 #117 + 'A' // 9c55 + '4N' // 9c56 #117 + '124Z' // 9c57 #3249 + '4N' // 9c58 #117 + '37K' // 9c59 #972 + 'b1I' // 9c5a-9c5c #34 + '4N' // 9c5d #117 + '1I' // 9c5e #34 + 'a4N' // 9c5f-9c60 #117 + '1I' // 9c61 #34 + 'A' // 9c62 + '4N' // 9c63 #117 + '8M' // 9c64 #220 + '1I' // 9c65 #34 + 'A' // 9c66 + 'a4N' // 9c67-9c68 #117 + 'b1I' // 9c69-9c6b #34 + 'A' // 9c6c + 'a1I' // 9c6d-9c6e #34 + 'A' // 9c6f + '1I' // 9c70 #34 + 'A' // 9c71 + '37I' // 9c72 #970 + 'aA' // 9c73-9c74 + '4N' // 9c75 #117 + '1I' // 9c76 #34 + '137U' // 9c77 #3582 + '37I' // 9c78 #970 + 'A' // 9c79 + '4N' // 9c7a #117 + '37I' // 9c7b #970 + '83R' // 9c7c #2175 + 'aA' // 9c7d-9c7e + 'a8M' // 9c7f-9c80 #220 + '3Y' // 9c81 #102 + 'a6F' // 9c82-9c83 #161 + 'A' // 9c84 + 'g6F' // 9c85-9c8c #161 + '3H' // 9c8d #85 + 'd6F' // 9c8e-9c92 #161 + 'A' // 9c93 + 'g6F' // 9c94-9c9b #161 + '2C' // 9c9c #54 + 'A' // 9c9d + 'e6F' // 9c9e-9ca3 #161 + '3W' // 9ca4 #100 + 'b6F' // 9ca5-9ca7 #161 + '3G' // 9ca8 #84 + '6F' // 9ca9 #161 + 'A' // 9caa + '6F' // 9cab #161 + 'A' // 9cac + 'a6F' // 9cad-9cae #161 + 'aA' // 9caf-9cb0 + 'f6F' // 9cb1-9cb7 #161 + '2K' // 9cb8 #62 + 'd6F' // 9cb9-9cbd #161 + 'A' // 9cbe + 'a6F' // 9cbf-9cc0 #161 + 'aA' // 9cc1-9cc2 + '6F' // 9cc3 #161 + '3G' // 9cc4 #84 + 'b6F' // 9cc5-9cc7 #161 + 'A' // 9cc8 + 'h6F' // 9cc9-9cd1 #161 + 'A' // 9cd2 + 'g6F' // 9cd3-9cda #161 + 'A' // 9cdb + 'a6F' // 9cdc-9cdd #161 + '3G' // 9cde #84 + '6F' // 9cdf #161 + 'A' // 9ce0 + 'b6F' // 9ce1-9ce3 #161 + 'A' // 9ce4 + '205Z' // 9ce5 #5355 + '1I' // 9ce6 #34 + '83O' // 9ce7 #2172 + 'A' // 9ce8 + '83U' // 9ce9 #2178 + 'A' // 9cea + 'a1I' // 9ceb-9cec #34 + 'A' // 9ced + 'a6F' // 9cee-9cef #161 + '1I' // 9cf0 #34 + 'A' // 9cf1 + '4N' // 9cf2 #117 + '201R' // 9cf3 #5243 + '182U' // 9cf4 #4752 + 'A' // 9cf5 + '64N' // 9cf6 #1677 + '1I' // 9cf7 #34 + 'A' // 9cf8 + '1I' // 9cf9 #34 + 'aA' // 9cfa-9cfb + 'a53U' // 9cfc-9cfd #1398 + 'cA' // 9cfe-9d01 + '83P' // 9d02 #2173 + '4N' // 9d03 #117 + 'aA' // 9d04-9d05 + 'a4N' // 9d06-9d07 #117 + '27V' // 9d08 #723 + '155Y' // 9d09 #4054 + 'A' // 9d0a + '1I' // 9d0b #34 + '21W' // 9d0c #568 + 'A' // 9d0d + '1I' // 9d0e #34 + 'A' // 9d0f + '37K' // 9d10 #972 + '1I' // 9d11 #34 + '27V' // 9d12 #723 + 'aA' // 9d13-9d14 + '4N' // 9d15 #117 + '21W' // 9d16 #568 + '4N' // 9d17 #117 + '1I' // 9d18 #34 + 'aA' // 9d19-9d1a + '48V' // 9d1b #1269 + '1I' // 9d1c #34 + '4N' // 9d1d #117 + '27V' // 9d1e #723 + '4N' // 9d1f #117 + 'A' // 9d20 + '21W' // 9d21 #568 + 'A' // 9d22 + '4N' // 9d23 #117 + 'aA' // 9d24-9d25 + '48V' // 9d26 #1269 + 'A' // 9d27 + '181A' // 9d28 #4706 + 'A' // 9d29 + 'b1I' // 9d2a-9d2c #34 + 'aA' // 9d2d-9d2e + 'a4N' // 9d2f-9d30 #117 + 'A' // 9d31 + 'a1I' // 9d32-9d33 #34 + '4N' // 9d34 #117 + 'aA' // 9d35-9d36 + '53U' // 9d37 #1398 + 'A' // 9d38 + '21W' // 9d39 #568 + '1I' // 9d3a #34 + '187I' // 9d3b #4870 + '1I' // 9d3c #34 + '4N' // 9d3d #117 + '1I' // 9d3e #34 + '131T' // 9d3f #3425 + 'A' // 9d40 + '1I' // 9d41 #34 + '4N' // 9d42 #117 + '1I' // 9d43 #34 + '32L' // 9d44 #843 + 'c1I' // 9d45-9d48 #34 + '21W' // 9d49 #568 + '1I' // 9d4a #34 + 'bA' // 9d4b-9d4d + '21W' // 9d4e #568 + 'A' // 9d4f + '4N' // 9d50 #117 + '48V' // 9d51 #1269 + 'a4N' // 9d52-9d53 #117 + '1I' // 9d54 #34 + 'cA' // 9d55-9d58 + '27V' // 9d59 #723 + 'aA' // 9d5a-9d5b + '83T' // 9d5c #2177 + '66E' // 9d5d #1720 + '4N' // 9d5e #117 + '27V' // 9d5f #723 + 'a83M' // 9d60-9d61 #2170 + 'c1I' // 9d62-9d65 #34 + 'bA' // 9d66-9d68 + '1I' // 9d69 #34 + '4N' // 9d6a #117 + '1I' // 9d6b #34 + '66E' // 9d6c #1720 + 'a21W' // 9d6d-9d6e #568 + '4N' // 9d6f #117 + '27V' // 9d70 #723 + 'A' // 9d71 + '64N' // 9d72 #1677 + '1I' // 9d73 #34 + 'aA' // 9d74-9d75 + '1I' // 9d76 #34 + '15G' // 9d77 #396 + 'aA' // 9d78-9d79 + '49N' // 9d7a #1287 + '1I' // 9d7b #34 + '32L' // 9d7c #843 + 'A' // 9d7d + '37H' // 9d7e #969 + 'cA' // 9d7f-9d82 + '32L' // 9d83 #843 + '49N' // 9d84 #1287 + 'A' // 9d85 + '1I' // 9d86 #34 + '15G' // 9d87 #396 + 'A' // 9d88 + '37H' // 9d89 #969 + '1I' // 9d8a #34 + 'aA' // 9d8b-9d8c + 'a1I' // 9d8d-9d8e #34 + '254Y' // 9d8f #6628 + 'aA' // 9d90-9d91 + '37H' // 9d92 #969 + '15G' // 9d93 #396 + 'A' // 9d94 + '1I' // 9d95 #34 + '15G' // 9d96 #396 + '1I' // 9d97 #34 + '15G' // 9d98 #396 + '1I' // 9d99 #34 + '15G' // 9d9a #396 + 'eA' // 9d9b-9da0 + '15G' // 9da1 #396 + '53V' // 9da2 #1399 + 'A' // 9da3 + '1I' // 9da4 #34 + '53V' // 9da5 #1399 + 'bA' // 9da6-9da8 + '83N' // 9da9 #2171 + '1I' // 9daa #34 + 'a15G' // 9dab-9dac #396 + 'A' // 9dad + '1I' // 9dae #34 + '179V' // 9daf #4675 + 'A' // 9db0 + 'a15G' // 9db1-9db2 #396 + 'A' // 9db3 + '155C' // 9db4 #4032 + '15G' // 9db5 #396 + 'aA' // 9db6-9db7 + '49N' // 9db8 #1287 + 'b15G' // 9db9-9dbb #396 + '37H' // 9dbc #969 + '32L' // 9dbd #843 + 'A' // 9dbe + '15G' // 9dbf #396 + 'b10P' // 9dc0-9dc2 #275 + '1I' // 9dc3 #34 + '43F' // 9dc4 #1123 + 'A' // 9dc5 + '1I' // 9dc6 #34 + '32K' // 9dc7 #842 + '19S' // 9dc8 #512 + '32K' // 9dc9 #842 + '1I' // 9dca #34 + 'cA' // 9dcb-9dce + '1I' // 9dcf #34 + 'bA' // 9dd0-9dd2 + '10P' // 9dd3 #275 + '32J' // 9dd4 #841 + '1I' // 9dd5 #34 + '10P' // 9dd6 #275 + '131R' // 9dd7 #3423 + 'A' // 9dd8 + 'a10P' // 9dd9-9dda #275 + 'bA' // 9ddb-9ddd + '10P' // 9dde #275 + '32K' // 9ddf #842 + '1I' // 9de0 #34 + 'A' // 9de1 + '19S' // 9de2 #512 + '1I' // 9de3 #34 + 'A' // 9de4 + 'a10P' // 9de5-9de6 #275 + '1I' // 9de7 #34 + '19S' // 9de8 #512 + '1I' // 9de9 #34 + 'A' // 9dea + '1I' // 9deb #34 + 'A' // 9dec + 'a1I' // 9ded-9dee #34 + 'a10P' // 9def-9df0 #275 + 'A' // 9df1 + '83J' // 9df2 #2167 + 'a10P' // 9df3-9df4 #275 + 'bA' // 9df5-9df7 + '83G' // 9df8 #2164 + '181S' // 9df9 #4724 + '83I' // 9dfa #2166 + 'A' // 9dfb + '24L' // 9dfc #635 + '10P' // 9dfd #275 + '1I' // 9dfe #34 + '19S' // 9dff #512 + 'aA' // 9e00-9e01 + '10P' // 9e02 #275 + 'cA' // 9e03-9e06 + '10P' // 9e07 #275 + 'aA' // 9e08-9e09 + '32K' // 9e0a #842 + 'A' // 9e0b + '24L' // 9e0c #635 + '1I' // 9e0d #34 + '32J' // 9e0e #841 + 'A' // 9e0f + '1I' // 9e10 #34 + '32K' // 9e11 #842 + '1I' // 9e12 #34 + 'aA' // 9e13-9e14 + '10P' // 9e15 #275 + '1I' // 9e16 #34 + 'A' // 9e17 + '19S' // 9e18 #512 + '1I' // 9e19 #34 + '43F' // 9e1a #1123 + '10P' // 9e1b #275 + '32J' // 9e1c #841 + '10P' // 9e1d #275 + '43F' // 9e1e #1123 + '137V' // 9e1f #3583 + '6F' // 9e20 #161 + '3I' // 9e21 #86 + '6F' // 9e22 #161 + '4C' // 9e23 #106 + 'A' // 9e24 + '2W' // 9e25 #74 + '3H' // 9e26 #85 + 'A' // 9e27 + 'd6F' // 9e28-9e2c #161 + '2Y' // 9e2d #76 + 'a6F' // 9e2e-9e2f #161 + 'c6S' // 9e30-9e33 #174 + 'A' // 9e34 + 'f6S' // 9e35-9e3b #174 + 'A' // 9e3c + '3X' // 9e3d #101 + '6S' // 9e3e #174 + '2L' // 9e3f #63 + 'd6S' // 9e40-9e44 #174 + '2L' // 9e45 #63 + 'h6S' // 9e46-9e4e #174 + '2L' // 9e4f #63 + 'A' // 9e50 + '6S' // 9e51 #174 + 'A' // 9e52 + '6S' // 9e53 #174 + 'A' // 9e54 + 'c6S' // 9e55-9e58 #174 + 'A' // 9e59 + 'b6S' // 9e5a-9e5c #174 + 'A' // 9e5d + 'e6S' // 9e5e-9e63 #174 + '2R' // 9e64 #69 + 'A' // 9e65 + 'h6S' // 9e66-9e6e #174 + 'A' // 9e6f + '2L' // 9e70 #63 + '6S' // 9e71 #174 + 'A' // 9e72 + '6S' // 9e73 #174 + 'A' // 9e74 + '83F' // 9e75 #2163 + 'aA' // 9e76-9e77 + '252Y' // 9e78 #6576 + '162R' // 9e79 #4229 + '1I' // 9e7a #34 + '32J' // 9e7b #841 + '131S' // 9e7c #3424 + '177F' // 9e7d #4607 + '6S' // 9e7e #174 + '191K' // 9e7f #4976 + 'a10P' // 9e80-9e81 #275 + '53Q' // 9e82 #1394 + '10P' // 9e83 #275 + 'a32J' // 9e84-9e85 #841 + '6S' // 9e86 #174 + 'a53Q' // 9e87-9e88 #1394 + 'aA' // 9e89-9e8a + 'a21V' // 9e8b-9e8c #567 + 'A' // 9e8d + 'a2F' // 9e8e-9e8f #57 + '53S' // 9e90 #1396 + '21V' // 9e91 #567 + '131Q' // 9e92 #3422 + '83H' // 9e93 #2165 + 'A' // 9e94 + '37F' // 9e95 #967 + '37G' // 9e96 #968 + '220M' // 9e97 #5732 + '19R' // 9e98 #511 + 'aA' // 9e99-9e9a + '2F' // 9e9b #57 + 'A' // 9e9c + '32I' // 9e9d #840 + '19R' // 9e9e #511 + '159H' // 9e9f #4141 + 'A' // 9ea0 + '19S' // 9ea1 #512 + '24L' // 9ea2 #635 + 'A' // 9ea3 + '21V' // 9ea4 #567 + '41C' // 9ea5 #1068 + '83K' // 9ea6 #2168 + 'A' // 9ea7 + '19R' // 9ea8 #511 + 'a21V' // 9ea9-9eaa #567 + '19S' // 9eab #512 + '19R' // 9eac #511 + '37G' // 9ead #968 + '2F' // 9eae #57 + '37G' // 9eaf #968 + '2F' // 9eb0 #57 + '24L' // 9eb1 #635 + 'A' // 9eb2 + '2F' // 9eb3 #57 + '32I' // 9eb4 #840 + '41C' // 9eb5 #1068 + 'A' // 9eb6 + '19S' // 9eb7 #512 + '49M' // 9eb8 #1286 + '252D' // 9eb9 #6555 + '256C' // 9eba #6658 + '212I' // 9ebb #5520 + '231U' // 9ebc #6026 + '143Y' // 9ebd #3742 + '32I' // 9ebe #840 + '21V' // 9ebf #567 + 'A' // 9ec0 + '24L' // 9ec1 #635 + 'A' // 9ec2 + '224I' // 9ec3 #5832 + '143H' // 9ec4 #3725 + 'A' // 9ec5 + '19R' // 9ec6 #511 + '24L' // 9ec7 #635 + '2F' // 9ec8 #57 + '6S' // 9ec9 #174 + 'A' // 9eca + '2F' // 9ecb #57 + '37G' // 9ecc #968 + '32I' // 9ecd #840 + '194M' // 9ece #5056 + '169Y' // 9ecf #4418 + '21V' // 9ed0 #567 + '224J' // 9ed1 #5833 + '258X' // 9ed2 #6731 + '6S' // 9ed3 #174 + '83E' // 9ed4 #2162 + '49M' // 9ed5 #1286 + '6S' // 9ed6 #174 + 'A' // 9ed7 + '191X' // 9ed8 #4989 + '254O' // 9ed9 #6618 + '19S' // 9eda #512 + '155X' // 9edb #4053 + '32I' // 9edc #840 + '21V' // 9edd #567 + '239M' // 9ede #6226 + 'a21V' // 9edf-9ee0 #567 + 'A' // 9ee1 + '53S' // 9ee2 #1396 + 'A' // 9ee3 + '2F' // 9ee4 #57 + '11Q' // 9ee5 #302 + 'A' // 9ee6 + '11Q' // 9ee7 #302 + '184L' // 9ee8 #4795 + 'a6S' // 9ee9-9eea #174 + 'A' // 9eeb + 'a2F' // 9eec-9eed #57 + '24M' // 9eee #636 + '121G' // 9eef #3152 + '2F' // 9ef0 #57 + '37F' // 9ef1 #967 + '2F' // 9ef2 #57 + '53R' // 9ef3 #1395 + '83D' // 9ef4 #2161 + '2F' // 9ef5 #57 + '11Q' // 9ef6 #302 + '24M' // 9ef7 #636 + '19R' // 9ef8 #511 + '11Q' // 9ef9 #302 + 'A' // 9efa + 'a11Q' // 9efb-9efc #302 + '24M' // 9efd #636 + '37F' // 9efe #967 + '24M' // 9eff #636 + 'aA' // 9f00-9f01 + '19R' // 9f02 #511 + '2F' // 9f03 #57 + 'bA' // 9f04-9f06 + 'a53P' // 9f07-9f08 #1393 + '24M' // 9f09 #636 + 'A' // 9f0a + '6S' // 9f0b #174 + 'A' // 9f0c + '6S' // 9f0d #174 + '169X' // 9f0e #4417 + '2F' // 9f0f #57 + '11Q' // 9f10 #302 + 'a2F' // 9f11-9f12 #57 + '195M' // 9f13 #5082 + '49M' // 9f14 #1286 + '24M' // 9f15 #636 + '2F' // 9f16 #57 + '37F' // 9f17 #967 + 'A' // 9f18 + '11Q' // 9f19 #302 + 'a2F' // 9f1a-9f1b #57 + 'bA' // 9f1c-9f1e + '2F' // 9f1f #57 + '191W' // 9f20 #4988 + '2F' // 9f21 #57 + '11Q' // 9f22 #302 + 'bA' // 9f23-9f25 + '19R' // 9f26 #511 + '24L' // 9f27 #635 + 'A' // 9f28 + '53R' // 9f29 #1395 + 'a2F' // 9f2a-9f2b #57 + '11Q' // 9f2c #302 + 'aA' // 9f2d-9f2e + '11Q' // 9f2f #302 + 'A' // 9f30 + '11Q' // 9f31 #302 + '2F' // 9f32 #57 + 'A' // 9f33 + '24M' // 9f34 #636 + 'aA' // 9f35-9f36 + '11Q' // 9f37 #302 + 'A' // 9f38 + '11Q' // 9f39 #302 + '2F' // 9f3a #57 + '190K' // 9f3b #4950 + '2F' // 9f3c #57 + 'a11Q' // 9f3d-9f3e #302 + '2F' // 9f3f #57 + 'A' // 9f40 + '11Q' // 9f41 #302 + 'A' // 9f42 + '2F' // 9f43 #57 + 'a19R' // 9f44-9f45 #511 + 'a2F' // 9f46-9f47 #57 + 'aA' // 9f48-9f49 + '41C' // 9f4a #1068 + '148J' // 9f4b #3857 + 'a83L' // 9f4c-9f4d #2169 + '53P' // 9f4e #1393 + '27U' // 9f4f #722 + '83A' // 9f50 #2158 + '6S' // 9f51 #174 + '169W' // 9f52 #4416 + '32G' // 9f53 #838 + '27U' // 9f54 #722 + 'a2F' // 9f55-9f56 #57 + '27U' // 9f57 #722 + '2F' // 9f58 #57 + '37E' // 9f59 #966 + '32G' // 9f5a #838 + 'A' // 9f5b + '37E' // 9f5c #966 + 'a2F' // 9f5d-9f5e #57 + '37D' // 9f5f #965 + '27U' // 9f60 #722 + '41C' // 9f61 #1068 + '83B' // 9f62 #2159 + '53O' // 9f63 #1392 + 'aA' // 9f64-9f65 + '53O' // 9f66 #1392 + '82X' // 9f67 #2155 + '2F' // 9f68 #57 + '32G' // 9f69 #838 + '37D' // 9f6a #965 + 'A' // 9f6b + '37D' // 9f6c #965 + 'd2F' // 9f6d-9f71 #57 + '27U' // 9f72 #722 + '2F' // 9f73 #57 + 'A' // 9f74 + '2F' // 9f75 #57 + '27U' // 9f76 #722 + '37D' // 9f77 #965 + 'aA' // 9f78-9f79 + '2F' // 9f7a #57 + 'aA' // 9f7b-9f7c + '2F' // 9f7d #57 + 'A' // 9f7e + '82Z' // 9f7f #2157 + 'a6S' // 9f80-9f81 #174 + 'A' // 9f82 + '50I' // 9f83 #1308 + '3N' // 9f84 #91 + 'g50I' // 9f85-9f8c #1308 + '227I' // 9f8d #5910 + '37E' // 9f8e #966 + '2F' // 9f8f #57 + '155W' // 9f90 #4052 + '32H' // 9f91 #839 + '2F' // 9f92 #57 + 'A' // 9f93 + '32H' // 9f94 #839 + '82W' // 9f95 #2154 + '32H' // 9f96 #839 + '32G' // 9f97 #838 + '83C' // 9f98 #2160 + '121H' // 9f99 #3153 + '3G' // 9f9a #84 + '50I' // 9f9b #1308 + '177E' // 9f9c #4606 + '2F' // 9f9d #57 + '247Y' // 9f9e #6446 + '82Y' // 9f9f #2156 + '32H' // 9fa0 #839 + '2F' // 9fa1 #57 + '32H' // 9fa2 #839 + '2F' // 9fa3 #57 + '37E' // 9fa4 #966 + '32G' // 9fa5 #838 + 'm43E' // 9fa6-9fb3 #1122 + '2F' // 9fb4 #57 + 'fA' // 9fb5-9fbb + 'f2F' // 9fbc-9fc2 #57 + 'A' // 9fc3 + '2F' // 9fc4 #57 + 'A' // 9fc5 + '2F' // 9fc6 #57 + 'd43E' // 9fc7-9fcb #1122 + '2F' // 9fcc #57 + 'bA' // 9fcd-9fcf + '43E' // 9fd0 #1122 + '1tA' // 9fd1-9fff + '44t73I' // a000-a48c #1906 + 'bA' // a48d-a48f + '2b73I' // a490-a4c6 #1906 + 'hA' // a4c7-a4cf + '1u72E' // a4d0-a4ff #1876 + '11m265A' // a500-a62b #6890 + 'sA' // a62c-a63f + '1tE' // a640-a66e #4 + '41X' // a66f #1089 + '1uE' // a670-a69f #4 + '3i71P' // a6a0-a6f7 #1861 + 'gA' // a6f8-a6ff + '7tE' // a700-a7ca #4 + 'dA' // a7cb-a7cf + 'aE' // a7d0-a7d1 #4 + 'A' // a7d2 + 'E' // a7d3 #4 + 'A' // a7d4 + 'dE' // a7d5-a7d9 #4 + 'wA' // a7da-a7f1 + 'mE' // a7f2-a7ff #4 + '1r264X' // a800-a82c #6887 + 'bA' // a82d-a82f + 'b263Q' // a830-a832 #6854 + 'b263R' // a833-a835 #6855 + 'c263S' // a836-a839 #6856 + 'eA' // a83a-a83f + '2c264R' // a840-a877 #6881 + 'gA' // a878-a87f + '2q72U' // a880-a8c5 #1892 + 'gA' // a8c6-a8cd + 'k72U' // a8ce-a8d9 #1892 + 'eA' // a8da-a8df + 'p17L' // a8e0-a8f0 #453 + '27H' // a8f1 #709 + '17L' // a8f2 #453 + '263U' // a8f3 #6858 + 'j17L' // a8f4-a8fe #453 + '36O' // a8ff #950 + '1s72B' // a900-a92d #1873 + '262H' // a92e #6819 + '72B' // a92f #1873 + '1i72T' // a930-a953 #1891 + 'jA' // a954-a95e + '72T' // a95f #1891 + '1eA' // a960-a97f + '2y50U' // a980-a9cd #1320 + 'A' // a9ce + '263J' // a9cf #6847 + 'i50U' // a9d0-a9d9 #1320 + 'cA' // a9da-a9dd + 'a50U' // a9de-a9df #1320 + '1d42D' // a9e0-a9fe #1095 + 'A' // a9ff + '2b42A' // aa00-aa36 #1092 + 'hA' // aa37-aa3f + 'm42A' // aa40-aa4d #1092 + 'aA' // aa4e-aa4f + 'i42A' // aa50-aa59 #1092 + 'aA' // aa5a-aa5b + 'c42A' // aa5c-aa5f #1092 + '1e42D' // aa60-aa7f #1095 + '2n73A' // aa80-aac2 #1898 + 'wA' // aac3-aada + 'd73A' // aadb-aadf #1898 + 'v50Y' // aae0-aaf6 #1324 + 'iA' // aaf7-ab00 + 'e3R' // ab01-ab06 #95 + 'aA' // ab07-ab08 + 'e3R' // ab09-ab0e #95 + 'aA' // ab0f-ab10 + 'e3R' // ab11-ab16 #95 + 'hA' // ab17-ab1f + 'f3R' // ab20-ab26 #95 + 'A' // ab27 + 'f3R' // ab28-ab2e #95 + 'A' // ab2f + '2gE' // ab30-ab6b #4 + 'cA' // ab6c-ab6f + '3a50Q' // ab70-abbf #1316 + '1s50Y' // abc0-abed #1324 + 'aA' // abee-abef + 'i50Y' // abf0-abf9 #1324 + 'eA' // abfa-abff + '14W' // ac00 #386 + '1A' // ac01 #26 + 'a27F' // ac02-ac03 #707 + 'X' // ac04 #23 + 'a27F' // ac05-ac06 #707 + '1W' // ac07 #48 + 'W' // ac08 #22 + 'f27F' // ac09-ac0f #707 + 'Z' // ac10 #25 + 'V' // ac11 #21 + 'U' // ac12 #20 + '1C' // ac13 #28 + 'U' // ac14 #20 + 'Z' // ac15 #25 + 'U' // ac16 #20 + 'a27F' // ac17-ac18 #707 + '1A' // ac19 #26 + '1W' // ac1a #48 + '27F' // ac1b #707 + 'X' // ac1c #23 + '1A' // ac1d #26 + 'a27F' // ac1e-ac1f #707 + '2B' // ac20 #53 + 'b27F' // ac21-ac23 #707 + 'Y' // ac24 #24 + 'g5U' // ac25-ac2c #150 + '3M' // ac2d #90 + '5U' // ac2e #150 + '1P' // ac2f #41 + '5U' // ac30 #150 + '1G' // ac31 #32 + 'e5U' // ac32-ac37 #150 + '2I' // ac38 #60 + 'f5U' // ac39-ac3f #150 + '1W' // ac40 #48 + 'k5U' // ac41-ac4c #150 + '1P' // ac4d #41 + '1g5U' // ac4e-ac6f #150 + 'X' // ac70 #23 + '1C' // ac71 #28 + 'a5U' // ac72-ac73 #150 + 'Z' // ac74 #25 + 'a5U' // ac75-ac76 #150 + '1G' // ac77 #32 + 'W' // ac78 #22 + 'f5U' // ac79-ac7f #150 + 'Z' // ac80 #25 + 'U' // ac81 #20 + '5U' // ac82 #150 + 'Z' // ac83 #25 + 'd5U' // ac84-ac88 #150 + '1E' // ac89 #30 + 'a5U' // ac8a-ac8b #150 + 'X' // ac8c #23 + 'b5U' // ac8d-ac8f #150 + '1C' // ac90 #28 + 'b5U' // ac91-ac93 #150 + '1J' // ac94 #35 + 'f5U' // ac95-ac9b #150 + '1E' // ac9c #30 + 'a5U' // ac9d-ac9e #150 + '1G' // ac9f #32 + '1A' // aca0 #26 + 'f5U' // aca1-aca7 #150 + 'W' // aca8 #22 + '1A' // aca9 #26 + '1D' // acaa #29 + '5U' // acab #150 + 'W' // acac #22 + 'b5U' // acad-acaf #150 + '1A' // acb0 #26 + 'f5U' // acb1-acb7 #150 + '1C' // acb8 #28 + '1G' // acb9 #32 + 'a5U' // acba-acbb #150 + '1D' // acbc #29 + 'X' // acbd #23 + 'b5U' // acbe-acc0 #150 + '1E' // acc1 #30 + 'a5U' // acc2-acc3 #150 + 'Z' // acc4 #25 + 'z5U' // acc5-acdf #150 + '14W' // ace0 #386 + 'Y' // ace1 #24 + 'a6E' // ace2-ace3 #160 + 'Y' // ace4 #24 + 'a6E' // ace5-ace6 #160 + '1C' // ace7 #28 + 'W' // ace8 #22 + 'f6E' // ace9-acef #160 + 'a1G' // acf0-acf1 #32 + '6E' // acf2 #160 + 'V' // acf3 #21 + '6E' // acf4 #160 + 'X' // acf5 #23 + '2B' // acf6 #53 + 'd6E' // acf7-acfb #160 + 'X' // acfc #23 + '1G' // acfd #32 + 'a6E' // acfe-acff #160 + 'X' // ad00 #23 + 'b6E' // ad01-ad03 #160 + '1E' // ad04 #30 + 'f6E' // ad05-ad0b #160 + '1W' // ad0c #48 + 'c6E' // ad0d-ad10 #160 + 'Z' // ad11 #25 + 'i6E' // ad12-ad1b #160 + '1D' // ad1c #29 + 'v6E' // ad1d-ad33 #160 + 'U' // ad34 #20 + 's6E' // ad35-ad48 #160 + '1F' // ad49 #31 + 'e6E' // ad4a-ad4f #160 + 'Z' // ad50 #25 + 'z6E' // ad51-ad6b #160 + 'X' // ad6c #23 + 'Z' // ad6d #25 + 'a6E' // ad6e-ad6f #160 + 'W' // ad70 #22 + 'a6E' // ad71-ad72 #160 + '1G' // ad73 #32 + 'V' // ad74 #21 + '1E' // ad75 #30 + '2I' // ad76 #60 + 'e6E' // ad77-ad7c #160 + '1E' // ad7d #30 + '6E' // ad7e #160 + '1C' // ad7f #28 + '6E' // ad80 #160 + 'V' // ad81 #21 + 'e6E' // ad82-ad87 #160 + '1W' // ad88 #48 + 'b6E' // ad89-ad8b #160 + '1A' // ad8c #26 + 'b6E' // ad8d-ad8f #160 + '2B' // ad90 #53 + 'j6E' // ad91-ad9b #160 + 'g5O' // ad9c-ada3 #144 + '1W' // ada4 #48 + 'z5O' // ada5-adbf #144 + 'W' // adc0 #22 + 'b5O' // adc1-adc3 #144 + '1P' // adc4 #41 + 'b5O' // adc5-adc7 #144 + '3M' // adc8 #90 + 'i5O' // adc9-add2 #144 + '3M' // add3 #90 + 'g5O' // add4-addb #144 + 'W' // addc #22 + 'b5O' // addd-addf #144 + 'U' // ade0 #20 + 'b5O' // ade1-ade3 #144 + '1P' // ade4 #41 + 'r5O' // ade5-adf7 #144 + 'X' // adf8 #23 + 'V' // adf9 #21 + 'a5O' // adfa-adfb #144 + 'Z' // adfc #25 + 'b5O' // adfd-adff #144 + 'Z' // ae00 #25 + '1W' // ae01 #48 + 'e5O' // ae02-ae07 #144 + 'Z' // ae08 #25 + '1A' // ae09 #26 + '5O' // ae0a #144 + '1F' // ae0b #31 + '5O' // ae0c #144 + '1F' // ae0d #31 + 'e5O' // ae0e-ae13 #144 + '3V' // ae14 #99 + 'z5O' // ae15-ae2f #144 + '14W' // ae30 #386 + 'b5O' // ae31-ae33 #144 + 'V' // ae34 #21 + 'b5O' // ae35-ae37 #144 + '1A' // ae38 #26 + 'f5O' // ae39-ae3f #144 + '1A' // ae40 #26 + '1P' // ae41 #41 + '5O' // ae42 #144 + '1F' // ae43 #31 + '5O' // ae44 #144 + '1E' // ae45 #30 + 'c5O' // ae46-ae49 #144 + '1D' // ae4a #29 + '5O' // ae4b #144 + 'Z' // ae4c #25 + 'a1P' // ae4d-ae4e #41 + '5O' // ae4f #144 + '1C' // ae50 #28 + 'b5O' // ae51-ae53 #144 + 'U' // ae54 #20 + '5O' // ae55 #144 + 'e12R' // ae56-ae5b #329 + '1D' // ae5c #29 + '1F' // ae5d #31 + 'b12R' // ae5e-ae60 #329 + '1E' // ae61 #30 + 'b12R' // ae62-ae64 #329 + '1W' // ae65 #48 + 'a12R' // ae66-ae67 #329 + 'Y' // ae68 #24 + 'b12R' // ae69-ae6b #329 + '3V' // ae6c #99 + 'v12R' // ae6d-ae83 #329 + '2I' // ae84 #60 + '2b12R' // ae85-aebb #329 + 'U' // aebc #20 + '1W' // aebd #48 + '1J' // aebe #35 + '12R' // aebf #329 + '3V' // aec0 #99 + 'b12R' // aec1-aec3 #329 + '1W' // aec4 #48 + 'f12R' // aec5-aecb #329 + '2I' // aecc #60 + '1J' // aecd #35 + '12R' // aece #329 + '1F' // aecf #31 + '12R' // aed0 #329 + '1P' // aed1 #41 + 'e12R' // aed2-aed7 #329 + '1A' // aed8 #26 + 'z12R' // aed9-aef3 #329 + '1C' // aef4 #28 + 'm12R' // aef5-af02 #329 + 'd19N' // af03-af07 #507 + '1J' // af08 #35 + '1h19N' // af09-af2b #507 + 'U' // af2c #20 + 'Y' // af2d #24 + 'e19N' // af2e-af33 #507 + '1C' // af34 #28 + 'f19N' // af35-af3b #507 + '1F' // af3c #31 + '1G' // af3d #32 + 'b19N' // af3e-af40 #507 + '1J' // af41 #35 + '1E' // af42 #30 + 'V' // af43 #21 + 'c19N' // af44-af47 #507 + '3M' // af48 #90 + '1J' // af49 #35 + 'r19N' // af4a-af5c #507 + '2B' // af5d #53 + 'e19N' // af5e-af63 #507 + '1F' // af64 #31 + 'z19N' // af65-af7f #507 + '2B' // af80 #53 + '1p19N' // af81-afab #507 + 'k7U' // afac-afb7 #202 + 'V' // afb8 #21 + '1P' // afb9 #41 + 'a7U' // afba-afbb #202 + '1C' // afbc #28 + 'b7U' // afbd-afbf #202 + 'U' // afc0 #20 + 'e7U' // afc1-afc6 #202 + '3V' // afc7 #99 + 'U' // afc8 #20 + '3V' // afc9 #99 + 'b7U' // afca-afcc #202 + '2B' // afcd #53 + 'e7U' // afce-afd3 #202 + '1F' // afd4 #31 + 'r7U' // afd5-afe7 #202 + '1W' // afe8 #48 + 'f7U' // afe9-afef #202 + '2B' // aff0 #53 + 'z7U' // aff1-b00b #202 + '1G' // b00c #32 + 'b7U' // b00d-b00f #202 + '1J' // b010 #35 + 'b7U' // b011-b013 #202 + '3M' // b014 #90 + 'f7U' // b015-b01b #202 + '3V' // b01c #99 + 'j7U' // b01d-b027 #202 + '3V' // b028 #99 + 'z7U' // b029-b043 #202 + '1D' // b044 #29 + 'b7U' // b045-b047 #202 + '1C' // b048 #28 + '7U' // b049 #202 + '1G' // b04a #32 + '7U' // b04b #202 + 'U' // b04c #20 + 'e7U' // b04d-b052 #202 + '1J' // b053 #35 + '1D' // b054 #29 + 'a7U' // b055-b056 #202 + '1G' // b057 #32 + 'd7U' // b058-b05c #202 + 'V' // b05d #21 + '7U' // b05e #202 + '1b5T' // b05f-b07b #149 + 'V' // b07c #21 + '2I' // b07d #60 + 'a5T' // b07e-b07f #149 + '1F' // b080 #31 + 'b5T' // b081-b083 #149 + '1F' // b084 #31 + 'f5T' // b085-b08b #149 + 'U' // b08c #20 + 'j5T' // b08d-b097 #149 + 'X' // b098 #23 + 'U' // b099 #20 + '1C' // b09a #28 + '5T' // b09b #149 + '1A' // b09c #26 + 'b5T' // b09d-b09f #149 + '1A' // b0a0 #26 + '2B' // b0a1 #53 + 'e5T' // b0a2-b0a7 #149 + 'Z' // b0a8 #25 + 'U' // b0a9 #20 + '5T' // b0aa #149 + '1E' // b0ab #30 + 'U' // b0ac #20 + '1C' // b0ad #28 + 'U' // b0ae #20 + '1E' // b0af #30 + '5T' // b0b0 #149 + '2I' // b0b1 #60 + '5T' // b0b2 #149 + '1E' // b0b3 #30 + 'X' // b0b4 #23 + '2B' // b0b5 #53 + 'a5T' // b0b6-b0b7 #149 + 'U' // b0b8 #20 + 'b5T' // b0b9-b0bb #149 + '1C' // b0bc #28 + 'f5T' // b0bd-b0c3 #149 + '1G' // b0c4 #32 + '1F' // b0c5 #31 + 'a5T' // b0c6-b0c7 #149 + 'a1D' // b0c8-b0c9 #29 + 'e5T' // b0ca-b0cf #149 + 'Y' // b0d0 #24 + 'n5T' // b0d1-b0df #149 + '1P' // b0e0 #41 + 'c5T' // b0e1-b0e4 #149 + 'Y' // b0e5 #24 + '1g5T' // b0e6-b107 #149 + '1A' // b108 #26 + '1J' // b109 #35 + 'a5T' // b10a-b10b #149 + '1F' // b10c #31 + 'b5T' // b10d-b10f #149 + 'V' // b110 #21 + 'a5T' // b111-b112 #149 + '1C' // b113 #28 + 'c5T' // b114-b117 #149 + 'V' // b118 #21 + 'a5T' // b119-b11a #149 + '1J' // b11b #35 + 'c5T' // b11c-b11f #149 + 'b8L' // b120-b122 #219 + 'U' // b123 #20 + 'Z' // b124 #25 + '1C' // b125 #28 + 'a8L' // b126-b127 #219 + '1J' // b128 #35 + 'b8L' // b129-b12b #219 + '1E' // b12c #30 + 'f8L' // b12d-b133 #219 + 'a3M' // b134-b135 #90 + '8L' // b136 #219 + 'W' // b137 #22 + '8L' // b138 #219 + '1W' // b139 #48 + 'e8L' // b13a-b13f #219 + 'W' // b140 #22 + '1C' // b141 #28 + 'a8L' // b142-b143 #219 + 'Z' // b144 #25 + 'j8L' // b145-b14f #219 + 'Y' // b150 #24 + 'b8L' // b151-b153 #219 + '1J' // b154 #35 + 'Y' // b155 #24 + 'a8L' // b156-b157 #219 + '3M' // b158 #90 + '1d8L' // b159-b177 #219 + 'Z' // b178 #25 + 'U' // b179 #20 + 'a8L' // b17a-b17b #219 + 'V' // b17c #21 + 'b8L' // b17d-b17f #219 + 'V' // b180 #21 + 'f8L' // b181-b187 #219 + '1G' // b188 #32 + 'c8L' // b189-b18c #219 + 'V' // b18d #21 + 'c8L' // b18e-b191 #219 + 'V' // b192 #21 + 'Y' // b193 #24 + '1E' // b194 #30 + 'r8L' // b195-b1a7 #219 + '1E' // b1a8 #30 + '1h8L' // b1a9-b1cb #219 + '1G' // b1cc #32 + 'h8L' // b1cd-b1d5 #219 + 'q19M' // b1d6-b1e7 #506 + '1F' // b1e8 #31 + 's19M' // b1e9-b1fc #506 + '2B' // b1fd #53 + 'e19M' // b1fe-b203 #506 + '1A' // b204 #26 + '1W' // b205 #48 + 'a19M' // b206-b207 #506 + 'W' // b208 #22 + 'b19M' // b209-b20b #506 + '1C' // b20c #28 + 'f19M' // b20d-b213 #506 + '1G' // b214 #32 + '2I' // b215 #60 + 'i19M' // b216-b21f #506 + '1E' // b220 #30 + '2b19M' // b221-b257 #506 + '1E' // b258 #30 + 'z19M' // b259-b273 #506 + 'Z' // b274 #25 + 'f19M' // b275-b27b #506 + 'g4V' // b27c-b283 #125 + '1E' // b284 #30 + 'j4V' // b285-b28f #125 + 'W' // b290 #22 + '1J' // b291 #35 + 'a4V' // b292-b293 #125 + 'X' // b294 #23 + 'b4V' // b295-b297 #125 + '1A' // b298 #26 + '1P' // b299 #41 + 'e4V' // b29a-b29f #125 + '2I' // b2a0 #60 + 'c4V' // b2a1-b2a4 #125 + 'Z' // b2a5 #25 + '1D' // b2a6 #29 + 'b4V' // b2a7-b2a9 #125 + '3M' // b2aa #90 + '4V' // b2ab #125 + '1F' // b2ac #31 + 'z4V' // b2ad-b2c7 #125 + 'X' // b2c8 #23 + 'Y' // b2c9 #24 + 'a4V' // b2ca-b2cb #125 + 'V' // b2cc #21 + 'b4V' // b2cd-b2cf #125 + '1D' // b2d0 #29 + 'f4V' // b2d1-b2d7 #125 + '1A' // b2d8 #26 + '1C' // b2d9 #28 + '4V' // b2da #125 + '1G' // b2db #32 + '4V' // b2dc #125 + 'Y' // b2dd #24 + 'e4V' // b2de-b2e3 #125 + '14W' // b2e4 #386 + 'U' // b2e5 #20 + '1J' // b2e6 #35 + '4V' // b2e7 #125 + 'Z' // b2e8 #25 + 'a4V' // b2e9-b2ea #125 + 'a1A' // b2eb-b2ec #26 + '1C' // b2ed #28 + '1G' // b2ee #32 + 'd4V' // b2ef-b2f3 #125 + '1A' // b2f4 #26 + 'W' // b2f5 #22 + '4V' // b2f6 #125 + 'U' // b2f7 #20 + '4V' // b2f8 #125 + 'Z' // b2f9 #25 + 'a4V' // b2fa-b2fb #125 + '2B' // b2fc #53 + 'a4V' // b2fd-b2fe #125 + '1E' // b2ff #30 + 'X' // b300 #23 + '1J' // b301 #35 + 'a4V' // b302-b303 #125 + '1G' // b304 #32 + 'b4V' // b305-b307 #125 + '3V' // b308 #99 + 'f4V' // b309-b30f #125 + '2I' // b310 #60 + '3V' // b311 #99 + '4V' // b312 #125 + '1A' // b313 #26 + '3V' // b314 #99 + '1E' // b315 #30 + 'e4V' // b316-b31b #125 + '3V' // b31c #99 + '1j4V' // b31d-b341 #125 + 'q7F' // b342-b353 #187 + 'Z' // b354 #25 + 'V' // b355 #21 + 'a7F' // b356-b357 #187 + '1A' // b358 #26 + 'b7F' // b359-b35b #187 + '1D' // b35c #29 + 'a7F' // b35d-b35e #187 + '3M' // b35f #90 + 'c7F' // b360-b363 #187 + '1D' // b364 #29 + '1P' // b365 #41 + '7F' // b366 #187 + '1C' // b367 #28 + '7F' // b368 #187 + '1C' // b369 #28 + 'c7F' // b36a-b36d #187 + '1F' // b36e #31 + '7F' // b36f #187 + 'Z' // b370 #25 + '1E' // b371 #30 + 'a7F' // b372-b373 #187 + '1C' // b374 #28 + 'b7F' // b375-b377 #187 + 'V' // b378 #21 + 'f7F' // b379-b37f #187 + '2B' // b380 #53 + 'a7F' // b381-b382 #187 + '1W' // b383 #48 + '7F' // b384 #187 + '1J' // b385 #35 + 'e7F' // b386-b38b #187 + '1W' // b38c #48 + '2b7F' // b38d-b3c3 #187 + 'X' // b3c4 #23 + 'W' // b3c5 #22 + 'a7F' // b3c6-b3c7 #187 + 'Y' // b3c8 #24 + 'a7F' // b3c9-b3ca #187 + '1C' // b3cb #28 + 'W' // b3cc #22 + 'f7F' // b3cd-b3d3 #187 + 'a1E' // b3d4-b3d5 #30 + '7F' // b3d6 #187 + '2I' // b3d7 #60 + '7F' // b3d8 #187 + 'X' // b3d9 #23 + '1c7F' // b3da-b3f7 #187 + 'c12Q' // b3f8-b3fb #328 + 'Y' // b3fc #24 + 'r12Q' // b3fd-b40f #328 + 'Y' // b410 #24 + 'f12Q' // b411-b417 #328 + 'Z' // b418 #25 + 'b12Q' // b419-b41b #328 + 'Z' // b41c #25 + 'b12Q' // b41d-b41f #328 + 'W' // b420 #22 + 'f12Q' // b421-b427 #328 + '1D' // b428 #29 + 'W' // b429 #22 + 'a12Q' // b42a-b42b #328 + '1W' // b42c #48 + '1h12Q' // b42d-b44f #328 + '1A' // b450 #26 + '1F' // b451 #31 + 'a12Q' // b452-b453 #328 + '1D' // b454 #29 + 'b12Q' // b455-b457 #328 + 'Y' // b458 #24 + 'f12Q' // b459-b45f #328 + '1E' // b460 #30 + '1W' // b461 #48 + 'b12Q' // b462-b464 #328 + '1D' // b465 #29 + 'e12Q' // b466-b46b #328 + '1P' // b46c #41 + 'r12Q' // b46d-b47f #328 + '1P' // b480 #41 + '1h12Q' // b481-b4a3 #328 + 'V' // b4a4 #21 + 'q6D' // b4a5-b4b6 #159 + '1D' // b4b7 #29 + 'g6D' // b4b8-b4bf #159 + 'U' // b4c0 #20 + 'f6D' // b4c1-b4c7 #159 + '1P' // b4c8 #41 + 'r6D' // b4c9-b4db #159 + 'X' // b4dc #23 + 'Y' // b4dd #24 + 'a6D' // b4de-b4df #159 + '1A' // b4e0 #26 + 'a6D' // b4e1-b4e2 #159 + '1D' // b4e3 #29 + 'X' // b4e4 #23 + 'f6D' // b4e5-b4eb #159 + '1C' // b4ec #28 + '1G' // b4ed #32 + '6D' // b4ee #159 + 'V' // b4ef #21 + '6D' // b4f0 #159 + 'Z' // b4f1 #25 + '1g6D' // b4f2-b513 #159 + 'Z' // b514 #25 + '1F' // b515 #31 + 'a6D' // b516-b517 #159 + '1G' // b518 #32 + 'a6D' // b519-b51a #159 + '2B' // b51b #53 + '1D' // b51c #29 + 'f6D' // b51d-b523 #159 + '3V' // b524 #99 + '1J' // b525 #35 + '6D' // b526 #159 + '1E' // b527 #30 + '6D' // b528 #159 + 'W' // b529 #22 + '2I' // b52a #60 + 'd6D' // b52b-b52f #159 + 'W' // b530 #22 + 'U' // b531 #20 + 'a6D' // b532-b533 #159 + '1E' // b534 #30 + 'b6D' // b535-b537 #159 + 'U' // b538 #20 + 'f6D' // b539-b53f #159 + '1F' // b540 #31 + 'c6D' // b541-b544 #159 + 'U' // b545 #20 + 'e6D' // b546-b54b #159 + '1A' // b54c #26 + 'b6D' // b54d-b54f #159 + 'U' // b550 #20 + 'j6D' // b551-b55b #159 + '1W' // b55c #48 + 'a6D' // b55d-b55e #159 + '24F' // b55f #629 + '3V' // b560 #99 + '1F' // b561 #31 + '1g24F' // b562-b583 #629 + '2B' // b584 #53 + 'z24F' // b585-b59f #629 + 'Y' // b5a0 #24 + '1D' // b5a1 #29 + 'a24F' // b5a2-b5a3 #629 + 'Y' // b5a4 #24 + 'b24F' // b5a5-b5a7 #629 + 'U' // b5a8 #20 + 'i24F' // b5a9-b5b2 #629 + '2B' // b5b3 #53 + '1J' // b5b4 #35 + 'e24F' // b5b5-b5ba #629 + 'Y' // b5bb #24 + '1G' // b5bc #32 + '2s24F' // b5bd-b604 #629 + 'j31S' // b605-b60f #824 + '1A' // b610 #26 + '1C' // b611 #28 + 'e31S' // b612-b617 #824 + '2I' // b618 #60 + 'k31S' // b619-b624 #824 + '1G' // b625 #32 + '4m31S' // b626-b69b #824 + 'a1G' // b69c-b69d #32 + 'e31S' // b69e-b6a3 #824 + '3M' // b6a4 #90 + 'a31S' // b6a5-b6a6 #824 + 'c19L' // b6a7-b6aa #505 + '1F' // b6ab #31 + 'd19L' // b6ac-b6b0 #505 + '1F' // b6b1 #31 + '2i19L' // b6b2-b6ef #505 + 'U' // b6f0 #20 + '2b19L' // b6f1-b727 #505 + 'U' // b728 #20 + '1J' // b729 #35 + 'a19L' // b72a-b72b #505 + '1F' // b72c #31 + 'a19L' // b72d-b72e #505 + '1J' // b72f #35 + '1F' // b730 #31 + 'f19L' // b731-b737 #505 + '1J' // b738 #35 + 'a19L' // b739-b73a #505 + 'U' // b73b #20 + 'g19L' // b73c-b743 #505 + '1F' // b744 #31 + 'g19L' // b745-b74c #505 + 'r7T' // b74d-b75f #201 + '1C' // b760 #28 + 'b7T' // b761-b763 #201 + '3V' // b764 #99 + 'o7T' // b765-b774 #201 + '2I' // b775 #60 + 'e7T' // b776-b77b #201 + 'X' // b77c #23 + 'W' // b77d #22 + 'a7T' // b77e-b77f #201 + '1A' // b780 #26 + 'b7T' // b781-b783 #201 + '1D' // b784 #29 + 'f7T' // b785-b78b #201 + '1A' // b78c #26 + 'V' // b78d #21 + '7T' // b78e #201 + '1W' // b78f #48 + '1D' // b790 #29 + '1A' // b791 #26 + 'd7T' // b792-b796 #201 + '2I' // b797 #60 + 'Z' // b798 #25 + 'V' // b799 #21 + 'a7T' // b79a-b79b #201 + 'W' // b79c #22 + 'j7T' // b79d-b7a7 #201 + 'W' // b7a8 #22 + 'V' // b7a9 #21 + '7T' // b7aa #201 + '1D' // b7ab #29 + '1G' // b7ac #32 + 'U' // b7ad #20 + 'e7T' // b7ae-b7b3 #201 + '1P' // b7b4 #41 + 'Y' // b7b5 #24 + 'r7T' // b7b6-b7c8 #201 + 'W' // b7c9 #22 + '1g7T' // b7ca-b7eb #201 + 'X' // b7ec #23 + 'U' // b7ed #20 + 'a7T' // b7ee-b7ef #201 + 'W' // b7f0 #22 + 'b7T' // b7f1-b7f3 #201 + '1D' // b7f4 #29 + 'f7T' // b7f5-b7fb #201 + 'W' // b7fc #22 + 'V' // b7fd #21 + '7T' // b7fe #201 + '1J' // b7ff #35 + '1F' // b800 #31 + '1G' // b801 #32 + 'd7T' // b802-b806 #201 + 'V' // b807 #21 + 'Z' // b808 #25 + 'U' // b809 #20 + 'a7T' // b80a-b80b #201 + 'V' // b80c #21 + 'b10I' // b80d-b80f #268 + '1E' // b810 #30 + 'f10I' // b811-b817 #268 + '1E' // b818 #30 + '1P' // b819 #41 + '10I' // b81a #268 + '1C' // b81b #28 + 'g10I' // b81c-b823 #268 + 'aZ' // b824-b825 #25 + 'a10I' // b826-b827 #268 + 'W' // b828 #22 + 'b10I' // b829-b82b #268 + '1D' // b82c #29 + 'f10I' // b82d-b833 #268 + '1C' // b834 #28 + '1D' // b835 #29 + '10I' // b836 #268 + '1J' // b837 #35 + 'Y' // b838 #24 + 'V' // b839 #21 + 'e10I' // b83a-b83f #268 + 'Y' // b840 #24 + 'z10I' // b841-b85b #268 + '14W' // b85c #386 + 'X' // b85d #23 + 'a10I' // b85e-b85f #268 + 'W' // b860 #22 + 'b10I' // b861-b863 #268 + 'U' // b864 #20 + 'f10I' // b865-b86b #268 + '1G' // b86c #32 + 'U' // b86d #20 + '10I' // b86e #268 + 'Y' // b86f #24 + '10I' // b870 #268 + 'U' // b871 #20 + '2i10I' // b872-b8af #268 + '1C' // b8b0 #28 + 'm10I' // b8b1-b8be #268 + 'l10H' // b8bf-b8cb #267 + 'Z' // b8cc #25 + 's10H' // b8cd-b8e0 #267 + '1C' // b8e1 #28 + 'e10H' // b8e2-b8e7 #267 + '1A' // b8e8 #26 + '1D' // b8e9 #29 + 'a10H' // b8ea-b8eb #267 + '1F' // b8ec #31 + 'b10H' // b8ed-b8ef #267 + '1F' // b8f0 #31 + 'f10H' // b8f1-b8f7 #267 + '1D' // b8f8 #29 + 'Y' // b8f9 #24 + '10H' // b8fa #267 + '1W' // b8fb #48 + '10H' // b8fc #267 + '2B' // b8fd #53 + 'e10H' // b8fe-b903 #267 + '1F' // b904 #31 + 'r10H' // b905-b917 #267 + '1P' // b918 #41 + 'f10H' // b919-b91f #267 + '3V' // b920 #99 + 'z10H' // b921-b93b #267 + '2B' // b93c #53 + 'z10H' // b93d-b957 #267 + '1A' // b958 #26 + '1G' // b959 #32 + 'a10H' // b95a-b95b #267 + '1G' // b95c #32 + 'b10H' // b95d-b95f #267 + 'Y' // b960 #24 + 'f10H' // b961-b967 #267 + '1F' // b968 #31 + 'c10H' // b969-b96c #267 + '1F' // b96d #31 + 'e5N' // b96e-b973 #143 + '1A' // b974 #26 + '1J' // b975 #35 + 'a5N' // b976-b977 #143 + 'Z' // b978 #25 + 'b5N' // b979-b97b #143 + 'X' // b97c #23 + 'f5N' // b97d-b983 #143 + '1A' // b984 #26 + '1G' // b985 #32 + '5N' // b986 #143 + '1F' // b987 #31 + '5N' // b988 #143 + '1C' // b989 #28 + 'c5N' // b98a-b98d #143 + '1E' // b98e #30 + '1b5N' // b98f-b9ab #143 + '14W' // b9ac #386 + 'W' // b9ad #22 + 'a5N' // b9ae-b9af #143 + '1A' // b9b0 #26 + 'b5N' // b9b1-b9b3 #143 + 'V' // b9b4 #21 + 'f5N' // b9b5-b9bb #143 + 'a1A' // b9bc-b9bd #26 + '5N' // b9be #143 + 'U' // b9bf #20 + '5N' // b9c0 #143 + 'W' // b9c1 #22 + 'e5N' // b9c2-b9c7 #143 + 'X' // b9c8 #23 + 'W' // b9c9 #22 + 'a5N' // b9ca-b9cb #143 + 'X' // b9cc #23 + '5N' // b9cd #143 + '1A' // b9ce #26 + '3M' // b9cf #90 + 'Z' // b9d0 #25 + '1C' // b9d1 #28 + 'e5N' // b9d2-b9d7 #143 + '1D' // b9d8 #29 + '1F' // b9d9 #31 + '5N' // b9da #143 + 'V' // b9db #21 + '5N' // b9dc #143 + 'aW' // b9dd-b9de #22 + 'a5N' // b9df-b9e0 #143 + '1C' // b9e1 #28 + '5N' // b9e2 #143 + '3V' // b9e3 #99 + 'Z' // b9e4 #25 + 'Y' // b9e5 #24 + 'a5N' // b9e6-b9e7 #143 + 'W' // b9e8 #22 + 'j5N' // b9e9-b9f3 #143 + '2B' // b9f4 #53 + 'U' // b9f5 #20 + '5N' // b9f6 #143 + '1W' // b9f7 #48 + '5N' // b9f8 #143 + '1D' // b9f9 #29 + '1C' // b9fa #28 + '1z5N' // b9fb-ba2f #143 + 'g6R' // ba30-ba37 #173 + 'aW' // ba38-ba39 #22 + 'a6R' // ba3a-ba3b #173 + 'V' // ba3c #21 + 'b6R' // ba3d-ba3f #173 + 'U' // ba40 #20 + 'f6R' // ba41-ba47 #173 + '1G' // ba48 #32 + 'a6R' // ba49-ba4a #173 + 'U' // ba4b #20 + '6R' // ba4c #173 + '1C' // ba4d #28 + 'e6R' // ba4e-ba53 #173 + 'X' // ba54 #23 + '1E' // ba55 #30 + 'a6R' // ba56-ba57 #173 + 'U' // ba58 #20 + 'b6R' // ba59-ba5b #173 + '1D' // ba5c #29 + 'f6R' // ba5d-ba63 #173 + '1D' // ba64 #29 + 'a6R' // ba65-ba66 #173 + '1J' // ba67 #35 + 'g6R' // ba68-ba6f #173 + '1A' // ba70 #26 + 'b6R' // ba71-ba73 #173 + 'X' // ba74 #23 + 'b6R' // ba75-ba77 #173 + '1C' // ba78 #28 + 'k6R' // ba79-ba84 #173 + 'X' // ba85 #23 + '6R' // ba86 #173 + 'U' // ba87 #20 + '1e6R' // ba88-baa7 #173 + 'X' // baa8 #23 + 'Z' // baa9 #25 + '6R' // baaa #173 + '1P' // baab #41 + 'U' // baac #20 + 'b6R' // baad-baaf #173 + 'V' // bab0 #21 + 'f6R' // bab1-bab7 #173 + 'Y' // bab8 #24 + '1P' // bab9 #41 + '6R' // baba #173 + 'W' // babb #22 + '6R' // babc #173 + '1D' // babd #29 + '1m6R' // babe-bae5 #173 + 'u14V' // bae6-bafb #385 + '1W' // bafc #48 + 'z14V' // bafd-bb17 #385 + '1D' // bb18 #29 + 'z14V' // bb19-bb33 #385 + 'X' // bb34 #23 + '1C' // bb35 #28 + '1F' // bb36 #31 + '14V' // bb37 #385 + 'X' // bb38 #23 + 'a14V' // bb39-bb3a #385 + '1D' // bb3b #29 + 'Z' // bb3c #25 + 'f14V' // bb3d-bb43 #385 + '2B' // bb44 #53 + 'a14V' // bb45-bb46 #385 + '1J' // bb47 #35 + '14V' // bb48 #385 + '1F' // bb49 #31 + 'e14V' // bb4a-bb4f #385 + 'Y' // bb50 #24 + 'b14V' // bb51-bb53 #385 + '1C' // bb54 #28 + 'b14V' // bb55-bb57 #385 + '1G' // bb58 #32 + 'i14V' // bb59-bb62 #385 + '3M' // bb63 #90 + '1q14V' // bb64-bb8f #385 + 's5M' // bb90-bba3 #142 + 'V' // bba4 #21 + 'f5M' // bba5-bbab #142 + '1E' // bbac #30 + 'r5M' // bbad-bbbf #142 + 'U' // bbc0 #20 + '2b5M' // bbc1-bbf7 #142 + 'X' // bbf8 #23 + '1C' // bbf9 #28 + 'a5M' // bbfa-bbfb #142 + '1A' // bbfc #26 + 'a5M' // bbfd-bbfe #142 + '1D' // bbff #29 + 'W' // bc00 #22 + 'f5M' // bc01-bc07 #142 + '2B' // bc08 #53 + 'a5M' // bc09-bc0a #142 + '1E' // bc0b #30 + '1G' // bc0c #32 + '1D' // bc0d #29 + '5M' // bc0e #142 + '1A' // bc0f #26 + '5M' // bc10 #142 + '1C' // bc11 #28 + 'a5M' // bc12-bc13 #142 + 'X' // bc14 #23 + '1A' // bc15 #26 + 'U' // bc16 #20 + '5M' // bc17 #142 + 'Z' // bc18 #25 + 'a5M' // bc19-bc1a #142 + '1A' // bc1b #26 + 'Z' // bc1c #25 + 'Y' // bc1d #24 + '5M' // bc1e #142 + '1J' // bc1f #35 + 'c5M' // bc20-bc23 #142 + 'aY' // bc24-bc25 #24 + '5M' // bc26 #142 + '2B' // bc27 #53 + '5M' // bc28 #142 + 'X' // bc29 #23 + 'b5M' // bc2a-bc2c #142 + '1F' // bc2d #31 + 'a5M' // bc2e-bc2f #142 + 'Z' // bc30 #25 + '1A' // bc31 #26 + 'a5M' // bc32-bc33 #142 + 'U' // bc34 #20 + 'b5M' // bc35-bc37 #142 + '1G' // bc38 #32 + 'f5M' // bc39-bc3f #142 + '1E' // bc40 #30 + 'a5M' // bc41-bc42 #142 + '1F' // bc43 #31 + '5M' // bc44 #142 + '1D' // bc45 #29 + 'b5M' // bc46-bc48 #142 + '2B' // bc49 #53 + 'c5M' // bc4a-bc4d #142 + '2a8K' // bc4e-bc83 #218 + 'Z' // bc84 #25 + '1G' // bc85 #32 + 'a8K' // bc86-bc87 #218 + 'X' // bc88 #23 + 'b8K' // bc89-bc8b #218 + 'W' // bc8c #22 + 'f8K' // bc8d-bc93 #218 + 'V' // bc94 #21 + '1A' // bc95 #26 + '8K' // bc96 #218 + '1D' // bc97 #29 + '8K' // bc98 #218 + '1W' // bc99 #48 + '1G' // bc9a #32 + 'd8K' // bc9b-bc9f #218 + 'W' // bca0 #22 + '1E' // bca1 #30 + 'a8K' // bca2-bca3 #218 + 'W' // bca4 #22 + 'b8K' // bca5-bca7 #218 + 'Y' // bca8 #24 + 'i8K' // bca9-bcb2 #218 + '1C' // bcb3 #28 + 'g8K' // bcb4-bcbb #218 + '1D' // bcbc #29 + 'Y' // bcbd #24 + 'a8K' // bcbe-bcbf #218 + '1A' // bcc0 #26 + 'b8K' // bcc1-bcc3 #218 + '1A' // bcc4 #26 + 'g8K' // bcc5-bccc #218 + '1E' // bccd #30 + 'b8K' // bcce-bcd0 #218 + 'W' // bcd1 #22 + 'b8K' // bcd2-bcd4 #218 + '2I' // bcd5 #60 + '1c8K' // bcd6-bcf3 #218 + 'X' // bcf4 #23 + 'Z' // bcf5 #25 + '1F' // bcf6 #31 + '8K' // bcf7 #218 + 'Z' // bcf8 #25 + 'b8K' // bcf9-bcfb #218 + 'W' // bcfc #22 + 'e8K' // bcfd-bd02 #218 + '12P' // bd03 #327 + 'Y' // bd04 #24 + '1C' // bd05 #28 + '12P' // bd06 #327 + 'U' // bd07 #20 + '12P' // bd08 #327 + 'W' // bd09 #22 + 'e12P' // bd0a-bd0f #327 + 'Y' // bd10 #24 + 'q12P' // bd11-bd22 #327 + '3M' // bd23 #90 + 'Y' // bd24 #24 + '1h12P' // bd25-bd47 #327 + '3V' // bd48 #99 + 'o12P' // bd49-bd58 #327 + '2I' // bd59 #60 + '1k12P' // bd5a-bd7f #327 + 'X' // bd80 #23 + '1A' // bd81 #26 + 'a12P' // bd82-bd83 #327 + 'Z' // bd84 #25 + 'b12P' // bd85-bd87 #327 + '1A' // bd88 #26 + '1F' // bd89 #31 + 'e12P' // bd8a-bd8f #327 + '1J' // bd90 #35 + 'a12P' // bd91-bd92 #327 + '1E' // bd93 #30 + '12P' // bd94 #327 + '1C' // bd95 #28 + 'b12P' // bd96-bd98 #327 + 'U' // bd99 #20 + 'u12P' // bd9a-bdaf #327 + '1i21Q' // bdb0-bdd3 #562 + '1C' // bdd4 #28 + 'z21Q' // bdd5-bdef #562 + 'W' // bdf0 #22 + 'z21Q' // bdf1-be0b #562 + 'Z' // be0c #25 + 'b21Q' // be0d-be0f #562 + '1C' // be10 #28 + 'b21Q' // be11-be13 #562 + 'Z' // be14 #25 + '1t21Q' // be15-be43 #562 + 'X' // be44 #23 + '1D' // be45 #29 + 'a21Q' // be46-be47 #562 + 'Y' // be48 #24 + 'b21Q' // be49-be4b #562 + 'V' // be4c #21 + 'f21Q' // be4d-be53 #562 + '1E' // be54 #30 + '2B' // be55 #53 + '11F' // be56 #291 + '1F' // be57 #31 + '11F' // be58 #291 + 'U' // be59 #20 + '1E' // be5a #30 + 'Y' // be5b #24 + 'c11F' // be5c-be5f #291 + 'W' // be60 #22 + '1F' // be61 #31 + 'a11F' // be62-be63 #291 + '2B' // be64 #53 + 'b11F' // be65-be67 #291 + 'Y' // be68 #24 + 'k11F' // be69-be74 #291 + '1D' // be75 #29 + 'e11F' // be76-be7b #291 + '1D' // be7c #29 + '3V' // be7d #99 + 'a11F' // be7e-be7f #291 + '2I' // be80 #60 + 'm11F' // be81-be8e #291 + '1P' // be8f #41 + '11F' // be90 #291 + '3M' // be91 #90 + 'u11F' // be92-bea7 #291 + '2I' // bea8 #60 + '1l11F' // bea9-becf #291 + '1C' // bed0 #28 + '2I' // bed1 #60 + 'a11F' // bed2-bed3 #291 + '1F' // bed4 #31 + 'a11F' // bed5-bed6 #291 + '1P' // bed7 #41 + '1W' // bed8 #48 + 'j11F' // bed9-bee3 #291 + 'a2I' // bee4-bee5 #60 + '1f11F' // bee6-bf06 #291 + '36M' // bf07 #948 + '1E' // bf08 #30 + '2b36M' // bf09-bf3f #948 + '1G' // bf40 #32 + 'n36M' // bf41-bf4f #948 + '1E' // bf50 #30 + '1C' // bf51 #28 + 'b36M' // bf52-bf54 #948 + '1E' // bf55 #30 + '3b36M' // bf56-bfa6 #948 + 'h27E' // bfa7-bfaf #706 + '2B' // bfb0 #53 + 's27E' // bfb1-bfc4 #706 + '2B' // bfc5 #53 + 'e27E' // bfc6-bfcb #706 + '1D' // bfcc #29 + '1W' // bfcd #48 + 'a27E' // bfce-bfcf #706 + 'U' // bfd0 #20 + 'b27E' // bfd1-bfd3 #706 + '1J' // bfd4 #35 + 'f27E' // bfd5-bfdb #706 + '1E' // bfdc #30 + '4c27E' // bfdd-c048 #706 + 'n6Q' // c049-c057 #172 + 'U' // c058 #20 + 'b6Q' // c059-c05b #172 + 'Y' // c05c #24 + 'b6Q' // c05d-c05f #172 + '2I' // c060 #60 + 'f6Q' // c061-c067 #172 + '1G' // c068 #32 + '1l6Q' // c069-c08f #172 + '1F' // c090 #31 + 'z6Q' // c091-c0ab #172 + '14W' // c0ac #386 + 'W' // c0ad #22 + 'a6Q' // c0ae-c0af #172 + 'Z' // c0b0 #25 + 'b6Q' // c0b1-c0b3 #172 + 'W' // c0b4 #22 + '6Q' // c0b5 #172 + '1D' // c0b6 #29 + 'd6Q' // c0b7-c0bb #172 + 'W' // c0bc #22 + '1G' // c0bd #32 + '6Q' // c0be #172 + '1W' // c0bf #48 + '1E' // c0c0 #30 + 'X' // c0c1 #23 + 'e6Q' // c0c2-c0c7 #172 + '1A' // c0c8 #26 + 'Z' // c0c9 #25 + 'a6Q' // c0ca-c0cb #172 + '1G' // c0cc #32 + 'b6Q' // c0cd-c0cf #172 + '1E' // c0d0 #30 + 'f6Q' // c0d1-c0d7 #172 + '1C' // c0d8 #28 + 'a6Q' // c0d9-c0da #172 + '3V' // c0db #99 + '6Q' // c0dc #172 + 'Z' // c0dd #25 + 'e6Q' // c0de-c0e3 #172 + 'U' // c0e4 #20 + 'f6Q' // c0e5-c0eb #172 + '1P' // c0ec #41 + 'f6Q' // c0ed-c0f3 #172 + '1E' // c0f4 #30 + 'U' // c0f5 #20 + '6Q' // c0f6 #172 + '1D' // c0f7 #29 + '6Q' // c0f8 #172 + '1W' // c0f9 #48 + 'e6Q' // c0fa-c0ff #172 + '1E' // c100 #30 + 'z6C' // c101-c11b #158 + 'X' // c11c #23 + '1A' // c11d #26 + '1E' // c11e #30 + '6C' // c11f #158 + 'X' // c120 #23 + 'b6C' // c121-c123 #158 + 'Z' // c124 #25 + 'f6C' // c125-c12b #158 + 'Y' // c12c #24 + 'U' // c12d #20 + '6C' // c12e #158 + '1G' // c12f #32 + '1F' // c130 #31 + 'X' // c131 #23 + 'e6C' // c132-c137 #158 + 'X' // c138 #23 + 'U' // c139 #20 + 'a6C' // c13a-c13b #158 + '1A' // c13c #26 + 'b6C' // c13d-c13f #158 + 'Y' // c140 #24 + 'f6C' // c141-c147 #158 + '1F' // c148 #31 + '1G' // c149 #32 + '6C' // c14a #158 + '1D' // c14b #29 + 'g6C' // c14c-c153 #158 + 'V' // c154 #21 + 'b6C' // c155-c157 #158 + '1A' // c158 #26 + 'b6C' // c159-c15b #158 + 'Y' // c15c #24 + 'i6C' // c15d-c166 #158 + '1W' // c167 #48 + 'Y' // c168 #24 + 'f6C' // c169-c16f #158 + '1E' // c170 #30 + 'f6C' // c171-c177 #158 + '2B' // c178 #53 + 'r6C' // c179-c18b #158 + 'X' // c18c #23 + '1A' // c18d #26 + 'a6C' // c18e-c18f #158 + 'W' // c190 #22 + 'b6C' // c191-c193 #158 + 'Y' // c194 #24 + 'f6C' // c195-c19b #158 + '1F' // c19c #31 + 'a6C' // c19d-c19e #158 + '1P' // c19f #41 + '6C' // c1a0 #158 + '1A' // c1a1 #26 + 'b6C' // c1a2-c1a4 #158 + '1P' // c1a5 #41 + 'u6C' // c1a6-c1bb #158 + 'g10G' // c1bc-c1c3 #266 + 'V' // c1c4 #21 + 'z10G' // c1c5-c1df #266 + '1G' // c1e0 #32 + 'z10G' // c1e1-c1fb #266 + 'W' // c1fc #22 + 'f10G' // c1fd-c203 #266 + '1W' // c204 #48 + 'g10G' // c205-c20c #266 + '1E' // c20d #30 + '10G' // c20e #266 + '1W' // c20f #48 + 'g10G' // c210-c217 #266 + 'X' // c218 #23 + 'Y' // c219 #24 + 'a10G' // c21a-c21b #266 + '1A' // c21c #26 + 'a10G' // c21d-c21e #266 + '2B' // c21f #53 + 'W' // c220 #22 + 'f10G' // c221-c227 #266 + 'U' // c228 #20 + 'a10G' // c229-c22a #266 + '1G' // c22b #32 + '10G' // c22c #266 + '1G' // c22d #32 + '10G' // c22e #266 + '1W' // c22f #48 + '10G' // c230 #266 + '3V' // c231 #99 + '1C' // c232 #28 + '1b10G' // c233-c24f #266 + '1G' // c250 #32 + 'f10G' // c251-c257 #266 + '1W' // c258 #48 + 'p10G' // c259-c269 #266 + 'a4J' // c26a-c26b #113 + 'V' // c26c #21 + 'b4J' // c26d-c26f #113 + '3V' // c270 #99 + 'b4J' // c271-c273 #113 + '1E' // c274 #30 + 'f4J' // c275-c27b #113 + '1J' // c27c #35 + 'Y' // c27d #24 + 'i4J' // c27e-c287 #113 + 'W' // c288 #22 + 'f4J' // c289-c28f #113 + '1E' // c290 #30 + 'f4J' // c291-c297 #113 + '1W' // c298 #48 + 'a4J' // c299-c29a #113 + '3M' // c29b #90 + 'g4J' // c29c-c2a3 #113 + '14W' // c2a4 #386 + 'b4J' // c2a5-c2a7 #113 + 'Y' // c2a8 #24 + 'b4J' // c2a9-c2ab #113 + 'V' // c2ac #21 + 'f4J' // c2ad-c2b3 #113 + '1D' // c2b4 #29 + 'X' // c2b5 #23 + '4J' // c2b6 #113 + '1C' // c2b7 #28 + '4J' // c2b8 #113 + 'W' // c2b9 #22 + 'a4J' // c2ba-c2bb #113 + '1W' // c2bc #48 + '1d4J' // c2bd-c2db #113 + '14W' // c2dc #386 + 'Z' // c2dd #25 + 'a4J' // c2de-c2df #113 + 'X' // c2e0 #23 + 'a4J' // c2e1-c2e2 #113 + '3V' // c2e3 #99 + 'Z' // c2e4 #25 + 'e4J' // c2e5-c2ea #113 + '1C' // c2eb #28 + 'Z' // c2ec #25 + 'V' // c2ed #21 + '4J' // c2ee #113 + '2I' // c2ef #60 + '4J' // c2f0 #113 + 'Y' // c2f1 #24 + 'c4J' // c2f2-c2f5 #113 + 'W' // c2f6 #22 + '4J' // c2f7 #113 + 'Y' // c2f8 #24 + '1F' // c2f9 #31 + 'a4J' // c2fa-c2fb #113 + '1G' // c2fc #32 + 'b4J' // c2fd-c2ff #113 + '1G' // c300 #32 + 'f4J' // c301-c307 #113 + '1E' // c308 #30 + 'c4J' // c309-c30c #113 + '1D' // c30d #29 + 'd4J' // c30e-c312 #113 + '1G' // c313 #32 + '2B' // c314 #53 + 'n4J' // c315-c323 #113 + '1J' // c324 #35 + 'c4J' // c325-c328 #113 + '2I' // c329 #60 + '4J' // c32a #113 + '2h24E' // c32b-c367 #628 + 'Y' // c368 #24 + '1E' // c369 #30 + 'a24E' // c36a-c36b #628 + '1F' // c36c #31 + 'b24E' // c36d-c36f #628 + '1G' // c370 #32 + 'f24E' // c371-c377 #628 + '1C' // c378 #28 + '1J' // c379 #35 + 'a24E' // c37a-c37b #628 + '1F' // c37c #31 + '3V' // c37d #99 + 'e24E' // c37e-c383 #628 + '1F' // c384 #31 + 'b24E' // c385-c387 #628 + '1W' // c388 #48 + '2r24E' // c389-c3cf #628 + 'g24D' // c3d0-c3d7 #627 + '1G' // c3d8 #32 + '1P' // c3d9 #41 + 'a24D' // c3da-c3db #627 + '2I' // c3dc #60 + 'a24D' // c3dd-c3de #627 + '1F' // c3df #31 + '1J' // c3e0 #35 + 'k24D' // c3e1-c3ec #627 + '3V' // c3ed #99 + 'e24D' // c3ee-c3f3 #627 + '3M' // c3f4 #90 + '2b24D' // c3f5-c42b #627 + '3M' // c42c #90 + '2b24D' // c42d-c463 #627 + '1P' // c464 #41 + '1J' // c465 #35 + 'n24D' // c466-c474 #627 + '4r31R' // c475-c4ef #823 + '1A' // c4f0 #26 + '2B' // c4f1 #53 + 'a31R' // c4f2-c4f3 #823 + 'V' // c4f4 #21 + 'b31R' // c4f5-c4f7 #823 + '1D' // c4f8 #29 + 'f31R' // c4f9-c4ff #823 + '1C' // c500 #28 + '1W' // c501 #48 + 'i31R' // c502-c50b #823 + '1W' // c50c #48 + 'i31R' // c50d-c516 #823 + 'p2V' // c517-c527 #73 + 'W' // c528 #22 + 'U' // c529 #20 + 'a2V' // c52a-c52b #73 + '1C' // c52c #28 + 'b2V' // c52d-c52f #73 + '3V' // c530 #99 + 'g2V' // c531-c538 #73 + '1E' // c539 #30 + '2V' // c53a #73 + '1J' // c53b #35 + '2V' // c53c #73 + '1P' // c53d #41 + 'e2V' // c53e-c543 #73 + 'X' // c544 #23 + 'W' // c545 #22 + 'a2V' // c546-c547 #73 + 'X' // c548 #23 + '1C' // c549 #28 + '1A' // c54a #26 + '2V' // c54b #73 + 'Z' // c54c #25 + 'e2V' // c54d-c552 #73 + '1P' // c553 #41 + 'aY' // c554-c555 #24 + '2V' // c556 #73 + '1G' // c557 #32 + 'W' // c558 #22 + 'Y' // c559 #24 + 'a2V' // c55a-c55b #73 + '3M' // c55c #90 + '2V' // c55d #73 + 'V' // c55e #21 + '2V' // c55f #73 + '1A' // c560 #26 + 'V' // c561 #21 + 'a2V' // c562-c563 #73 + 'Y' // c564 #24 + 'b2V' // c565-c567 #73 + '1C' // c568 #28 + 'f2V' // c569-c56f #73 + '1P' // c570 #41 + 'V' // c571 #21 + '2V' // c572 #73 + '3M' // c573 #90 + '2V' // c574 #73 + '1F' // c575 #31 + 'e2V' // c576-c57b #73 + 'aZ' // c57c-c57d #25 + 'a2V' // c57e-c57f #73 + '1G' // c580 #32 + 'b2V' // c581-c583 #73 + '1F' // c584 #31 + 'a2V' // c585-c586 #73 + '1E' // c587 #30 + 'c2V' // c588-c58b #73 + '1J' // c58c #35 + '2B' // c58d #53 + '2V' // c58e #73 + '3M' // c58f #90 + '2V' // c590 #73 + 'Z' // c591 #25 + 'd2V' // c592-c596 #73 + '2I' // c597 #60 + '1C' // c598 #28 + 'z2V' // c599-c5b3 #73 + 'X' // c5b4 #23 + 'V' // c5b5 #21 + 'a2V' // c5b6-c5b7 #73 + '1A' // c5b8 #26 + '1W' // c5b9 #48 + '2V' // c5ba #73 + '1D' // c5bb #29 + 'W' // c5bc #22 + '2I' // c5bd #60 + 'e2V' // c5be-c5c3 #73 + 'V' // c5c4 #21 + 'aZ' // c5c5-c5c6 #25 + 'U' // c5c7 #20 + '1A' // c5c8 #26 + '1C' // c5c9 #28 + 'a2V' // c5ca-c5cb #73 + '1P' // c5cc #41 + '2V' // c5cd #73 + '1W' // c5ce #48 + '2V' // c5cf #73 + 'X' // c5d0 #23 + '1D' // c5d1 #29 + 'a2V' // c5d2-c5d3 #73 + 'W' // c5d4 #22 + 'b2V' // c5d5-c5d7 #73 + 'Y' // c5d8 #24 + 'f2V' // c5d9-c5df #73 + '1D' // c5e0 #29 + 'a2V' // c5e1-c5e2 #73 + '1G' // c5e3 #32 + '2V' // c5e4 #73 + '2I' // c5e5 #60 + 'e2V' // c5e6-c5eb #73 + 'X' // c5ec #23 + 'Z' // c5ed #25 + '1D' // c5ee #29 + '2V' // c5ef #73 + 'X' // c5f0 #23 + 'b2V' // c5f1-c5f3 #73 + '1A' // c5f4 #26 + 'f4F' // c5f5-c5fb #109 + 'Y' // c5fc #24 + '1D' // c5fd #29 + '4F' // c5fe #109 + '1E' // c5ff #30 + 'W' // c600 #22 + 'X' // c601 #23 + 'c4F' // c602-c605 #109 + '1D' // c606 #29 + '4F' // c607 #109 + 'Z' // c608 #25 + 'f4F' // c609-c60f #109 + '1J' // c610 #35 + 'i4F' // c611-c61a #109 + '1C' // c61b #28 + 'g4F' // c61c-c623 #109 + 'X' // c624 #23 + 'Y' // c625 #24 + 'a4F' // c626-c627 #109 + '1A' // c628 #26 + 'b4F' // c629-c62b #109 + '1A' // c62c #26 + '4F' // c62d #109 + '1G' // c62e #32 + 'c4F' // c62f-c632 #109 + '1P' // c633 #41 + '1G' // c634 #32 + 'U' // c635 #20 + '4F' // c636 #109 + 'U' // c637 #20 + '4F' // c638 #109 + '1C' // c639 #28 + 'e4F' // c63a-c63f #109 + 'Z' // c640 #25 + '1W' // c641 #48 + 'a4F' // c642-c643 #109 + 'W' // c644 #22 + 'b4F' // c645-c647 #109 + '1W' // c648 #48 + 'i4F' // c649-c652 #109 + '1J' // c653 #35 + 'aV' // c654-c655 #21 + 'e4F' // c656-c65b #109 + 'Y' // c65c #24 + 'b4F' // c65d-c65f #109 + '1F' // c660 #31 + 'b4F' // c661-c663 #109 + '2B' // c664 #53 + 'r4F' // c665-c677 #109 + '1A' // c678 #26 + 'b4F' // c679-c67b #109 + '1C' // c67c #28 + 'v4F' // c67d-c693 #109 + 'X' // c694 #23 + 'V' // c695 #21 + 'a4F' // c696-c697 #109 + '2B' // c698 #53 + 'b4F' // c699-c69b #109 + '2I' // c69c #60 + 'f4F' // c69d-c6a3 #109 + '1P' // c6a4 #41 + '3V' // c6a5 #99 + '4F' // c6a6 #109 + '2B' // c6a7 #53 + '4F' // c6a8 #109 + 'X' // c6a9 #23 + 'e4F' // c6aa-c6af #109 + 'X' // c6b0 #23 + 'Y' // c6b1 #24 + 'a4F' // c6b2-c6b3 #109 + 'X' // c6b4 #23 + 'b4F' // c6b5-c6b7 #109 + 'Z' // c6b8 #25 + 'a4F' // c6b9-c6ba #109 + 'd4Q' // c6bb-c6bf #120 + 'W' // c6c0 #22 + '2B' // c6c1 #53 + '4Q' // c6c2 #120 + 'W' // c6c3 #22 + '4Q' // c6c4 #120 + '1D' // c6c5 #29 + 'e4Q' // c6c6-c6cb #120 + '1A' // c6cc #26 + '1J' // c6cd #35 + 'a4Q' // c6ce-c6cf #120 + 'X' // c6d0 #23 + 'b4Q' // c6d1-c6d3 #120 + 'Z' // c6d4 #25 + 'f4Q' // c6d5-c6db #120 + '3V' // c6dc #99 + 'b4Q' // c6dd-c6df #120 + '1D' // c6e0 #29 + 'f4Q' // c6e1-c6e7 #120 + 'V' // c6e8 #21 + 'b4Q' // c6e9-c6eb #120 + '1E' // c6ec #30 + 'b4Q' // c6ed-c6ef #120 + '1G' // c6f0 #32 + 'g4Q' // c6f1-c6f8 #120 + 'V' // c6f9 #21 + 'i4Q' // c6fa-c703 #120 + 'X' // c704 #23 + 'b4Q' // c705-c707 #120 + '1D' // c708 #29 + 'b4Q' // c709-c70b #120 + '1E' // c70c #30 + 'i4Q' // c70d-c716 #120 + '1C' // c717 #28 + '4Q' // c718 #120 + '1F' // c719 #31 + 'e4Q' // c71a-c71f #120 + 'X' // c720 #23 + 'W' // c721 #22 + 'a4Q' // c722-c723 #120 + 'V' // c724 #21 + 'b4Q' // c725-c727 #120 + 'Y' // c728 #24 + 'k4Q' // c729-c734 #120 + 'U' // c735 #20 + 'e4Q' // c736-c73b #120 + 'X' // c73c #23 + '1W' // c73d #48 + 'a4Q' // c73e-c73f #120 + 'X' // c740 #23 + 'b4Q' // c741-c743 #120 + 'X' // c744 #23 + 'f4Q' // c745-c74b #120 + 'X' // c74c #23 + '1G' // c74d #32 + 'b4Q' // c74e-c750 #120 + 'V' // c751 #21 + 'e4Q' // c752-c757 #120 + 'X' // c758 #23 + 'z4Q' // c759-c773 #120 + '14W' // c774 #386 + 'V' // c775 #21 + 'a4Q' // c776-c777 #120 + '14W' // c778 #386 + 'b4L' // c779-c77b #115 + 'X' // c77c #23 + 'U' // c77d #20 + 'd4L' // c77e-c782 #115 + '1G' // c783 #32 + 'Z' // c784 #25 + 'X' // c785 #23 + '4L' // c786 #115 + 'U' // c787 #20 + 'X' // c788 #23 + 'U' // c789 #20 + '1C' // c78a #28 + '4L' // c78b #115 + '3M' // c78c #90 + '4L' // c78d #115 + '1G' // c78e #32 + '4L' // c78f #115 + 'aX' // c790-c791 #23 + 'a4L' // c792-c793 #115 + 'Y' // c794 #24 + '4L' // c795 #115 + '1G' // c796 #32 + '4L' // c797 #115 + 'W' // c798 #22 + 'f4L' // c799-c79f #115 + 'V' // c7a0 #21 + 'W' // c7a1 #22 + '4L' // c7a2 #115 + '1P' // c7a3 #41 + '3V' // c7a4 #99 + 'X' // c7a5 #23 + '1P' // c7a6 #41 + 'd4L' // c7a7-c7ab #115 + 'Z' // c7ac #25 + '1E' // c7ad #30 + 'm4L' // c7ae-c7bb #115 + '1G' // c7bc #32 + 'c4L' // c7bd-c7c0 #115 + 'V' // c7c1 #21 + 'e4L' // c7c2-c7c7 #115 + '1E' // c7c8 #30 + 's4L' // c7c9-c7dc #115 + '3M' // c7dd #90 + '1g4L' // c7de-c7ff #115 + 'aZ' // c800-c801 #25 + 'a4L' // c802-c803 #115 + 'X' // c804 #23 + 'b4L' // c805-c807 #115 + '1A' // c808 #26 + '4L' // c809 #115 + '1C' // c80a #28 + 'd4L' // c80b-c80f #115 + '1A' // c810 #26 + 'W' // c811 #22 + '4L' // c812 #115 + '1J' // c813 #35 + '4L' // c814 #115 + 'X' // c815 #23 + '1F' // c816 #31 + 'd4L' // c817-c81b #115 + 'X' // c81c #23 + '1D' // c81d #29 + 'a4L' // c81e-c81f #115 + '1D' // c820 #29 + 'b4L' // c821-c823 #115 + '1D' // c824 #29 + 'i4L' // c825-c82e #115 + '1J' // c82f #35 + 'g4L' // c830-c837 #115 + 'W' // c838 #22 + 'b4L' // c839-c83b #115 + '1E' // c83c #30 + 'c4L' // c83d-c840 #115 + 'j19K' // c841-c84b #504 + 'Y' // c84c #24 + '1h19K' // c84d-c86f #504 + 'X' // c870 #23 + 'W' // c871 #22 + 'a19K' // c872-c873 #504 + 'V' // c874 #21 + 'b19K' // c875-c877 #504 + '1D' // c878 #29 + 'f19K' // c879-c87f #504 + 'V' // c880 #21 + '1E' // c881 #30 + 'b19K' // c882-c884 #504 + '1A' // c885 #26 + '1P' // c886 #41 + 'c19K' // c887-c88a #504 + '1A' // c88b #26 + 'Y' // c88c #24 + '2b19K' // c88d-c8c3 #504 + 'U' // c8c4 #20 + 'z19K' // c8c5-c8df #504 + 'Y' // c8e0 #24 + 'g19K' // c8e1-c8e8 #504 + 'k11E' // c8e9-c8f4 #290 + '3M' // c8f5 #90 + 'e11E' // c8f6-c8fb #290 + 'X' // c8fc #23 + 'Y' // c8fd #24 + 'a11E' // c8fe-c8ff #290 + '1A' // c900 #26 + 'b11E' // c901-c903 #290 + 'W' // c904 #22 + 'f11E' // c905-c90b #290 + 'a1C' // c90c-c90d #28 + 'b11E' // c90e-c910 #290 + 'X' // c911 #23 + 'e11E' // c912-c917 #290 + '1D' // c918 #29 + 'r11E' // c919-c92b #290 + '1G' // c92c #32 + '1h11E' // c92d-c94f #290 + '1C' // c950 #28 + 'b11E' // c951-c953 #290 + '3V' // c954 #99 + 'v11E' // c955-c96b #290 + '1D' // c96c #29 + 'f11E' // c96d-c973 #290 + '3V' // c974 #99 + 'r11E' // c975-c987 #290 + '1A' // c988 #26 + 'U' // c989 #20 + 'a11E' // c98a-c98b #290 + 'U' // c98c #20 + 'b11E' // c98d-c98f #290 + 'W' // c990 #22 + 'd11E' // c991-c995 #290 + 'a8Z' // c996-c997 #233 + 'Y' // c998 #24 + '1J' // c999 #35 + 'b8Z' // c99a-c99c #233 + '1A' // c99d #26 + '1g8Z' // c99e-c9bf #233 + '14W' // c9c0 #386 + 'Z' // c9c1 #25 + 'a8Z' // c9c2-c9c3 #233 + 'X' // c9c4 #23 + 'b8Z' // c9c5-c9c7 #233 + '1A' // c9c8 #26 + 'f8Z' // c9c9-c9cf #233 + 'U' // c9d0 #20 + 'Z' // c9d1 #25 + '8Z' // c9d2 #233 + 'U' // c9d3 #20 + '8Z' // c9d4 #233 + 'V' // c9d5 #21 + '2I' // c9d6 #60 + 'a8Z' // c9d7-c9d8 #233 + 'a1P' // c9d9-c9da #41 + '8Z' // c9db #233 + 'W' // c9dc #22 + 'Y' // c9dd #24 + 'a8Z' // c9de-c9df #233 + '1J' // c9e0 #35 + 'b8Z' // c9e1-c9e3 #233 + '1D' // c9e4 #29 + 'a8Z' // c9e5-c9e6 #233 + '1C' // c9e7 #28 + 'c8Z' // c9e8-c9eb #233 + '1P' // c9ec #41 + '2B' // c9ed #53 + 'b8Z' // c9ee-c9f0 #233 + '1D' // c9f1 #29 + 'e8Z' // c9f2-c9f7 #233 + 'V' // c9f8 #21 + 'r8Z' // c9f9-ca0b #233 + '1P' // ca0c #41 + '1a8Z' // ca0d-ca28 #233 + '2I' // ca29 #60 + '1e8Z' // ca2a-ca49 #233 + 'a13M' // ca4a-ca4b #350 + '1G' // ca4c #32 + '1F' // ca4d #31 + 'a13M' // ca4e-ca4f #350 + '1W' // ca50 #48 + 'b13M' // ca51-ca53 #350 + '1F' // ca54 #31 + 'f13M' // ca55-ca5b #350 + '2I' // ca5c #60 + 'c13M' // ca5d-ca60 #350 + '1W' // ca61 #48 + '1g13M' // ca62-ca83 #350 + '2I' // ca84 #60 + '2b13M' // ca85-cabb #350 + '1F' // cabc #31 + 'V' // cabd #21 + 'a13M' // cabe-cabf #350 + '2B' // cac0 #53 + 'b13M' // cac1-cac3 #350 + '1J' // cac4 #35 + 'f13M' // cac5-cacb #350 + '2B' // cacc #53 + 'c13M' // cacd-cad0 #350 + '3M' // cad1 #90 + '13M' // cad2 #350 + '1J' // cad3 #35 + 'd13M' // cad4-cad8 #350 + '2B' // cad9 #53 + 'y13M' // cada-caf3 #350 + '3e71B' // caf4-cb47 #1847 + '1E' // cb48 #30 + '1F' // cb49 #31 + '2r71B' // cb4a-cb90 #1847 + '2n13L' // cb91-cbd3 #349 + '1J' // cbd4 #35 + 'n13L' // cbd5-cbe3 #349 + '1C' // cbe4 #28 + '1l13L' // cbe5-cc0b #349 + 'U' // cc0c #20 + 'Y' // cc0d #24 + 'a13L' // cc0e-cc0f #349 + '1J' // cc10 #35 + 'b13L' // cc11-cc13 #349 + '1F' // cc14 #31 + 'f13L' // cc15-cc1b #349 + '1G' // cc1c #32 + 'c13L' // cc1d-cc20 #349 + '1P' // cc21 #41 + '1J' // cc22 #35 + 'd13L' // cc23-cc27 #349 + 'Z' // cc28 #25 + 'V' // cc29 #21 + 'a13L' // cc2a-cc2b #349 + 'V' // cc2c #21 + '13L' // cc2d #349 + '1D' // cc2e #29 + '13L' // cc2f #349 + 'Y' // cc30 #24 + 'f13L' // cc31-cc37 #349 + 'W' // cc38 #22 + '2I' // cc39 #60 + '13L' // cc3a #349 + '2I' // cc3b #60 + '13L' // cc3c #349 + 'a1A' // cc3d-cc3e #26 + 'd9L' // cc3f-cc43 #245 + 'W' // cc44 #22 + '1A' // cc45 #26 + 'a9L' // cc46-cc47 #245 + '3M' // cc48 #90 + 'b9L' // cc49-cc4b #245 + '1W' // cc4c #48 + 'f9L' // cc4d-cc53 #245 + '1G' // cc54 #32 + 'c9L' // cc55-cc58 #245 + '1G' // cc59 #32 + 'e9L' // cc5a-cc5f #245 + '1E' // cc60 #30 + '2b9L' // cc61-cc97 #245 + 'Z' // cc98 #25 + 'U' // cc99 #20 + 'a9L' // cc9a-cc9b #245 + 'Z' // cc9c #25 + 'b9L' // cc9d-cc9f #245 + 'W' // cca0 #22 + 'f9L' // cca1-cca7 #245 + 'V' // cca8 #21 + '1C' // cca9 #28 + '9L' // ccaa #245 + 'V' // ccab #21 + '9L' // ccac #245 + 'Z' // ccad #25 + 'e9L' // ccae-ccb3 #245 + 'X' // ccb4 #23 + '2B' // ccb5 #53 + 'a9L' // ccb6-ccb7 #245 + '1J' // ccb8 #35 + 'b9L' // ccb9-ccbb #245 + '1J' // ccbc #35 + 'r9L' // ccbd-cccf #245 + 'V' // ccd0 #21 + 'r9L' // ccd1-cce3 #245 + '1C' // cce4 #28 + 'i9L' // cce5-ccee #245 + 'x24C' // ccef-cd07 #626 + '1A' // cd08 #26 + '1D' // cd09 #29 + 'a24C' // cd0a-cd0b #626 + 'U' // cd0c #20 + 'm24C' // cd0d-cd1a #626 + '1P' // cd1b #41 + '24C' // cd1c #626 + 'W' // cd1d #22 + 'm24C' // cd1e-cd2b #626 + 'U' // cd2c #20 + '1t24C' // cd2d-cd5b #626 + 'Z' // cd5c #25 + 'z24C' // cd5d-cd77 #626 + '1P' // cd78 #41 + 'x24C' // cd79-cd91 #626 + 'a14U' // cd92-cd93 #384 + 'Z' // cd94 #25 + 'W' // cd95 #22 + 'a14U' // cd96-cd97 #384 + 'U' // cd98 #20 + 'b14U' // cd99-cd9b #384 + 'Z' // cd9c #25 + 'f14U' // cd9d-cda3 #384 + 'U' // cda4 #20 + '1J' // cda5 #35 + 'b14U' // cda6-cda8 #384 + 'W' // cda9 #22 + 'e14U' // cdaa-cdaf #384 + '1C' // cdb0 #28 + 'r14U' // cdb1-cdc3 #384 + '1P' // cdc4 #41 + 'f14U' // cdc5-cdcb #384 + '2B' // cdcc #53 + 'z14U' // cdcd-cde7 #384 + 'Z' // cde8 #25 + 'z14U' // cde9-ce03 #384 + '1G' // ce04 #32 + 'z14U' // ce05-ce1f #384 + '1A' // ce20 #26 + 'V' // ce21 #21 + 'r14U' // ce22-ce34 #384 + 'V' // ce35 #21 + 'e14U' // ce36-ce3b #384 + '1a7S' // ce3c-ce57 #200 + 'X' // ce58 #23 + 'U' // ce59 #20 + 'a7S' // ce5a-ce5b #200 + 'W' // ce5c #22 + 'b7S' // ce5d-ce5f #200 + 'U' // ce60 #20 + 'f7S' // ce61-ce67 #200 + 'Z' // ce68 #25 + '1E' // ce69 #30 + '7S' // ce6a #200 + '1E' // ce6b #30 + '7S' // ce6c #200 + 'U' // ce6d #20 + 'e7S' // ce6e-ce73 #200 + 'X' // ce74 #23 + '1E' // ce75 #30 + 'a7S' // ce76-ce77 #200 + '1C' // ce78 #28 + 'b7S' // ce79-ce7b #200 + 'V' // ce7c #21 + 'f7S' // ce7d-ce83 #200 + '1J' // ce84 #35 + 'c7S' // ce85-ce88 #200 + '2I' // ce89 #60 + 'e7S' // ce8a-ce8f #200 + 'V' // ce90 #21 + 'b7S' // ce91-ce93 #200 + '1C' // ce94 #28 + 'b7S' // ce95-ce97 #200 + '1C' // ce98 #28 + 'f7S' // ce99-ce9f #200 + 'U' // cea0 #20 + '1D' // cea1 #29 + '7S' // cea2 #200 + '1J' // cea3 #35 + 'g7S' // cea4-ceab #200 + '1W' // ceac #48 + '2b7S' // cead-cee3 #200 + '1A' // cee4 #26 + '2I' // cee5 #60 + 'a7S' // cee6-cee7 #200 + 'V' // cee8 #21 + 'b7S' // cee9-ceeb #200 + 'Y' // ceec #24 + 'b7S' // ceed-ceef #200 + 'c8J' // cef0-cef3 #217 + 'V' // cef4 #21 + '1D' // cef5 #29 + '8J' // cef6 #217 + '1D' // cef7 #29 + '1P' // cef8 #41 + 'f8J' // cef9-ceff #217 + 'W' // cf00 #22 + 'b8J' // cf01-cf03 #217 + '1E' // cf04 #30 + 'b8J' // cf05-cf07 #217 + '1E' // cf08 #30 + 'f8J' // cf09-cf0f #217 + '3M' // cf10 #90 + 'a8J' // cf11-cf12 #217 + 'Y' // cf13 #24 + 'g8J' // cf14-cf1b #217 + 'Y' // cf1c #24 + 'b8J' // cf1d-cf1f #217 + '3M' // cf20 #90 + 'n8J' // cf21-cf2f #217 + '1G' // cf30 #32 + '1h8J' // cf31-cf53 #217 + 'Z' // cf54 #25 + '1E' // cf55 #30 + 'a8J' // cf56-cf57 #217 + 'W' // cf58 #22 + 'b8J' // cf59-cf5b #217 + 'Y' // cf5c #24 + 'f8J' // cf5d-cf63 #217 + '1D' // cf64 #29 + '2I' // cf65 #60 + '8J' // cf66 #217 + '1J' // cf67 #35 + '8J' // cf68 #217 + '1D' // cf69 #29 + 'e8J' // cf6a-cf6f #217 + '1E' // cf70 #30 + 's8J' // cf71-cf84 #217 + '2I' // cf85 #60 + 'e8J' // cf86-cf8b #217 + '1C' // cf8c #28 + 't8J' // cf8d-cfa1 #217 + '1g14T' // cfa2-cfc3 #383 + '1G' // cfc4 #32 + 'z14T' // cfc5-cfdf #383 + 'V' // cfe0 #21 + '1J' // cfe1 #35 + 'a14T' // cfe2-cfe3 #383 + '1P' // cfe4 #41 + 'b14T' // cfe5-cfe7 #383 + '1D' // cfe8 #29 + 'k14T' // cfe9-cff4 #383 + '1F' // cff5 #31 + 'e14T' // cff6-cffb #383 + '1E' // cffc #30 + 'b14T' // cffd-cfff #383 + '3V' // d000 #99 + 'b14T' // d001-d003 #383 + '1F' // d004 #31 + 'r14T' // d005-d017 #383 + '1C' // d018 #28 + 'z14T' // d019-d033 #383 + '1C' // d034 #28 + '1D' // d035 #29 + 'a14T' // d036-d037 #383 + '1F' // d038 #31 + 'b14T' // d039-d03b #383 + '3M' // d03c #90 + 'm14T' // d03d-d04a #383 + 'd6P' // d04b-d04f #171 + 'Y' // d050 #24 + 'f6P' // d051-d057 #171 + '2I' // d058 #60 + 'r6P' // d059-d06b #171 + 'X' // d06c #23 + 'b6P' // d06d-d06f #171 + 'V' // d070 #21 + 'b6P' // d071-d073 #171 + '1A' // d074 #26 + 'f6P' // d075-d07b #171 + 'Y' // d07c #24 + '3M' // d07d #90 + '1k6P' // d07e-d0a3 #171 + '1A' // d0a4 #26 + '1P' // d0a5 #41 + 'a6P' // d0a6-d0a7 #171 + 'Y' // d0a8 #24 + 'b6P' // d0a9-d0ab #171 + '1D' // d0ac #29 + 'f6P' // d0ad-d0b3 #171 + '1F' // d0b4 #31 + '1P' // d0b5 #41 + '6P' // d0b6 #171 + '1E' // d0b7 #30 + '6P' // d0b8 #171 + 'V' // d0b9 #21 + 'e6P' // d0ba-d0bf #171 + 'X' // d0c0 #23 + 'V' // d0c1 #21 + 'a6P' // d0c2-d0c3 #171 + 'V' // d0c4 #21 + 'b6P' // d0c5-d0c7 #171 + 'V' // d0c8 #21 + 'f6P' // d0c9-d0cf #171 + 'aU' // d0d0-d0d1 #20 + '6P' // d0d2 #171 + '1F' // d0d3 #31 + '1W' // d0d4 #48 + 'U' // d0d5 #20 + 'e6P' // d0d6-d0db #171 + 'Z' // d0dc #25 + '1A' // d0dd #26 + 'a6P' // d0de-d0df #171 + '1F' // d0e0 #31 + 'b6P' // d0e1-d0e3 #171 + '2B' // d0e4 #53 + 'f6P' // d0e5-d0eb #171 + '1P' // d0ec #41 + '1F' // d0ed #31 + 'b6P' // d0ee-d0f0 #171 + '1C' // d0f1 #28 + 'r6P' // d0f2-d104 #171 + '1p11D' // d105-d12f #289 + 'X' // d130 #23 + '1F' // d131 #31 + 'a11D' // d132-d133 #289 + 'Y' // d134 #24 + 'b11D' // d135-d137 #289 + 'Y' // d138 #24 + 'f11D' // d139-d13f #289 + '1G' // d140 #32 + 'a11D' // d141-d142 #289 + '2B' // d143 #53 + '11D' // d144 #289 + '2I' // d145 #60 + 'e11D' // d146-d14b #289 + 'Z' // d14c #25 + 'U' // d14d #20 + 'a11D' // d14e-d14f #289 + 'W' // d150 #22 + 'b11D' // d151-d153 #289 + 'Y' // d154 #24 + 'f11D' // d155-d15b #289 + 'V' // d15c #21 + '1P' // d15d #41 + '1o11D' // d15e-d187 #289 + '1P' // d188 #41 + 'v11D' // d189-d19f #289 + 'Z' // d1a0 #25 + 'V' // d1a1 #21 + 'a11D' // d1a2-d1a3 #289 + 'U' // d1a4 #20 + 'b11D' // d1a5-d1a7 #289 + '1E' // d1a8 #30 + 'f11D' // d1a9-d1af #289 + '1F' // d1b0 #31 + '1C' // d1b1 #28 + 'a11D' // d1b2-d1b3 #289 + '24B' // d1b4 #625 + 'Z' // d1b5 #25 + '2i24B' // d1b6-d1f3 #625 + 'Y' // d1f4 #24 + '2b24B' // d1f5-d22b #625 + 'W' // d22c #22 + '2I' // d22d #60 + 'a24B' // d22e-d22f #625 + '1D' // d230 #29 + 'b24B' // d231-d233 #625 + '1D' // d234 #29 + 'f24B' // d235-d23b #625 + '1P' // d23c #41 + 'c24B' // d23d-d240 #625 + '1J' // d241 #35 + 't24B' // d242-d256 #625 + '1n16E' // d257-d27f #420 + '1C' // d280 #28 + 'z16E' // d281-d29b #420 + 'U' // d29c #20 + 'b16E' // d29d-d29f #420 + '3M' // d2a0 #90 + 'j16E' // d2a1-d2ab #420 + '1P' // d2ac #41 + 'j16E' // d2ad-d2b7 #420 + 'X' // d2b8 #23 + '1A' // d2b9 #26 + 'a16E' // d2ba-d2bb #420 + 'Y' // d2bc #24 + 'b16E' // d2bd-d2bf #420 + 'V' // d2c0 #21 + 'f16E' // d2c1-d2c7 #420 + '1E' // d2c8 #30 + '1l16E' // d2c9-d2ef #420 + 'Z' // d2f0 #25 + 'U' // d2f1 #20 + 'a16E' // d2f2-d2f3 #420 + '1D' // d2f4 #29 + 'b16E' // d2f5-d2f7 #420 + '1C' // d2f8 #28 + 'e16E' // d2f9-d2fe #420 + '4U' // d2ff #124 + 'V' // d300 #21 + 'U' // d301 #20 + 'b4U' // d302-d304 #124 + 'W' // d305 #22 + 'e4U' // d306-d30b #124 + 'Z' // d30c #25 + '1J' // d30d #35 + '3M' // d30e #90 + '4U' // d30f #124 + 'Z' // d310 #25 + 'b4U' // d311-d313 #124 + 'V' // d314 #21 + 'f4U' // d315-d31b #124 + '1J' // d31c #35 + '1D' // d31d #29 + '4U' // d31e #124 + '1F' // d31f #31 + '2B' // d320 #53 + '1G' // d321 #32 + 'b4U' // d322-d324 #124 + '2I' // d325 #60 + 'a4U' // d326-d327 #124 + 'W' // d328 #22 + 'Y' // d329 #24 + 'a4U' // d32a-d32b #124 + 'Y' // d32c #24 + 'b4U' // d32d-d32f #124 + '2B' // d330 #53 + 'f4U' // d331-d337 #124 + '1C' // d338 #28 + 'c4U' // d339-d33c #124 + '1F' // d33d #31 + '2i4U' // d33e-d37b #124 + 'W' // d37c #22 + '2I' // d37d #60 + 'a4U' // d37e-d37f #124 + '1C' // d380 #28 + 'b4U' // d381-d383 #124 + '1E' // d384 #30 + 'f4U' // d385-d38b #124 + '1G' // d38c #32 + '3M' // d38d #90 + 'b4U' // d38e-d390 #124 + '1J' // d391 #35 + 'e4U' // d392-d397 #124 + 'Z' // d398 #25 + '1G' // d399 #32 + 'a4U' // d39a-d39b #124 + 'U' // d39c #20 + 'b4U' // d39d-d39f #124 + '1F' // d3a0 #31 + 'f4U' // d3a1-d3a7 #124 + '3M' // d3a8 #90 + 'a4U' // d3a9-d3aa #124 + '1F' // d3ab #31 + '4U' // d3ac #124 + '2I' // d3ad #60 + 'e4U' // d3ae-d3b3 #124 + '1C' // d3b4 #28 + 'b4U' // d3b5-d3b7 #124 + '1A' // d3b8 #26 + 'b4U' // d3b9-d3bb #124 + 'U' // d3bc #20 + 'a4U' // d3bd-d3be #124 + 'h21P' // d3bf-d3c7 #561 + '3M' // d3c8 #90 + '1A' // d3c9 #26 + 'e21P' // d3ca-d3cf #561 + 'Y' // d3d0 #24 + 'z21P' // d3d1-d3eb #561 + 'X' // d3ec #23 + 'V' // d3ed #21 + 'a21P' // d3ee-d3ef #561 + 'W' // d3f0 #22 + 'b21P' // d3f1-d3f3 #561 + 'Y' // d3f4 #24 + 'f21P' // d3f5-d3fb #561 + 'Y' // d3fc #24 + 'c21P' // d3fd-d400 #561 + '1J' // d401 #35 + '3k21P' // d402-d45b #561 + 'Z' // d45c #25 + 'f21P' // d45d-d463 #561 + 's21O' // d464-d477 #560 + 'V' // d478 #21 + '1J' // d479 #35 + 'a21O' // d47a-d47b #560 + '1E' // d47c #30 + 'b21O' // d47d-d47f #560 + 'V' // d480 #21 + 'f21O' // d481-d487 #560 + '1A' // d488 #26 + 'a21O' // d489-d48a #560 + '1E' // d48b #30 + '21O' // d48c #560 + 'V' // d48d #21 + '3k21O' // d48e-d4e7 #560 + 'U' // d4e8 #20 + 'z21O' // d4e9-d503 #560 + 'X' // d504 #23 + 'a21O' // d505-d506 #560 + '7E' // d507 #186 + 'V' // d508 #21 + 'b7E' // d509-d50b #186 + 'Z' // d50c #25 + 'f7E' // d50d-d513 #186 + '1F' // d514 #31 + '1l7E' // d515-d53b #186 + 'Z' // d53c #25 + 'V' // d53d #21 + 'a7E' // d53e-d53f #186 + 'U' // d540 #20 + 'b7E' // d541-d543 #186 + '1A' // d544 #26 + 'g7E' // d545-d54c #186 + '2B' // d54d #53 + '7E' // d54e #186 + '1G' // d54f #32 + '7E' // d550 #186 + 'W' // d551 #22 + 'e7E' // d552-d557 #186 + '14W' // d558 #386 + '1A' // d559 #26 + 'a7E' // d55a-d55b #186 + 'X' // d55c #23 + 'b7E' // d55d-d55f #186 + 'Z' // d560 #25 + 'c7E' // d561-d564 #186 + '3V' // d565 #99 + 'a7E' // d566-d567 #186 + 'Z' // d568 #25 + 'X' // d569 #23 + '7E' // d56a #186 + 'Y' // d56b #24 + '7E' // d56c #186 + '1A' // d56d #26 + 'e7E' // d56e-d573 #186 + 'X' // d574 #23 + '1D' // d575 #29 + 'a7E' // d576-d577 #186 + '1D' // d578 #29 + 'j7E' // d579-d583 #186 + '1F' // d584 #31 + 'a7E' // d585-d586 #186 + '1G' // d587 #32 + '1A' // d588 #26 + 'Z' // d589 #25 + 'z7E' // d58a-d5a4 #186 + 'W' // d5a5 #22 + 'u7E' // d5a6-d5bb #186 + 'k4T' // d5bc-d5c7 #123 + 'W' // d5c8 #22 + '1J' // d5c9 #35 + 'a4T' // d5ca-d5cb #123 + 'Y' // d5cc #24 + 'b4T' // d5cd-d5cf #123 + '1G' // d5d0 #32 + 'f4T' // d5d1-d5d7 #123 + 'W' // d5d8 #22 + 'a4T' // d5d9-d5da #123 + '1J' // d5db #35 + '4T' // d5dc #123 + '1E' // d5dd #30 + 'e4T' // d5de-d5e3 #123 + 'V' // d5e4 #21 + '3M' // d5e5 #90 + 'a4T' // d5e6-d5e7 #123 + '1E' // d5e8 #30 + 'b4T' // d5e9-d5eb #123 + 'U' // d5ec #20 + 'i4T' // d5ed-d5f6 #123 + '1W' // d5f7 #48 + '4T' // d5f8 #123 + '3M' // d5f9 #90 + 'e4T' // d5fa-d5ff #123 + 'aY' // d600-d601 #24 + 'a4T' // d602-d603 #123 + 'Z' // d604 #25 + 'b4T' // d605-d607 #123 + '1D' // d608 #29 + 'f4T' // d609-d60f #123 + '1C' // d610 #28 + 'V' // d611 #21 + 'a4T' // d612-d613 #123 + '1D' // d614 #29 + '1A' // d615 #26 + 'e4T' // d616-d61b #123 + 'V' // d61c #21 + 'z4T' // d61d-d637 #123 + 'X' // d638 #23 + 'Y' // d639 #24 + 'a4T' // d63a-d63b #123 + 'W' // d63c #22 + 'b4T' // d63d-d63f #123 + 'U' // d640 #20 + 'f4T' // d641-d647 #123 + '1A' // d648 #26 + '1J' // d649 #35 + 'b4T' // d64a-d64c #123 + 'W' // d64d #22 + 'e4T' // d64e-d653 #123 + 'X' // d654 #23 + '1A' // d655 #26 + 'a4T' // d656-d657 #123 + '1A' // d658 #26 + 'b4T' // d659-d65b #123 + '1A' // d65c #26 + 'i4T' // d65d-d666 #123 + '3V' // d667 #99 + '4T' // d668 #123 + 'W' // d669 #22 + 'n4T' // d66a-d678 #123 + 'r12O' // d679-d68b #326 + 'X' // d68c #23 + 'V' // d68d #21 + 'p12O' // d68e-d69e #326 + '1C' // d69f #28 + '12O' // d6a0 #326 + '1F' // d6a1 #31 + 'e12O' // d6a2-d6a7 #326 + 'V' // d6a8 #21 + 'z12O' // d6a9-d6c3 #326 + 'Z' // d6c4 #25 + '3V' // d6c5 #99 + 'a12O' // d6c6-d6c7 #326 + 'Y' // d6c8 #24 + 'b12O' // d6c9-d6cb #326 + '1F' // d6cc #31 + 'f12O' // d6cd-d6d3 #326 + '1J' // d6d4 #35 + 'a12O' // d6d5-d6d6 #326 + '1W' // d6d7 #48 + 'k12O' // d6d8-d6e3 #326 + '3M' // d6e4 #90 + 'b12O' // d6e5-d6e7 #326 + '1G' // d6e8 #32 + 'r12O' // d6e9-d6fb #326 + '1C' // d6fc #28 + 'z12O' // d6fd-d717 #326 + '1D' // d718 #29 + 'f12O' // d719-d71f #326 + '1E' // d720 #30 + 'a12O' // d721-d722 #326 + 'e9K' // d723-d728 #244 + '1P' // d729 #41 + 'i9K' // d72a-d733 #244 + 'W' // d734 #22 + 's9K' // d735-d748 #244 + '1F' // d749 #31 + 'e9K' // d74a-d74f #244 + 'U' // d750 #20 + '1D' // d751 #29 + 'a9K' // d752-d753 #244 + 'Y' // d754 #24 + 'b9K' // d755-d757 #244 + '1C' // d758 #28 + '1E' // d759 #30 + 'e9K' // d75a-d75f #244 + '1F' // d760 #31 + '1D' // d761 #29 + 'b9K' // d762-d764 #244 + 'Y' // d765 #24 + 'b9K' // d766-d768 #244 + '2I' // d769 #60 + 'a9K' // d76a-d76b #244 + 'W' // d76c #22 + 'b9K' // d76d-d76f #244 + '1G' // d770 #32 + 'v9K' // d771-d787 #244 + 'Z' // d788 #25 + 'b9K' // d789-d78b #244 + '1D' // d78c #29 + 'b9K' // d78d-d78f #244 + '1D' // d790 #29 + 'f9K' // d791-d797 #244 + 'V' // d798 #21 + '1F' // d799 #31 + '9K' // d79a #244 + '1P' // d79b #41 + '9K' // d79c #244 + '3V' // d79d #99 + 'e9K' // d79e-d7a3 #244 + '328kA' // d7a4-f8ff + '35X' // f900 #933 + '82V' // f901 #2153 + '53N' // f902 #1391 + '35X' // f903 #933 + '53M' // f904 #1390 + '35X' // f905 #933 + '53N' // f906 #1391 + '53M' // f907 #1390 + 'a35X' // f908-f909 #933 + '247X' // f90a #6445 + '1f35X' // f90b-f92b #933 + '247W' // f92c #6444 + '82U' // f92d #2152 + '247U' // f92e #6442 + 'd7Q' // f92f-f933 #198 + '82S' // f934 #2150 + 'a7Q' // f935-f936 #198 + '82T' // f937 #2151 + '1g7Q' // f938-f959 #198 + '260N' // f95a #6773 + 'b7Q' // f95b-f95d #198 + '21U' // f95e #566 + '49L' // f95f #1285 + 'a7Q' // f960-f961 #198 + '21U' // f962 #566 + 'a7Q' // f963-f964 #198 + '27T' // f965 #721 + '7Q' // f966 #198 + '27T' // f967 #721 + 'd7Q' // f968-f96c #198 + '82L' // f96d #2143 + 'e7Q' // f96e-f973 #198 + '247T' // f974 #6441 + '7Q' // f975 #198 + '21U' // f976 #566 + '7Q' // f977 #198 + '21U' // f978 #566 + '27T' // f979 #721 + 'c7Q' // f97a-f97d #198 + '27T' // f97e #721 + 'a7Q' // f97f-f980 #198 + '247V' // f981 #6443 + 'g7Q' // f982-f989 #198 + '27T' // f98a #721 + 'b7Q' // f98b-f98d #198 + '27T' // f98e #721 + 'l7Q' // f98f-f99b #198 + '21U' // f99c #566 + 'a7Q' // f99d-f99e #198 + '21U' // f99f #566 + 'i7Q' // f9a0-f9a9 #198 + '49L' // f9aa #1285 + 'i7Q' // f9ab-f9b4 #198 + '21U' // f9b5 #566 + 'f7Q' // f9b6-f9bc #198 + '21U' // f9bd #566 + '82M' // f9be #2144 + 'j7Q' // f9bf-f9c9 #198 + 'e13H' // f9ca-f9cf #345 + '82J' // f9d0 #2141 + 'f13H' // f9d1-f9d7 #345 + '21T' // f9d8 #565 + 'b13H' // f9d9-f9db #345 + '21T' // f9dc #565 + '37C' // f9dd #964 + 'a13H' // f9de-f9df #345 + '37C' // f9e0 #964 + 'b13H' // f9e1-f9e3 #345 + '37C' // f9e4 #964 + 'c13H' // f9e5-f9e8 #345 + '21T' // f9e9 #565 + 'h13H' // f9ea-f9f2 #345 + '260M' // f9f3 #6772 + '21T' // f9f4 #565 + 'd13H' // f9f5-f9f9 #345 + '21T' // f9fa #565 + 'a13H' // f9fb-f9fc #345 + '21T' // f9fd #565 + '13H' // f9fe #345 + '21T' // f9ff #565 + 'e13H' // fa00-fa05 #345 + '21T' // fa06 #565 + '13H' // fa07 #345 + '37C' // fa08 #964 + '13H' // fa09 #345 + '82K' // fa0a #2142 + '13H' // fa0b #345 + '82R' // fa0c #2149 + 'A' // fa0d + 'a49L' // fa0e-fa0f #1285 + '13G' // fa10 #344 + '252O' // fa11 #6566 + '3m13G' // fa12-fa6d #344 + '5oA' // fa6e-faff + '69N' // fb00 #1807 + '82I' // fb01 #2140 + '247S' // fb02 #6440 + 'a69N' // fb03-fb04 #1807 + 'aE' // fb05-fb06 #4 + 'kA' // fb07-fb12 + 'd36P' // fb13-fb17 #951 + 'dA' // fb18-fb1c + 'y21R' // fb1d-fb36 #563 + 'A' // fb37 + 'd21R' // fb38-fb3c #563 + 'A' // fb3d + '21R' // fb3e #563 + 'A' // fb3f + 'a21R' // fb40-fb41 #563 + 'A' // fb42 + 'a21R' // fb43-fb44 #563 + 'A' // fb45 + 'i21R' // fb46-fb4f #563 + '4j4W' // fb50-fbc2 #126 + 'oA' // fbc3-fbd2 + '13x4W' // fbd3-fd3d #126 + 'a263C' // fd3e-fd3f #6840 + '3a4W' // fd40-fd8f #126 + 'aA' // fd90-fd91 + '2a4W' // fd92-fdc7 #126 + 'fA' // fdc8-fdce + '4W' // fdcf #126 + '1eA' // fdd0-fdef + 'a4W' // fdf0-fdf1 #126 + '41Y' // fdf2 #1090 + 'i4W' // fdf3-fdfc #126 + '41Y' // fdfd #1090 + 'a4W' // fdfe-fdff #126 + '262K' // fe00 #6822 + 'nA' // fe01-fe0f + 'i13G' // fe10-fe19 #344 + 'eA' // fe1a-fe1f + 'cE' // fe20-fe23 #4 + 'b261X' // fe24-fe26 #6809 + 'cE' // fe27-fe2a #4 + 'b261U' // fe2b-fe2d #6806 + 'a41X' // fe2e-fe2f #1089 + '162Q' // fe30 #4228 + '64M' // fe31 #1676 + '13G' // fe32 #344 + 'c19Q' // fe33-fe36 #510 + '69M' // fe37 #1806 + 'j19Q' // fe38-fe42 #510 + '82N' // fe43 #2145 + '19Q' // fe44 #510 + 'c13G' // fe45-fe48 #344 + 'h19Q' // fe49-fe51 #510 + '126M' // fe52 #3288 + 'A' // fe53 + '19Q' // fe54 #510 + '64M' // fe55 #1676 + 'a19Q' // fe56-fe57 #510 + '13G' // fe58 #344 + 'c19Q' // fe59-fe5c #510 + 'a131P' // fe5d-fe5e #3421 + 'g19Q' // fe5f-fe66 #510 + 'A' // fe67 + 'b19Q' // fe68-fe6a #510 + '69M' // fe6b #1806 + 'cA' // fe6c-fe6f + 'd4W' // fe70-fe74 #126 + 'A' // fe75 + '5d4W' // fe76-fefc #126 + 'aA' // fefd-fefe + '71J' // feff #1855 + 'A' // ff00 + '245O' // ff01 #6384 + '126L' // ff02 #3287 + '142Y' // ff03 #3716 + '53J' // ff04 #1387 + '162C' // ff05 #4214 + '176X' // ff06 #4599 + '53J' // ff07 #1387 + 'a245N' // ff08-ff09 #6383 + '176G' // ff0a #4582 + '169J' // ff0b #4403 + '247K' // ff0c #6432 + '205R' // ff0d #5347 + '214F' // ff0e #5569 + '213T' // ff0f #5557 + '169O' // ff10 #4408 + '184H' // ff11 #4791 + '176Y' // ff12 #4600 + '176W' // ff13 #4598 + '162G' // ff14 #4218 + '169N' // ff15 #4407 + '155L' // ff16 #4041 + '155J' // ff17 #4039 + '155K' // ff18 #4040 + '155I' // ff19 #4038 + '246T' // ff1a #6415 + '219A' // ff1b #5694 + '149L' // ff1c #3885 + '149H' // ff1d #3881 + '184E' // ff1e #4788 + '245C' // ff1f #6372 + '143J' // ff20 #3727 + '149M' // ff21 #3886 + '137C' // ff22 #3564 + '64K' // ff23 #1674 + '137D' // ff24 #3565 + '63L' // ff25 #1649 + '53K' // ff26 #1388 + '43D' // ff27 #1121 + '53K' // ff28 #1388 + '53L' // ff29 #1389 + '43D' // ff2a #1121 + 'a53L' // ff2b-ff2c #1389 + '63L' // ff2d #1649 + '125Y' // ff2e #3274 + '137H' // ff2f #3569 + '64K' // ff30 #1674 + '82O' // ff31 #2146 + '82Q' // ff32 #2148 + '137I' // ff33 #3570 + '126A' // ff34 #3276 + '82P' // ff35 #2147 + '43D' // ff36 #1121 + '53I' // ff37 #1386 + '149F' // ff38 #3879 + '81Y' // ff39 #2130 + '81L' // ff3a #2117 + '64Y' // ff3b #1688 + '81X' // ff3c #2129 + '64Y' // ff3d #1688 + '82B' // ff3e #2133 + '52Y' // ff3f #1376 + '53I' // ff40 #1386 + '32F' // ff41 #837 + '81F' // ff42 #2111 + '32F' // ff43 #837 + '52Z' // ff44 #1377 + '32F' // ff45 #837 + '80V' // ff46 #2101 + '53E' // ff47 #1382 + '53C' // ff48 #1380 + '32F' // ff49 #837 + '52V' // ff4a #1373 + '53E' // ff4b #1382 + '53C' // ff4c #1380 + '82D' // ff4d #2135 + '81R' // ff4e #2123 + '32F' // ff4f #837 + '53A' // ff50 #1378 + '80S' // ff51 #2098 + 'b81T' // ff52-ff54 #2125 + '52Z' // ff55 #1377 + '81A' // ff56 #2106 + '82G' // ff57 #2138 + '52Y' // ff58 #1376 + '53A' // ff59 #1378 + '52V' // ff5a #1373 + '52U' // ff5b #1372 + '214A' // ff5c #5564 + '52U' // ff5d #1372 + '224D' // ff5e #5827 + 'a13G' // ff5f-ff60 #344 + '81W' // ff61 #2128 + 'a82A' // ff62-ff63 #2132 + '81O' // ff64 #2120 + '82H' // ff65 #2139 + '13G' // ff66 #344 + '80X' // ff67 #2103 + '81C' // ff68 #2108 + '247R' // ff69 #6439 + '80Z' // ff6a #2105 + '252X' // ff6b #6575 + '43C' // ff6c #1120 + '81E' // ff6d #2110 + '81B' // ff6e #2107 + '53H' // ff6f #1385 + '82E' // ff70 #2136 + '81S' // ff71 #2124 + '53H' // ff72 #1385 + '43C' // ff73 #1120 + '81G' // ff74 #2112 + '43C' // ff75 #1120 + '81Q' // ff76 #2122 + '81V' // ff77 #2127 + '37B' // ff78 #963 + '52W' // ff79 #1374 + '53D' // ff7a #1381 + '53B' // ff7b #1379 + '53G' // ff7c #1384 + '37B' // ff7d #963 + '52X' // ff7e #1375 + '70U' // ff7f #1840 + '81Z' // ff80 #2131 + '81N' // ff81 #2119 + '80W' // ff82 #2102 + '53D' // ff83 #1381 + '37B' // ff84 #963 + '52X' // ff85 #1375 + '43B' // ff86 #1119 + '13G' // ff87 #344 + '70U' // ff88 #1840 + '53F' // ff89 #1383 + '53G' // ff8a #1384 + '81J' // ff8b #2115 + '37B' // ff8c #963 + '43B' // ff8d #1119 + '53B' // ff8e #1379 + '81K' // ff8f #2116 + '52W' // ff90 #1374 + '81H' // ff91 #2113 + '43B' // ff92 #1119 + '80Y' // ff93 #2104 + '80U' // ff94 #2100 + '251U' // ff95 #6546 + '252A' // ff96 #6552 + '53F' // ff97 #1383 + 'a81U' // ff98-ff99 #2126 + '81M' // ff9a #2118 + '81P' // ff9b #2121 + '81D' // ff9c #2109 + '82C' // ff9d #2134 + '82F' // ff9e #2137 + '149J' // ff9f #3883 + 'A' // ffa0 + 'w13G' // ffa1-ffb8 #344 + '80T' // ffb9 #2099 + 'd13G' // ffba-ffbe #344 + 'bA' // ffbf-ffc1 + 'e13G' // ffc2-ffc7 #344 + 'aA' // ffc8-ffc9 + 'e13G' // ffca-ffcf #344 + 'aA' // ffd0-ffd1 + 'd13G' // ffd2-ffd6 #344 + '19F' // ffd7 #499 + 'aA' // ffd8-ffd9 + 'b19F' // ffda-ffdc #499 + 'bA' // ffdd-ffdf + 'b80R' // ffe0-ffe2 #2097 + '81I' // ffe3 #2114 + '247Q' // ffe4 #6438 + '125W' // ffe5 #3272 + '247P' // ffe6 #6437 + 'A' // ffe7 + '19F' // ffe8 #499 + '43A' // ffe9 #1118 + '19F' // ffea #499 + '43A' // ffeb #1118 + '19F' // ffec #499 + '43A' // ffed #1118 + '19F' // ffee #499 + 'iA' // ffef-fff8 + 'bF' // fff9-fffb #5 + 'aE' // fffc-fffd #4 + 'aA' // fffe-ffff + 'k24I' // 10000-1000b #632 + 'A' // 1000c + 'y24I' // 1000d-10026 #632 + 'A' // 10027 + 'r24I' // 10028-1003a #632 + 'A' // 1003b + 'a24I' // 1003c-1003d #632 + 'A' // 1003e + 'n24I' // 1003f-1004d #632 + 'aA' // 1004e-1004f + 'm24I' // 10050-1005d #632 + '1gA' // 1005e-1007f + '4r24I' // 10080-100fa #632 + 'dA' // 100fb-100ff + 'b71W' // 10100-10102 #1868 + 'cA' // 10103-10106 + '1r71W' // 10107-10133 #1868 + 'bA' // 10134-10136 + 'h24I' // 10137-1013f #632 + '2zF' // 10140-1018e #5 + 'A' // 1018f + 'lF' // 10190-1019c #5 + 'bA' // 1019d-1019f + 'F' // 101a0 #5 + '1tA' // 101a1-101cf + '1sF' // 101d0-101fd #5 + '4yA' // 101fe-1027f + '1b264E' // 10280-1029c #6868 + 'bA' // 1029d-1029f + '1v263M' // 102a0-102d0 #6850 + 'nA' // 102d1-102df + '1a78O' // 102e0-102fb #2042 + 'cA' // 102fc-102ff + '1i72O' // 10300-10323 #1886 + 'hA' // 10324-1032c + 'b72O' // 1032d-1032f #1886 + 'z263Y' // 10330-1034a #6862 + 'dA' // 1034b-1034f + '1p264L' // 10350-1037a #6875 + 'dA' // 1037b-1037f + '1c73F' // 10380-1039d #1903 + 'A' // 1039e + '73F' // 1039f #1903 + '1i72P' // 103a0-103c3 #1887 + 'cA' // 103c4-103c7 + 'm72P' // 103c8-103d5 #1887 + '1oA' // 103d6-103ff + '3a263O' // 10400-1044f #6852 + '1u264U' // 10450-1047f #6884 + '1c72R' // 10480-1049d #1889 + 'aA' // 1049e-1049f + 'i72R' // 104a0-104a9 #1889 + 'eA' // 104aa-104af + '1i72Q' // 104b0-104d3 #1888 + 'cA' // 104d4-104d7 + '1i72Q' // 104d8-104fb #1888 + 'cA' // 104fc-104ff + '1m263V' // 10500-10527 #6859 + 'gA' // 10528-1052f + '1y71U' // 10530-10563 #1866 + 'jA' // 10564-1056e + '71U' // 1056f #1866 + '5mA' // 10570-105ff + '11x50W' // 10600-10736 #1322 + 'hA' // 10737-1073f + 'u50W' // 10740-10755 #1322 + 'iA' // 10756-1075f + 'g50W' // 10760-10767 #1322 + 'wA' // 10768-1077f + 'eE' // 10780-10785 #4 + 'A' // 10786 + '1oE' // 10787-107b0 #4 + 'A' // 107b1 + 'hE' // 107b2-107ba #4 + '2pA' // 107bb-107ff + 'e31T' // 10800-10805 #825 + 'aA' // 10806-10807 + '31T' // 10808 #825 + 'A' // 10809 + '1q31T' // 1080a-10835 #825 + 'A' // 10836 + 'a31T' // 10837-10838 #825 + 'bA' // 10839-1083b + '31T' // 1083c #825 + 'aA' // 1083d-1083e + '31T' // 1083f #825 + 'u71X' // 10840-10855 #1869 + 'A' // 10856 + 'h71X' // 10857-1085f #1869 + '1e264P' // 10860-1087f #6879 + '1d72L' // 10880-1089e #1883 + 'gA' // 1089f-108a6 + 'h72L' // 108a7-108af #1883 + '1uA' // 108b0-108df + 'r50T' // 108e0-108f2 #1319 + 'A' // 108f3 + 'a50T' // 108f4-108f5 #1319 + 'dA' // 108f6-108fa + 'd50T' // 108fb-108ff #1319 + '1a72S' // 10900-1091b #1890 + 'bA' // 1091c-1091e + '72S' // 1091f #1890 + 'y72F' // 10920-10939 #1877 + 'dA' // 1093a-1093e + '72F' // 1093f #1877 + '2kA' // 10940-1097f + '2c50Z' // 10980-109b7 #1325 + 'cA' // 109b8-109bb + 's50Z' // 109bc-109cf #1325 + 'aA' // 109d0-109d1 + '1s50Z' // 109d2-109ff #1325 + 'c24H' // 10a00-10a03 #631 + 'A' // 10a04 + 'a24H' // 10a05-10a06 #631 + 'dA' // 10a07-10a0b + 'g24H' // 10a0c-10a13 #631 + 'A' // 10a14 + 'b24H' // 10a15-10a17 #631 + 'A' // 10a18 + '1b24H' // 10a19-10a35 #631 + 'aA' // 10a36-10a37 + 'b24H' // 10a38-10a3a #631 + 'cA' // 10a3b-10a3e + 'i24H' // 10a3f-10a48 #631 + 'fA' // 10a49-10a4f + 'h24H' // 10a50-10a58 #631 + 'fA' // 10a59-10a5f + '1e264N' // 10a60-10a7f #6877 + '1e264K' // 10a80-10a9f #6874 + '1eA' // 10aa0-10abf + '1l72H' // 10ac0-10ae6 #1879 + 'cA' // 10ae7-10aea + 'k72H' // 10aeb-10af6 #1879 + 'hA' // 10af7-10aff + '2a71N' // 10b00-10b35 #1859 + 'bA' // 10b36-10b38 + 'f71N' // 10b39-10b3f #1859 + 'u71Z' // 10b40-10b55 #1871 + 'aA' // 10b56-10b57 + 'g71Z' // 10b58-10b5f #1871 + 'r71Y' // 10b60-10b72 #1870 + 'dA' // 10b73-10b77 + 'g71Y' // 10b78-10b7f #1870 + 'q51D' // 10b80-10b91 #1329 + 'fA' // 10b92-10b98 + 'c51D' // 10b99-10b9c #1329 + 'kA' // 10b9d-10ba8 + 'f51D' // 10ba9-10baf #1329 + '3aA' // 10bb0-10bff + '2t264O' // 10c00-10c48 #6878 + '2bA' // 10c49-10c7f + '1x51C' // 10c80-10cb2 #1328 + 'lA' // 10cb3-10cbf + '1x51C' // 10cc0-10cf2 #1328 + 'fA' // 10cf3-10cf9 + 'e51C' // 10cfa-10cff #1328 + '13mA' // 10d00-10e5f + '1dF' // 10e60-10e7e #5 + '4uA' // 10e7f-10efc + 'b4W' // 10efd-10eff #126 + '1m264M' // 10f00-10f27 #6876 + 'gA' // 10f28-10f2f + '1o264V' // 10f30-10f59 #6885 + '5cA' // 10f5a-10fdf + 'v263W' // 10fe0-10ff6 #6860 + 'hA' // 10ff7-10fff + '2y50O' // 11000-1104d #1314 + 'cA' // 1104e-11051 + '1i50O' // 11052-11075 #1314 + 'hA' // 11076-1107e + '50O' // 1107f #1314 + '2n72A' // 11080-110c2 #1872 + 'iA' // 110c3-110cc + '72A' // 110cd #1872 + 'aA' // 110ce-110cf + 'x72W' // 110d0-110e8 #1894 + 'fA' // 110e9-110ef + 'i72W' // 110f0-110f9 #1894 + 'eA' // 110fa-110ff + '1z71V' // 11100-11134 #1867 + 'A' // 11135 + 'q71V' // 11136-11147 #1867 + 'gA' // 11148-1114f + '1l264F' // 11150-11176 #6869 + 'hA' // 11177-1117f + '3q264T' // 11180-111df #6883 + 'A' // 111e0 + 's15C' // 111e1-111f4 #392 + 'jA' // 111f5-111ff + 'q72C' // 11200-11211 #1874 + 'A' // 11212 + '1s72C' // 11213-11240 #1874 + '2jA' // 11241-1127f + 'f36R' // 11280-11286 #953 + 'A' // 11287 + '36R' // 11288 #953 + 'A' // 11289 + 'c36R' // 1128a-1128d #953 + 'A' // 1128e + 'n36R' // 1128f-1129d #953 + 'A' // 1129e + 'j36R' // 1129f-112a9 #953 + 'eA' // 112aa-112af + '2f72D' // 112b0-112ea #1875 + 'dA' // 112eb-112ef + 'i72D' // 112f0-112f9 #1875 + 'eA' // 112fa-112ff + '11O' // 11300 #300 + '31U' // 11301 #826 + '11O' // 11302 #300 + '31U' // 11303 #826 + 'A' // 11304 + 'g11O' // 11305-1130c #300 + 'aA' // 1130d-1130e + 'a11O' // 1130f-11310 #300 + 'aA' // 11311-11312 + 'u11O' // 11313-11328 #300 + 'A' // 11329 + 'f11O' // 1132a-11330 #300 + 'A' // 11331 + 'a11O' // 11332-11333 #300 + 'A' // 11334 + 'd11O' // 11335-11339 #300 + 'A' // 1133a + 'a31U' // 1133b-1133c #826 + 'g11O' // 1133d-11344 #300 + 'aA' // 11345-11346 + 'a11O' // 11347-11348 #300 + 'aA' // 11349-1134a + 'b11O' // 1134b-1134d #300 + 'aA' // 1134e-1134f + '11O' // 11350 #300 + 'eA' // 11351-11356 + '11O' // 11357 #300 + 'dA' // 11358-1135c + 'f11O' // 1135d-11363 #300 + 'aA' // 11364-11365 + 'f11O' // 11366-1136c #300 + 'bA' // 1136d-1136f + 'd11O' // 11370-11374 #300 + '5hA' // 11375-113ff + '3m72M' // 11400-1145b #1884 + 'A' // 1145c + 'd72M' // 1145d-11461 #1884 + '1cA' // 11462-1147f + '2s73E' // 11480-114c7 #1902 + 'gA' // 114c8-114cf + 'i73E' // 114d0-114d9 #1902 + '6iA' // 114da-1157f + '2a72V' // 11580-115b5 #1893 + 'aA' // 115b6-115b7 + '1k72V' // 115b8-115dd #1893 + '1gA' // 115de-115ff + '2p72I' // 11600-11644 #1880 + 'jA' // 11645-1164f + 'i72I' // 11650-11659 #1880 + 'eA' // 1165a-1165f + 'l31X' // 11660-1166c #829 + 'rA' // 1166d-1167f + '2e73B' // 11680-116b9 #1899 + 'eA' // 116ba-116bf + 'i73B' // 116c0-116c9 #1899 + '18aA' // 116ca-1189f + '3d73H' // 118a0-118f2 #1905 + 'kA' // 118f3-118fe + '73H' // 118ff #1905 + '9uA' // 11900-119ff + '2s265B' // 11a00-11a47 #6891 + 'gA' // 11a48-11a4f + '3d264W' // 11a50-11aa2 #6886 + 'lA' // 11aa3-11aaf + 'o50P' // 11ab0-11abf #1315 + '2d264Q' // 11ac0-11af8 #6880 + 'fA' // 11af9-11aff + 'i17L' // 11b00-11b09 #453 + '9kA' // 11b0a-11bff + 'h41Z' // 11c00-11c08 #1091 + 'A' // 11c09 + '1r41Z' // 11c0a-11c36 #1091 + 'A' // 11c37 + 'm41Z' // 11c38-11c45 #1091 + 'iA' // 11c46-11c4f + '1b41Z' // 11c50-11c6c #1091 + 'bA' // 11c6d-11c6f + '1e50X' // 11c70-11c8f #1323 + 'aA' // 11c90-11c91 + 'u50X' // 11c92-11ca7 #1323 + 'A' // 11ca8 + 'm50X' // 11ca9-11cb6 #1323 + '2tA' // 11cb7-11cff + 'f27K' // 11d00-11d06 #712 + 'A' // 11d07 + 'a27K' // 11d08-11d09 #712 + 'A' // 11d0a + '1q27K' // 11d0b-11d36 #712 + 'bA' // 11d37-11d39 + '27K' // 11d3a #712 + 'A' // 11d3b + 'a27K' // 11d3c-11d3d #712 + 'A' // 11d3e + 'h27K' // 11d3f-11d47 #712 + 'gA' // 11d48-11d4f + 'i27K' // 11d50-11d59 #712 + 'eA' // 11d5a-11d5f + 'e31V' // 11d60-11d65 #827 + 'A' // 11d66 + 'a31V' // 11d67-11d68 #827 + 'A' // 11d69 + '1j31V' // 11d6a-11d8e #827 + 'A' // 11d8f + 'a31V' // 11d90-11d91 #827 + 'A' // 11d92 + 'e31V' // 11d93-11d98 #827 + 'fA' // 11d99-11d9f + 'i31V' // 11da0-11da9 #827 + '19wA' // 11daa-11faf + '72E' // 11fb0 #1876 + 'nA' // 11fb1-11fbf + '1w73C' // 11fc0-11ff1 #1900 + 'lA' // 11ff2-11ffe + '73C' // 11fff #1900 + '35k36W' // 12000-12399 #958 + '3wA' // 1239a-123ff + '4f36W' // 12400-1246e #958 + 'A' // 1246f + 'd36W' // 12470-12474 #958 + 'jA' // 12475-1247f + '7m36W' // 12480-12543 #958 + '105qA' // 12544-12fff + '33s42X' // 13000-1336d #1115 + '80G' // 1336e #2086 + '7j42X' // 1336f-1342f #1115 + 'pA' // 13430-13440 + 'e42X' // 13441-13446 #1115 + '154tA' // 13447-143ff + '22j262Z' // 14400-14646 #6837 + '331zA' // 14647-167ff + '21v71P' // 16800-16a38 #1861 + 'fA' // 16a39-16a3f + '1d51B' // 16a40-16a5e #1327 + 'A' // 16a5f + 'i51B' // 16a60-16a69 #1327 + 'cA' // 16a6a-16a6d + 'a51B' // 16a6e-16a6f #1327 + '3qA' // 16a70-16acf + '1c71Q' // 16ad0-16aed #1862 + 'aA' // 16aee-16aef + 'e71Q' // 16af0-16af5 #1862 + 'iA' // 16af6-16aff + '2q36S' // 16b00-16b45 #954 + 'iA' // 16b46-16b4f + 'i36S' // 16b50-16b59 #954 + 'A' // 16b5a + 'f36S' // 16b5b-16b61 #954 + 'A' // 16b62 + 't36S' // 16b63-16b77 #954 + 'dA' // 16b78-16b7c + 'r36S' // 16b7d-16b8f #954 + '26kA' // 16b90-16e3f + '3l264H' // 16e40-16e9a #6871 + '3vA' // 16e9b-16eff + '2v51A' // 16f00-16f4a #1326 + 'cA' // 16f4b-16f4e + '2d51A' // 16f4f-16f87 #1326 + 'fA' // 16f88-16f8e + 'p51A' // 16f8f-16f9f #1326 + '2lA' // 16fa0-16fe0 + '72N' // 16fe1 #1885 + '645kA' // 16fe2-1b16f + '15e72N' // 1b170-1b2fb #1885 + '88sA' // 1b2fc-1bbff + '4b32E' // 1bc00-1bc6a #836 + 'dA' // 1bc6b-1bc6f + 'l32E' // 1bc70-1bc7c #836 + 'bA' // 1bc7d-1bc7f + 'h32E' // 1bc80-1bc88 #836 + 'fA' // 1bc89-1bc8f + 'i32E' // 1bc90-1bc99 #836 + 'aA' // 1bc9a-1bc9b + 'g32E' // 1bc9c-1bca3 #836 + '190oA' // 1bca4-1cfff + '9k41U' // 1d000-1d0f5 #1086 + 'iA' // 1d0f6-1d0ff + '1l41U' // 1d100-1d126 #1086 + 'aA' // 1d127-1d128 + '7k41U' // 1d129-1d1ea #1086 + 'tA' // 1d1eb-1d1ff + '2q41U' // 1d200-1d245 #1086 + '4qA' // 1d246-1d2bf + 'sF' // 1d2c0-1d2d3 #5 + 'kA' // 1d2d4-1d2df + 's77M' // 1d2e0-1d2f3 #2014 + 'kA' // 1d2f4-1d2ff + '3hF' // 1d300-1d356 #5 + 'hA' // 1d357-1d35f + 'xF' // 1d360-1d378 #5 + '5dA' // 1d379-1d3ff + '3fM' // 1d400-1d454 #12 + 'A' // 1d455 + '2rM' // 1d456-1d49c #12 + 'A' // 1d49d + 'aM' // 1d49e-1d49f #12 + 'aA' // 1d4a0-1d4a1 + 'M' // 1d4a2 #12 + 'aA' // 1d4a3-1d4a4 + 'aM' // 1d4a5-1d4a6 #12 + 'aA' // 1d4a7-1d4a8 + 'cM' // 1d4a9-1d4ac #12 + 'A' // 1d4ad + 'kM' // 1d4ae-1d4b9 #12 + 'A' // 1d4ba + 'M' // 1d4bb #12 + 'A' // 1d4bc + 'fM' // 1d4bd-1d4c3 #12 + 'A' // 1d4c4 + '2lM' // 1d4c5-1d505 #12 + 'A' // 1d506 + 'cM' // 1d507-1d50a #12 + 'aA' // 1d50b-1d50c + 'gM' // 1d50d-1d514 #12 + 'A' // 1d515 + 'fM' // 1d516-1d51c #12 + 'A' // 1d51d + '1aM' // 1d51e-1d539 #12 + 'A' // 1d53a + 'cM' // 1d53b-1d53e #12 + 'A' // 1d53f + 'dM' // 1d540-1d544 #12 + 'A' // 1d545 + 'M' // 1d546 #12 + 'bA' // 1d547-1d549 + 'fM' // 1d54a-1d550 #12 + 'A' // 1d551 + '13aM' // 1d552-1d6a5 #12 + 'aA' // 1d6a6-1d6a7 + '11eM' // 1d6a8-1d7cb #12 + 'aA' // 1d7cc-1d7cd + '1wM' // 1d7ce-1d7ff #12 + '68wA' // 1d800-1deff + '1dE' // 1df00-1df1e #4 + '8pA' // 1df1f-1dfff + 'f27I' // 1e000-1e006 #710 + 'A' // 1e007 + 'p27I' // 1e008-1e018 #710 + 'aA' // 1e019-1e01a + 'f27I' // 1e01b-1e021 #710 + 'A' // 1e022 + 'a27I' // 1e023-1e024 #710 + 'A' // 1e025 + 'd27I' // 1e026-1e02a #710 + '25jA' // 1e02b-1e2bf + '2e73G' // 1e2c0-1e2f9 #1904 + 'dA' // 1e2fa-1e2fe + '73G' // 1e2ff #1904 + '47yA' // 1e300-1e7df + 'f3R' // 1e7e0-1e7e6 #95 + 'A' // 1e7e7 + 'c3R' // 1e7e8-1e7eb #95 + 'A' // 1e7ec + 'a3R' // 1e7ed-1e7ee #95 + 'A' // 1e7ef + 'n3R' // 1e7f0-1e7fe #95 + '9vA' // 1e7ff-1e8ff + '2w50L' // 1e900-1e94b #1311 + 'cA' // 1e94c-1e94f + 'i50L' // 1e950-1e959 #1311 + 'cA' // 1e95a-1e95d + 'a50L' // 1e95e-1e95f #1311 + '30dA' // 1e960-1ec70 + '2o264D' // 1ec71-1ecb4 #6867 + '12rA' // 1ecb5-1edff + 'cM' // 1ee00-1ee03 #12 + 'A' // 1ee04 + 'zM' // 1ee05-1ee1f #12 + 'A' // 1ee20 + 'aM' // 1ee21-1ee22 #12 + 'A' // 1ee23 + 'M' // 1ee24 #12 + 'aA' // 1ee25-1ee26 + 'M' // 1ee27 #12 + 'A' // 1ee28 + 'iM' // 1ee29-1ee32 #12 + 'A' // 1ee33 + 'cM' // 1ee34-1ee37 #12 + 'A' // 1ee38 + 'M' // 1ee39 #12 + 'A' // 1ee3a + 'M' // 1ee3b #12 + 'eA' // 1ee3c-1ee41 + 'M' // 1ee42 #12 + 'cA' // 1ee43-1ee46 + 'M' // 1ee47 #12 + 'A' // 1ee48 + 'M' // 1ee49 #12 + 'A' // 1ee4a + 'M' // 1ee4b #12 + 'A' // 1ee4c + 'bM' // 1ee4d-1ee4f #12 + 'A' // 1ee50 + 'aM' // 1ee51-1ee52 #12 + 'A' // 1ee53 + 'M' // 1ee54 #12 + 'aA' // 1ee55-1ee56 + 'M' // 1ee57 #12 + 'A' // 1ee58 + 'M' // 1ee59 #12 + 'A' // 1ee5a + 'M' // 1ee5b #12 + 'A' // 1ee5c + 'M' // 1ee5d #12 + 'A' // 1ee5e + 'M' // 1ee5f #12 + 'A' // 1ee60 + 'aM' // 1ee61-1ee62 #12 + 'A' // 1ee63 + 'M' // 1ee64 #12 + 'aA' // 1ee65-1ee66 + 'cM' // 1ee67-1ee6a #12 + 'A' // 1ee6b + 'fM' // 1ee6c-1ee72 #12 + 'A' // 1ee73 + 'cM' // 1ee74-1ee77 #12 + 'A' // 1ee78 + 'cM' // 1ee79-1ee7c #12 + 'A' // 1ee7d + 'M' // 1ee7e #12 + 'A' // 1ee7f + 'iM' // 1ee80-1ee89 #12 + 'A' // 1ee8a + 'pM' // 1ee8b-1ee9b #12 + 'dA' // 1ee9c-1eea0 + 'bM' // 1eea1-1eea3 #12 + 'A' // 1eea4 + 'dM' // 1eea5-1eea9 #12 + 'A' // 1eeaa + 'pM' // 1eeab-1eebb #12 + '1yA' // 1eebc-1eeef + 'aM' // 1eef0-1eef1 #12 + '10iA' // 1eef2-1efff + 'cF' // 1f000-1f003 #5 + '6J' // 1f004 #165 + '1lF' // 1f005-1f02b #5 + 'cA' // 1f02c-1f02f + '3uF' // 1f030-1f093 #5 + 'kA' // 1f094-1f09f + 'nF' // 1f0a0-1f0ae #5 + 'aA' // 1f0af-1f0b0 + 'nF' // 1f0b1-1f0bf #5 + 'A' // 1f0c0 + 'mF' // 1f0c1-1f0ce #5 + '6J' // 1f0cf #165 + 'A' // 1f0d0 + '1jF' // 1f0d1-1f0f5 #5 + 'iA' // 1f0f6-1f0ff + 'l12J' // 1f100-1f10c #321 + 'bF' // 1f10d-1f10f #5 + '1v12J' // 1f110-1f140 #321 + 'a17P' // 1f141-1f142 #457 + 'l12J' // 1f143-1f14f #321 + '17P' // 1f150 #457 + 'b12J' // 1f151-1f153 #321 + '17P' // 1f154 #457 + 'b12J' // 1f155-1f157 #321 + '17P' // 1f158 #457 + 'a12J' // 1f159-1f15a #321 + '17P' // 1f15b #457 + '12J' // 1f15c #321 + 'a17P' // 1f15d-1f15e #457 + 'b12J' // 1f15f-1f161 #321 + 'a17P' // 1f162-1f163 #457 + 'h12J' // 1f164-1f16c #321 + 'bF' // 1f16d-1f16f #5 + 'a74M' // 1f170-1f171 #1936 + 'a12J' // 1f172-1f173 #321 + '17P' // 1f174 #457 + 'a12J' // 1f175-1f176 #321 + 'a17P' // 1f177-1f178 #457 + 'c12J' // 1f179-1f17c #321 + '17P' // 1f17d #457 + 'a74N' // 1f17e-1f17f #1937 + 'm12J' // 1f180-1f18d #321 + '74Y' // 1f18e #1948 + 'a12J' // 1f18f-1f190 #321 + '42H' // 1f191 #1099 + 'c51N' // 1f192-1f195 #1339 + '42H' // 1f196 #1099 + '42G' // 1f197 #1098 + 'b51N' // 1f198-1f19a #1339 + 'q12J' // 1f19b-1f1ac #321 + 'F' // 1f1ad #5 + '2cA' // 1f1ae-1f1e5 + 'y73J' // 1f1e6-1f1ff #1907 + '19F' // 1f200 #499 + 'a42H' // 1f201-1f202 #1099 + 'lA' // 1f203-1f20f + 'i19F' // 1f210-1f219 #499 + '42F' // 1f21a #1097 + 's19F' // 1f21b-1f22e #499 + '42G' // 1f22f #1098 + 'a19F' // 1f230-1f231 #499 + '42F' // 1f232 #1097 + '42G' // 1f233 #1098 + '42F' // 1f234 #1097 + 'b51M' // 1f235-1f237 #1338 + '74X' // 1f238 #1947 + 'a74L' // 1f239-1f23a #1935 + 'C' // 1f23b #2 + 'cA' // 1f23c-1f23f + 'hC' // 1f240-1f248 #2 + 'fA' // 1f249-1f24f + 'a51M' // 1f250-1f251 #1338 + '6qA' // 1f252-1f2ff + '5F' // 1f300 #135 + '4X' // 1f301 #127 + '2S' // 1f302 #70 + '4X' // 1f303 #127 + 'a5F' // 1f304-1f305 #135 + 'a4X' // 1f306-1f307 #127 + '73M' // 1f308 #1910 + '4X' // 1f309 #127 + 'b5F' // 1f30a-1f30c #135 + 'b5Z' // 1f30d-1f30f #155 + '51H' // 1f310 #1333 + 'c5F' // 1f311-1f314 #135 + '5Z' // 1f315 #155 + 'c5F' // 1f316-1f319 #135 + 'a27N' // 1f31a-1f31b #715 + '36U' // 1f31c #956 + 'b27N' // 1f31d-1f31f #715 + '5F' // 1f320 #135 + '75E' // 1f321 #1954 + 'aF' // 1f322-1f323 #5 + 'f5Z' // 1f324-1f32a #155 + '36U' // 1f32b #956 + '5Z' // 1f32c #155 + 'c15F' // 1f32d-1f330 #395 + 'd5F' // 1f331-1f335 #135 + '32A' // 1f336 #832 + 'e5F' // 1f337-1f33c #135 + '15F' // 1f33d #395 + '75R' // 1f33e #1967 + 'd5F' // 1f33f-1f343 #135 + '75T' // 1f344 #1969 + 'e15F' // 1f345-1f34a #395 + '75Q' // 1f34b #1966 + '1l15F' // 1f34c-1f372 #395 + '51V' // 1f373 #1347 + 'c15F' // 1f374-1f377 #395 + '32A' // 1f378 #832 + 'b15F' // 1f379-1f37b #395 + '51V' // 1f37c #1347 + '32A' // 1f37d #832 + 'a15F' // 1f37e-1f37f #395 + 'a5P' // 1f380-1f381 #145 + '75N' // 1f382 #1963 + '42L' // 1f383 #1103 + '42K' // 1f384 #1102 + '4S' // 1f385 #122 + 'b5P' // 1f386-1f388 #145 + '42L' // 1f389 #1103 + 'a5P' // 1f38a-1f38b #145 + '15E' // 1f38c #394 + 'd5P' // 1f38d-1f391 #145 + '2S' // 1f392 #70 + '51R' // 1f393 #1343 + 'aF' // 1f394-1f395 #5 + 'a6J' // 1f396-1f397 #165 + 'F' // 1f398 #5 + 'b6J' // 1f399-1f39b #165 + 'aF' // 1f39c-1f39d #5 + 'a6J' // 1f39e-1f39f #165 + 'b4X' // 1f3a0-1f3a2 #127 + '5P' // 1f3a3 #145 + '42K' // 1f3a4 #1102 + '5P' // 1f3a5 #145 + '3S' // 1f3a6 #96 + '6J' // 1f3a7 #165 + '42K' // 1f3a8 #1102 + '42I' // 1f3a9 #1100 + '4X' // 1f3aa #127 + '5P' // 1f3ab #145 + 'b6J' // 1f3ac-1f3ae #165 + 'e5P' // 1f3af-1f3b4 #145 + 'a3S' // 1f3b5-1f3b6 #96 + 'd5P' // 1f3b7-1f3bb #145 + '3S' // 1f3bc #96 + '42I' // 1f3bd #1100 + 'b5P' // 1f3be-1f3c0 #145 + '15E' // 1f3c1 #394 + '27O' // 1f3c2 #716 + '32B' // 1f3c3 #833 + '27O' // 1f3c4 #716 + '5P' // 1f3c5 #145 + '6J' // 1f3c6 #165 + '4S' // 1f3c7 #122 + 'a5P' // 1f3c8-1f3c9 #145 + 'b27O' // 1f3ca-1f3cc #716 + 'a9O' // 1f3cd-1f3ce #248 + 'd5P' // 1f3cf-1f3d3 #145 + '5Z' // 1f3d4 #155 + '9O' // 1f3d5 #248 + '5Z' // 1f3d6 #155 + 'd9O' // 1f3d7-1f3db #248 + 'b5Z' // 1f3dc-1f3de #155 + 'a9O' // 1f3df-1f3e0 #248 + 'e4X' // 1f3e1-1f3e6 #127 + '3S' // 1f3e7 #96 + 'b4X' // 1f3e8-1f3ea #127 + '42M' // 1f3eb #1104 + '4X' // 1f3ec #127 + '51T' // 1f3ed #1345 + '2S' // 1f3ee #70 + 'a4X' // 1f3ef-1f3f0 #127 + 'aF' // 1f3f1-1f3f2 #5 + '73O' // 1f3f3 #1912 + '15E' // 1f3f4 #394 + '5Z' // 1f3f5 #155 + 'F' // 1f3f6 #5 + '2E' // 1f3f7 #56 + 'a5P' // 1f3f8-1f3f9 #145 + '2S' // 1f3fa #70 + 'd75X' // 1f3fb-1f3ff #1973 + 'g5F' // 1f400-1f407 #135 + '5Z' // 1f408 #155 + 'k5F' // 1f409-1f414 #135 + '5Z' // 1f415 #155 + 'h5F' // 1f416-1f41e #135 + '5Z' // 1f41f #155 + 'e5F' // 1f420-1f425 #135 + '75U' // 1f426 #1970 + 'w5F' // 1f427-1f43e #135 + '5Z' // 1f43f #155 + '3O' // 1f440 #92 + '73U' // 1f441 #1918 + '17O' // 1f442 #456 + 'b3O' // 1f443-1f445 #92 + 'c17O' // 1f446-1f449 #456 + 'a3O' // 1f44a-1f44b #92 + 'b17O' // 1f44c-1f44e #456 + 'a3O' // 1f44f-1f450 #92 + 'a2S' // 1f451-1f452 #70 + '2E' // 1f453 #56 + 'n2S' // 1f454-1f462 #70 + 'b3O' // 1f463-1f465 #92 + 'a4S' // 1f466-1f467 #122 + 'a32B' // 1f468-1f469 #833 + '27O' // 1f46a #716 + 'm4S' // 1f46b-1f478 #122 + 'b3O' // 1f479-1f47b #92 + '4S' // 1f47c #122 + '17O' // 1f47d #456 + '42L' // 1f47e #1103 + 'a3O' // 1f47f-1f480 #92 + 'b4S' // 1f481-1f483 #122 + '2S' // 1f484 #70 + '3O' // 1f485 #92 + 'a4S' // 1f486-1f487 #122 + '4X' // 1f488 #127 + 'a2S' // 1f489-1f48a #70 + '51X' // 1f48b #1349 + '75H' // 1f48c #1957 + 'a2S' // 1f48d-1f48e #70 + '4S' // 1f48f #122 + '5F' // 1f490 #135 + '4S' // 1f491 #122 + '4X' // 1f492 #127 + 'e3O' // 1f493-1f498 #92 + 'c27L' // 1f499-1f49c #713 + 'a3O' // 1f49d-1f49e #92 + '27L' // 1f49f #713 + '3S' // 1f4a0 #96 + '2S' // 1f4a1 #70 + '3S' // 1f4a2 #96 + '2E' // 1f4a3 #56 + '3O' // 1f4a4 #92 + '51Z' // 1f4a5 #1351 + '3O' // 1f4a6 #92 + '5F' // 1f4a7 #135 + 'b3O' // 1f4a8-1f4aa #92 + '27N' // 1f4ab #715 + 'a3S' // 1f4ac-1f4ad #96 + '5F' // 1f4ae #135 + '3O' // 1f4af #92 + '2E' // 1f4b0 #56 + 'a3S' // 1f4b1-1f4b2 #96 + '2E' // 1f4b3 #56 + 'd2S' // 1f4b4-1f4b8 #70 + '4M' // 1f4b9 #116 + '4X' // 1f4ba #127 + '51R' // 1f4bb #1343 + '42J' // 1f4bc #1101 + 'a2S' // 1f4bd-1f4be #70 + '2E' // 1f4bf #56 + 'g2S' // 1f4c0-1f4c7 #70 + 'c2E' // 1f4c8-1f4cb #56 + 'm2S' // 1f4cc-1f4d9 #70 + '2E' // 1f4da #56 + '3S' // 1f4db #96 + 'b2S' // 1f4dc-1f4de #70 + '2E' // 1f4df #56 + 'a2S' // 1f4e0-1f4e1 #70 + '42I' // 1f4e2 #1100 + '2S' // 1f4e3 #70 + 'b2E' // 1f4e4-1f4e6 #56 + 'b2S' // 1f4e7-1f4e9 #70 + 'c2E' // 1f4ea-1f4ed #56 + 'c2S' // 1f4ee-1f4f1 #70 + 'd3S' // 1f4f2-1f4f6 #96 + '6J' // 1f4f7 #165 + '5P' // 1f4f8 #145 + 'b6J' // 1f4f9-1f4fb #165 + '5P' // 1f4fc #145 + '6J' // 1f4fd #165 + 'F' // 1f4fe #5 + '2S' // 1f4ff #70 + 'b3S' // 1f500-1f502 #96 + '4M' // 1f503 #116 + 'b3S' // 1f504-1f506 #96 + 'c4M' // 1f507-1f50a #116 + 'a2S' // 1f50b-1f50c #70 + '2E' // 1f50d #56 + 'c2S' // 1f50e-1f511 #70 + 'a2E' // 1f512-1f513 #56 + '2S' // 1f514 #70 + '3S' // 1f515 #96 + 'a2S' // 1f516-1f517 #70 + 'l3S' // 1f518-1f524 #96 + '75S' // 1f525 #1968 + '2S' // 1f526 #70 + '42J' // 1f527 #1101 + 'a2S' // 1f528-1f529 #70 + '15F' // 1f52a #395 + '5P' // 1f52b #145 + '42J' // 1f52c #1101 + 'a2S' // 1f52d-1f52e #70 + 'n3S' // 1f52f-1f53d #96 + 'gF' // 1f53e-1f545 #5 + 'b2M' // 1f546-1f548 #64 + '17N' // 1f549 #455 + '5Z' // 1f54a #155 + 'b4X' // 1f54b-1f54d #127 + '3S' // 1f54e #96 + '2M' // 1f54f #64 + 'w2E' // 1f550-1f567 #56 + 'fF' // 1f568-1f56e #5 + 'a2E' // 1f56f-1f570 #56 + 'aF' // 1f571-1f572 #5 + '36U' // 1f573 #956 + 'a27O' // 1f574-1f575 #716 + '2E' // 1f576 #56 + 'a5Z' // 1f577-1f578 #155 + '6J' // 1f579 #165 + '4S' // 1f57a #122 + 'kF' // 1f57b-1f586 #5 + '2E' // 1f587 #56 + 'aF' // 1f588-1f589 #5 + 'a2E' // 1f58a-1f58b #56 + 'a51Q' // 1f58c-1f58d #1342 + 'aF' // 1f58e-1f58f #5 + '17O' // 1f590 #456 + 'cF' // 1f591-1f594 #5 + 'a3O' // 1f595-1f596 #92 + 'lF' // 1f597-1f5a3 #5 + '27L' // 1f5a4 #713 + '2E' // 1f5a5 #56 + 'aF' // 1f5a6-1f5a7 #5 + '2E' // 1f5a8 #56 + 'gF' // 1f5a9-1f5b0 #5 + 'a2E' // 1f5b1-1f5b2 #56 + 'hF' // 1f5b3-1f5bb #5 + '6J' // 1f5bc #165 + 'dF' // 1f5bd-1f5c1 #5 + 'b2E' // 1f5c2-1f5c4 #56 + 'kF' // 1f5c5-1f5d0 #5 + 'b2E' // 1f5d1-1f5d3 #56 + 'gF' // 1f5d4-1f5db #5 + 'b2E' // 1f5dc-1f5de #56 + 'aF' // 1f5df-1f5e0 #5 + '2E' // 1f5e1 #56 + 'F' // 1f5e2 #5 + '17O' // 1f5e3 #456 + 'cF' // 1f5e4-1f5e7 #5 + '4M' // 1f5e8 #116 + 'eF' // 1f5e9-1f5ee #5 + '4M' // 1f5ef #116 + 'bF' // 1f5f0-1f5f2 #5 + '2E' // 1f5f3 #56 + 'eF' // 1f5f4-1f5f9 #5 + '9O' // 1f5fa #248 + 'd4X' // 1f5fb-1f5ff #127 + 'o3O' // 1f600-1f60f #92 + '52A' // 1f610 #1352 + '1v3O' // 1f611-1f641 #92 + '51Z' // 1f642 #1351 + 'a3O' // 1f643-1f644 #92 + 'b4S' // 1f645-1f647 #122 + 'b27N' // 1f648-1f64a #715 + '4S' // 1f64b #122 + '3O' // 1f64c #92 + 'a4S' // 1f64d-1f64e #122 + '3O' // 1f64f #92 + '1uF' // 1f650-1f67f #5 + '42M' // 1f680 #1104 + 'e4X' // 1f681-1f686 #127 + '9O' // 1f687 #248 + 'd4X' // 1f688-1f68c #127 + '9O' // 1f68d #248 + 'b4X' // 1f68e-1f690 #127 + '9O' // 1f691 #248 + '42M' // 1f692 #1104 + '4X' // 1f693 #127 + '9O' // 1f694 #248 + 'b4X' // 1f695-1f697 #127 + '9O' // 1f698 #248 + 'i4X' // 1f699-1f6a2 #127 + '4S' // 1f6a3 #122 + 'd4X' // 1f6a4-1f6a8 #127 + '15E' // 1f6a9 #394 + '2S' // 1f6aa #70 + '3S' // 1f6ab #96 + '2S' // 1f6ac #70 + '4M' // 1f6ad #116 + 'c3S' // 1f6ae-1f6b1 #96 + '9O' // 1f6b2 #248 + '3S' // 1f6b3 #96 + 'a4S' // 1f6b4-1f6b5 #122 + '32B' // 1f6b6 #833 + 'a3S' // 1f6b7-1f6b8 #96 + 'a4M' // 1f6b9-1f6ba #116 + '3S' // 1f6bb #96 + '4M' // 1f6bc #116 + '2S' // 1f6bd #70 + '3S' // 1f6be #96 + '2S' // 1f6bf #70 + '4S' // 1f6c0 #122 + '2S' // 1f6c1 #70 + 'c3S' // 1f6c2-1f6c5 #96 + 'dF' // 1f6c6-1f6ca #5 + '2E' // 1f6cb #56 + '4S' // 1f6cc #122 + 'b2E' // 1f6cd-1f6cf #56 + '3S' // 1f6d0 #96 + '51H' // 1f6d1 #1333 + '2S' // 1f6d2 #70 + 'aF' // 1f6d3-1f6d4 #5 + 'a9O' // 1f6d5-1f6d6 #248 + '4M' // 1f6d7 #116 + 'cA' // 1f6d8-1f6db + '3S' // 1f6dc #96 + 'b4X' // 1f6dd-1f6df #127 + 'a2E' // 1f6e0-1f6e1 #56 + 'c9O' // 1f6e2-1f6e5 #248 + 'bF' // 1f6e6-1f6e8 #5 + '9O' // 1f6e9 #248 + 'F' // 1f6ea #5 + 'a4X' // 1f6eb-1f6ec #127 + 'bA' // 1f6ed-1f6ef + '2E' // 1f6f0 #56 + 'aF' // 1f6f1-1f6f2 #5 + '9O' // 1f6f3 #248 + 'b4X' // 1f6f4-1f6f6 #127 + '6J' // 1f6f7 #165 + '9O' // 1f6f8 #248 + '6J' // 1f6f9 #165 + 'a9O' // 1f6fa-1f6fb #248 + '6J' // 1f6fc #165 + 'bA' // 1f6fd-1f6ff + '4k2M' // 1f700-1f773 #64 + 'bF' // 1f774-1f776 #5 + 'cA' // 1f777-1f77a + '3pF' // 1f77b-1f7d9 #5 + 'eA' // 1f7da-1f7df + 'h4M' // 1f7e0-1f7e8 #116 + '51I' // 1f7e9 #1334 + '4M' // 1f7ea #116 + '51I' // 1f7eb #1334 + 'cA' // 1f7ec-1f7ef + '3S' // 1f7f0 #96 + 'nA' // 1f7f1-1f7ff + 'kF' // 1f800-1f80b #5 + 'cA' // 1f80c-1f80f + '2cF' // 1f810-1f847 #5 + 'gA' // 1f848-1f84f + 'iF' // 1f850-1f859 #5 + 'eA' // 1f85a-1f85f + '1mF' // 1f860-1f887 #5 + 'gA' // 1f888-1f88f + '1cF' // 1f890-1f8ad #5 + 'aA' // 1f8ae-1f8af + 'aF' // 1f8b0-1f8b1 #5 + '2yA' // 1f8b2-1f8ff + 'kF' // 1f900-1f90b #5 + '3O' // 1f90c #92 + 'a27L' // 1f90d-1f90e #713 + 'm3O' // 1f90f-1f91c #92 + '51X' // 1f91d #1349 + 'g3O' // 1f91e-1f925 #92 + '4S' // 1f926 #122 + 'h3O' // 1f927-1f92f #92 + 'a4S' // 1f930-1f931 #122 + 'a3O' // 1f932-1f933 #92 + 'f4S' // 1f934-1f93a #122 + 'F' // 1f93b #5 + 'b4S' // 1f93c-1f93e #122 + '5P' // 1f93f #145 + '5F' // 1f940 #135 + '5P' // 1f941 #145 + 'b15F' // 1f942-1f944 #395 + '5P' // 1f945 #145 + 'F' // 1f946 #5 + 'h5P' // 1f947-1f94f #145 + '1e15F' // 1f950-1f96f #395 + 'f3O' // 1f970-1f976 #92 + '4S' // 1f977 #122 + 'b3O' // 1f978-1f97a #92 + 'd2S' // 1f97b-1f97f #70 + '1c5F' // 1f980-1f99d #135 + '51U' // 1f99e #1346 + '5F' // 1f99f #135 + '27N' // 1f9a0 #715 + 'h5F' // 1f9a1-1f9a9 #135 + '51U' // 1f9aa #1346 + 'c5F' // 1f9ab-1f9ae #135 + '75F' // 1f9af #1955 + 'c4S' // 1f9b0-1f9b3 #122 + 'c3O' // 1f9b4-1f9b7 #92 + 'a4S' // 1f9b8-1f9b9 #122 + '75D' // 1f9ba #1953 + '3O' // 1f9bb #92 + 'a75P' // 1f9bc-1f9bd #1965 + 'a3O' // 1f9be-1f9bf #92 + 'k15F' // 1f9c0-1f9cb #395 + 'a4S' // 1f9cc-1f9cd #122 + '32B' // 1f9ce #833 + '4S' // 1f9cf #122 + '3O' // 1f9d0 #92 + 'a32B' // 1f9d1-1f9d2 #833 + 'l4S' // 1f9d3-1f9df #122 + '3O' // 1f9e0 #92 + '27L' // 1f9e1 #713 + 'd2S' // 1f9e2-1f9e6 #70 + 'b5P' // 1f9e7-1f9e9 #145 + 'b2S' // 1f9ea-1f9ec #70 + '4X' // 1f9ed #127 + 'd2S' // 1f9ee-1f9f2 #70 + '75C' // 1f9f3 #1952 + '2S' // 1f9f4 #70 + 'a5P' // 1f9f5-1f9f6 #145 + 'h2S' // 1f9f7-1f9ff #70 + '3eF' // 1fa00-1fa53 #5 + 'kA' // 1fa54-1fa5f + 'mF' // 1fa60-1fa6d #5 + 'aA' // 1fa6e-1fa6f + '6J' // 1fa70 #165 + '51Q' // 1fa71 #1342 + 'b2E' // 1fa72-1fa74 #56 + 'b27L' // 1fa75-1fa77 #713 + '17O' // 1fa78 #456 + '75I' // 1fa79 #1958 + '2E' // 1fa7a #56 + '2S' // 1fa7b #70 + '4X' // 1fa7c #127 + 'bA' // 1fa7d-1fa7f + 'a6J' // 1fa80-1fa81 #165 + '27O' // 1fa82 #716 + 'b6J' // 1fa83-1fa85 #165 + '2E' // 1fa86 #56 + 'a5P' // 1fa87-1fa88 #145 + '24J' // 1fa89 #633 + 'dA' // 1fa8a-1fa8e + '24J' // 1fa8f #633 + '5Z' // 1fa90 #155 + 'b2E' // 1fa91-1fa93 #56 + 'a6J' // 1fa94-1fa95 #165 + '2E' // 1fa96 #56 + 'a6J' // 1fa97-1fa98 #165 + 'g2E' // 1fa99-1faa0 #56 + '6J' // 1faa1 #165 + 'e2E' // 1faa2-1faa7 #56 + '5Z' // 1faa8 #155 + '5P' // 1faa9 #145 + 'd2S' // 1faaa-1faae #70 + '3S' // 1faaf #96 + 'f5Z' // 1fab0-1fab6 #155 + 'f5F' // 1fab7-1fabd #135 + '24J' // 1fabe #633 + '5F' // 1fabf #135 + 'b17O' // 1fac0-1fac2 #456 + 'b4S' // 1fac3-1fac5 #122 + '24J' // 1fac6 #633 + 'fA' // 1fac7-1facd + 'a5F' // 1face-1facf #135 + 'f32A' // 1fad0-1fad6 #832 + 'd15F' // 1fad7-1fadb #395 + '24J' // 1fadc #633 + 'aA' // 1fadd-1fade + '24J' // 1fadf #633 + 'f3O' // 1fae0-1fae6 #92 + '5F' // 1fae7 #135 + '3O' // 1fae8 #92 + '24J' // 1fae9 #633 + 'eA' // 1faea-1faef + 'h3O' // 1faf0-1faf8 #92 + 'fA' // 1faf9-1faff + '5pF' // 1fb00-1fb92 #5 + 'A' // 1fb93 + '2bF' // 1fb94-1fbca #5 + '1jA' // 1fbcb-1fbef + 'iF' // 1fbf0-1fbf9 #5 + '39zA' // 1fbfa-2000a + 'C' // 2000b #2 + 'tA' // 2000c-20020 + '2A' // 20021 #52 + '1aA' // 20022-2003d + '2A' // 2003e #52 + 'fA' // 2003f-20045 + '2A' // 20046 #52 + 'fA' // 20047-2004d + '2A' // 2004e #52 + 'xA' // 2004f-20067 + '2A' // 20068 #52 + '1bA' // 20069-20085 + 'a2A' // 20086-20087 #52 + 'A' // 20088 + 'C' // 20089 #2 + '80P' // 2008a #2095 + 'hA' // 2008b-20093 + '2A' // 20094 #52 + 'lA' // 20095-200a1 + 'C' // 200a2 #2 + 'A' // 200a3 + 'C' // 200a4 #2 + 'jA' // 200a5-200af + 'C' // 200b0 #2 + 'xA' // 200b1-200c9 + 'c2A' // 200ca-200cd #52 + 'bA' // 200ce-200d0 + '2A' // 200d1 #52 + '1aA' // 200d2-200ed + '2A' // 200ee #52 + 'eA' // 200ef-200f4 + 'C' // 200f5 #2 + 'uA' // 200f6-2010b + '2A' // 2010c #52 + 'A' // 2010d + '2A' // 2010e #52 + 'hA' // 2010f-20117 + '2A' // 20118 #52 + '2jA' // 20119-20157 + 'C' // 20158 #2 + '2tA' // 20159-201a1 + 'C' // 201a2 #2 + 'A' // 201a3 + '2A' // 201a4 #52 + 'cA' // 201a5-201a8 + '2A' // 201a9 #52 + 'A' // 201aa + '2A' // 201ab #52 + 'tA' // 201ac-201c0 + '2A' // 201c1 #52 + 'qA' // 201c2-201d3 + '2A' // 201d4 #52 + '1bA' // 201d5-201f1 + '2A' // 201f2 #52 + 'pA' // 201f3-20203 + '2A' // 20204 #52 + 'fA' // 20205-2020b + '2A' // 2020c #52 + 'eA' // 2020d-20212 + 'C' // 20213 #2 + '2A' // 20214 #52 + '1iA' // 20215-20238 + '2A' // 20239 #52 + '1fA' // 2023a-2025a + '2A' // 2025b #52 + 'wA' // 2025c-20273 + 'a2A' // 20274-20275 #52 + '1hA' // 20276-20298 + '2A' // 20299 #52 + 'cA' // 2029a-2029d + '2A' // 2029e #52 + 'A' // 2029f + '2A' // 202a0 #52 + 'uA' // 202a1-202b6 + '2A' // 202b7 #52 + 'fA' // 202b8-202be + 'a2A' // 202bf-202c0 #52 + '1iA' // 202c1-202e4 + '2A' // 202e5 #52 + '1iA' // 202e6-20309 + '2A' // 2030a #52 + 'yA' // 2030b-20324 + '2A' // 20325 #52 + 'dA' // 20326-2032a + 'C' // 2032b #2 + 'tA' // 2032c-20340 + '2A' // 20341 #52 + 'bA' // 20342-20344 + 'b2A' // 20345-20347 #52 + '1nA' // 20348-20370 + 'C' // 20371 #2 + 'kA' // 20372-2037d + 'b2A' // 2037e-20380 #52 + 'C' // 20381 #2 + '1cA' // 20382-2039f + '2A' // 203a0 #52 + 'eA' // 203a1-203a6 + '2A' // 203a7 #52 + 'lA' // 203a8-203b4 + '2A' // 203b5 #52 + 'rA' // 203b6-203c8 + '2A' // 203c9 #52 + 'A' // 203ca + '2A' // 203cb #52 + '1nA' // 203cc-203f4 + '2A' // 203f5 #52 + 'bA' // 203f6-203f8 + 'C' // 203f9 #2 + 'aA' // 203fa-203fb + '2A' // 203fc #52 + 'uA' // 203fd-20412 + 'a2A' // 20413-20414 #52 + 'iA' // 20415-2041e + '2A' // 2041f #52 + '1oA' // 20420-20449 + 'C' // 2044a #2 + 'yA' // 2044b-20464 + '2A' // 20465 #52 + '1fA' // 20466-20486 + '80Q' // 20487 #2096 + 'eA' // 20488-2048d + 'O' // 2048e #14 + 'aA' // 2048f-20490 + 'aO' // 20491-20492 #14 + 'oA' // 20493-204a2 + 'O' // 204a3 #14 + '1xA' // 204a4-204d6 + 'O' // 204d7 #14 + '1iA' // 204d8-204fb + 'O' // 204fc #14 + 'A' // 204fd + 'O' // 204fe #14 + 'iA' // 204ff-20508 + 'C' // 20509 #2 + '1zA' // 2050a-2053e + 'C' // 2053f #2 + 'fA' // 20540-20546 + 'O' // 20547 #14 + '2qA' // 20548-2058d + 'O' // 2058e #14 + 'uA' // 2058f-205a4 + 'O' // 205a5 #14 + 'jA' // 205a6-205b0 + 'C' // 205b1 #2 + 'A' // 205b2 + 'O' // 205b3 #14 + 'nA' // 205b4-205c2 + 'O' // 205c3 #14 + 'eA' // 205c4-205c9 + 'O' // 205ca #14 + 'dA' // 205cb-205cf + 'O' // 205d0 #14 + 'cA' // 205d1-205d4 + 'O' // 205d5 #14 + 'C' // 205d6 #2 + 'gA' // 205d7-205de + 'aO' // 205df-205e0 #14 + 'iA' // 205e1-205ea + 'O' // 205eb #14 + '1jA' // 205ec-20610 + '42Z' // 20611 #1117 + 'bA' // 20612-20614 + 'O' // 20615 #14 + 'bA' // 20616-20618 + 'aO' // 20619-2061a #14 + 'lA' // 2061b-20627 + 'C' // 20628 #2 + 'fA' // 20629-2062f + 'O' // 20630 #14 + '1jA' // 20631-20655 + 'O' // 20656 #14 + '1dA' // 20657-20675 + 'O' // 20676 #14 + '4lA' // 20677-206eb + 'C' // 206ec #2 + '1fA' // 206ed-2070d + 'O' // 2070e #14 + '1gA' // 2070f-20730 + 'O' // 20731 #14 + '1bA' // 20732-2074e + 'C' // 2074f #2 + '1nA' // 20750-20778 + '27S' // 20779 #720 + '2yA' // 2077a-207c7 + 'C' // 207c8 #2 + '2iA' // 207c9-20806 + 'C' // 20807 #2 + '1iA' // 20808-2082b + 'O' // 2082c #14 + 'lA' // 2082d-20839 + 'C' // 2083a #2 + '2cA' // 2083b-20872 + 'O' // 20873 #14 + '2pA' // 20874-208b8 + 'C' // 208b9 #2 + 'zA' // 208ba-208d4 + 'O' // 208d5 #14 + '2cA' // 208d6-2090d + 'C' // 2090e #2 + 'fA' // 2090f-20915 + 'O' // 20916 #14 + 'kA' // 20917-20922 + 'O' // 20923 #14 + '1uA' // 20924-20953 + 'O' // 20954 #14 + '1iA' // 20955-20978 + 'O' // 20979 #14 + 'aA' // 2097a-2097b + 'C' // 2097c #2 + 'fA' // 2097d-20983 + 'C' // 20984 #2 + 'wA' // 20985-2099c + 'C' // 2099d #2 + '2tA' // 2099e-209e6 + 'O' // 209e7 #14 + '1nA' // 209e8-20a10 + 'O' // 20a11 #14 + '2iA' // 20a12-20a4f + 'O' // 20a50 #14 + 'rA' // 20a51-20a63 + 'C' // 20a64 #2 + 'iA' // 20a65-20a6e + 'O' // 20a6f #14 + 'yA' // 20a70-20a89 + 'O' // 20a8a #14 + '1nA' // 20a8b-20ab3 + 'O' // 20ab4 #14 + 'lA' // 20ab5-20ac1 + 'O' // 20ac2 #14 + 'iA' // 20ac3-20acc + 'O' // 20acd #14 + 'dA' // 20ace-20ad2 + 'C' // 20ad3 #2 + '2dA' // 20ad4-20b0c + 'O' // 20b0d #14 + 'nA' // 20b0e-20b1c + 'C' // 20b1d #2 + '4hA' // 20b1e-20b8e + 'O' // 20b8f #14 + 'nA' // 20b90-20b9e + '42Z' // 20b9f #1117 + 'gA' // 20ba0-20ba7 + 'aO' // 20ba8-20ba9 #14 + 'lA' // 20baa-20bb6 + 'C' // 20bb7 #2 + 'fA' // 20bb8-20bbe + 'O' // 20bbf #14 + 'eA' // 20bc0-20bc5 + 'O' // 20bc6 #14 + 'cA' // 20bc7-20bca + 'O' // 20bcb #14 + 'uA' // 20bcc-20be1 + 'O' // 20be2 #14 + 'gA' // 20be3-20bea + 'O' // 20beb #14 + 'nA' // 20bec-20bfa + 'O' // 20bfb #14 + 'bA' // 20bfc-20bfe + 'O' // 20bff #14 + 'jA' // 20c00-20c0a + 'O' // 20c0b #14 + 'A' // 20c0c + 'O' // 20c0d #14 + 'qA' // 20c0e-20c1f + 'O' // 20c20 #14 + 'rA' // 20c21-20c33 + 'O' // 20c34 #14 + 'dA' // 20c35-20c39 + 'aO' // 20c3a-20c3b #14 + 'dA' // 20c3c-20c40 + '27S' // 20c41 #720 + 'aO' // 20c42-20c43 #14 + 'nA' // 20c44-20c52 + 'O' // 20c53 #14 + 'pA' // 20c54-20c64 + 'O' // 20c65 #14 + 'pA' // 20c66-20c76 + 'O' // 20c77 #14 + '27S' // 20c78 #720 + 'bA' // 20c79-20c7b + 'O' // 20c7c #14 + 'oA' // 20c7d-20c8c + 'O' // 20c8d #14 + 'gA' // 20c8e-20c95 + 'O' // 20c96 #14 + 'dA' // 20c97-20c9b + 'O' // 20c9c #14 + 'wA' // 20c9d-20cb4 + 'O' // 20cb5 #14 + 'aA' // 20cb6-20cb7 + 'O' // 20cb8 #14 + 'uA' // 20cb9-20cce + 'O' // 20ccf #14 + 'bA' // 20cd0-20cd2 + 'cO' // 20cd3-20cd6 #14 + 'eA' // 20cd7-20cdc + 'O' // 20cdd #14 + 'nA' // 20cde-20cec + 'O' // 20ced #14 + 'pA' // 20cee-20cfe + 'O' // 20cff #14 + 'tA' // 20d00-20d14 + 'O' // 20d15 #14 + 'qA' // 20d16-20d27 + 'O' // 20d28 #14 + 'gA' // 20d29-20d30 + 'aO' // 20d31-20d32 #14 + 'qA' // 20d33-20d44 + 'C' // 20d45 #2 + 'cO' // 20d46-20d49 #14 + 'aA' // 20d4a-20d4b + 'bO' // 20d4c-20d4e #14 + 'hA' // 20d4f-20d57 + 'C' // 20d58 #2 + 'uA' // 20d59-20d6e + 'O' // 20d6f #14 + 'A' // 20d70 + '27S' // 20d71 #720 + 'aA' // 20d72-20d73 + 'O' // 20d74 #14 + 'fA' // 20d75-20d7b + 'O' // 20d7c #14 + 'A' // 20d7d + 'aO' // 20d7e-20d7f #14 + 'uA' // 20d80-20d95 + 'O' // 20d96 #14 + 'dA' // 20d97-20d9b + 'O' // 20d9c #14 + 'iA' // 20d9d-20da6 + 'O' // 20da7 #14 + 'iA' // 20da8-20db1 + 'O' // 20db2 #14 + 'tA' // 20db3-20dc7 + 'O' // 20dc8 #14 + 'wA' // 20dc9-20de0 + 'C' // 20de1 #2 + '1gA' // 20de2-20e03 + 'O' // 20e04 #14 + 'cA' // 20e05-20e08 + 'aO' // 20e09-20e0a #14 + 'aA' // 20e0b-20e0c + 'dO' // 20e0d-20e11 #14 + 'cA' // 20e12-20e15 + 'O' // 20e16 #14 + 'eA' // 20e17-20e1c + 'O' // 20e1d #14 + '1sA' // 20e1e-20e4b + 'O' // 20e4c #14 + 'vA' // 20e4d-20e63 + 'C' // 20e64 #2 + 'gA' // 20e65-20e6c + '42Z' // 20e6d #1117 + 'dA' // 20e6e-20e72 + 'O' // 20e73 #14 + 'A' // 20e74 + 'fO' // 20e75-20e7b #14 + 'oA' // 20e7c-20e8b + 'O' // 20e8c #14 + 'gA' // 20e8d-20e94 + 'C' // 20e95 #2 + 'O' // 20e96 #14 + 'A' // 20e97 + '27S' // 20e98 #720 + 'cA' // 20e99-20e9c + 'O' // 20e9d #14 + 'cA' // 20e9e-20ea1 + 'O' // 20ea2 #14 + 'fA' // 20ea3-20ea9 + 'bO' // 20eaa-20eac #14 + 'hA' // 20ead-20eb5 + 'O' // 20eb6 #14 + '1eA' // 20eb7-20ed6 + 'aO' // 20ed7-20ed8 #14 + 'cA' // 20ed9-20edc + 'O' // 20edd #14 + 'yA' // 20ede-20ef7 + 'O' // 20ef8 #14 + '27S' // 20ef9 #720 + 'aO' // 20efa-20efb #14 + '1fA' // 20efc-20f1c + 'O' // 20f1d #14 + 'gA' // 20f1e-20f25 + 'O' // 20f26 #14 + 'eA' // 20f27-20f2c + 'aO' // 20f2d-20f2e #14 + 'A' // 20f2f + 'aO' // 20f30-20f31 #14 + 'hA' // 20f32-20f3a + 'O' // 20f3b #14 + 'oA' // 20f3c-20f4b + 'O' // 20f4c #14 + 'qA' // 20f4d-20f5e + 'C' // 20f5f #2 + 'cA' // 20f60-20f63 + 'O' // 20f64 #14 + '1mA' // 20f65-20f8c + 'O' // 20f8d #14 + 'aA' // 20f8e-20f8f + 'O' // 20f90 #14 + '1aA' // 20f91-20fac + 'O' // 20fad #14 + 'eA' // 20fae-20fb3 + 'bO' // 20fb4-20fb6 #14 + 'dA' // 20fb7-20fbb + 'O' // 20fbc #14 + '1gA' // 20fbd-20fde + 'O' // 20fdf #14 + 'iA' // 20fe0-20fe9 + 'cO' // 20fea-20fed #14 + '1kA' // 20fee-21013 + 'O' // 21014 #14 + 'gA' // 21015-2101c + 'aO' // 2101d-2101e #14 + '1uA' // 2101f-2104e + 'O' // 2104f #14 + 'kA' // 21050-2105b + 'O' // 2105c #14 + 'qA' // 2105d-2106e + 'O' // 2106f #14 + 'dA' // 21070-21074 + 'O' // 21075 #14 + 'bS' // 21076-21078 #18 + 'aA' // 21079-2107a + '52T' // 2107b #1371 + 'kA' // 2107c-21087 + 'S' // 21088 #18 + 'lA' // 21089-21095 + 'S' // 21096 #18 + 'eA' // 21097-2109c + 'S' // 2109d #18 + 'uA' // 2109e-210b3 + 'S' // 210b4 #18 + 'iA' // 210b5-210be + 'aS' // 210bf-210c0 #18 + '52T' // 210c1 #1371 + 'dA' // 210c2-210c6 + 'bS' // 210c7-210c9 #18 + 'dA' // 210ca-210ce + 'S' // 210cf #18 + 'bA' // 210d0-210d2 + 'S' // 210d3 #18 + 'oA' // 210d4-210e3 + 'S' // 210e4 #18 + 'nA' // 210e5-210f3 + 'bS' // 210f4-210f6 #18 + '2cA' // 210f7-2112e + 'S' // 2112f #18 + 'jA' // 21130-2113a + 'S' // 2113b #18 + 'A' // 2113c + 'S' // 2113d #18 + 'fA' // 2113e-21144 + 'S' // 21145 #18 + 'aA' // 21146-21147 + 'S' // 21148 #18 + 'eA' // 21149-2114e + 'S' // 2114f #18 + '1uA' // 21150-2117f + 'S' // 21180 #18 + 'eA' // 21181-21186 + 'S' // 21187 #18 + '3bA' // 21188-211d8 + 'S' // 211d9 #18 + '1lA' // 211da-21200 + 'C' // 21201 #2 + '2eA' // 21202-2123b + 'S' // 2123c #18 + 'C' // 2123d #2 + 'pA' // 2123e-2124e + 'S' // 2124f #18 + 'dA' // 21250-21254 + 'C' // 21255 #2 + '1cA' // 21256-21273 + 'C' // 21274 #2 + 'eA' // 21275-2127a + 'C' // 2127b #2 + 'S' // 2127c #18 + '1pA' // 2127d-212a7 + 'aS' // 212a8-212a9 #18 + 'eA' // 212aa-212af + 'S' // 212b0 #18 + '1kA' // 212b1-212d6 + 'C' // 212d7 #2 + 'jA' // 212d8-212e2 + 'S' // 212e3 #18 + 'C' // 212e4 #2 + 'wA' // 212e5-212fc + 'C' // 212fd #2 + 'S' // 212fe #18 + 'bA' // 212ff-21301 + 'cS' // 21302-21305 #18 + 'tA' // 21306-2131a + 'C' // 2131b #2 + 'yA' // 2131c-21335 + '80O' // 21336 #2094 + 'bA' // 21337-21339 + 'S' // 2133a #18 + 'hA' // 2133b-21343 + 'C' // 21344 #2 + '1uA' // 21345-21374 + 'aS' // 21375-21376 #18 + 'vA' // 21377-2138d + 'S' // 2138e #18 + 'hA' // 2138f-21397 + 'S' // 21398 #18 + 'bA' // 21399-2139b + 'S' // 2139c #18 + '1lA' // 2139d-213c3 + 'C' // 213c4 #2 + 'aS' // 213c5-213c6 #18 + '1kA' // 213c7-213ec + 'S' // 213ed #18 + 'oA' // 213ee-213fd + 'S' // 213fe #18 + 'sA' // 213ff-21412 + 'S' // 21413 #18 + 'aA' // 21414-21415 + 'S' // 21416 #18 + 'lA' // 21417-21423 + 'S' // 21424 #18 + 'yA' // 21425-2143e + 'S' // 2143f #18 + 'qA' // 21440-21451 + 'S' // 21452 #18 + 'A' // 21453 + 'aS' // 21454-21455 #18 + 'vA' // 21456-2146c + 'aC' // 2146d-2146e #2 + 'zA' // 2146f-21489 + 'S' // 2148a #18 + 'kA' // 2148b-21496 + 'S' // 21497 #18 + '1cA' // 21498-214b5 + 'S' // 214b6 #18 + '1vA' // 214b7-214e7 + 'S' // 214e8 #18 + 'sA' // 214e9-214fc + 'S' // 214fd #18 + '4pA' // 214fe-21576 + 'S' // 21577 #18 + 'iA' // 21578-21581 + 'S' // 21582 #18 + 'rA' // 21583-21595 + 'S' // 21596 #18 + '2kA' // 21597-215d6 + 'C' // 215d7 #2 + '1wA' // 215d8-21609 + 'S' // 2160a #18 + 'gA' // 2160b-21612 + 'S' // 21613 #18 + 'dA' // 21614-21618 + 'S' // 21619 #18 + '1iA' // 2161a-2163d + 'S' // 2163e #18 + 'gA' // 2163f-21646 + 'C' // 21647 #2 + 'xA' // 21648-21660 + 'S' // 21661 #18 + '1uA' // 21662-21691 + 'S' // 21692 #18 + '1fA' // 21693-216b3 + 'C' // 216b4 #2 + 'bA' // 216b5-216b7 + 'S' // 216b8 #18 + 'A' // 216b9 + 'S' // 216ba #18 + 'dA' // 216bb-216bf + 'bS' // 216c0-216c2 #18 + 'oA' // 216c3-216d2 + 'S' // 216d3 #18 + 'A' // 216d4 + 'S' // 216d5 #18 + 'hA' // 216d6-216de + 'S' // 216df #18 + 'eA' // 216e0-216e5 + 'bS' // 216e6-216e8 #18 + 'pA' // 216e9-216f9 + 'bS' // 216fa-216fc #18 + 'A' // 216fd + 'S' // 216fe #18 + 'fA' // 216ff-21705 + 'C' // 21706 #2 + 'eA' // 21707-2170c + 'S' // 2170d #18 + 'aA' // 2170e-2170f + 'S' // 21710 #18 + 'tA' // 21711-21725 + 'S' // 21726 #18 + 'rA' // 21727-21739 + 'bS' // 2173a-2173c #18 + 'dA' // 2173d-21741 + 'C' // 21742 #2 + 'sA' // 21743-21756 + 'S' // 21757 #18 + 'sA' // 21758-2176b + 'eS' // 2176c-21771 #18 + 'A' // 21772 + 'aS' // 21773-21774 #18 + '2aA' // 21775-217aa + 'S' // 217ab #18 + 'cA' // 217ac-217af + 'eS' // 217b0-217b5 #18 + 'lA' // 217b6-217c2 + 'S' // 217c3 #18 + 'bA' // 217c4-217c6 + 'S' // 217c7 #18 + 'pA' // 217c8-217d8 + 'cS' // 217d9-217dc #18 + 'aA' // 217dd-217de + 'S' // 217df #18 + 'nA' // 217e0-217ee + 'S' // 217ef #18 + 'dA' // 217f0-217f4 + 'aS' // 217f5-217f6 #18 + 'A' // 217f7 + 'dS' // 217f8-217fc #18 + '1hA' // 217fd-2181f + 'S' // 21820 #18 + 'fA' // 21821-21827 + 'bS' // 21828-2182a #18 + 'aA' // 2182b-2182c + 'S' // 2182d #18 + 'jA' // 2182e-21838 + 'bS' // 21839-2183b #18 + 'cA' // 2183c-2183f + 'S' // 21840 #18 + 'cA' // 21841-21844 + 'S' // 21845 #18 + 'kA' // 21846-21851 + 'S' // 21852 #18 + 'jA' // 21853-2185d + 'S' // 2185e #18 + 'aA' // 2185f-21860 + 'cS' // 21861-21864 #18 + 'qA' // 21865-21876 + 'S' // 21877 #18 + 'bA' // 21878-2187a + 'S' // 2187b #18 + 'fA' // 2187c-21882 + 'bS' // 21883-21885 #18 + 'wA' // 21886-2189d + 'dS' // 2189e-218a2 #18 + 'yA' // 218a3-218bc + 'C' // 218bd #2 + 'aS' // 218be-218bf #18 + 'pA' // 218c0-218d0 + 'S' // 218d1 #18 + 'cA' // 218d2-218d5 + 'cS' // 218d6-218d9 #18 + '1eA' // 218da-218f9 + 'S' // 218fa #18 + 'gA' // 218fb-21902 + 'bS' // 21903-21905 #18 + 'iA' // 21906-2190f + 'bS' // 21910-21912 #18 + 'aA' // 21913-21914 + 'S' // 21915 #18 + 'eA' // 21916-2191b + 'S' // 2191c #18 + 'dA' // 2191d-21921 + 'S' // 21922 #18 + 'cA' // 21923-21926 + 'D' // 21927 #3 + 'rA' // 21928-2193a + 'D' // 2193b #3 + 'gA' // 2193c-21943 + 'D' // 21944 #3 + 'rA' // 21945-21957 + 'D' // 21958 #3 + 'pA' // 21959-21969 + 'D' // 2196a #3 + 'pA' // 2196b-2197b + 'D' // 2197c #3 + 'bA' // 2197d-2197f + 'D' // 21980 #3 + 'aA' // 21981-21982 + 'D' // 21983 #3 + 'cA' // 21984-21987 + 'D' // 21988 #3 + 'lA' // 21989-21995 + 'D' // 21996 #3 + '1qA' // 21997-219c2 + 'C' // 219c3 #2 + 'vA' // 219c4-219da + 'D' // 219db #3 + 'vA' // 219dc-219f2 + 'D' // 219f3 #3 + '1kA' // 219f4-21a19 + 'C' // 21a1a #2 + 'qA' // 21a1b-21a2c + 'D' // 21a2d #3 + 'eA' // 21a2e-21a33 + 'D' // 21a34 #3 + 'oA' // 21a35-21a44 + 'D' // 21a45 #3 + 'dA' // 21a46-21a4a + 'D' // 21a4b #3 + 'vA' // 21a4c-21a62 + 'D' // 21a63 #3 + '8oA' // 21a64-21b43 + 'D' // 21b44 #3 + '4sA' // 21b45-21bc0 + 'aD' // 21bc1-21bc2 #3 + '3xA' // 21bc3-21c29 + 'D' // 21c2a #3 + '1pA' // 21c2b-21c55 + 'C' // 21c56 #2 + 'xA' // 21c57-21c6f + 'D' // 21c70 #3 + '1vA' // 21c71-21ca1 + 'D' // 21ca2 #3 + 'aA' // 21ca3-21ca4 + 'D' // 21ca5 #3 + 'eA' // 21ca6-21cab + 'D' // 21cac #3 + '4wA' // 21cad-21d2c + 'C' // 21d2d #2 + 'vA' // 21d2e-21d44 + 'C' // 21d45 #2 + 'D' // 21d46 #3 + 'kA' // 21d47-21d52 + 'D' // 21d53 #3 + 'iA' // 21d54-21d5d + 'D' // 21d5e #3 + 'bA' // 21d5f-21d61 + 'C' // 21d62 #2 + 'tA' // 21d63-21d77 + 'C' // 21d78 #2 + 'vA' // 21d79-21d8f + 'D' // 21d90 #3 + 'A' // 21d91 + 'C' // 21d92 #2 + 'hA' // 21d93-21d9b + 'C' // 21d9c #2 + 'cA' // 21d9d-21da0 + 'C' // 21da1 #2 + 'sA' // 21da2-21db5 + 'D' // 21db6 #3 + 'C' // 21db7 #2 + 'aA' // 21db8-21db9 + 'D' // 21dba #3 + 'nA' // 21dbb-21dc9 + 'D' // 21dca #3 + 'eA' // 21dcb-21dd0 + 'D' // 21dd1 #3 + 'mA' // 21dd2-21ddf + 'C' // 21de0 #2 + 'iA' // 21de1-21dea + 'D' // 21deb #3 + 'lA' // 21dec-21df8 + 'D' // 21df9 #3 + '1gA' // 21dfa-21e1b + 'D' // 21e1c #3 + 'eA' // 21e1d-21e22 + 'D' // 21e23 #3 + 'nA' // 21e24-21e32 + 'aC' // 21e33-21e34 #2 + 'aA' // 21e35-21e36 + 'D' // 21e37 #3 + 'dA' // 21e38-21e3c + 'D' // 21e3d #3 + '2vA' // 21e3e-21e88 + 'D' // 21e89 #3 + 'yA' // 21e8a-21ea3 + 'D' // 21ea4 #3 + 'bA' // 21ea5-21ea7 + 'D' // 21ea8 #3 + '1dA' // 21ea9-21ec7 + 'D' // 21ec8 #3 + 'kA' // 21ec9-21ed4 + 'D' // 21ed5 #3 + '2dA' // 21ed6-21f0e + 'D' // 21f0f #3 + 'dA' // 21f10-21f14 + 'D' // 21f15 #3 + 'gA' // 21f16-21f1d + 'C' // 21f1e #2 + '2vA' // 21f1f-21f69 + 'D' // 21f6a #3 + 'jA' // 21f6b-21f75 + 'C' // 21f76 #2 + '1lA' // 21f77-21f9d + 'D' // 21f9e #3 + 'aA' // 21f9f-21fa0 + 'D' // 21fa1 #3 + '2qA' // 21fa2-21fe7 + 'D' // 21fe8 #3 + 'pA' // 21fe9-21ff9 + 'C' // 21ffa #2 + '2uA' // 21ffb-22044 + 'D' // 22045 #3 + 'bA' // 22046-22048 + 'D' // 22049 #3 + '1yA' // 2204a-2207d + 'D' // 2207e #3 + 'zA' // 2207f-22099 + 'D' // 2209a #3 + '1qA' // 2209b-220c6 + 'D' // 220c7 #3 + '1yA' // 220c8-220fb + 'D' // 220fc #3 + '1rA' // 220fd-22129 + 'D' // 2212a #3 + '1uA' // 2212b-2215a + 'D' // 2215b #3 + 'vA' // 2215c-22172 + 'D' // 22173 #3 + 'eA' // 22174-22179 + 'D' // 2217a #3 + 'C' // 2217b #2 + '1jA' // 2217c-221a0 + 'D' // 221a1 #3 + '1dA' // 221a2-221c0 + 'D' // 221c1 #3 + 'A' // 221c2 + 'D' // 221c3 #3 + '2oA' // 221c4-22207 + 'D' // 22208 #3 + 'nA' // 22209-22217 + 'C' // 22218 #2 + '3tA' // 22219-2227b + 'D' // 2227c #3 + '6dA' // 2227d-2231d + 'C' // 2231e #2 + 'aA' // 2231f-22320 + 'D' // 22321 #3 + 'bA' // 22322-22324 + 'D' // 22325 #3 + '5dA' // 22326-223ac + 'C' // 223ad #2 + 'nA' // 223ae-223bc + 'D' // 223bd #3 + 'qA' // 223be-223cf + 'D' // 223d0 #3 + 'eA' // 223d1-223d6 + 'D' // 223d7 #3 + '1gA' // 223d8-223f9 + 'D' // 223fa #3 + '4aA' // 223fb-22464 + 'D' // 22465 #3 + 'jA' // 22466-22470 + 'D' // 22471 #3 + 'xA' // 22472-2248a + 'D' // 2248b #3 + 'dA' // 2248c-22490 + 'D' // 22491 #3 + '1cA' // 22492-224af + 'D' // 224b0 #3 + 'jA' // 224b1-224bb + 'D' // 224bc #3 + 'cA' // 224bd-224c0 + 'D' // 224c1 #3 + 'fA' // 224c2-224c8 + 'D' // 224c9 #3 + 'aA' // 224ca-224cb + 'D' // 224cc #3 + '1eA' // 224cd-224ec + 'D' // 224ed #3 + '1jA' // 224ee-22512 + 'D' // 22513 #3 + 'fA' // 22514-2251a + 'D' // 2251b #3 + 'sA' // 2251c-2252f + 'D' // 22530 #3 + '1hA' // 22531-22553 + 'D' // 22554 #3 + '2cA' // 22555-2258c + 'D' // 2258d #3 + '1fA' // 2258e-225ae + 'D' // 225af #3 + 'mA' // 225b0-225bd + 'D' // 225be #3 + '2uA' // 225bf-22608 + 'C' // 22609 #2 + 'pA' // 2260a-2261a + 'aD' // 2261b-2261c #3 + 'mA' // 2261d-2262a + 'D' // 2262b #3 + '2gA' // 2262c-22667 + 'D' // 22668 #3 + 'pA' // 22669-22679 + 'D' // 2267a #3 + 'zA' // 2267b-22695 + 'D' // 22696 #3 + 'A' // 22697 + 'D' // 22698 #3 + '3kA' // 22699-226f2 + 'C' // 226f3 #2 + 'bD' // 226f4-226f6 #3 + 'zA' // 226f7-22711 + 'D' // 22712 #3 + 'A' // 22713 + 'D' // 22714 #3 + 'eA' // 22715-2271a + 'D' // 2271b #3 + 'bA' // 2271c-2271e + 'D' // 2271f #3 + 'iA' // 22720-22729 + 'D' // 2272a #3 + '2uA' // 2272b-22774 + 'D' // 22775 #3 + 'jA' // 22776-22780 + 'D' // 22781 #3 + 'sA' // 22782-22795 + 'D' // 22796 #3 + '1bA' // 22797-227b3 + 'aD' // 227b4-227b5 #3 + 'vA' // 227b6-227cc + 'D' // 227cd #3 + '1zA' // 227ce-22802 + 'D' // 22803 #3 + '3hA' // 22804-2285a + 'C' // 2285b #2 + 'bA' // 2285c-2285e + 'aD' // 2285f-22860 #3 + 'oA' // 22861-22870 + 'D' // 22871 #3 + '2dA' // 22872-228aa + 'C' // 228ab #2 + 'A' // 228ac + 'D' // 228ad #3 + 'rA' // 228ae-228c0 + 'D' // 228c1 #3 + '1zA' // 228c2-228f6 + 'D' // 228f7 #3 + '1sA' // 228f8-22925 + 'D' // 22926 #3 + 'qA' // 22927-22938 + 'D' // 22939 #3 + 'tA' // 2293a-2294e + 'D' // 2294f #3 + 'vA' // 22950-22966 + 'D' // 22967 #3 + 'bA' // 22968-2296a + 'D' // 2296b #3 + 'sA' // 2296c-2297f + 'D' // 22980 #3 + 'mA' // 22981-2298e + 'C' // 2298f #2 + 'bA' // 22990-22992 + 'D' // 22993 #3 + '8aA' // 22994-22a65 + 'D' // 22a66 #3 + '3bA' // 22a67-22ab7 + 'C' // 22ab8 #2 + 'uA' // 22ab9-22ace + 'D' // 22acf #3 + 'dA' // 22ad0-22ad4 + 'D' // 22ad5 #3 + 'oA' // 22ad6-22ae5 + 'D' // 22ae6 #3 + 'A' // 22ae7 + 'D' // 22ae8 #3 + '1jA' // 22ae9-22b0d + 'D' // 22b0e #3 + 'rA' // 22b0f-22b21 + 'D' // 22b22 #3 + '1aA' // 22b23-22b3e + 'D' // 22b3f #3 + 'bA' // 22b40-22b42 + 'D' // 22b43 #3 + 'aA' // 22b44-22b45 + 'C' // 22b46 #2 + 'gA' // 22b47-22b4e + 'aC' // 22b4f-22b50 #2 + 'xA' // 22b51-22b69 + 'D' // 22b6a #3 + '2fA' // 22b6b-22ba5 + 'C' // 22ba6 #2 + '1hA' // 22ba7-22bc9 + 'D' // 22bca #3 + 'bA' // 22bcb-22bcd + 'D' // 22bce #3 + '2yA' // 22bcf-22c1c + 'C' // 22c1d #2 + 'eA' // 22c1e-22c23 + 'C' // 22c24 #2 + 'A' // 22c25 + 'aD' // 22c26-22c27 #3 + 'oA' // 22c28-22c37 + 'D' // 22c38 #3 + 'rA' // 22c39-22c4b + 'D' // 22c4c #3 + 'cA' // 22c4d-22c50 + '80N' // 22c51 #2093 + 'bA' // 22c52-22c54 + 'D' // 22c55 #3 + 'kA' // 22c56-22c61 + 'D' // 22c62 #3 + '1jA' // 22c63-22c87 + 'D' // 22c88 #3 + 'qA' // 22c89-22c9a + 'D' // 22c9b #3 + 'dA' // 22c9c-22ca0 + 'D' // 22ca1 #3 + 'fA' // 22ca2-22ca8 + 'D' // 22ca9 #3 + 'gA' // 22caa-22cb1 + 'D' // 22cb2 #3 + 'cA' // 22cb3-22cb6 + 'D' // 22cb7 #3 + 'iA' // 22cb8-22cc1 + 'D' // 22cc2 #3 + 'bA' // 22cc3-22cc5 + 'D' // 22cc6 #3 + 'aA' // 22cc7-22cc8 + 'D' // 22cc9 #3 + '2hA' // 22cca-22d06 + 'aD' // 22d07-22d08 #3 + 'hA' // 22d09-22d11 + 'D' // 22d12 #3 + '1vA' // 22d13-22d43 + 'D' // 22d44 #3 + 'fA' // 22d45-22d4b + 'D' // 22d4c #3 + 'yA' // 22d4d-22d66 + 'D' // 22d67 #3 + '1jA' // 22d68-22d8c + 'D' // 22d8d #3 + 'fA' // 22d8e-22d94 + 'D' // 22d95 #3 + 'iA' // 22d96-22d9f + 'D' // 22da0 #3 + 'aA' // 22da1-22da2 + 'aD' // 22da3-22da4 #3 + 'qA' // 22da5-22db6 + 'D' // 22db7 #3 + '1nA' // 22db8-22de0 + 'C' // 22de1 #2 + 'kA' // 22de2-22ded + 'D' // 22dee #3 + '1cA' // 22def-22e0c + 'D' // 22e0d #3 + '1mA' // 22e0e-22e35 + 'D' // 22e36 #3 + 'jA' // 22e37-22e41 + '80M' // 22e42 #2092 + '1zA' // 22e43-22e77 + 'D' // 22e78 #3 + 'qA' // 22e79-22e8a + 'Q' // 22e8b #16 + '1lA' // 22e8c-22eb2 + 'Q' // 22eb3 #16 + '2fA' // 22eb4-22eee + 'Q' // 22eef #16 + '5aA' // 22ef0-22f73 + 'Q' // 22f74 #16 + '3hA' // 22f75-22fcb + 'Q' // 22fcc #16 + 'uA' // 22fcd-22fe2 + 'Q' // 22fe3 #16 + 'fA' // 22fe4-22fea + 'C' // 22feb #2 + '2rA' // 22fec-23032 + 'Q' // 23033 #16 + 'oA' // 23034-23043 + 'Q' // 23044 #16 + 'eA' // 23045-2304a + 'Q' // 2304b #16 + 'yA' // 2304c-23065 + 'Q' // 23066 #16 + 'uA' // 23067-2307c + 'aQ' // 2307d-2307e #16 + 'nA' // 2307f-2308d + 'Q' // 2308e #16 + '1mA' // 2308f-230b6 + 'Q' // 230b7 #16 + 'cA' // 230b8-230bb + 'Q' // 230bc #16 + '1bA' // 230bd-230d9 + 'Q' // 230da #16 + '1mA' // 230db-23102 + 'Q' // 23103 #16 + '2dA' // 23104-2313c + 'Q' // 2313d #16 + '2jA' // 2313e-2317c + 'Q' // 2317d #16 + 'cA' // 2317e-23181 + 'Q' // 23182 #16 + '1fA' // 23183-231a3 + 'aQ' // 231a4-231a5 #16 + 'lA' // 231a6-231b2 + 'Q' // 231b3 #16 + 'aA' // 231b4-231b5 + 'C' // 231b6 #2 + 'kA' // 231b7-231c2 + 'aC' // 231c3-231c4 #2 + 'bA' // 231c5-231c7 + 'aQ' // 231c8-231c9 #16 + '1eA' // 231ca-231e9 + 'Q' // 231ea #16 + 'iA' // 231eb-231f4 + 'C' // 231f5 #2 + 'A' // 231f6 + 'bQ' // 231f7-231f9 #16 + 'tA' // 231fa-2320e + 'Q' // 2320f #16 + 'tA' // 23210-23224 + 'Q' // 23225 #16 + 'hA' // 23226-2322e + 'Q' // 2322f #16 + 'A' // 23230 + 'cQ' // 23231-23234 #16 + '1fA' // 23235-23255 + 'Q' // 23256 #16 + 'fA' // 23257-2325d + 'Q' // 2325e #16 + 'bA' // 2325f-23261 + 'Q' // 23262 #16 + '1cA' // 23263-23280 + 'Q' // 23281 #16 + 'fA' // 23282-23288 + 'aQ' // 23289-2328a #16 + '1eA' // 2328b-232aa + 'bQ' // 232ab-232ad #16 + '1iA' // 232ae-232d1 + 'Q' // 232d2 #16 + 'lA' // 232d3-232df + 'aQ' // 232e0-232e1 #16 + '1cA' // 232e2-232ff + 'Q' // 23300 #16 + 'hA' // 23301-23309 + 'Q' // 2330a #16 + 'sA' // 2330b-2331e + 'Q' // 2331f #16 + '3cA' // 23320-23371 + 'C' // 23372 #2 + '2lA' // 23373-233b3 + '80L' // 233b4 #2091 + 'vA' // 233b5-233cb + '27R' // 233cc #719 + 'bA' // 233cd-233cf + 'C' // 233d0 #2 + 'A' // 233d1 + 'aC' // 233d2-233d3 #2 + 'A' // 233d4 + 'C' // 233d5 #2 + 'cA' // 233d6-233d9 + 'C' // 233da #2 + 'bA' // 233db-233dd + 'Q' // 233de #16 + 'C' // 233df #2 + 'cA' // 233e0-233e3 + 'C' // 233e4 #2 + 'A' // 233e5 + 'Q' // 233e6 #16 + 'lA' // 233e7-233f3 + 'aQ' // 233f4-233f5 #16 + 'bA' // 233f6-233f8 + 'aQ' // 233f9-233fa #16 + 'bA' // 233fb-233fd + '27R' // 233fe #719 + 'A' // 233ff + 'Q' // 23400 #16 + '2iA' // 23401-2343e + 'Q' // 2343f #16 + 'iA' // 23440-23449 + 'aC' // 2344a-2344b #2 + 'cA' // 2344c-2344f + 'Q' // 23450 #16 + 'C' // 23451 #2 + 'rA' // 23452-23464 + 'C' // 23465 #2 + 'hA' // 23466-2346e + 'Q' // 2346f #16 + 'aA' // 23470-23471 + 'Q' // 23472 #16 + '4hA' // 23473-234e3 + 'C' // 234e4 #2 + 'Q' // 234e5 #16 + '1xA' // 234e6-23518 + 'Q' // 23519 #16 + 'uA' // 2351a-2352f + 'Q' // 23530 #16 + '1eA' // 23531-23550 + 'Q' // 23551 #16 + 'gA' // 23552-23559 + '27R' // 2355a #719 + 'kA' // 2355b-23566 + 'Q' // 23567 #16 + '1qA' // 23568-23593 + 'C' // 23594 #2 + 'Q' // 23595 #16 + 'bA' // 23596-23598 + 'Q' // 23599 #16 + 'aA' // 2359a-2359b + 'Q' // 2359c #16 + '1cA' // 2359d-235ba + 'Q' // 235bb #16 + 'gA' // 235bc-235c3 + 'C' // 235c4 #2 + 'gA' // 235c5-235cc + 'bQ' // 235cd-235cf #16 + '1hA' // 235d0-235f2 + 'Q' // 235f3 #16 + 'kA' // 235f4-235ff + 'Q' // 23600 #16 + 'uA' // 23601-23616 + 'Q' // 23617 #16 + 'aA' // 23618-23619 + 'Q' // 2361a #16 + '1bA' // 2361b-23637 + 'bC' // 23638-2363a #2 + 'A' // 2363b + 'Q' // 2363c #16 + 'bA' // 2363d-2363f + 'Q' // 23640 #16 + 'eA' // 23641-23646 + 'C' // 23647 #2 + 'pA' // 23648-23658 + 'Q' // 23659 #16 + 'dA' // 2365a-2365e + 'Q' // 2365f #16 + 'vA' // 23660-23676 + 'Q' // 23677 #16 + 'uA' // 23678-2368d + 'Q' // 2368e #16 + 'nA' // 2368f-2369d + 'Q' // 2369e #16 + 'fA' // 2369f-236a5 + 'Q' // 236a6 #16 + 'eA' // 236a7-236ac + 'Q' // 236ad #16 + 'kA' // 236ae-236b9 + 'Q' // 236ba #16 + '1iA' // 236bb-236de + 'Q' // 236df #16 + 'mA' // 236e0-236ed + 'Q' // 236ee #16 + 'sA' // 236ef-23702 + 'Q' // 23703 #16 + 'gA' // 23704-2370b + 'C' // 2370c #2 + 'hA' // 2370d-23715 + 'Q' // 23716 #16 + 'dA' // 23717-2371b + 'C' // 2371c #2 + 'bA' // 2371d-2371f + 'Q' // 23720 #16 + 'kA' // 23721-2372c + 'Q' // 2372d #16 + 'A' // 2372e + 'Q' // 2372f #16 + 'nA' // 23730-2373e + '27R' // 2373f #719 + '1hA' // 23740-23762 + 'aC' // 23763-23764 #2 + 'A' // 23765 + 'Q' // 23766 #16 + 'yA' // 23767-23780 + 'Q' // 23781 #16 + '1eA' // 23782-237a1 + 'Q' // 237a2 #16 + 'xA' // 237a3-237bb + 'Q' // 237bc #16 + 'dA' // 237bd-237c1 + 'Q' // 237c2 #16 + 'qA' // 237c3-237d4 + 'bQ' // 237d5-237d7 #16 + 'nA' // 237d8-237e6 + 'C' // 237e7 #2 + 'hA' // 237e8-237f0 + 'C' // 237f1 #2 + 'lA' // 237f2-237fe + 'C' // 237ff #2 + '1iA' // 23800-23823 + 'C' // 23824 #2 + 'tA' // 23825-23839 + 'Q' // 2383a #16 + 'aA' // 2383b-2383c + 'C' // 2383d #2 + '14wA' // 2383e-239c1 + 'Q' // 239c2 #16 + '8dA' // 239c3-23a97 + 'C' // 23a98 #2 + 'mA' // 23a99-23aa6 + 'Q' // 23aa7 #16 + '1xA' // 23aa8-23ada + 'Q' // 23adb #16 + 'qA' // 23adc-23aed + 'Q' // 23aee #16 + 'jA' // 23aef-23af9 + 'Q' // 23afa #16 + '1dA' // 23afb-23b19 + 'Q' // 23b1a #16 + '2jA' // 23b1b-23b59 + 'Q' // 23b5a #16 + '10cA' // 23b5b-23c62 + 'Q' // 23c63 #16 + 'zA' // 23c64-23c7e + 'C' // 23c7f #2 + 'xA' // 23c80-23c98 + 'bQ' // 23c99-23c9b #16 + 'xA' // 23c9c-23cb4 + 'Q' // 23cb5 #16 + 'A' // 23cb6 + 'Q' // 23cb7 #16 + 'eA' // 23cb8-23cbd + 'C' // 23cbe #2 + 'gA' // 23cbf-23cc6 + 'bQ' // 23cc7-23cc9 #16 + '1wA' // 23cca-23cfb + 'aQ' // 23cfc-23cfd #16 + '27R' // 23cfe #719 + 'Q' // 23cff #16 + 'C' // 23d00 #2 + 'lA' // 23d01-23d0d + 'C' // 23d0e #2 + '1vA' // 23d0f-23d3f + '27R' // 23d40 #719 + 'yA' // 23d41-23d5a + 'Q' // 23d5b #16 + '1gA' // 23d5c-23d7d + 'Q' // 23d7e #16 + 'oA' // 23d7f-23d8e + 'Q' // 23d8f #16 + '1kA' // 23d90-23db5 + 'gQ' // 23db6-23dbd #16 + 'tA' // 23dbe-23dd2 + 'C' // 23dd3 #2 + 'nA' // 23dd4-23de2 + 'Q' // 23de3 #16 + 'sA' // 23de4-23df7 + 'Q' // 23df8 #16 + 'aC' // 23df9-23dfa #2 + 'jA' // 23dfb-23e05 + 'Q' // 23e06 #16 + 'iA' // 23e07-23e10 + 'Q' // 23e11 #16 + 'yA' // 23e12-23e2b + 'eQ' // 23e2c-23e31 #16 + 'fA' // 23e32-23e38 + 'Q' // 23e39 #16 + '2yA' // 23e3a-23e87 + 'cQ' // 23e88-23e8b #16 + '1rA' // 23e8c-23eb8 + 'Q' // 23eb9 #16 + 'dA' // 23eba-23ebe + 'Q' // 23ebf #16 + 'vA' // 23ec0-23ed6 + 'Q' // 23ed7 #16 + '1dA' // 23ed8-23ef6 + 'eQ' // 23ef7-23efc #16 + '2cA' // 23efd-23f34 + 'Q' // 23f35 #16 + 'jA' // 23f36-23f40 + 'Q' // 23f41 #16 + 'gA' // 23f42-23f49 + 'Q' // 23f4a #16 + 'uA' // 23f4b-23f60 + 'R' // 23f61 #17 + '1aA' // 23f62-23f7d + 'C' // 23f7e #2 + 'cR' // 23f7f-23f82 #17 + 'kA' // 23f83-23f8e + 'R' // 23f8f #17 + '1iA' // 23f90-23fb3 + 'R' // 23fb4 #17 + 'aA' // 23fb5-23fb6 + 'R' // 23fb7 #17 + 'gA' // 23fb8-23fbf + 'R' // 23fc0 #17 + 'cA' // 23fc1-23fc4 + 'R' // 23fc5 #17 + '1jA' // 23fc6-23fea + 'eR' // 23feb-23ff0 #17 + '1eA' // 23ff1-24010 + 'R' // 24011 #17 + '1lA' // 24012-24038 + 'dR' // 24039-2403d #17 + 'lA' // 2403e-2404a + 'C' // 2404b #2 + 'jA' // 2404c-24056 + 'R' // 24057 #17 + '1rA' // 24058-24084 + 'R' // 24085 #17 + 'dA' // 24086-2408a + 'bR' // 2408b-2408d #17 + 'bA' // 2408e-24090 + 'R' // 24091 #17 + 'cA' // 24092-24095 + 'C' // 24096 #2 + '1wA' // 24097-240c8 + 'R' // 240c9 #17 + 'vA' // 240ca-240e0 + 'R' // 240e1 #17 + 'iA' // 240e2-240eb + 'R' // 240ec #17 + 'uA' // 240ed-24102 + 'C' // 24103 #2 + 'R' // 24104 #17 + 'iA' // 24105-2410e + 'R' // 2410f #17 + 'hA' // 24110-24118 + 'R' // 24119 #17 + '1jA' // 2411a-2413e + 'aR' // 2413f-24140 #17 + 'bA' // 24141-24143 + 'R' // 24144 #17 + 'hA' // 24145-2414d + 'R' // 2414e #17 + 'eA' // 2414f-24154 + 'bR' // 24155-24157 #17 + 'cA' // 24158-2415b + 'R' // 2415c #17 + 'aA' // 2415d-2415e + 'R' // 2415f #17 + 'A' // 24160 + 'R' // 24161 #17 + 'tA' // 24162-24176 + 'R' // 24177 #17 + 'aA' // 24178-24179 + 'R' // 2417a #17 + '1mA' // 2417b-241a2 + 'bR' // 241a3-241a5 #17 + 'eA' // 241a6-241ab + 'R' // 241ac #17 + 'gA' // 241ad-241b4 + 'R' // 241b5 #17 + 'oA' // 241b6-241c5 + 'C' // 241c6 #2 + 'eA' // 241c7-241cc + 'R' // 241cd #17 + 'sA' // 241ce-241e1 + 'R' // 241e2 #17 + 'xA' // 241e3-241fb + 'R' // 241fc #17 + 'A' // 241fd + 'C' // 241fe #2 + '1aA' // 241ff-2421a + 'R' // 2421b #17 + '1tA' // 2421c-2424a + 'R' // 2424b #17 + 'iA' // 2424c-24255 + 'R' // 24256 #17 + 'aA' // 24257-24258 + 'R' // 24259 #17 + '1aA' // 2425a-24275 + 'bR' // 24276-24278 #17 + 'jA' // 24279-24283 + 'R' // 24284 #17 + 'mA' // 24285-24292 + 'R' // 24293 #17 + 'A' // 24294 + 'R' // 24295 #17 + 'nA' // 24296-242a4 + 'R' // 242a5 #17 + 'xA' // 242a6-242be + 'R' // 242bf #17 + 'A' // 242c0 + 'R' // 242c1 #17 + 'fA' // 242c2-242c8 + 'aR' // 242c9-242ca #17 + '1hA' // 242cb-242ed + '52S' // 242ee #1370 + 'jA' // 242ef-242f9 + 'R' // 242fa #17 + 'qA' // 242fb-2430c + 'R' // 2430d #17 + 'kA' // 2430e-24319 + 'R' // 2431a #17 + 'xA' // 2431b-24333 + 'R' // 24334 #17 + 'rA' // 24335-24347 + 'R' // 24348 #17 + 'xA' // 24349-24361 + 'cR' // 24362-24365 #17 + '1kA' // 24366-2438b + 'R' // 2438c #17 + 'hA' // 2438d-24395 + 'R' // 24396 #17 + 'dA' // 24397-2439b + 'R' // 2439c #17 + '1dA' // 2439d-243bb + 'C' // 243bc #2 + 'R' // 243bd #17 + 'bA' // 243be-243c0 + 'R' // 243c1 #17 + 'mA' // 243c2-243cf + 'C' // 243d0 #2 + 'wA' // 243d1-243e8 + 'aR' // 243e9-243ea #17 + 'fA' // 243eb-243f1 + 'R' // 243f2 #17 + 'dA' // 243f3-243f7 + 'R' // 243f8 #17 + 'jA' // 243f9-24403 + 'R' // 24404 #17 + '1uA' // 24405-24434 + 'aR' // 24435-24436 #17 + '1hA' // 24437-24459 + 'aR' // 2445a-2445b #17 + 'vA' // 2445c-24472 + 'R' // 24473 #17 + 'rA' // 24474-24486 + 'aR' // 24487-24488 #17 + '1uA' // 24489-244b8 + 'R' // 244b9 #17 + 'aA' // 244ba-244bb + 'R' // 244bc #17 + 'pA' // 244bd-244cd + 'R' // 244ce #17 + 'cA' // 244cf-244d2 + 'R' // 244d3 #17 + 'aA' // 244d4-244d5 + 'R' // 244d6 #17 + '1sA' // 244d7-24504 + 'R' // 24505 #17 + 'zA' // 24506-24520 + 'R' // 24521 #17 + '3gA' // 24522-24577 + 'R' // 24578 #17 + '2zA' // 24579-245c7 + 'R' // 245c8 #17 + '2zA' // 245c9-24617 + 'R' // 24618 #17 + 'oA' // 24619-24628 + 'C' // 24629 #2 + 'R' // 2462a #17 + '2eA' // 2462b-24664 + 'R' // 24665 #17 + 'mA' // 24666-24673 + 'R' // 24674 #17 + '1gA' // 24675-24696 + 'R' // 24697 #17 + 'lA' // 24698-246a4 + 'C' // 246a5 #2 + '1sA' // 246a6-246d3 + 'R' // 246d4 #17 + '1vA' // 246d5-24705 + 'R' // 24706 #17 + '1cA' // 24707-24724 + 'R' // 24725 #17 + 'hA' // 24726-2472e + 'R' // 2472f #17 + '3pA' // 24730-2478e + 'R' // 2478f #17 + '3aA' // 24790-247df + 'R' // 247e0 #17 + 'oA' // 247e1-247f0 + 'C' // 247f1 #2 + '1eA' // 247f2-24811 + 'R' // 24812 #17 + 'oA' // 24813-24822 + 'R' // 24823 #17 + '3oA' // 24824-24881 + 'R' // 24882 #17 + 'rA' // 24883-24895 + 'C' // 24896 #2 + '3cA' // 24897-248e8 + '52S' // 248e9 #1370 + 'eA' // 248ea-248ef + 'cR' // 248f0-248f3 #17 + 'fA' // 248f4-248fa + 'R' // 248fb #17 + 'bA' // 248fc-248fe + 'bR' // 248ff-24901 #17 + 'iA' // 24902-2490b + 'R' // 2490c #17 + 'hA' // 2490d-24915 + 'aR' // 24916-24917 #17 + 'A' // 24918 + 'R' // 24919 #17 + 'tA' // 2491a-2492e + 'R' // 2492f #17 + 'bA' // 24930-24932 + 'aR' // 24933-24934 #17 + 'hA' // 24935-2493d + 'eR' // 2493e-24943 #17 + '1cA' // 24944-24961 + 'aR' // 24962-24963 #17 + 'oA' // 24964-24973 + 'bR' // 24974-24976 #17 + 'cA' // 24977-2497a + 'R' // 2497b #17 + 'bA' // 2497c-2497e + 'R' // 2497f #17 + 'aA' // 24980-24981 + 'R' // 24982 #17 + 'dA' // 24983-24987 + 'gR' // 24988-2498f #17 + 'cA' // 24990-24993 + 'R' // 24994 #17 + 'nA' // 24995-249a3 + 'R' // 249a4 #17 + 'aA' // 249a5-249a6 + 'R' // 249a7 #17 + 'A' // 249a8 + 'R' // 249a9 #17 + 'A' // 249aa + 'bR' // 249ab-249ad #17 + 'hA' // 249ae-249b6 + 'cR' // 249b7-249ba #17 + 'P' // 249bb #15 + 'hA' // 249bc-249c4 + 'P' // 249c5 #15 + 'iA' // 249c6-249cf + 'P' // 249d0 #15 + 'hA' // 249d1-249d9 + 'P' // 249da #15 + 'bA' // 249db-249dd + 'aP' // 249de-249df #15 + 'bA' // 249e0-249e2 + 'P' // 249e3 #15 + 'A' // 249e4 + 'P' // 249e5 #15 + 'eA' // 249e6-249eb + 'aP' // 249ec-249ed #15 + 'gA' // 249ee-249f5 + 'cP' // 249f6-249f9 #15 + 'A' // 249fa + 'P' // 249fb #15 + 'qA' // 249fc-24a0d + 'P' // 24a0e #15 + 'bA' // 24a0f-24a11 + '37A' // 24a12 #962 + 'P' // 24a13 #15 + 'A' // 24a14 + 'P' // 24a15 #15 + 'jA' // 24a16-24a20 + 'iP' // 24a21-24a2a #15 + 'rA' // 24a2b-24a3d + 'P' // 24a3e #15 + 'bA' // 24a3f-24a41 + 'P' // 24a42 #15 + 'aA' // 24a43-24a44 + 'P' // 24a45 #15 + 'cA' // 24a46-24a49 + 'P' // 24a4a #15 + 'aA' // 24a4b-24a4c + 'C' // 24a4d #2 + 'cP' // 24a4e-24a51 #15 + 'jA' // 24a52-24a5c + 'P' // 24a5d #15 + 'fA' // 24a5e-24a64 + 'bP' // 24a65-24a67 #15 + 'hA' // 24a68-24a70 + 'P' // 24a71 #15 + 'dA' // 24a72-24a76 + 'cP' // 24a77-24a7a #15 + 'pA' // 24a7b-24a8b + 'P' // 24a8c #15 + 'eA' // 24a8d-24a92 + 'cP' // 24a93-24a96 #15 + 'lA' // 24a97-24aa3 + 'cP' // 24aa4-24aa7 #15 + 'hA' // 24aa8-24ab0 + 'bP' // 24ab1-24ab3 #15 + 'eA' // 24ab4-24ab9 + 'bP' // 24aba-24abc #15 + 'bA' // 24abd-24abf + 'P' // 24ac0 #15 + 'eA' // 24ac1-24ac6 + 'P' // 24ac7 #15 + 'aA' // 24ac8-24ac9 + 'P' // 24aca #15 + 'eA' // 24acb-24ad0 + 'P' // 24ad1 #15 + 'lA' // 24ad2-24ade + 'P' // 24adf #15 + 'aA' // 24ae0-24ae1 + 'P' // 24ae2 #15 + 'eA' // 24ae3-24ae8 + 'P' // 24ae9 #15 + '1jA' // 24aea-24b0e + 'P' // 24b0f #15 + '2qA' // 24b10-24b55 + 'C' // 24b56 #2 + 'vA' // 24b57-24b6d + 'P' // 24b6e #15 + 'C' // 24b6f #2 + '5bA' // 24b70-24bf4 + 'P' // 24bf5 #15 + 'rA' // 24bf6-24c08 + 'P' // 24c09 #15 + 'kA' // 24c0a-24c15 + 'C' // 24c16 #2 + '5dA' // 24c17-24c9d + 'aP' // 24c9e-24c9f #15 + '1nA' // 24ca0-24cc8 + 'P' // 24cc9 #15 + 'nA' // 24cca-24cd8 + 'P' // 24cd9 #15 + '1qA' // 24cda-24d05 + 'P' // 24d06 #15 + 'kA' // 24d07-24d12 + 'P' // 24d13 #15 + 'C' // 24d14 #2 + '6fA' // 24d15-24db7 + 'P' // 24db8 #15 + '1vA' // 24db9-24de9 + 'aP' // 24dea-24deb #15 + 'wA' // 24dec-24e03 + 'C' // 24e04 #2 + 'hA' // 24e05-24e0d + 'C' // 24e0e #2 + '1mA' // 24e0f-24e36 + 'C' // 24e37 #2 + 'bA' // 24e38-24e3a + 'P' // 24e3b #15 + 'sA' // 24e3c-24e4f + 'P' // 24e50 #15 + 'xA' // 24e51-24e69 + 'C' // 24e6a #2 + '1eA' // 24e6b-24e8a + 'C' // 24e8b #2 + 'xA' // 24e8c-24ea4 + 'P' // 24ea5 #15 + 'A' // 24ea6 + 'P' // 24ea7 #15 + '3wA' // 24ea8-24f0d + 'P' // 24f0e #15 + '2xA' // 24f0f-24f5b + 'P' // 24f5c #15 + '1jA' // 24f5d-24f81 + 'P' // 24f82 #15 + 'bA' // 24f83-24f85 + 'P' // 24f86 #15 + 'oA' // 24f87-24f96 + 'P' // 24f97 #15 + 'aA' // 24f98-24f99 + 'P' // 24f9a #15 + 'mA' // 24f9b-24fa8 + 'P' // 24fa9 #15 + 'mA' // 24faa-24fb7 + 'P' // 24fb8 #15 + 'hA' // 24fb9-24fc1 + 'P' // 24fc2 #15 + '1tA' // 24fc3-24ff1 + 'C' // 24ff2 #2 + '2dA' // 24ff3-2502b + 'P' // 2502c #15 + '1bA' // 2502d-25049 + 'C' // 2504a #2 + 'fA' // 2504b-25051 + 'P' // 25052 #15 + 'aA' // 25053-25054 + 'C' // 25055 #2 + '2rA' // 25056-2509c + 'P' // 2509d #15 + '5aA' // 2509e-25121 + 'C' // 25122 #2 + 'gA' // 25123-2512a + '37A' // 2512b #962 + '1aA' // 2512c-25147 + 'P' // 25148 #15 + '1yA' // 25149-2517c + 'aP' // 2517d-2517e #15 + '1oA' // 2517f-251a8 + 'C' // 251a9 #2 + '1hA' // 251aa-251cc + '52R' // 251cd #1369 + 'tA' // 251ce-251e2 + 'P' // 251e3 #15 + 'A' // 251e4 + 'C' // 251e5 #2 + 'aP' // 251e6-251e7 #15 + '2aA' // 251e8-2521d + 'C' // 2521e #2 + 'A' // 2521f + 'aP' // 25220-25221 #15 + '1oA' // 25222-2524b + 'C' // 2524c #2 + 'bA' // 2524d-2524f + 'P' // 25250 #15 + '2sA' // 25251-25298 + 'P' // 25299 #15 + '1rA' // 2529a-252c6 + 'P' // 252c7 #15 + 'oA' // 252c8-252d7 + 'P' // 252d8 #15 + '1zA' // 252d9-2530d + 'P' // 2530e #15 + 'aA' // 2530f-25310 + 'P' // 25311 #15 + 'A' // 25312 + 'P' // 25313 #15 + '9zA' // 25314-25418 + 'P' // 25419 #15 + 'jA' // 2541a-25424 + 'P' // 25425 #15 + 'gA' // 25426-2542d + 'C' // 2542e #2 + 'aP' // 2542f-25430 #15 + 'tA' // 25431-25445 + 'P' // 25446 #15 + '1jA' // 25447-2546b + 'P' // 2546c #15 + 'A' // 2546d + '37A' // 2546e #962 + '1dA' // 2546f-2548d + 'C' // 2548e #2 + 'jA' // 2548f-25499 + 'P' // 2549a #15 + '2iA' // 2549b-254d8 + 'C' // 254d9 #2 + '1yA' // 254da-2550d + 'C' // 2550e #2 + '1gA' // 2550f-25530 + 'P' // 25531 #15 + 'bA' // 25532-25534 + 'P' // 25535 #15 + 'hA' // 25536-2553e + 'P' // 2553f #15 + 'zA' // 25540-2555a + 'cP' // 2555b-2555e #15 + 'bA' // 2555f-25561 + 'P' // 25562 #15 + 'aA' // 25563-25564 + 'aP' // 25565-25566 #15 + 'yA' // 25567-25580 + 'P' // 25581 #15 + 'aA' // 25582-25583 + 'P' // 25584 #15 + 'iA' // 25585-2558e + 'P' // 2558f #15 + 'vA' // 25590-255a6 + 'C' // 255a7 #2 + 'pA' // 255a8-255b8 + 'P' // 255b9 #15 + 'zA' // 255ba-255d4 + 'P' // 255d5 #15 + 'dA' // 255d6-255da + 'P' // 255db #15 + 'cA' // 255dc-255df + 'P' // 255e0 #15 + '1iA' // 255e1-25604 + 'P' // 25605 #15 + '1tA' // 25606-25634 + 'P' // 25635 #15 + 'zA' // 25636-25650 + 'P' // 25651 #15 + '1rA' // 25652-2567e + 'C' // 2567f #2 + 'bA' // 25680-25682 + '37A' // 25683 #962 + 'pA' // 25684-25694 + 'P' // 25695 #15 + '2xA' // 25696-256e2 + 'P' // 256e3 #15 + 'qA' // 256e4-256f5 + 'P' // 256f6 #15 + 'nA' // 256f7-25705 + 'P' // 25706 #15 + 'uA' // 25707-2571c + 'P' // 2571d #15 + 'fA' // 2571e-25724 + 'P' // 25725 #15 + 'vA' // 25726-2573c + 'P' // 2573d #15 + '1xA' // 2573e-25770 + 'C' // 25771 #2 + 'P' // 25772 #15 + '2aA' // 25773-257a8 + 'C' // 257a9 #2 + 'iA' // 257aa-257b3 + 'C' // 257b4 #2 + 'qA' // 257b5-257c6 + 'P' // 257c7 #15 + 'vA' // 257c8-257de + 'bP' // 257df-257e1 #15 + '4lA' // 257e2-25856 + 'P' // 25857 #15 + 'dA' // 25858-2585c + 'P' // 2585d #15 + 'sA' // 2585e-25871 + 'P' // 25872 #15 + 'A' // 25873 + 'C' // 25874 #2 + '3dA' // 25875-258c7 + 'P' // 258c8 #15 + 'tA' // 258c9-258dd + 'P' // 258de #15 + 'aA' // 258df-258e0 + 'P' // 258e1 #15 + '1fA' // 258e2-25902 + 'P' // 25903 #15 + '2mA' // 25904-25945 + 'P' // 25946 #15 + 'nA' // 25947-25955 + 'P' // 25956 #15 + '3fA' // 25957-259ab + 'P' // 259ac #15 + 'vA' // 259ad-259c3 + 'C' // 259c4 #2 + 'fA' // 259c5-259cb + '52R' // 259cc #1369 + 'fA' // 259cd-259d3 + 'C' // 259d4 #2 + '4vA' // 259d5-25a53 + 'P' // 25a54 #15 + '2kA' // 25a55-25a94 + 'P' // 25a95 #15 + 'eA' // 25a96-25a9b + 'K' // 25a9c #10 + 'pA' // 25a9d-25aad + 'aK' // 25aae-25aaf #10 + '1lA' // 25ab0-25ad6 + 'C' // 25ad7 #2 + 'jA' // 25ad8-25ae2 + 'aC' // 25ae3-25ae4 #2 + 'cA' // 25ae5-25ae8 + 'K' // 25ae9 #10 + 'fA' // 25aea-25af0 + 'C' // 25af1 #2 + '4yA' // 25af2-25b73 + 'K' // 25b74 #10 + 'sA' // 25b75-25b88 + 'K' // 25b89 #10 + '1mA' // 25b8a-25bb1 + 'C' // 25bb2 #2 + 'aK' // 25bb3-25bb4 #10 + 'pA' // 25bb5-25bc5 + 'K' // 25bc6 #10 + '1bA' // 25bc7-25be3 + 'K' // 25be4 #10 + 'bA' // 25be5-25be7 + 'K' // 25be8 #10 + 'wA' // 25be9-25c00 + 'K' // 25c01 #10 + 'cA' // 25c02-25c05 + 'K' // 25c06 #10 + 'yA' // 25c07-25c20 + 'K' // 25c21 #10 + '1mA' // 25c22-25c49 + 'K' // 25c4a #10 + 'C' // 25c4b #2 + 'wA' // 25c4c-25c63 + 'C' // 25c64 #2 + 'K' // 25c65 #10 + '1pA' // 25c66-25c90 + 'K' // 25c91 #10 + 'qA' // 25c92-25ca3 + 'K' // 25ca4 #10 + 'zA' // 25ca5-25cbf + 'aK' // 25cc0-25cc1 #10 + '2gA' // 25cc2-25cfd + 'K' // 25cfe #10 + '1fA' // 25cff-25d1f + 'K' // 25d20 #10 + 'nA' // 25d21-25d2f + 'K' // 25d30 #10 + 'qA' // 25d31-25d42 + 'K' // 25d43 #10 + '3fA' // 25d44-25d98 + 'K' // 25d99 #10 + 'fA' // 25d9a-25da0 + 'C' // 25da1 #2 + 'vA' // 25da2-25db8 + 'K' // 25db9 #10 + '3eA' // 25dba-25e0d + 'K' // 25e0e #10 + '1dA' // 25e0f-25e2d + 'C' // 25e2e #2 + 'yA' // 25e2f-25e48 + 'K' // 25e49 #10 + 'kA' // 25e4a-25e55 + 'C' // 25e56 #2 + 'jA' // 25e57-25e61 + 'C' // 25e62 #2 + 'aA' // 25e63-25e64 + 'C' // 25e65 #2 + 'zA' // 25e66-25e80 + 'bK' // 25e81-25e83 #10 + '1gA' // 25e84-25ea5 + 'K' // 25ea6 #10 + 'tA' // 25ea7-25ebb + 'K' // 25ebc #10 + 'dA' // 25ebd-25ec1 + 'C' // 25ec2 #2 + 'sA' // 25ec3-25ed6 + 'K' // 25ed7 #10 + '80K' // 25ed8 #2090 + 'nA' // 25ed9-25ee7 + 'B' // 25ee8 #1 + '1vA' // 25ee9-25f19 + 'K' // 25f1a #10 + 'gA' // 25f1b-25f22 + 'B' // 25f23 #1 + '1lA' // 25f24-25f4a + 'K' // 25f4b #10 + 'oA' // 25f4c-25f5b + 'B' // 25f5c #1 + '4nA' // 25f5d-25fd3 + 'B' // 25fd4 #1 + 'jA' // 25fd5-25fdf + 'B' // 25fe0 #1 + 'aK' // 25fe1-25fe2 #10 + 'wA' // 25fe3-25ffa + 'B' // 25ffb #1 + 'oA' // 25ffc-2600b + 'B' // 2600c #1 + 'iA' // 2600d-26016 + 'B' // 26017 #1 + 'hA' // 26018-26020 + 'K' // 26021 #10 + 'fA' // 26022-26028 + 'K' // 26029 #10 + '1cA' // 2602a-26047 + 'K' // 26048 #10 + 'vA' // 26049-2605f + 'B' // 26060 #1 + 'bA' // 26061-26063 + 'K' // 26064 #10 + '1cA' // 26065-26082 + 'K' // 26083 #10 + 'rA' // 26084-26096 + 'K' // 26097 #10 + 'kA' // 26098-260a3 + 'aK' // 260a4-260a5 #10 + '2rA' // 260a6-260ec + 'B' // 260ed #1 + 'sA' // 260ee-26101 + 'K' // 26102 #10 + '1cA' // 26103-26120 + 'K' // 26121 #10 + '2bA' // 26122-26158 + 'cK' // 26159-2615c #10 + '3aA' // 2615d-261ac + 'aK' // 261ad-261ae #10 + 'bA' // 261af-261b1 + 'K' // 261b2 #10 + '1oA' // 261b3-261dc + 'K' // 261dd #10 + '2oA' // 261de-26221 + 'B' // 26222 #1 + '1zA' // 26223-26257 + 'K' // 26258 #10 + 'gA' // 26259-26260 + 'K' // 26261 #10 + 'gA' // 26262-26269 + '36Z' // 2626a #961 + 'K' // 2626b #10 + 'cA' // 2626c-2626f + 'B' // 26270 #1 + 'tA' // 26271-26285 + 'B' // 26286 #1 + '2tA' // 26287-262cf + 'K' // 262d0 #10 + '3uA' // 262d1-26334 + 'K' // 26335 #10 + 'tA' // 26336-2634a + 'K' // 2634b #10 + '36Z' // 2634c #961 + 'cA' // 2634d-26350 + 'K' // 26351 #10 + '4cA' // 26352-263bd + 'K' // 263be #10 + '2aA' // 263bf-263f4 + 'K' // 263f5 #10 + 'aA' // 263f6-263f7 + 'K' // 263f8 #10 + 'hA' // 263f9-26401 + '36Z' // 26402 #961 + 'lA' // 26403-2640f + 'bK' // 26410-26412 #10 + '2bA' // 26413-26449 + 'K' // 2644a #10 + '1cA' // 2644b-26468 + 'K' // 26469 #10 + 'yA' // 2646a-26483 + 'K' // 26484 #10 + 'bA' // 26485-26487 + 'aK' // 26488-26489 #10 + 'bA' // 2648a-2648c + 'K' // 2648d #10 + 'iA' // 2648e-26497 + 'K' // 26498 #10 + '4pA' // 26499-26511 + 'K' // 26512 #10 + '3pA' // 26513-26571 + 'K' // 26572 #10 + '1rA' // 26573-2659f + 'K' // 265a0 #10 + 'kA' // 265a1-265ac + 'K' // 265ad #10 + 'pA' // 265ae-265be + 'K' // 265bf #10 + '3cA' // 265c0-26611 + 'K' // 26612 #10 + 'rA' // 26613-26625 + 'K' // 26626 #10 + '3hA' // 26627-2667d + 'B' // 2667e #1 + '1uA' // 2667f-266ae + 'K' // 266af #10 + 'B' // 266b0 #1 + 'K' // 266b1 #10 + 'bA' // 266b2-266b4 + 'K' // 266b5 #10 + '1iA' // 266b6-266d9 + 'K' // 266da #10 + 'lA' // 266db-266e7 + 'K' // 266e8 #10 + 'rA' // 266e9-266fb + 'K' // 266fc #10 + 'xA' // 266fd-26715 + 'K' // 26716 #10 + 'eA' // 26717-2671c + 'B' // 2671d #1 + '1hA' // 2671e-26740 + 'K' // 26741 #10 + '3hA' // 26742-26798 + 'K' // 26799 #10 + 'xA' // 2679a-267b2 + 'aK' // 267b3-267b4 #10 + 'vA' // 267b5-267cb + '52Q' // 267cc #1368 + '2zA' // 267cd-2681b + 'K' // 2681c #10 + '1nA' // 2681d-26845 + 'K' // 26846 #10 + 'vA' // 26847-2685d + 'K' // 2685e #10 + 'nA' // 2685f-2686d + 'K' // 2686e #10 + 'xA' // 2686f-26887 + 'K' // 26888 #10 + 'A' // 26889 + 'K' // 2688a #10 + 'gA' // 2688b-26892 + 'K' // 26893 #10 + '1xA' // 26894-268c6 + 'K' // 268c7 #10 + 'tA' // 268c8-268dc + 'B' // 268dd #1 + 'kA' // 268de-268e9 + 'B' // 268ea #1 + '1hA' // 268eb-2690d + 'K' // 2690e #10 + 'aA' // 2690f-26910 + 'K' // 26911 #10 + 'sA' // 26912-26925 + 'K' // 26926 #10 + 'qA' // 26927-26938 + 'K' // 26939 #10 + 'vA' // 2693a-26950 + '36Z' // 26951 #961 + '1bA' // 26952-2696e + 'B' // 2696f #1 + '1nA' // 26970-26998 + 'B' // 26999 #1 + 'mA' // 2699a-269a7 + 'K' // 269a8 #10 + 'kA' // 269a9-269b4 + 'K' // 269b5 #10 + '1lA' // 269b6-269dc + 'B' // 269dd #1 + 'sA' // 269de-269f1 + '52Q' // 269f2 #1368 + 'fA' // 269f3-269f9 + 'K' // 269fa #10 + '1hA' // 269fb-26a1d + 'B' // 26a1e #1 + 'mA' // 26a1f-26a2c + 'aK' // 26a2d-26a2e #10 + 'dA' // 26a2f-26a33 + 'K' // 26a34 #10 + 'lA' // 26a35-26a41 + 'K' // 26a42 #10 + 'mA' // 26a43-26a50 + 'aK' // 26a51-26a52 #10 + 'dA' // 26a53-26a57 + 'B' // 26a58 #1 + '1xA' // 26a59-26a8b + 'B' // 26a8c #1 + '1oA' // 26a8d-26ab6 + 'B' // 26ab7 #1 + '2rA' // 26ab8-26afe + 'B' // 26aff #1 + 'dA' // 26b00-26b04 + 'K' // 26b05 #10 + 'cA' // 26b06-26b09 + 'K' // 26b0a #10 + 'gA' // 26b0b-26b12 + 'K' // 26b13 #10 + 'A' // 26b14 + 'K' // 26b15 #10 + 'lA' // 26b16-26b22 + 'K' // 26b23 #10 + 'cA' // 26b24-26b27 + 'K' // 26b28 #10 + '1lA' // 26b29-26b4f + 'cK' // 26b50-26b53 #10 + 'fA' // 26b54-26b5a + 'K' // 26b5b #10 + 'xA' // 26b5c-26b74 + 'K' // 26b75 #10 + 'kA' // 26b76-26b81 + 'K' // 26b82 #10 + 'rA' // 26b83-26b95 + 'aK' // 26b96-26b97 #10 + 'dA' // 26b98-26b9c + 'K' // 26b9d #10 + 'tA' // 26b9e-26bb2 + 'K' // 26bb3 #10 + 'kA' // 26bb4-26bbf + 'K' // 26bc0 #10 + '2aA' // 26bc1-26bf6 + 'K' // 26bf7 #10 + '1nA' // 26bf8-26c20 + 'K' // 26c21 #10 + 'fA' // 26c22-26c28 + 'B' // 26c29 #1 + 'uA' // 26c2a-26c3f + 'aK' // 26c40-26c41 #10 + 'cA' // 26c42-26c45 + 'K' // 26c46 #10 + '1qA' // 26c47-26c72 + 'B' // 26c73 #1 + 'iA' // 26c74-26c7d + 'dK' // 26c7e-26c82 #10 + 'zA' // 26c83-26c9d + 'B' // 26c9e #1 + 'dA' // 26c9f-26ca3 + 'K' // 26ca4 #10 + 'qA' // 26ca5-26cb6 + 'aK' // 26cb7-26cb8 #10 + 'cA' // 26cb9-26cbc + 'K' // 26cbd #10 + 'aA' // 26cbe-26cbf + 'K' // 26cc0 #10 + 'aA' // 26cc1-26cc2 + 'K' // 26cc3 #10 + 'lA' // 26cc4-26cd0 + 'K' // 26cd1 #10 + 'jA' // 26cd2-26cdc + 'B' // 26cdd #1 + '2oA' // 26cde-26d21 + 'hK' // 26d22-26d2a #10 + '1kA' // 26d2b-26d50 + 'K' // 26d51 #10 + '1gA' // 26d52-26d73 + 'K' // 26d74 #10 + '1pA' // 26d75-26d9f + 'gJ' // 26da0-26da7 #9 + 'eA' // 26da8-26dad + 'J' // 26dae #9 + '1rA' // 26daf-26ddb + 'J' // 26ddc #9 + 'lA' // 26ddd-26de9 + 'aJ' // 26dea-26deb #9 + 'cA' // 26dec-26def + 'J' // 26df0 #9 + 'nA' // 26df1-26dff + 'J' // 26e00 #9 + 'cA' // 26e01-26e04 + 'J' // 26e05 #9 + 'A' // 26e06 + 'J' // 26e07 #9 + 'iA' // 26e08-26e11 + 'J' // 26e12 #9 + '1rA' // 26e13-26e3f + 'B' // 26e40 #1 + 'A' // 26e41 + 'cJ' // 26e42-26e45 #9 + '1dA' // 26e46-26e64 + 'B' // 26e65 #1 + 'gA' // 26e66-26e6d + 'J' // 26e6e #9 + 'bA' // 26e6f-26e71 + 'J' // 26e72 #9 + 'cA' // 26e73-26e76 + 'J' // 26e77 #9 + 'kA' // 26e78-26e83 + 'J' // 26e84 #9 + 'bA' // 26e85-26e87 + 'J' // 26e88 #9 + 'aA' // 26e89-26e8a + 'J' // 26e8b #9 + 'lA' // 26e8c-26e98 + 'J' // 26e99 #9 + '2aA' // 26e9a-26ecf + 'gJ' // 26ed0-26ed7 #9 + '2yA' // 26ed8-26f25 + 'J' // 26f26 #9 + '2wA' // 26f27-26f72 + 'aJ' // 26f73-26f74 #9 + '1dA' // 26f75-26f93 + 'B' // 26f94 #1 + 'iA' // 26f95-26f9e + 'J' // 26f9f #9 + 'A' // 26fa0 + 'J' // 26fa1 #9 + '1aA' // 26fa2-26fbd + 'J' // 26fbe #9 + '1dA' // 26fbf-26fdd + 'aJ' // 26fde-26fdf #9 + 'uA' // 26fe0-26ff5 + 'bB' // 26ff6-26ff8 #1 + 'tA' // 26ff9-2700d + 'J' // 2700e #9 + '2gA' // 2700f-2704a + 'J' // 2704b #9 + 'eA' // 2704c-27051 + 'aJ' // 27052-27053 #9 + '1yA' // 27054-27087 + 'J' // 27088 #9 + '1iA' // 27089-270ac + 'bJ' // 270ad-270af #9 + '1bA' // 270b0-270cc + 'J' // 270cd #9 + 'cA' // 270ce-270d1 + 'J' // 270d2 #9 + '1bA' // 270d3-270ef + 'J' // 270f0 #9 + 'bA' // 270f1-270f3 + 'B' // 270f4 #1 + 'bA' // 270f5-270f7 + 'J' // 270f8 #9 + 'oA' // 270f9-27108 + 'J' // 27109 #9 + 'aA' // 2710a-2710b + 'J' // 2710c #9 + '36Y' // 2710d #960 + 'wA' // 2710e-27125 + 'aJ' // 27126-27127 #9 + 'pA' // 27128-27138 + 'B' // 27139 #1 + '1oA' // 2713a-27163 + 'aJ' // 27164-27165 #9 + 'nA' // 27166-27174 + 'J' // 27175 #9 + '3hA' // 27176-271cc + 'J' // 271cd #9 + '2xA' // 271ce-2721a + 'J' // 2721b #9 + '2vA' // 2721c-27266 + 'J' // 27267 #9 + 'wA' // 27268-2727f + 'J' // 27280 #9 + 'cA' // 27281-27284 + 'J' // 27285 #9 + 'dA' // 27286-2728a + 'J' // 2728b #9 + '1kA' // 2728c-272b1 + 'J' // 272b2 #9 + 'bA' // 272b3-272b5 + 'J' // 272b6 #9 + '1tA' // 272b7-272e5 + 'J' // 272e6 #9 + '4bA' // 272e7-27351 + 'J' // 27352 #9 + '2rA' // 27353-27399 + 'J' // 2739a #9 + '2jA' // 2739b-273d9 + 'aB' // 273da-273db #1 + '1gA' // 273dc-273fd + 'B' // 273fe #1 + 'J' // 273ff #9 + 'oA' // 27400-2740f + 'B' // 27410 #1 + 'pA' // 27411-27421 + 'J' // 27422 #9 + '1kA' // 27423-27448 + 'B' // 27449 #1 + 'eA' // 2744a-2744f + 'J' // 27450 #9 + '1xA' // 27451-27483 + 'J' // 27484 #9 + 'A' // 27485 + 'J' // 27486 #9 + '9bA' // 27487-27573 + 'J' // 27574 #9 + '1sA' // 27575-275a2 + 'J' // 275a3 #9 + '2gA' // 275a4-275df + 'J' // 275e0 #9 + 'bA' // 275e1-275e3 + 'J' // 275e4 #9 + 'wA' // 275e5-275fc + 'aJ' // 275fd-275fe #9 + 'gA' // 275ff-27606 + 'J' // 27607 #9 + 'cA' // 27608-2760b + 'J' // 2760c #9 + 'fA' // 2760d-27613 + 'aB' // 27614-27615 #1 + 'zA' // 27616-27630 + 'B' // 27631 #1 + 'J' // 27632 #9 + 'eA' // 27633-27638 + 'J' // 27639 #9 + 'zA' // 2763a-27654 + 'aJ' // 27655-27656 #9 + '80J' // 27657 #2089 + '1qA' // 27658-27683 + 'B' // 27684 #1 + 'mA' // 27685-27692 + 'B' // 27693 #1 + 'J' // 27694 #9 + '4pA' // 27695-2770d + 'B' // 2770e #1 + 'J' // 2770f #9 + 'rA' // 27710-27722 + 'B' // 27723 #1 + 'pA' // 27724-27734 + 'aJ' // 27735-27736 #9 + 'iA' // 27737-27740 + 'J' // 27741 #9 + 'oA' // 27742-27751 + 'B' // 27752 #1 + 'jA' // 27753-2775d + 'J' // 2775e #9 + '1jA' // 2775f-27783 + 'aJ' // 27784-27785 #9 + '2qA' // 27786-277cb + 'J' // 277cc #9 + '5hA' // 277cd-27857 + 'J' // 27858 #9 + 'vA' // 27859-2786f + 'J' // 27870 #9 + '1qA' // 27871-2789c + 'J' // 2789d #9 + 'sA' // 2789e-278b1 + '36Y' // 278b2 #960 + 'tA' // 278b3-278c7 + 'J' // 278c8 #9 + '3lA' // 278c9-27923 + 'J' // 27924 #9 + '2mA' // 27925-27966 + 'J' // 27967 #9 + 'qA' // 27968-27979 + 'J' // 2797a #9 + 'iA' // 2797b-27984 + 'B' // 27985 #1 + 'yA' // 27986-2799f + 'J' // 279a0 #9 + 'rA' // 279a1-279b3 + 'B' // 279b4 #1 + '1mA' // 279b5-279dc + 'J' // 279dd #9 + '1dA' // 279de-279fc + 'J' // 279fd #9 + 'kA' // 279fe-27a09 + 'J' // 27a0a #9 + 'bA' // 27a0b-27a0d + 'J' // 27a0e #9 + '1tA' // 27a0f-27a3d + 'J' // 27a3e #9 + 'sA' // 27a3f-27a52 + 'J' // 27a53 #9 + 'dA' // 27a54-27a58 + 'J' // 27a59 #9 + '1dA' // 27a5a-27a78 + 'J' // 27a79 #9 + 'iA' // 27a7a-27a83 + '36Y' // 27a84 #960 + '2cA' // 27a85-27abc + 'aJ' // 27abd-27abe #9 + '1zA' // 27abf-27af3 + 'J' // 27af4 #9 + 'pA' // 27af5-27b05 + 'J' // 27b06 #9 + 'cA' // 27b07-27b0a + 'J' // 27b0b #9 + 'kA' // 27b0c-27b17 + 'J' // 27b18 #9 + '1dA' // 27b19-27b37 + 'bJ' // 27b38-27b3a #9 + 'lA' // 27b3b-27b47 + 'J' // 27b48 #9 + '1aA' // 27b49-27b64 + 'J' // 27b65 #9 + '2xA' // 27b66-27bb2 + 'B' // 27bb3 #1 + 'iA' // 27bb4-27bbd + 'B' // 27bbe #1 + 'gA' // 27bbf-27bc6 + 'B' // 27bc7 #1 + '1lA' // 27bc8-27bee + 'J' // 27bef #9 + 'cA' // 27bf0-27bf3 + 'J' // 27bf4 #9 + '1bA' // 27bf5-27c11 + 'J' // 27c12 #9 + '1nA' // 27c13-27c3b + 'B' // 27c3c #1 + '1tA' // 27c3d-27c6b + 'J' // 27c6c #9 + '2oA' // 27c6d-27cb0 + 'J' // 27cb1 #9 + 'eA' // 27cb2-27cb7 + 'B' // 27cb8 #1 + 'kA' // 27cb9-27cc4 + 'J' // 27cc5 #9 + '3zA' // 27cc6-27d2e + 'J' // 27d2f #9 + '1hA' // 27d30-27d52 + 'aJ' // 27d53-27d54 #9 + 'pA' // 27d55-27d65 + 'J' // 27d66 #9 + 'kA' // 27d67-27d72 + '36Y' // 27d73 #960 + 'oA' // 27d74-27d83 + 'J' // 27d84 #9 + 'iA' // 27d85-27d8e + 'J' // 27d8f #9 + 'gA' // 27d90-27d97 + 'J' // 27d98 #9 + 'fA' // 27d99-27d9f + 'B' // 27da0 #1 + '1aA' // 27da1-27dbc + 'J' // 27dbd #9 + '1cA' // 27dbe-27ddb + 'J' // 27ddc #9 + '1xA' // 27ddd-27e0f + 'B' // 27e10 #1 + '2gA' // 27e11-27e4c + 'J' // 27e4d #9 + 'A' // 27e4e + 'J' // 27e4f #9 + '3pA' // 27e50-27eae + 'B' // 27eaf #1 + '4uA' // 27eb0-27f2d + 'J' // 27f2e #9 + '5eA' // 27f2f-27fb6 + 'B' // 27fb7 #1 + '2lA' // 27fb8-27ff8 + 'J' // 27ff9 #9 + 'gA' // 27ffa-28001 + 'J' // 28002 #9 + 'eA' // 28003-28008 + 'J' // 28009 #9 + 'sA' // 2800a-2801d + 'J' // 2801e #9 + 'cA' // 2801f-28022 + 'aJ' // 28023-28024 #9 + '1hA' // 28025-28047 + 'J' // 28048 #9 + '2eA' // 28049-28082 + 'J' // 28083 #9 + 'eA' // 28084-28089 + 'B' // 2808a #1 + 'dA' // 2808b-2808f + 'J' // 28090 #9 + '1oA' // 28091-280ba + 'B' // 280bb #1 + 'A' // 280bc + 'aJ' // 280bd-280be #9 + '1nA' // 280bf-280e7 + 'aJ' // 280e8-280e9 #9 + 'iA' // 280ea-280f3 + 'J' // 280f4 #9 + '2dA' // 280f5-2812d + 'J' // 2812e #9 + '1eA' // 2812f-2814e + 'J' // 2814f #9 + 'lA' // 28150-2815c + 'J' // 2815d #9 + 'pA' // 2815e-2816e + 'J' // 2816f #9 + 'xA' // 28170-28188 + 'N' // 28189 #13 + '1jA' // 2818a-281ae + 'N' // 281af #13 + 'kA' // 281b0-281bb + 'N' // 281bc #13 + '2uA' // 281bd-28206 + 'N' // 28207 #13 + 'oA' // 28208-28217 + 'N' // 28218 #13 + 'A' // 28219 + 'N' // 2821a #13 + '2fA' // 2821b-28255 + 'N' // 28256 #13 + '1eA' // 28257-28276 + 'B' // 28277 #1 + 'cA' // 28278-2827b + 'N' // 2827c #13 + 'dA' // 2827d-28281 + 'B' // 28282 #1 + 'wA' // 28283-2829a + 'N' // 2829b #13 + '1vA' // 2829c-282cc + 'N' // 282cd #13 + 'sA' // 282ce-282e1 + '80I' // 282e2 #2088 + 'oA' // 282e3-282f2 + 'B' // 282f3 #1 + 'qA' // 282f4-28305 + 'N' // 28306 #13 + 'pA' // 28307-28317 + 'N' // 28318 #13 + 'uA' // 28319-2832e + 'N' // 2832f #13 + 'iA' // 28330-28339 + 'N' // 2833a #13 + '1oA' // 2833b-28364 + 'N' // 28365 #13 + 'fA' // 28366-2836c + 'N' // 2836d #13 + 'nA' // 2836e-2837c + 'N' // 2837d #13 + 'kA' // 2837e-28389 + 'N' // 2838a #13 + '2mA' // 2838b-283cc + 'B' // 283cd #1 + '2iA' // 283ce-2840b + 'B' // 2840c #1 + 'dA' // 2840d-28411 + 'N' // 28412 #13 + '2mA' // 28413-28454 + 'B' // 28455 #1 + 'qA' // 28456-28467 + 'N' // 28468 #13 + 'bA' // 28469-2846b + 'N' // 2846c #13 + 'eA' // 2846d-28472 + 'N' // 28473 #13 + 'mA' // 28474-28481 + 'N' // 28482 #13 + '3jA' // 28483-284db + 'B' // 284dc #1 + '1iA' // 284dd-28500 + 'N' // 28501 #13 + '2eA' // 28502-2853b + 'aN' // 2853c-2853d #13 + '1rA' // 2853e-2856a + 'B' // 2856b #1 + 'N' // 2856c #13 + '3lA' // 2856d-285c7 + 'aB' // 285c8-285c9 #1 + '1cA' // 285ca-285e7 + 'N' // 285e8 #13 + 'jA' // 285e9-285f3 + 'N' // 285f4 #13 + 'jA' // 285f5-285ff + 'N' // 28600 #13 + 'iA' // 28601-2860a + 'N' // 2860b #13 + 'xA' // 2860c-28624 + 'N' // 28625 #13 + 'tA' // 28626-2863a + 'N' // 2863b #13 + '4eA' // 2863c-286a9 + 'aN' // 286aa-286ab #13 + 'eA' // 286ac-286b1 + 'N' // 286b2 #13 + 'hA' // 286b3-286bb + 'N' // 286bc #13 + 'yA' // 286bd-286d6 + 'B' // 286d7 #1 + 'N' // 286d8 #13 + 'lA' // 286d9-286e5 + 'N' // 286e6 #13 + 'rA' // 286e7-286f9 + 'B' // 286fa #1 + 'sA' // 286fb-2870e + 'N' // 2870f #13 + 'bA' // 28710-28712 + 'N' // 28713 #13 + '9eA' // 28714-28803 + 'N' // 28804 #13 + '1kA' // 28805-2882a + 'N' // 2882b #13 + '8pA' // 2882c-2890c + 'N' // 2890d #13 + '1jA' // 2890e-28932 + 'N' // 28933 #13 + 'qA' // 28934-28945 + 'B' // 28946 #1 + 'A' // 28947 + 'N' // 28948 #13 + '36X' // 28949 #959 + 'kA' // 2894a-28955 + 'N' // 28956 #13 + 'lA' // 28957-28963 + 'N' // 28964 #13 + 'bA' // 28965-28967 + 'N' // 28968 #13 + 'aA' // 28969-2896a + 'B' // 2896b #1 + 'aN' // 2896c-2896d #13 + 'oA' // 2896e-2897d + 'N' // 2897e #13 + 'gA' // 2897f-28986 + 'aB' // 28987-28988 #1 + 'N' // 28989 #13 + '1cA' // 2898a-289a7 + 'N' // 289a8 #13 + 'A' // 289a9 + 'aN' // 289aa-289ab #13 + 'kA' // 289ac-289b7 + 'N' // 289b8 #13 + 'A' // 289b9 + 'aB' // 289ba-289bb #1 + 'N' // 289bc #13 + 'bA' // 289bd-289bf + 'N' // 289c0 #13 + 'zA' // 289c1-289db + 'N' // 289dc #13 + 'A' // 289dd + 'N' // 289de #13 + 'aA' // 289df-289e0 + 'N' // 289e1 #13 + 'A' // 289e2 + 'aN' // 289e3-289e4 #13 + 'aA' // 289e5-289e6 + 'aN' // 289e7-289e8 #13 + 'oA' // 289e9-289f8 + 'cN' // 289f9-289fc #13 + 'qA' // 289fd-28a0e + 'N' // 28a0f #13 + 'eA' // 28a10-28a15 + 'N' // 28a16 #13 + 'fA' // 28a17-28a1d + 'B' // 28a1e #1 + 'eA' // 28a1f-28a24 + 'N' // 28a25 #13 + 'bA' // 28a26-28a28 + '36X' // 28a29 #959 + 'gA' // 28a2a-28a31 + 'N' // 28a32 #13 + 'bA' // 28a33-28a35 + 'N' // 28a36 #13 + 'kA' // 28a37-28a42 + 'B' // 28a43 #1 + 'gN' // 28a44-28a4b #13 + 'lA' // 28a4c-28a58 + 'aN' // 28a59-28a5a #13 + 'uA' // 28a5b-28a70 + 'B' // 28a71 #1 + 'nA' // 28a72-28a80 + 'bN' // 28a81-28a83 #13 + 'tA' // 28a84-28a98 + 'B' // 28a99 #1 + 'bN' // 28a9a-28a9c #13 + '1hA' // 28a9d-28abf + 'N' // 28ac0 #13 + 'dA' // 28ac1-28ac5 + 'N' // 28ac6 #13 + 'cA' // 28ac7-28aca + 'aN' // 28acb-28acc #13 + 'B' // 28acd #1 + 'N' // 28ace #13 + 'mA' // 28acf-28adc + 'B' // 28add #1 + 'eN' // 28ade-28ae3 #13 + 'B' // 28ae4 #1 + 'N' // 28ae5 #13 + 'cA' // 28ae6-28ae9 + 'N' // 28aea #13 + 'pA' // 28aeb-28afb + 'N' // 28afc #13 + 'nA' // 28afd-28b0b + 'N' // 28b0c #13 + 'eA' // 28b0d-28b12 + 'N' // 28b13 #13 + 'lA' // 28b14-28b20 + 'aN' // 28b21-28b22 #13 + 'gA' // 28b23-28b2a + 'bN' // 28b2b-28b2d #13 + 'A' // 28b2e + 'N' // 28b2f #13 + 'uA' // 28b30-28b45 + 'N' // 28b46 #13 + 'dA' // 28b47-28b4b + 'N' // 28b4c #13 + 'A' // 28b4d + 'N' // 28b4e #13 + 'A' // 28b4f + 'N' // 28b50 #13 + 'qA' // 28b51-28b62 + 'cN' // 28b63-28b66 #13 + 'dA' // 28b67-28b6b + 'N' // 28b6c #13 + '1gA' // 28b6d-28b8e + 'N' // 28b8f #13 + 'hA' // 28b90-28b98 + 'N' // 28b99 #13 + 'aA' // 28b9a-28b9b + 'aN' // 28b9c-28b9d #13 + 'zA' // 28b9e-28bb8 + 'N' // 28bb9 #13 + 'fA' // 28bba-28bc0 + 'B' // 28bc1 #1 + 'N' // 28bc2 #13 + 'aA' // 28bc3-28bc4 + 'N' // 28bc5 #13 + 'mA' // 28bc6-28bd3 + 'N' // 28bd4 #13 + 'aA' // 28bd5-28bd6 + 'N' // 28bd7 #13 + 'A' // 28bd8 + 'aN' // 28bd9-28bda #13 + 'kA' // 28bdb-28be6 + 'eN' // 28be7-28bec #13 + 'aA' // 28bed-28bee + 'B' // 28bef #1 + 'dA' // 28bf0-28bf4 + 'N' // 28bf5 #13 + 'hA' // 28bf6-28bfe + 'N' // 28bff #13 + 'bA' // 28c00-28c02 + 'N' // 28c03 #13 + 'dA' // 28c04-28c08 + 'N' // 28c09 #13 + 'qA' // 28c0a-28c1b + 'aN' // 28c1c-28c1d #13 + 'dA' // 28c1e-28c22 + 'N' // 28c23 #13 + 'aA' // 28c24-28c25 + 'N' // 28c26 #13 + 'cA' // 28c27-28c2a + 'N' // 28c2b #13 + 'cA' // 28c2c-28c2f + 'N' // 28c30 #13 + 'gA' // 28c31-28c38 + 'N' // 28c39 #13 + 'A' // 28c3a + 'N' // 28c3b #13 + '5kA' // 28c3c-28cc9 + 'N' // 28cca #13 + 'aA' // 28ccb-28ccc + 'N' // 28ccd #13 + 'cA' // 28cce-28cd1 + 'N' // 28cd2 #13 + 'iA' // 28cd3-28cdc + 'B' // 28cdd #1 + '1wA' // 28cde-28d0f + 'B' // 28d10 #1 + '1hA' // 28d11-28d33 + 'N' // 28d34 #13 + '2gA' // 28d35-28d70 + 'B' // 28d71 #1 + '1lA' // 28d72-28d98 + 'N' // 28d99 #13 + '1dA' // 28d9a-28db8 + 'N' // 28db9 #13 + '2lA' // 28dba-28dfa + 'B' // 28dfb #1 + 'rA' // 28dfc-28e0e + '36X' // 28e0f #959 + 'fA' // 28e10-28e16 + 'B' // 28e17 #1 + 'fA' // 28e18-28e1e + 'B' // 28e1f #1 + 'uA' // 28e20-28e35 + '36X' // 28e36 #959 + 'aA' // 28e37-28e38 + 'N' // 28e39 #13 + '1pA' // 28e3a-28e64 + 'aN' // 28e65-28e66 #13 + '1gA' // 28e67-28e88 + 'B' // 28e89 #1 + 'lA' // 28e8a-28e96 + 'N' // 28e97 #13 + 'sA' // 28e98-28eab + 'N' // 28eac #13 + 'dA' // 28ead-28eb1 + 'aH' // 28eb2-28eb3 #7 + '1jA' // 28eb4-28ed8 + 'H' // 28ed9 #7 + 'lA' // 28eda-28ee6 + 'H' // 28ee7 #7 + 'bA' // 28ee8-28eea + 'B' // 28eeb #1 + 'iA' // 28eec-28ef5 + 'B' // 28ef6 #1 + '2fA' // 28ef7-28f31 + 'B' // 28f32 #1 + '5oA' // 28f33-28fc4 + 'H' // 28fc5 #7 + '1wA' // 28fc6-28ff7 + 'B' // 28ff8 #1 + '4wA' // 28ff9-29078 + 'H' // 29079 #7 + 'mA' // 2907a-29087 + 'H' // 29088 #7 + 'aA' // 29089-2908a + 'H' // 2908b #7 + 'fA' // 2908c-29092 + 'H' // 29093 #7 + 'zA' // 29094-290ae + 'bH' // 290af-290b1 #7 + 'mA' // 290b2-290bf + 'H' // 290c0 #7 + '1hA' // 290c1-290e3 + 'aH' // 290e4-290e5 #7 + 'eA' // 290e6-290eb + 'aH' // 290ec-290ed #7 + '1dA' // 290ee-2910c + 'H' // 2910d #7 + 'aA' // 2910e-2910f + 'H' // 29110 #7 + '1pA' // 29111-2913b + 'H' // 2913c #7 + 'oA' // 2913d-2914c + 'H' // 2914d #7 + 'lA' // 2914e-2915a + 'H' // 2915b #7 + 'aA' // 2915c-2915d + 'H' // 2915e #7 + 'pA' // 2915f-2916f + 'H' // 29170 #7 + '1pA' // 29171-2919b + 'H' // 2919c #7 + 'jA' // 2919d-291a7 + 'H' // 291a8 #7 + '1qA' // 291a9-291d4 + 'H' // 291d5 #7 + 'tA' // 291d6-291ea + 'H' // 291eb #7 + '6wA' // 291ec-2929f + 'B' // 292a0 #1 + 'oA' // 292a1-292b0 + 'B' // 292b1 #1 + '13xA' // 292b2-2941c + 'H' // 2941d #7 + 'aA' // 2941e-2941f + 'H' // 29420 #7 + 'qA' // 29421-29432 + 'H' // 29433 #7 + 'jA' // 29434-2943e + 'H' // 2943f #7 + 'gA' // 29440-29447 + 'H' // 29448 #7 + '2rA' // 29449-2948f + 'B' // 29490 #1 + '2jA' // 29491-294cf + 'H' // 294d0 #7 + 'gA' // 294d1-294d8 + 'aH' // 294d9-294da #7 + 'iA' // 294db-294e4 + 'H' // 294e5 #7 + 'A' // 294e6 + 'H' // 294e7 #7 + '6yA' // 294e8-2959d + 'H' // 2959e #7 + 'pA' // 2959f-295af + 'H' // 295b0 #7 + 'fA' // 295b1-295b7 + 'H' // 295b8 #7 + 'uA' // 295b9-295ce + 'B' // 295cf #1 + 'fA' // 295d0-295d6 + 'H' // 295d7 #7 + 'pA' // 295d8-295e8 + 'H' // 295e9 #7 + 'iA' // 295ea-295f3 + 'H' // 295f4 #7 + '5gA' // 295f5-2967e + 'B' // 2967f #1 + '4gA' // 29680-296ef + 'B' // 296f0 #1 + '1mA' // 296f1-29718 + 'B' // 29719 #1 + 'eA' // 2971a-2971f + 'H' // 29720 #7 + 'pA' // 29721-29731 + 'H' // 29732 #7 + '1bA' // 29733-2974f + 'B' // 29750 #1 + '4zA' // 29751-297d3 + 'H' // 297d4 #7 + '2fA' // 297d5-2980f + '42Y' // 29810 #1116 + '2qA' // 29811-29856 + 'H' // 29857 #7 + '2wA' // 29858-298a3 + 'H' // 298a4 #7 + '1fA' // 298a5-298c5 + 'B' // 298c6 #1 + 'iA' // 298c7-298d0 + 'H' // 298d1 #7 + 'wA' // 298d2-298e9 + 'H' // 298ea #7 + 'eA' // 298eb-298f0 + 'H' // 298f1 #7 + 'gA' // 298f2-298f9 + 'H' // 298fa #7 + 'gA' // 298fb-29902 + 'H' // 29903 #7 + 'A' // 29904 + 'H' // 29905 #7 + '1nA' // 29906-2992e + 'H' // 2992f #7 + 'tA' // 29930-29944 + 'H' // 29945 #7 + 'A' // 29946 + 'bH' // 29947-29949 #7 + 'rA' // 2994a-2995c + 'H' // 2995d #7 + 'kA' // 2995e-29969 + 'H' // 2996a #7 + '1wA' // 2996b-2999c + 'H' // 2999d #7 + '1jA' // 2999e-299c2 + 'H' // 299c3 #7 + 'dA' // 299c4-299c8 + 'H' // 299c9 #7 + '3oA' // 299ca-29a27 + 'H' // 29a28 #7 + '1iA' // 29a29-29a4c + 'H' // 29a4d #7 + '1iA' // 29a4e-29a71 + 'B' // 29a72 #1 + '5oA' // 29a73-29b04 + 'H' // 29b05 #7 + 'gA' // 29b06-29b0d + 'H' // 29b0e #7 + '7oA' // 29b0f-29bd4 + 'H' // 29bd5 #7 + '5zA' // 29bd6-29c72 + 'H' // 29c73 #7 + '2dA' // 29c74-29cac + 'H' // 29cad #7 + '5mA' // 29cae-29d3d + 'H' // 29d3e #7 + 'kA' // 29d3f-29d4a + 'B' // 29d4b #1 + 'mA' // 29d4c-29d59 + '80H' // 29d5a #2087 + '1fA' // 29d5b-29d7b + 'H' // 29d7c #7 + 'zA' // 29d7d-29d97 + 'H' // 29d98 #7 + 'aA' // 29d99-29d9a + 'H' // 29d9b #7 + '2jA' // 29d9c-29dda + 'B' // 29ddb #1 + 'yA' // 29ddc-29df5 + 'H' // 29df6 #7 + 'nA' // 29df7-29e05 + 'H' // 29e06 #7 + 'mA' // 29e07-29e14 + 'B' // 29e15 #1 + 'vA' // 29e16-29e2c + 'H' // 29e2d #7 + 'nA' // 29e2e-29e3c + 'B' // 29e3d #1 + 'jA' // 29e3e-29e48 + 'B' // 29e49 #1 + '1cA' // 29e4a-29e67 + 'H' // 29e68 #7 + '1fA' // 29e69-29e89 + 'B' // 29e8a #1 + '1fA' // 29e8b-29eab + 'H' // 29eac #7 + 'bA' // 29ead-29eaf + 'H' // 29eb0 #7 + 'qA' // 29eb1-29ec2 + 'H' // 29ec3 #7 + 'B' // 29ec4 #1 + 'uA' // 29ec5-29eda + 'B' // 29edb #1 + 'lA' // 29edc-29ee8 + 'B' // 29ee9 #1 + 'mA' // 29eea-29ef7 + 'H' // 29ef8 #7 + '1oA' // 29ef9-29f22 + 'H' // 29f23 #7 + 'kA' // 29f24-29f2f + 'H' // 29f30 #7 + '5cA' // 29f31-29fb6 + 'H' // 29fb7 #7 + 'uA' // 29fb8-29fcd + 'B' // 29fce #1 + 'gA' // 29fcf-29fd6 + 'B' // 29fd7 #1 + 'eA' // 29fd8-29fdd + 'H' // 29fde #7 + '1zA' // 29fdf-2a013 + 'H' // 2a014 #7 + 'dA' // 2a015-2a019 + 'B' // 2a01a #1 + 'sA' // 2a01b-2a02e + 'B' // 2a02f #1 + '3cA' // 2a030-2a081 + 'B' // 2a082 #1 + 'cA' // 2a083-2a086 + 'H' // 2a087 #7 + '1vA' // 2a088-2a0b8 + 'H' // 2a0b9 #7 + '1lA' // 2a0ba-2a0e0 + 'H' // 2a0e1 #7 + 'jA' // 2a0e2-2a0ec + 'H' // 2a0ed #7 + 'dA' // 2a0ee-2a0f2 + 'H' // 2a0f3 #7 + 'cA' // 2a0f4-2a0f7 + 'H' // 2a0f8 #7 + 'B' // 2a0f9 #1 + 'cA' // 2a0fa-2a0fd + 'H' // 2a0fe #7 + 'gA' // 2a0ff-2a106 + 'H' // 2a107 #7 + 'zA' // 2a108-2a122 + 'H' // 2a123 #7 + 'nA' // 2a124-2a132 + 'aH' // 2a133-2a134 #7 + 'zA' // 2a135-2a14f + 'H' // 2a150 #7 + '2jA' // 2a151-2a18f + 'B' // 2a190 #1 + 'A' // 2a191 + 'aH' // 2a192-2a193 #7 + 'vA' // 2a194-2a1aa + 'H' // 2a1ab #7 + 'gA' // 2a1ac-2a1b3 + 'aH' // 2a1b4-2a1b5 #7 + '1nA' // 2a1b6-2a1de + 'H' // 2a1df #7 + 'tA' // 2a1e0-2a1f4 + 'H' // 2a1f5 #7 + '1oA' // 2a1f6-2a21f + 'H' // 2a220 #7 + 'qA' // 2a221-2a232 + 'H' // 2a233 #7 + '3pA' // 2a234-2a292 + 'H' // 2a293 #7 + 'jA' // 2a294-2a29e + 'H' // 2a29f #7 + 'qA' // 2a2a0-2a2b1 + '42Y' // 2a2b2 #1116 + 'A' // 2a2b3 + 'H' // 2a2b4 #7 + 'A' // 2a2b5 + 'H' // 2a2b6 #7 + 'bA' // 2a2b7-2a2b9 + 'H' // 2a2ba #7 + 'aA' // 2a2bb-2a2bc + 'H' // 2a2bd #7 + '1fA' // 2a2be-2a2de + 'H' // 2a2df #7 + '1dA' // 2a2e0-2a2fe + 'H' // 2a2ff #7 + '3bA' // 2a300-2a350 + 'H' // 2a351 #7 + '2eA' // 2a352-2a38b + 'B' // 2a38c #1 + '1aA' // 2a38d-2a3a8 + 'H' // 2a3a9 #7 + '2nA' // 2a3aa-2a3ec + 'H' // 2a3ed #7 + '2qA' // 2a3ee-2a433 + 'H' // 2a434 #7 + 'aA' // 2a435-2a436 + 'B' // 2a437 #1 + '1hA' // 2a438-2a45a + 'H' // 2a45b #7 + '13wA' // 2a45c-2a5c5 + 'H' // 2a5c6 #7 + 'cA' // 2a5c7-2a5ca + 'H' // 2a5cb #7 + '1jA' // 2a5cc-2a5f0 + 'B' // 2a5f1 #1 + 'nA' // 2a5f2-2a600 + 'H' // 2a601 #7 + 'B' // 2a602 #1 + 'vA' // 2a603-2a619 + 'B' // 2a61a #1 + 'vA' // 2a61b-2a631 + 'H' // 2a632 #7 + 'vA' // 2a633-2a649 + 'H' // 2a64a #7 + 'oA' // 2a64b-2a65a + 'H' // 2a65b #7 + '2xA' // 2a65c-2a6a8 + 'H' // 2a6a9 #7 + 'gA' // 2a6aa-2a6b1 + 'B' // 2a6b2 #1 + '31lA' // 2a6b3-2a9e5 + 'B' // 2a9e6 #1 + '40gA' // 2a9e7-2adfe + 'H' // 2adff #7 + '91gA' // 2ae00-2b745 + 'B' // 2b746 #1 + 'iA' // 2b747-2b750 + 'B' // 2b751 #1 + 'A' // 2b752 + 'B' // 2b753 #1 + 'eA' // 2b754-2b759 + 'B' // 2b75a #1 + 'A' // 2b75b + 'B' // 2b75c #1 + 'gA' // 2b75d-2b764 + 'B' // 2b765 #1 + 'oA' // 2b766-2b775 + 'aB' // 2b776-2b777 #1 + 'cA' // 2b778-2b77b + 'B' // 2b77c #1 + 'dA' // 2b77d-2b781 + 'B' // 2b782 #1 + 'eA' // 2b783-2b788 + 'B' // 2b789 #1 + 'A' // 2b78a + 'B' // 2b78b #1 + 'aA' // 2b78c-2b78d + 'B' // 2b78e #1 + 'dA' // 2b78f-2b793 + 'B' // 2b794 #1 + 'vA' // 2b795-2b7ab + 'B' // 2b7ac #1 + 'aA' // 2b7ad-2b7ae + 'B' // 2b7af #1 + 'lA' // 2b7b0-2b7bc + 'B' // 2b7bd #1 + 'jA' // 2b7be-2b7c8 + 'B' // 2b7c9 #1 + 'dA' // 2b7ca-2b7ce + 'B' // 2b7cf #1 + 'aA' // 2b7d0-2b7d1 + 'B' // 2b7d2 #1 + 'dA' // 2b7d3-2b7d7 + 'B' // 2b7d8 #1 + 'vA' // 2b7d9-2b7ef + 'B' // 2b7f0 #1 + '1aA' // 2b7f1-2b80c + 'B' // 2b80d #1 + 'hA' // 2b80e-2b816 + 'B' // 2b817 #1 + 'aA' // 2b818-2b819 + 'B' // 2b81a #1 + '287bA' // 2b81b-2d543 + 'B' // 2d544 #1 + '129xA' // 2d545-2e277 + 'B' // 2e278 #1 + '28wA' // 2e279-2e568 + 'B' // 2e569 #1 + '14sA' // 2e56a-2e6e9 + 'B' // 2e6ea #1 + '168hA' // 2e6eb-2f803 + 'B' // 2f804 #1 + 'iA' // 2f805-2f80e + 'B' // 2f80f #1 + 'dA' // 2f810-2f814 + 'B' // 2f815 #1 + 'aA' // 2f816-2f817 + 'B' // 2f818 #1 + 'A' // 2f819 + 'B' // 2f81a #1 + 'fA' // 2f81b-2f821 + 'B' // 2f822 #1 + 'aA' // 2f823-2f824 + 'H' // 2f825 #7 + 'aA' // 2f826-2f827 + 'B' // 2f828 #1 + 'bA' // 2f829-2f82b + 'B' // 2f82c #1 + 'eA' // 2f82d-2f832 + 'B' // 2f833 #1 + 'fA' // 2f834-2f83a + 'H' // 2f83b #7 + 'bA' // 2f83c-2f83e + 'B' // 2f83f #1 + 'H' // 2f840 #7 + 'dA' // 2f841-2f845 + 'B' // 2f846 #1 + 'jA' // 2f847-2f851 + 'B' // 2f852 #1 + 'nA' // 2f853-2f861 + 'B' // 2f862 #1 + 'iA' // 2f863-2f86c + 'B' // 2f86d #1 + 'dA' // 2f86e-2f872 + 'B' // 2f873 #1 + 'bA' // 2f874-2f876 + 'B' // 2f877 #1 + 'H' // 2f878 #7 + 'jA' // 2f879-2f883 + 'B' // 2f884 #1 + 'nA' // 2f885-2f893 + 'H' // 2f894 #7 + 'cA' // 2f895-2f898 + 'aB' // 2f899-2f89a #1 + 'jA' // 2f89b-2f8a5 + '42Y' // 2f8a6 #1116 + 'dA' // 2f8a7-2f8ab + 'B' // 2f8ac #1 + 'dA' // 2f8ad-2f8b1 + 'B' // 2f8b2 #1 + 'bA' // 2f8b3-2f8b5 + 'B' // 2f8b6 #1 + 'uA' // 2f8b7-2f8cc + 'H' // 2f8cd #7 + 'dA' // 2f8ce-2f8d2 + 'B' // 2f8d3 #1 + 'fA' // 2f8d4-2f8da + 'aB' // 2f8db-2f8dc #1 + 'cA' // 2f8dd-2f8e0 + 'B' // 2f8e1 #1 + 'bA' // 2f8e2-2f8e4 + 'B' // 2f8e5 #1 + 'cA' // 2f8e6-2f8e9 + 'B' // 2f8ea #1 + 'aA' // 2f8eb-2f8ec + 'B' // 2f8ed #1 + 'mA' // 2f8ee-2f8fb + 'B' // 2f8fc #1 + 'eA' // 2f8fd-2f902 + 'B' // 2f903 #1 + 'fA' // 2f904-2f90a + 'B' // 2f90b #1 + 'bA' // 2f90c-2f90e + 'B' // 2f90f #1 + 'iA' // 2f910-2f919 + 'B' // 2f91a #1 + 'dA' // 2f91b-2f91f + 'aB' // 2f920-2f921 #1 + '1hA' // 2f922-2f944 + 'B' // 2f945 #1 + 'A' // 2f946 + 'B' // 2f947 #1 + '1iA' // 2f948-2f96b + 'B' // 2f96c #1 + '1lA' // 2f96d-2f993 + 'H' // 2f994 #7 + 'B' // 2f995 #1 + '1aA' // 2f996-2f9b1 + 'H' // 2f9b2 #7 + 'hA' // 2f9b3-2f9bb + 'H' // 2f9bc #7 + 'rA' // 2f9bd-2f9cf + 'B' // 2f9d0 #1 + 'bA' // 2f9d1-2f9d3 + 'H' // 2f9d4 #7 + 'hA' // 2f9d5-2f9dd + 'aB' // 2f9de-2f9df #1 + 'sA' // 2f9e0-2f9f3 + 'B' // 2f9f4 #1 + '27789zA' // 2f9f5-e0061 + 'a15E' // e0062-e0063 #394 + 'A' // e0064 + '15E' // e0065 #394 + 'A' // e0066 + '15E' // e0067 #394 + 'cA' // e0068-e006b + '15E' // e006c #394 + 'A' // e006d + '15E' // e006e #394 + 'cA' // e006f-e0072 + 'a15E' // e0073-e0074 #394 + 'aA' // e0075-e0076 + '15E' // e0077 #394 + 'fA' // e0078-e007e + '15E' // e007f #394 + '7556wA' // e0080-10ffff ; diff --git a/lib/web_ui/lib/src/engine/font_fallbacks.dart b/lib/web_ui/lib/src/engine/font_fallbacks.dart index fe7589c560ffe..bbd1da6196d72 100644 --- a/lib/web_ui/lib/src/engine/font_fallbacks.dart +++ b/lib/web_ui/lib/src/engine/font_fallbacks.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:meta/meta.dart'; import 'package:ui/src/engine.dart'; abstract class FallbackFontRegistry { @@ -12,17 +13,18 @@ abstract class FallbackFontRegistry { void updateFallbackFontFamilies(List families); } +bool _isNotoSansSC(NotoFont font) => font.name.startsWith('Noto Sans SC'); +bool _isNotoSansTC(NotoFont font) => font.name.startsWith('Noto Sans TC'); +bool _isNotoSansHK(NotoFont font) => font.name.startsWith('Noto Sans HK'); +bool _isNotoSansJP(NotoFont font) => font.name.startsWith('Noto Sans JP'); +bool _isNotoSansKR(NotoFont font) => font.name.startsWith('Noto Sans KR'); + /// Global static font fallback data. class FontFallbackManager { factory FontFallbackManager(FallbackFontRegistry registry) => FontFallbackManager._(registry, getFallbackFontList()); FontFallbackManager._(this.registry, this.fallbackFonts) : - _notoSansSC = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans SC'), - _notoSansTC = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans TC'), - _notoSansHK = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans HK'), - _notoSansJP = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans JP'), - _notoSansKR = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans KR'), _notoSymbols = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans Symbols') { downloadQueue = FallbackFontDownloadQueue(this); } @@ -39,11 +41,17 @@ class FontFallbackManager { final List fallbackFonts; - final NotoFont _notoSansSC; - final NotoFont _notoSansTC; - final NotoFont _notoSansHK; - final NotoFont _notoSansJP; - final NotoFont _notoSansKR; + // By default, we use the system language to determine the user's preferred + // language. This can be overridden through [debugUserPreferredLanguage] for testing. + String _language = domWindow.navigator.language; + + @visibleForTesting + String get debugUserPreferredLanguage => _language; + + @visibleForTesting + set debugUserPreferredLanguage(String value) { + _language = value; + } final NotoFont _notoSymbols; @@ -257,56 +265,49 @@ class FontFallbackManager { } } - // If the list of best fonts are all CJK fonts, choose the best one based - // on locale. Otherwise just choose the first font. + NotoFont? bestFontForLanguage; if (bestFonts.length > 1) { + // If the list of best fonts are all CJK fonts, choose the best one based + // on user preferred language. Otherwise just choose the first font. if (bestFonts.every((NotoFont font) => - font == _notoSansSC || - font == _notoSansTC || - font == _notoSansHK || - font == _notoSansJP || - font == _notoSansKR)) { - final String language = domWindow.navigator.language; - - if (language == 'zh-Hans' || - language == 'zh-CN' || - language == 'zh-SG' || - language == 'zh-MY') { - if (bestFonts.contains(_notoSansSC)) { - bestFont = _notoSansSC; - } - } else if (language == 'zh-Hant' || - language == 'zh-TW' || - language == 'zh-MO') { - if (bestFonts.contains(_notoSansTC)) { - bestFont = _notoSansTC; - } - } else if (language == 'zh-HK') { - if (bestFonts.contains(_notoSansHK)) { - bestFont = _notoSansHK; - } - } else if (language == 'ja') { - if (bestFonts.contains(_notoSansJP)) { - bestFont = _notoSansJP; - } - } else if (language == 'ko') { - if (bestFonts.contains(_notoSansKR)) { - bestFont = _notoSansKR; - } - } else if (bestFonts.contains(_notoSansSC)) { - bestFont = _notoSansSC; + _isNotoSansSC(font) || + _isNotoSansTC(font) || + _isNotoSansHK(font) || + _isNotoSansJP(font) || + _isNotoSansKR(font))) { + if (_language == 'zh-Hans' || + _language == 'zh-CN' || + _language == 'zh-SG' || + _language == 'zh-MY') { + bestFontForLanguage = bestFonts.firstWhereOrNull(_isNotoSansSC); + } else if (_language == 'zh-Hant' || + _language == 'zh-TW' || + _language == 'zh-MO') { + bestFontForLanguage = bestFonts.firstWhereOrNull(_isNotoSansTC); + } else if (_language == 'zh-HK') { + bestFontForLanguage = bestFonts.firstWhereOrNull(_isNotoSansHK); + } else if (_language == 'ja') { + bestFontForLanguage = bestFonts.firstWhereOrNull(_isNotoSansJP); + } else if (_language == 'ko') { + bestFontForLanguage = bestFonts.firstWhereOrNull(_isNotoSansKR); + } else { + // Default to `Noto Sans SC` when the user preferred language is not CJK. + bestFontForLanguage = bestFonts.firstWhereOrNull(_isNotoSansSC); } } else { // To be predictable, if there is a tie for best font, choose a font // from this list first, then just choose the first font. if (bestFonts.contains(_notoSymbols)) { bestFont = _notoSymbols; - } else if (bestFonts.contains(_notoSansSC)) { - bestFont = _notoSansSC; + } else { + final notoSansSC = bestFonts.firstWhereOrNull(_isNotoSansSC); + if (notoSansSC != null) { + bestFont = notoSansSC; + } } } } - return bestFont!; + return bestFontForLanguage ?? bestFont!; } late final List fontComponents = diff --git a/lib/web_ui/lib/src/engine/html/renderer.dart b/lib/web_ui/lib/src/engine/html/renderer.dart index 1bcdc2f53a1cf..fa9aee9f78a8b 100644 --- a/lib/web_ui/lib/src/engine/html/renderer.dart +++ b/lib/web_ui/lib/src/engine/html/renderer.dart @@ -111,7 +111,7 @@ class HtmlRenderer implements Renderer { ui.ImageFilter createBlurImageFilter( {double sigmaX = 0.0, double sigmaY = 0.0, - ui.TileMode tileMode = ui.TileMode.clamp}) => + ui.TileMode? tileMode}) => EngineImageFilter.blur( sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index 126c14770ded3..62f722eee772b 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -717,7 +717,7 @@ abstract class EngineImageFilter implements ui.ImageFilter { factory EngineImageFilter.blur({ required double sigmaX, required double sigmaY, - required ui.TileMode tileMode, + required ui.TileMode? tileMode, }) = _BlurEngineImageFilter; factory EngineImageFilter.matrix({ @@ -732,11 +732,11 @@ abstract class EngineImageFilter implements ui.ImageFilter { } class _BlurEngineImageFilter extends EngineImageFilter { - _BlurEngineImageFilter({ this.sigmaX = 0.0, this.sigmaY = 0.0, this.tileMode = ui.TileMode.clamp }) : super._(); + _BlurEngineImageFilter({ this.sigmaX = 0.0, this.sigmaY = 0.0, this.tileMode }) : super._(); final double sigmaX; final double sigmaY; - final ui.TileMode tileMode; + final ui.TileMode? tileMode; // TODO(ferhat): implement TileMode. @override diff --git a/lib/web_ui/lib/src/engine/js_interop/js_loader.dart b/lib/web_ui/lib/src/engine/js_interop/js_loader.dart index 4f5df1f5130f0..ab625fbc9b419 100644 --- a/lib/web_ui/lib/src/engine/js_interop/js_loader.dart +++ b/lib/web_ui/lib/src/engine/js_interop/js_loader.dart @@ -57,8 +57,8 @@ abstract class FlutterEngineInitializer{ required InitializeEngineFn initializeEngine, required ImmediateRunAppFn autoStart, }) => FlutterEngineInitializer._( - initializeEngine: (([JsFlutterConfiguration? config]) => futureToPromise(initializeEngine(config) as Future)).toJS, - autoStart: (() => futureToPromise(autoStart() as Future)).toJS, + initializeEngine: (([JsFlutterConfiguration? config]) => (initializeEngine(config) as Future).toPromise).toJS, + autoStart: (() => (autoStart() as Future).toPromise).toJS, ); external factory FlutterEngineInitializer._({ required JSFunction initializeEngine, @@ -75,7 +75,7 @@ abstract class FlutterEngineInitializer{ @staticInterop abstract class FlutterAppRunner { factory FlutterAppRunner({required RunAppFn runApp,}) => FlutterAppRunner._( - runApp: (([RunAppFnParameters? args]) => futureToPromise(runApp(args) as Future)).toJS + runApp: (([RunAppFnParameters? args]) => (runApp(args) as Future).toPromise).toJS ); /// Runs a flutter app diff --git a/lib/web_ui/lib/src/engine/js_interop/js_promise.dart b/lib/web_ui/lib/src/engine/js_interop/js_promise.dart index b535cf94368cb..598b2f41e7e7d 100644 --- a/lib/web_ui/lib/src/engine/js_interop/js_promise.dart +++ b/lib/web_ui/lib/src/engine/js_interop/js_promise.dart @@ -6,32 +6,30 @@ library js_promise; import 'dart:js_interop'; - -import 'package:js/js_util.dart' as js_util; - -import '../util.dart'; - -extension CallExtension on JSFunction { - external void call(JSAny? this_, JSAny? object); -} - -@JS('Promise') -external JSAny get _promiseConstructor; - -JSPromise createPromise(JSFunction executor) => - js_util.callConstructor( - _promiseConstructor, - [executor], - ); - - -JSPromise futureToPromise(Future future) { - return createPromise((JSFunction resolver, JSFunction rejecter) { - future.then( - (T value) => resolver.call(null, value), - onError: (Object? error) { - printWarning('Rejecting promise with error: $error'); - rejecter.call(null, null); +import 'dart:js_interop_unsafe'; + +/// This is the same as package:js_interop's FutureToPromise (.toJS), but with +/// a more descriptive error message. +extension CustomFutureOfJSAnyToJSPromise on Future { + /// A [JSPromise] that either resolves with the result of the completed + /// [Future] or rejects with an object that contains its error. + JSPromise get toPromise { + // TODO(ditman): Move to js_interop's .toJS, https://github.com/dart-lang/sdk/issues/56898 + return JSPromise((JSFunction resolve, JSFunction reject) { + then((JSAny? value) { + resolve.callAsFunction(resolve, value); + }, onError: (Object error, StackTrace stackTrace) { + final errorConstructor = globalContext['Error']! as JSFunction; + var userError = '$error\n'; + // Only append the stack trace string if it looks like a DDC one... + final stackTraceString = stackTrace.toString(); + if (!stackTraceString.startsWith('\n')) { + userError += '\nDart stack trace:\n$stackTraceString'; + } + final wrapper = + errorConstructor.callAsConstructor(userError.toJS); + reject.callAsFunction(reject, wrapper); }); - }.toJS); + }.toJS); + } } diff --git a/lib/web_ui/lib/src/engine/layers.dart b/lib/web_ui/lib/src/engine/layers.dart index f3658ddee3c4c..0949a02f376a2 100644 --- a/lib/web_ui/lib/src/engine/layers.dart +++ b/lib/web_ui/lib/src/engine/layers.dart @@ -2,18 +2,58 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:math' as math; import 'dart:typed_data'; import 'package:meta/meta.dart'; -import 'package:ui/src/engine/scene_painting.dart'; -import 'package:ui/src/engine/vector_math.dart'; +import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -class EngineRootLayer with PictureEngineLayer {} +class EngineRootLayer with PictureEngineLayer { + @override + final NoopOperation operation = const NoopOperation(); + + @override + EngineRootLayer emptyClone() => EngineRootLayer(); +} + +class NoopOperation implements LayerOperation { + const NoopOperation(); + + @override + PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling(); + + @override + ui.Rect mapRect(ui.Rect contentRect) => contentRect; + + @override + void pre(SceneCanvas canvas) { + canvas.save(); + } + + @override + void post(SceneCanvas canvas) { + canvas.restore(); + } + + @override + bool get affectsBackdrop => false; + + @override + String toString() => 'NoopOperation()'; +} class BackdropFilterLayer with PictureEngineLayer - implements ui.BackdropFilterEngineLayer {} + implements ui.BackdropFilterEngineLayer { + BackdropFilterLayer(this.operation); + + @override + final LayerOperation operation; + + @override + BackdropFilterLayer emptyClone() => BackdropFilterLayer(operation); +} class BackdropFilterOperation implements LayerOperation { BackdropFilterOperation(this.filter, this.mode); @@ -24,27 +64,36 @@ class BackdropFilterOperation implements LayerOperation { ui.Rect mapRect(ui.Rect contentRect) => contentRect; @override - void pre(SceneCanvas canvas, ui.Rect contentRect) { - canvas.saveLayerWithFilter(contentRect, ui.Paint()..blendMode = mode, filter); + void pre(SceneCanvas canvas) { + canvas.saveLayerWithFilter(ui.Rect.largest, ui.Paint()..blendMode = mode, filter); } @override - void post(SceneCanvas canvas, ui.Rect contentRect) { + void post(SceneCanvas canvas) { canvas.restore(); } @override PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling(); - // The backdrop filter actually has an effect on the scene even if it contains - // no pictures, so we return true here. @override - bool get shouldDrawIfEmpty => true; + bool get affectsBackdrop => true; + + @override + String toString() => 'BackdropFilterOperation(filter: $filter, mode: $mode)'; } class ClipPathLayer with PictureEngineLayer - implements ui.ClipPathEngineLayer {} + implements ui.ClipPathEngineLayer { + ClipPathLayer(this.operation); + + @override + final ClipPathOperation operation; + + @override + ClipPathLayer emptyClone() => ClipPathLayer(operation); +} class ClipPathOperation implements LayerOperation { ClipPathOperation(this.path, this.clip); @@ -55,7 +104,7 @@ class ClipPathOperation implements LayerOperation { ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(path.getBounds()); @override - void pre(SceneCanvas canvas, ui.Rect contentRect) { + void pre(SceneCanvas canvas) { canvas.save(); canvas.clipPath(path, doAntiAlias: clip != ui.Clip.hardEdge); if (clip == ui.Clip.antiAliasWithSaveLayer) { @@ -64,7 +113,7 @@ class ClipPathOperation implements LayerOperation { } @override - void post(SceneCanvas canvas, ui.Rect contentRect) { + void post(SceneCanvas canvas) { if (clip == ui.Clip.antiAliasWithSaveLayer) { canvas.restore(); } @@ -77,12 +126,23 @@ class ClipPathOperation implements LayerOperation { } @override - bool get shouldDrawIfEmpty => false; + bool get affectsBackdrop => false; + + @override + String toString() => 'ClipPathOperation(path: $path, clip: $clip)'; } class ClipRectLayer with PictureEngineLayer - implements ui.ClipRectEngineLayer {} + implements ui.ClipRectEngineLayer { + ClipRectLayer(this.operation); + + @override + final ClipRectOperation operation; + + @override + ClipRectLayer emptyClone() => ClipRectLayer(operation); +} class ClipRectOperation implements LayerOperation { const ClipRectOperation(this.rect, this.clip); @@ -93,7 +153,7 @@ class ClipRectOperation implements LayerOperation { ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(rect); @override - void pre(SceneCanvas canvas, ui.Rect contentRect) { + void pre(SceneCanvas canvas) { canvas.save(); canvas.clipRect(rect, doAntiAlias: clip != ui.Clip.hardEdge); if (clip == ui.Clip.antiAliasWithSaveLayer) { @@ -102,7 +162,7 @@ class ClipRectOperation implements LayerOperation { } @override - void post(SceneCanvas canvas, ui.Rect contentRect) { + void post(SceneCanvas canvas) { if (clip == ui.Clip.antiAliasWithSaveLayer) { canvas.restore(); } @@ -115,12 +175,23 @@ class ClipRectOperation implements LayerOperation { } @override - bool get shouldDrawIfEmpty => false; + bool get affectsBackdrop => false; + + @override + String toString() => 'ClipRectOperation(rect: $rect, clip: $clip)'; } class ClipRRectLayer with PictureEngineLayer - implements ui.ClipRRectEngineLayer {} + implements ui.ClipRRectEngineLayer { + ClipRRectLayer(this.operation); + + @override + final ClipRRectOperation operation; + + @override + ClipRRectLayer emptyClone() => ClipRRectLayer(operation); +} class ClipRRectOperation implements LayerOperation { const ClipRRectOperation(this.rrect, this.clip); @@ -131,7 +202,7 @@ class ClipRRectOperation implements LayerOperation { ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(rrect.outerRect); @override - void pre(SceneCanvas canvas, ui.Rect contentRect) { + void pre(SceneCanvas canvas) { canvas.save(); canvas.clipRRect(rrect, doAntiAlias: clip != ui.Clip.hardEdge); if (clip == ui.Clip.antiAliasWithSaveLayer) { @@ -140,7 +211,7 @@ class ClipRRectOperation implements LayerOperation { } @override - void post(SceneCanvas canvas, ui.Rect contentRect) { + void post(SceneCanvas canvas) { if (clip == ui.Clip.antiAliasWithSaveLayer) { canvas.restore(); } @@ -153,12 +224,23 @@ class ClipRRectOperation implements LayerOperation { } @override - bool get shouldDrawIfEmpty => false; + bool get affectsBackdrop => false; + + @override + String toString() => 'ClipRRectOperation(rrect: $rrect, clip: $clip)'; } class ColorFilterLayer with PictureEngineLayer - implements ui.ColorFilterEngineLayer {} + implements ui.ColorFilterEngineLayer { + ColorFilterLayer(this.operation); + + @override + final ColorFilterOperation operation; + + @override + ColorFilterLayer emptyClone() => ColorFilterLayer(operation); +} class ColorFilterOperation implements LayerOperation { ColorFilterOperation(this.filter); @@ -168,12 +250,12 @@ class ColorFilterOperation implements LayerOperation { ui.Rect mapRect(ui.Rect contentRect) => contentRect; @override - void pre(SceneCanvas canvas, ui.Rect contentRect) { - canvas.saveLayer(contentRect, ui.Paint()..colorFilter = filter); + void pre(SceneCanvas canvas) { + canvas.saveLayer(ui.Rect.largest, ui.Paint()..colorFilter = filter); } @override - void post(SceneCanvas canvas, ui.Rect contentRect) { + void post(SceneCanvas canvas) { canvas.restore(); } @@ -181,12 +263,23 @@ class ColorFilterOperation implements LayerOperation { PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling(); @override - bool get shouldDrawIfEmpty => false; + bool get affectsBackdrop => false; + + @override + String toString() => 'ColorFilterOperation(filter: $filter)'; } class ImageFilterLayer with PictureEngineLayer - implements ui.ImageFilterEngineLayer {} + implements ui.ImageFilterEngineLayer { + ImageFilterLayer(this.operation); + + @override + final ImageFilterOperation operation; + + @override + ImageFilterLayer emptyClone() => ImageFilterLayer(operation); +} class ImageFilterOperation implements LayerOperation { ImageFilterOperation(this.filter, this.offset); @@ -197,17 +290,16 @@ class ImageFilterOperation implements LayerOperation { ui.Rect mapRect(ui.Rect contentRect) => filter.filterBounds(contentRect); @override - void pre(SceneCanvas canvas, ui.Rect contentRect) { + void pre(SceneCanvas canvas) { if (offset != ui.Offset.zero) { canvas.save(); canvas.translate(offset.dx, offset.dy); } - final ui.Rect adjustedContentRect = filter.filterBounds(contentRect); - canvas.saveLayer(adjustedContentRect, ui.Paint()..imageFilter = filter); + canvas.saveLayer(ui.Rect.largest, ui.Paint()..imageFilter = filter); } @override - void post(SceneCanvas canvas, ui.Rect contentRect) { + void post(SceneCanvas canvas) { if (offset != ui.Offset.zero) { canvas.restore(); } @@ -216,22 +308,42 @@ class ImageFilterOperation implements LayerOperation { @override PlatformViewStyling createPlatformViewStyling() { + PlatformViewStyling styling = const PlatformViewStyling(); if (offset != ui.Offset.zero) { - return PlatformViewStyling( + styling = PlatformViewStyling( position: PlatformViewPosition.offset(offset) ); - } else { - return const PlatformViewStyling(); } + final Matrix4? transform = filter.transform; + if (transform != null) { + styling = PlatformViewStyling.combine( + styling, + PlatformViewStyling( + position: PlatformViewPosition.transform(transform), + ), + ); + } + return const PlatformViewStyling(); } @override - bool get shouldDrawIfEmpty => false; + bool get affectsBackdrop => false; + + @override + String toString() => 'ImageFilterOperation(filter: $filter)'; } class OffsetLayer with PictureEngineLayer - implements ui.OffsetEngineLayer {} + implements ui.OffsetEngineLayer { + OffsetLayer(this.operation); + + @override + final OffsetOperation operation; + + @override + OffsetLayer emptyClone() => OffsetLayer(operation); +} class OffsetOperation implements LayerOperation { OffsetOperation(this.dx, this.dy); @@ -242,13 +354,13 @@ class OffsetOperation implements LayerOperation { ui.Rect mapRect(ui.Rect contentRect) => contentRect.shift(ui.Offset(dx, dy)); @override - void pre(SceneCanvas canvas, ui.Rect cullRect) { + void pre(SceneCanvas canvas) { canvas.save(); canvas.translate(dx, dy); } @override - void post(SceneCanvas canvas, ui.Rect contentRect) { + void post(SceneCanvas canvas) { canvas.restore(); } @@ -258,12 +370,23 @@ class OffsetOperation implements LayerOperation { ); @override - bool get shouldDrawIfEmpty => false; + bool get affectsBackdrop => false; + + @override + String toString() => 'OffsetOperation(dx: $dx, dy: $dy)'; } class OpacityLayer with PictureEngineLayer - implements ui.OpacityEngineLayer {} + implements ui.OpacityEngineLayer { + OpacityLayer(this.operation); + + @override + final OpacityOperation operation; + + @override + OpacityLayer emptyClone() => OpacityLayer(operation); +} class OpacityOperation implements LayerOperation { OpacityOperation(this.alpha, this.offset); @@ -274,20 +397,19 @@ class OpacityOperation implements LayerOperation { ui.Rect mapRect(ui.Rect contentRect) => contentRect.shift(offset); @override - void pre(SceneCanvas canvas, ui.Rect cullRect) { + void pre(SceneCanvas canvas) { if (offset != ui.Offset.zero) { canvas.save(); canvas.translate(offset.dx, offset.dy); - cullRect = cullRect.shift(-offset); } canvas.saveLayer( - cullRect, + ui.Rect.largest, ui.Paint()..color = ui.Color.fromARGB(alpha, 0, 0, 0) ); } @override - void post(SceneCanvas canvas, ui.Rect contentRect) { + void post(SceneCanvas canvas) { canvas.restore(); if (offset != ui.Offset.zero) { canvas.restore(); @@ -301,12 +423,23 @@ class OpacityOperation implements LayerOperation { ); @override - bool get shouldDrawIfEmpty => false; + bool get affectsBackdrop => false; + + @override + String toString() => 'OpacityOperation(offset: $offset, alpha: $alpha)'; } class TransformLayer with PictureEngineLayer - implements ui.TransformEngineLayer {} + implements ui.TransformEngineLayer { + TransformLayer(this.operation); + + @override + final TransformOperation operation; + + @override + TransformLayer emptyClone() => TransformLayer(operation); +} class TransformOperation implements LayerOperation { TransformOperation(this.transform); @@ -319,13 +452,13 @@ class TransformOperation implements LayerOperation { ui.Rect mapRect(ui.Rect contentRect) => matrix.transformRect(contentRect); @override - void pre(SceneCanvas canvas, ui.Rect cullRect) { + void pre(SceneCanvas canvas) { canvas.save(); canvas.transform(transform); } @override - void post(SceneCanvas canvas, ui.Rect contentRect) { + void post(SceneCanvas canvas) { canvas.restore(); } @@ -335,12 +468,23 @@ class TransformOperation implements LayerOperation { ); @override - bool get shouldDrawIfEmpty => false; + bool get affectsBackdrop => false; + + @override + String toString() => 'TransformOperation(matrix: $matrix)'; } class ShaderMaskLayer with PictureEngineLayer - implements ui.ShaderMaskEngineLayer {} + implements ui.ShaderMaskEngineLayer { + ShaderMaskLayer(this.operation); + + @override + final ShaderMaskOperation operation; + + @override + ShaderMaskLayer emptyClone() => ShaderMaskLayer(operation); +} class ShaderMaskOperation implements LayerOperation { ShaderMaskOperation(this.shader, this.maskRect, this.blendMode); @@ -352,15 +496,15 @@ class ShaderMaskOperation implements LayerOperation { ui.Rect mapRect(ui.Rect contentRect) => contentRect; @override - void pre(SceneCanvas canvas, ui.Rect contentRect) { + void pre(SceneCanvas canvas) { canvas.saveLayer( - contentRect, + ui.Rect.largest, ui.Paint(), ); } @override - void post(SceneCanvas canvas, ui.Rect contentRect) { + void post(SceneCanvas canvas) { canvas.save(); canvas.translate(maskRect.left, maskRect.top); canvas.drawRect( @@ -377,7 +521,10 @@ class ShaderMaskOperation implements LayerOperation { PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling(); @override - bool get shouldDrawIfEmpty => false; + bool get affectsBackdrop => false; + + @override + String toString() => 'ShaderMaskOperation(shader: $shader, maskRect: $maskRect, blendMode: $blendMode)'; } class PlatformView { @@ -389,49 +536,61 @@ class PlatformView { final ui.Rect bounds; final PlatformViewStyling styling; -} -sealed class LayerSlice { - void dispose(); + @override + String toString() { + return 'PlatformView(viewId: $viewId, bounds: $bounds, styling: $styling)'; + } } -// A slice that contains one or more platform views to be rendered. -class PlatformViewSlice implements LayerSlice { - PlatformViewSlice(this.views, this.occlusionRect); +class LayerSlice { + LayerSlice(this.picture, this.platformViews); - List views; + // The picture of native flutter content to be rendered + ScenePicture picture; - // A conservative estimate of what area platform views in this slice may cover. - // This is expressed in the coordinate space of the parent. - ui.Rect? occlusionRect; + // Platform views to be placed on top of the flutter content. + final List platformViews; - @override - void dispose() {} + void dispose() { + picture.dispose(); + } } -// A slice that contains flutter content to be rendered int he form of a single -// ScenePicture. -class PictureSlice implements LayerSlice { - PictureSlice(this.picture); +mixin PictureEngineLayer implements ui.EngineLayer { + // Each layer is represented as a series of "slices" which contain flutter content + // with platform views on top. This is ordered from bottommost to topmost. + List slices = []; - ScenePicture picture; + List drawCommands = []; + PlatformViewStyling platformViewStyling = const PlatformViewStyling(); - @override - void dispose() => picture.dispose(); -} + LayerOperation get operation; -mixin PictureEngineLayer implements ui.EngineLayer { - // Each layer is represented as a series of "slices" which contain either - // flutter content or platform views. Slices in this list are ordered from - // bottom to top. - List slices = []; + PictureEngineLayer emptyClone(); @override void dispose() { - for (final LayerSlice slice in slices) { - slice.dispose(); + for (final LayerSlice? slice in slices) { + slice?.dispose(); } } + + @override + String toString() { + return 'PictureEngineLayer($operation)'; + } + + bool get isSimple { + if (slices.length > 1) { + return false; + } + final LayerSlice? singleSlice = slices.firstOrNull; + if (singleSlice == null || singleSlice.platformViews.isEmpty) { + return true; + } + return false; + } } abstract class LayerOperation { @@ -442,22 +601,40 @@ abstract class LayerOperation { // layer operation. ui.Rect mapRect(ui.Rect contentRect); - void pre(SceneCanvas canvas, ui.Rect contentRect); - void post(SceneCanvas canvas, ui.Rect contentRect); + void pre(SceneCanvas canvas); + void post(SceneCanvas canvas); PlatformViewStyling createPlatformViewStyling(); /// Indicates whether this operation's `pre` and `post` methods should be /// invoked even if it contains no pictures. (Most operations don't need to /// actually be performed at all if they don't contain any pictures.) - bool get shouldDrawIfEmpty; + bool get affectsBackdrop; +} + +sealed class LayerDrawCommand { } -class PictureDrawCommand { - PictureDrawCommand(this.offset, this.picture); +class PictureDrawCommand extends LayerDrawCommand { + PictureDrawCommand(this.offset, this.picture, this.sliceIndex); - ui.Offset offset; - ui.Picture picture; + final int sliceIndex; + final ui.Offset offset; + final ScenePicture picture; +} + +class PlatformViewDrawCommand extends LayerDrawCommand { + PlatformViewDrawCommand(this.viewId, this.bounds, this.sliceIndex); + + final int sliceIndex; + final int viewId; + final ui.Rect bounds; +} + +class RetainedLayerDrawCommand extends LayerDrawCommand { + RetainedLayerDrawCommand(this.layer); + + final PictureEngineLayer layer; } // Represents how a platform view should be positioned in the scene. @@ -477,6 +654,17 @@ class PlatformViewPosition { bool get isZero => (offset == null) && (transform == null); + ui.Rect mapLocalToGlobal(ui.Rect rect) { + if (offset != null) { + return rect.shift(offset!); + } + if (transform != null) { + return transform!.transformRect(rect); + } + return rect; + } + + // Note that by construction only one of these can be set at any given time, not both. final ui.Offset? offset; final Matrix4? transform; @@ -527,6 +715,17 @@ class PlatformViewPosition { int get hashCode { return Object.hash(offset, transform); } + + @override + String toString() { + if (offset != null) { + return 'PlatformViewPosition(offset: $offset)'; + } + if (transform != null) { + return 'PlatformViewPosition(transform: $transform)'; + } + return 'PlatformViewPosition(zero)'; + } } // Represents the styling to be performed on a platform view when it is @@ -545,6 +744,10 @@ class PlatformViewStyling { final double opacity; final PlatformViewClip clip; + ui.Rect mapLocalToGlobal(ui.Rect rect) { + return position.mapLocalToGlobal(rect.intersect(clip.outerRect)); + } + static PlatformViewStyling combine(PlatformViewStyling outer, PlatformViewStyling inner) { // Attempt to reuse one of the existing immutable objects. if (outer.isDefault) { @@ -575,6 +778,11 @@ class PlatformViewStyling { int get hashCode { return Object.hash(position, opacity, clip); } + + @override + String toString() { + return 'PlatformViewStyling(position: $position, clip: $clip, opacity: $opacity)'; + } } sealed class PlatformViewClip { @@ -642,7 +850,7 @@ class PlatformViewNoClip implements PlatformViewClip { ui.Rect get innerRect => ui.Rect.zero; @override - ui.Rect get outerRect => ui.Rect.zero; + ui.Rect get outerRect => ui.Rect.largest; } class PlatformViewRectClip implements PlatformViewClip { @@ -763,164 +971,186 @@ class PlatformViewPathClip implements PlatformViewClip { ui.Rect get outerRect => path.getBounds(); } +class LayerSliceBuilder { + @visibleForTesting + static (ui.PictureRecorder, SceneCanvas) Function(ui.Rect)? debugRecorderFactory; + + static (ui.PictureRecorder, SceneCanvas) defaultRecorderFactory(ui.Rect rect) { + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final SceneCanvas canvas = ui.Canvas(recorder, rect) as SceneCanvas; + return (recorder, canvas); + } + + void addPicture(ui.Offset offset, ScenePicture picture) { + pictures.add((picture, offset)); + final ui.Rect pictureRect = picture.cullRect.shift(offset); + cullRect = cullRect?.expandToInclude(pictureRect) ?? pictureRect; + } + + (ui.PictureRecorder, SceneCanvas) createRecorder(ui.Rect rect) => + debugRecorderFactory != null ? debugRecorderFactory!(rect) : defaultRecorderFactory(rect); + + LayerSlice buildWithOperation(LayerOperation operation, ui.Rect? backdropRect) { + final ui.Rect effectiveRect; + if (backdropRect != null && cullRect != null) { + effectiveRect = cullRect!.expandToInclude(backdropRect); + } else { + effectiveRect = backdropRect ?? cullRect ?? ui.Rect.zero; + } + final (recorder, canvas) = createRecorder(operation.mapRect(effectiveRect)); + operation.pre(canvas); + for (final (picture, offset) in pictures) { + if (offset != ui.Offset.zero) { + canvas.save(); + canvas.translate(offset.dx, offset.dy); + canvas.drawPicture(picture); + canvas.restore(); + } else { + canvas.drawPicture(picture); + } + } + operation.post(canvas); + final ui.Picture picture = recorder.endRecording(); + return LayerSlice(picture as ScenePicture, platformViews); + } + + final List<(ScenePicture, ui.Offset)> pictures = []; + ui.Rect? cullRect; + final List platformViews = []; +} + class LayerBuilder { factory LayerBuilder.rootLayer() { - return LayerBuilder._(null, EngineRootLayer(), null); + return LayerBuilder._(null, EngineRootLayer()); } factory LayerBuilder.childLayer({ required LayerBuilder parent, required PictureEngineLayer layer, - required LayerOperation operation }) { - return LayerBuilder._(parent, layer, operation); + return LayerBuilder._(parent, layer); } LayerBuilder._( this.parent, - this.layer, - this.operation); - - @visibleForTesting - static (ui.PictureRecorder, SceneCanvas) Function(ui.Rect)? debugRecorderFactory; + this.layer); final LayerBuilder? parent; final PictureEngineLayer layer; - final LayerOperation? operation; - final List pendingPictures = []; - List pendingPlatformViews = []; - ui.Rect? picturesRect; - ui.Rect? platformViewRect; - PlatformViewStyling? _memoizedPlatformViewStyling; + final List sliceBuilders = []; + final List drawCommands = []; - PlatformViewStyling get platformViewStyling { - return _memoizedPlatformViewStyling ??= operation?.createPlatformViewStyling() ?? const PlatformViewStyling(); + ui.Rect? getCurrentBackdropRectAtSliceIndex(int index) { + final parentRect = parent?.getCurrentBackdropRectAtSliceIndex(index); + final sliceBuilder = index < sliceBuilders.length ? sliceBuilders[index] : null; + final sliceRect = sliceBuilder?.cullRect; + final ui.Rect? combinedRect; + if (sliceRect != null && parentRect != null) { + combinedRect = parentRect.expandToInclude(sliceRect); + } else { + combinedRect = parentRect ?? sliceRect; + } + return combinedRect == null ? null : layer.operation.mapRect(combinedRect); } - (ui.PictureRecorder, SceneCanvas) _createRecorder(ui.Rect rect) { - if (debugRecorderFactory != null) { - return debugRecorderFactory!(rect); + int getCurrentSliceCount() { + final parentSliceCount = parent?.getCurrentSliceCount(); + if (parentSliceCount != null) { + return math.max(parentSliceCount, sliceBuilders.length); + } else { + return sliceBuilders.length; } - final ui.PictureRecorder recorder = ui.PictureRecorder(); - final SceneCanvas canvas = ui.Canvas(recorder, rect) as SceneCanvas; - return (recorder, canvas); } - void flushSlices() { - if (pendingPictures.isNotEmpty || (operation?.shouldDrawIfEmpty ?? false)) { - // Merge the existing draw commands into a single picture and add a slice - // with that picture to the slice list. - final ui.Rect drawnRect = picturesRect ?? ui.Rect.zero; - final ui.Rect rect = operation?.mapRect(drawnRect) ?? drawnRect; - final (ui.PictureRecorder recorder, SceneCanvas canvas) = _createRecorder(rect); - - operation?.pre(canvas, rect); - for (final PictureDrawCommand command in pendingPictures) { - if (command.offset != ui.Offset.zero) { - canvas.save(); - canvas.translate(command.offset.dx, command.offset.dy); - canvas.drawPicture(command.picture); - canvas.restore(); - } else { - canvas.drawPicture(command.picture); - } - } - operation?.post(canvas, rect); - final ui.Picture picture = recorder.endRecording(); - layer.slices.add(PictureSlice(picture as ScenePicture)); - } + PlatformViewStyling? _memoizedPlatformViewStyling; + PlatformViewStyling get platformViewStyling { + return _memoizedPlatformViewStyling ??= layer.operation.createPlatformViewStyling(); + } - if (pendingPlatformViews.isNotEmpty) { - // Take any pending platform views and lower them into a platform view - // slice. - ui.Rect? occlusionRect = platformViewRect; - if (occlusionRect != null && operation != null) { - occlusionRect = operation!.mapRect(occlusionRect); - } - layer.slices.add(PlatformViewSlice(pendingPlatformViews, occlusionRect)); + PlatformViewStyling? _memoizedGlobalPlatformViewStyling; + PlatformViewStyling get globalPlatformViewStyling { + if (_memoizedGlobalPlatformViewStyling != null) { + return _memoizedGlobalPlatformViewStyling!; } + if (parent != null) { + return _memoizedGlobalPlatformViewStyling ??= PlatformViewStyling.combine(parent!.globalPlatformViewStyling, platformViewStyling); + } + return _memoizedGlobalPlatformViewStyling ??= platformViewStyling; + } - pendingPictures.clear(); - pendingPlatformViews = []; - - // All the pictures and platform views have been lowered into slices. Clear - // our occlusion rectangles. - picturesRect = null; - platformViewRect = null; + LayerSliceBuilder getOrCreateSliceBuilderAtIndex(int index) { + while (sliceBuilders.length <= index) { + sliceBuilders.add(null); + } + final LayerSliceBuilder? existingSliceBuilder = sliceBuilders[index]; + if (existingSliceBuilder != null) { + return existingSliceBuilder; + } + final LayerSliceBuilder newSliceBuilder = LayerSliceBuilder(); + sliceBuilders[index] = newSliceBuilder; + return newSliceBuilder; } void addPicture( ui.Offset offset, ui.Picture picture, { - bool isComplexHint = false, - bool willChangeHint = false + required int sliceIndex, }) { - final ui.Rect cullRect = (picture as ScenePicture).cullRect; - final ui.Rect shiftedRect = cullRect.shift(offset); - - final ui.Rect? currentPlatformViewRect = platformViewRect; - if (currentPlatformViewRect != null) { - // Whenever we add a picture to our layer, we try to see if the picture - // will overlap with any platform views that are currently on top of our - // drawing surface. If they don't overlap with the platform views, they - // can be grouped with the existing pending pictures. - if (pendingPictures.isEmpty || currentPlatformViewRect.overlaps(shiftedRect)) { - // If they do overlap with the platform views, however, we should flush - // all the current content into slices and start anew with a fresh - // group of pictures and platform views that will be rendered on top of - // the previous content. Note that we also flush if we have no pending - // pictures to group with. This is the case when platform views are - // the first thing in our stack of objects to composite, and it doesn't - // make sense to try to put a picture slice below the first platform - // view slice, even if the picture doesn't overlap. - flushSlices(); - } - } - pendingPictures.add(PictureDrawCommand(offset, picture)); - picturesRect = picturesRect?.expandToInclude(shiftedRect) ?? shiftedRect; + final LayerSliceBuilder sliceBuilder = getOrCreateSliceBuilderAtIndex(sliceIndex); + sliceBuilder.addPicture(offset, picture as ScenePicture); + drawCommands.add(PictureDrawCommand(offset, picture, sliceIndex)); } void addPlatformView( int viewId, { - ui.Offset offset = ui.Offset.zero, - double width = 0.0, - double height = 0.0 + required ui.Rect bounds, + required int sliceIndex, }) { - final ui.Rect bounds = ui.Rect.fromLTWH(offset.dx, offset.dy, width, height); - platformViewRect = platformViewRect?.expandToInclude(bounds) ?? bounds; - pendingPlatformViews.add(PlatformView(viewId, bounds, platformViewStyling)); + final LayerSliceBuilder sliceBuilder = getOrCreateSliceBuilderAtIndex(sliceIndex); + sliceBuilder.platformViews.add(PlatformView(viewId, bounds, platformViewStyling)); + drawCommands.add(PlatformViewDrawCommand(viewId, bounds, sliceIndex)); } void mergeLayer(PictureEngineLayer layer) { - // When we merge layers, we attempt to merge slices as much as possible as - // well, based on ordering of pictures and platform views and reusing the - // occlusion logic for determining where we can lower each picture. - for (final LayerSlice slice in layer.slices) { - switch (slice) { - case PictureSlice(): - addPicture(ui.Offset.zero, slice.picture); - case PlatformViewSlice(): - final ui.Rect? occlusionRect = slice.occlusionRect; - if (occlusionRect != null) { - platformViewRect = platformViewRect?.expandToInclude(occlusionRect) ?? occlusionRect; - } - for (final PlatformView view in slice.views) { - // Merge the platform view styling of this layer with the nested - // platform views. - final PlatformViewStyling styling = PlatformViewStyling.combine( - platformViewStyling, - view.styling, - ); - pendingPlatformViews.add(PlatformView(view.viewId, view.bounds, styling)); - } + for (int i = 0; i < layer.slices.length; i++) { + final LayerSlice? slice = layer.slices[i]; + if (slice != null) { + final LayerSliceBuilder sliceBuilder = getOrCreateSliceBuilderAtIndex(i); + sliceBuilder.addPicture(ui.Offset.zero, slice.picture); + sliceBuilder.platformViews.addAll(slice.platformViews.map((PlatformView view) { + return PlatformView(view.viewId, view.bounds, PlatformViewStyling.combine(platformViewStyling, view.styling)); + })); } } + drawCommands.add(RetainedLayerDrawCommand(layer)); } - PictureEngineLayer build() { - // Lower any pending pictures or platform views to their respective slices. - flushSlices(); + PictureEngineLayer sliceUp() { + final int sliceCount = layer.operation.affectsBackdrop ? getCurrentSliceCount() : sliceBuilders.length; + final slices = []; + for (int i = 0; i < sliceCount; i++) { + final ui.Rect? backdropRect; + if (layer.operation.affectsBackdrop) { + backdropRect = getCurrentBackdropRectAtSliceIndex(i); + } else { + backdropRect = null; + } + final LayerSliceBuilder? builder; + if (backdropRect != null) { + builder = getOrCreateSliceBuilderAtIndex(i); + } else { + builder = i < sliceBuilders.length ? sliceBuilders[i] : null; + } + slices.add(builder?.buildWithOperation(layer.operation, backdropRect)); + } + layer.slices = slices; return layer; } + + PictureEngineLayer build() { + layer.drawCommands = drawCommands; + layer.platformViewStyling = platformViewStyling; + return sliceUp(); + } } diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 2da5356ff13d5..8070fd1e71d42 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -1298,14 +1298,18 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// Engine code should use this method instead of the callback directly. /// Otherwise zones won't work properly. void invokeOnSemanticsAction( - int nodeId, ui.SemanticsAction action, ByteData? args) { + int viewId, + int nodeId, + ui.SemanticsAction action, + ByteData? args, + ) { invoke1( _onSemanticsActionEvent, _onSemanticsActionEventZone, ui.SemanticsActionEvent( type: action, nodeId: nodeId, - viewId: 0, // TODO(goderbauer): Wire up the real view ID. + viewId: viewId, arguments: args, ), ); diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart b/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart index 97aa20e408216..f3aa40ac161ed 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart @@ -12,11 +12,11 @@ final class ViewFocusBinding { ViewFocusBinding(this._viewManager, this._onViewFocusChange); - /// Wether [FlutterView] focus changes will be reported and performed. + /// Whether [FlutterView] focus changes will be reported and performed. /// /// DO NOT rely on this bit as it will go away soon. You're warned :)! @visibleForTesting - static bool isEnabled = false; + static bool isEnabled = true; final FlutterViewManager _viewManager; final ui.ViewFocusChangeCallback _onViewFocusChange; @@ -27,18 +27,20 @@ final class ViewFocusBinding { StreamSubscription? _onViewCreatedListener; void init() { + // We need a global listener here to know if the user was pressing "shift" + // when the Flutter view receives focus, to move the Flutter focus to the + // *last* focusable element. domDocument.body?.addEventListener(_keyDown, _handleKeyDown); domDocument.body?.addEventListener(_keyUp, _handleKeyUp); - domDocument.body?.addEventListener(_focusin, _handleFocusin); - domDocument.body?.addEventListener(_focusout, _handleFocusout); + + // If so, update `_handleViewCreated` and add a `_handleViewDisposed` to attach + // and remove the focus/blur listener. _onViewCreatedListener = _viewManager.onViewCreated.listen(_handleViewCreated); } void dispose() { domDocument.body?.removeEventListener(_keyDown, _handleKeyDown); domDocument.body?.removeEventListener(_keyUp, _handleKeyUp); - domDocument.body?.removeEventListener(_focusin, _handleFocusin); - domDocument.body?.removeEventListener(_focusout, _handleFocusout); _onViewCreatedListener?.cancel(); } @@ -48,13 +50,14 @@ final class ViewFocusBinding { } final DomElement? viewElement = _viewManager[viewId]?.dom.rootElement; - if (state == ui.ViewFocusState.focused) { - // Only move the focus to the flutter view if nothing inside it is focused already. - if (viewId != _viewId(domDocument.activeElement)) { - viewElement?.focusWithoutScroll(); - } - } else { - viewElement?.blur(); + switch (state) { + case ui.ViewFocusState.focused: + // Only move the focus to the flutter view if nothing inside it is focused already. + if (viewId != _viewId(domDocument.activeElement)) { + viewElement?.focusWithoutScroll(); + } + case ui.ViewFocusState.unfocused: + viewElement?.blur(); } } @@ -115,8 +118,8 @@ final class ViewFocusBinding { direction: _viewFocusDirection, ); } - _maybeMarkViewAsFocusable(_lastViewId, reachableByKeyboard: true); - _maybeMarkViewAsFocusable(viewId, reachableByKeyboard: false); + _updateViewKeyboardReachability(_lastViewId, reachable: true); + _updateViewKeyboardReachability(viewId, reachable: false); _lastViewId = viewId; _onViewFocusChange(event); } @@ -127,29 +130,32 @@ final class ViewFocusBinding { } void _handleViewCreated(int viewId) { - _maybeMarkViewAsFocusable(viewId, reachableByKeyboard: true); + final DomElement? rootElement = _viewManager[viewId]?.dom.rootElement; + + rootElement?.addEventListener(_focusin, _handleFocusin); + rootElement?.addEventListener(_focusout, _handleFocusout); + + _updateViewKeyboardReachability(viewId, reachable: true); } - void _maybeMarkViewAsFocusable( + // Controls whether the Flutter view identified by [viewId] is reachable by + // keyboard. + void _updateViewKeyboardReachability( int? viewId, { - required bool reachableByKeyboard, + required bool reachable, }) { if (viewId == null) { return; } final DomElement? rootElement = _viewManager[viewId]?.dom.rootElement; - if (EngineSemantics.instance.semanticsEnabled) { - rootElement?.removeAttribute('tabindex'); - } else { - // A tabindex with value zero means the DOM element can be reached by using - // the keyboard (tab, shift + tab). When its value is -1 it is still focusable - // but can't be focused by the result of keyboard events This is specially - // important when the semantics tree is enabled as it puts DOM nodes inside - // the flutter view and having it with a zero tabindex messes the focus - // traversal order when pressing tab or shift tab. - rootElement?.setAttribute('tabindex', reachableByKeyboard ? 0 : -1); - } + // A tabindex with value zero means the DOM element can be reached using the + // keyboard (tab, shift + tab). When its value is -1 it is still focusable + // but can't be focused as the result of keyboard events. This is specially + // important when the semantics tree is enabled as it puts DOM nodes inside + // the flutter view and having it with a zero tabindex messes the focus + // traversal order when pressing tab or shift tab. + rootElement?.setAttribute('tabindex', reachable ? 0 : -1); } static const String _focusin = 'focusin'; diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 11502593eb97e..091212d5407dd 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -37,18 +37,27 @@ typedef _PointerDataCallback = void Function(DomEvent event, List 0x1 << n; -/// Convert the `button` property of PointerEvent or MouseEvent to a bit mask of -/// its `buttons` property. +/// Convert the `button` property of PointerEvent to a bit mask of its `buttons` +/// property. /// /// The `button` property is a integer describing the button changed in an event, /// which is sequentially 0 for LMB, 1 for MMB, 2 for RMB, 3 for backward and @@ -262,7 +271,7 @@ class ClickDebouncer { /// Forwards the event to the framework, unless it is deduplicated because /// the corresponding pointer down/up events were recently flushed to the /// framework already. - void onClick(DomEvent click, int semanticsNodeId, bool isListening) { + void onClick(DomEvent click, int viewId, int semanticsNodeId, bool isListening) { assert(click.type == 'click'); if (!isDebouncing) { @@ -271,7 +280,7 @@ class ClickDebouncer { // recently and if the node is currently listening to event, forward to // the framework. if (isListening && _shouldSendClickEventToFramework(click)) { - _sendSemanticsTapToFramework(click, semanticsNodeId); + _sendSemanticsTapToFramework(click, viewId, semanticsNodeId); } return; } @@ -283,7 +292,7 @@ class ClickDebouncer { final DebounceState state = _state!; _state = null; state.timer.cancel(); - _sendSemanticsTapToFramework(click, semanticsNodeId); + _sendSemanticsTapToFramework(click, viewId, semanticsNodeId); } else { // The semantic node is not listening to taps. Flush the pointer events // for the framework to figure out what to do with them. It's possible @@ -292,7 +301,11 @@ class ClickDebouncer { } } - void _sendSemanticsTapToFramework(DomEvent click, int semanticsNodeId) { + void _sendSemanticsTapToFramework( + DomEvent click, + int viewId, + int semanticsNodeId, + ) { // Tappable nodes can be nested inside other tappable nodes. If a click // lands on an inner element and is allowed to propagate, it will also // land on the ancestor tappable, leading to both the descendant and the @@ -303,7 +316,11 @@ class ClickDebouncer { click.stopPropagation(); EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsNodeId, ui.SemanticsAction.tap, null); + viewId, + semanticsNodeId, + ui.SemanticsAction.tap, + null, + ); reset(); } @@ -1009,21 +1026,45 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { } }); - // Why `domWindow` you ask? See this fiddle: https://jsfiddle.net/ditman/7towxaqp - _addPointerEventListener(_globalTarget, 'pointermove', (DomPointerEvent event) { - final int device = _getPointerId(event); + // Move event listeners should be added to `_globalTarget` instead of + // `_viewTarget`. This is because `_viewTarget` (the root) captures pointers + // by default, meaning a pointer that starts within `_viewTarget` continues + // sending move events to its listener even when dragged outside. + // + // In contrast, `_globalTarget` (a regular
) stops sending move events + // when the pointer moves outside its bounds and resumes them only when the + // pointer re-enters. + // + // For demonstration, see this fiddle: https://jsfiddle.net/ditman/7towxaqp + // + // TODO(dkwingsmt): Investigate whether we can configure the behavior for + // `_viewTarget`. https://github.com/flutter/flutter/issues/157968 + _addPointerEventListener(_globalTarget, 'pointermove', (DomPointerEvent moveEvent) { + final int device = _getPointerId(moveEvent); final _ButtonSanitizer sanitizer = _ensureSanitizer(device); final List pointerData = []; - final List expandedEvents = _expandEvents(event); + final List expandedEvents = _expandEvents(moveEvent); for (final DomPointerEvent event in expandedEvents) { final _SanitizedDetails? up = sanitizer.sanitizeMissingRightClickUp(buttons: event.buttons!.toInt()); if (up != null) { - _convertEventsToPointerData(data: pointerData, event: event, details: up); + _convertEventsToPointerData( + data: pointerData, + event: event, + details: up, + pointerId: device, + eventTarget: moveEvent.target, + ); } final _SanitizedDetails move = sanitizer.sanitizeMoveEvent(buttons: event.buttons!.toInt()); - _convertEventsToPointerData(data: pointerData, event: event, details: move); + _convertEventsToPointerData( + data: pointerData, + event: event, + details: move, + pointerId: device, + eventTarget: moveEvent.target, + ); } - _callback(event, pointerData); + _callback(moveEvent, pointerData); }); _addPointerEventListener(_viewTarget, 'pointerleave', (DomPointerEvent event) { @@ -1077,12 +1118,17 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { required List data, required DomPointerEvent event, required _SanitizedDetails details, + // `pointerId` and `eventTarget` are optional but useful when it's not + // desired to get those values from the event object. For example, when the + // event is a coalesced event. + int? pointerId, + DomEventTarget? eventTarget, }) { final ui.PointerDeviceKind kind = _pointerTypeToDeviceKind(event.pointerType!); final double tilt = _computeHighestTilt(event); final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!); final num? pressure = event.pressure; - final ui.Offset offset = computeEventOffsetToTarget(event, _view); + final ui.Offset offset = computeEventOffsetToTarget(event, _view, eventTarget: eventTarget); _pointerDataConverter.convert( data, viewId: _view.viewId, @@ -1090,7 +1136,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { timeStamp: timeStamp, kind: kind, signalKind: ui.PointerSignalKind.none, - device: _getPointerId(event), + device: pointerId ?? _getPointerId(event), physicalX: offset.dx * _view.devicePixelRatio, physicalY: offset.dy * _view.devicePixelRatio, buttons: details.buttons, @@ -1133,12 +1179,21 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { } int _getPointerId(DomPointerEvent event) { - // We force `device: _mouseDeviceId` on mouse pointers because Wheel events - // might come before any PointerEvents, and since wheel events don't contain - // pointerId we always assign `device: _mouseDeviceId` to them. - final ui.PointerDeviceKind kind = _pointerTypeToDeviceKind(event.pointerType!); - return kind == ui.PointerDeviceKind.mouse ? _mouseDeviceId : - event.pointerId!.toInt(); + // All mouse pointer events are given `_mouseDeviceId`, including wheel + // events, because wheel events might come before any other PointerEvents, + // and wheel PointerEvents don't contain pointerIds. + return switch(_pointerTypeToDeviceKind(event.pointerType!)) { + ui.PointerDeviceKind.mouse => _mouseDeviceId, + + ui.PointerDeviceKind.stylus || + ui.PointerDeviceKind.invertedStylus => _stylusDeviceId, + + // Trackpad processing doesn't call this function. + ui.PointerDeviceKind.trackpad => throw Exception('Unreachable'), + + ui.PointerDeviceKind.touch || + ui.PointerDeviceKind.unknown => event.pointerId!.toInt(), + }; } /// Tilt angle is -90 to + 90. Take maximum deflection and convert to radians. diff --git a/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart b/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart index 319a3620cd741..82954325ab85e 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart @@ -12,18 +12,26 @@ import '../text_editing/text_editing.dart'; import '../vector_math.dart'; import '../window.dart'; -/// Returns an [ui.Offset] of the position of [event], relative to the position of [actualTarget]. +/// Returns an [ui.Offset] of the position of [event], relative to the position +/// of the Flutter [view]. /// /// The offset is *not* multiplied by DPR or anything else, it's the closest /// to what the DOM would return if we had currentTarget readily available. /// -/// This needs an `actualTarget`, because the `event.currentTarget` (which is what -/// this would really need to use) gets lost when the `event` comes from a "coalesced" -/// event. +/// This takes an optional `eventTarget`, because the `event.target` may have +/// the wrong value for "coalesced" events. See: +/// +/// - https://github.com/flutter/flutter/issues/155987 +/// - https://github.com/flutter/flutter/issues/159804 +/// - https://g-issues.chromium.org/issues/382473107 /// /// It also takes into account semantics being enabled to fix the case where /// offsetX, offsetY == 0 (TalkBack events). -ui.Offset computeEventOffsetToTarget(DomMouseEvent event, EngineFlutterView view) { +ui.Offset computeEventOffsetToTarget( + DomMouseEvent event, + EngineFlutterView view, { + DomEventTarget? eventTarget, +}) { final DomElement actualTarget = view.dom.rootElement; // On a TalkBack event if (EngineSemantics.instance.semanticsEnabled && event.offsetX == 0 && event.offsetY == 0) { @@ -31,16 +39,17 @@ ui.Offset computeEventOffsetToTarget(DomMouseEvent event, EngineFlutterView view } // On one of our text-editing nodes - final bool isInput = view.dom.textEditingHost.contains(event.target! as DomNode); + eventTarget ??= event.target!; + final bool isInput = view.dom.textEditingHost.contains(eventTarget as DomNode); if (isInput) { final EditableTextGeometry? inputGeometry = textEditing.strategy.geometry; if (inputGeometry != null) { - return _computeOffsetForInputs(event, inputGeometry); + return _computeOffsetForInputs(event, eventTarget, inputGeometry); } } // On another DOM Element (normally a platform view) - final bool isTargetOutsideOfShadowDOM = event.target != actualTarget; + final bool isTargetOutsideOfShadowDOM = eventTarget != actualTarget; if (isTargetOutsideOfShadowDOM) { final DomRect origin = actualTarget.getBoundingClientRect(); // event.clientX/Y and origin.x/y are relative **to the viewport**. @@ -64,8 +73,14 @@ ui.Offset computeEventOffsetToTarget(DomMouseEvent event, EngineFlutterView view /// sent from the framework, which includes information on how to transform the /// underlying input element. We transform the `event.offset` points we receive /// using the values from the input's transform matrix. -ui.Offset _computeOffsetForInputs(DomMouseEvent event, EditableTextGeometry inputGeometry) { - final DomElement targetElement = event.target! as DomHTMLElement; +/// +/// See [computeEventOffsetToTarget] for more information about `eventTarget`. +ui.Offset _computeOffsetForInputs( + DomMouseEvent event, + DomEventTarget eventTarget, + EditableTextGeometry inputGeometry, +) { + final DomElement targetElement = eventTarget as DomElement; final DomHTMLElement domElement = textEditing.strategy.activeDomElement; assert(targetElement == domElement, 'The targeted input element must be the active input element'); final Float32List transformValues = inputGeometry.globalTransform; diff --git a/lib/web_ui/lib/src/engine/renderer.dart b/lib/web_ui/lib/src/engine/renderer.dart index e0c3a59d63a72..aa3e8bfc9be66 100644 --- a/lib/web_ui/lib/src/engine/renderer.dart +++ b/lib/web_ui/lib/src/engine/renderer.dart @@ -120,7 +120,7 @@ abstract class Renderer { ui.ImageFilter createBlurImageFilter({ double sigmaX = 0.0, double sigmaY = 0.0, - ui.TileMode tileMode = ui.TileMode.clamp}); + ui.TileMode? tileMode}); ui.ImageFilter createDilateImageFilter({ double radiusX = 0.0, double radiusY = 0.0}); ui.ImageFilter createErodeImageFilter({ double radiusX = 0.0, double radiusY = 0.0}); ui.ImageFilter createMatrixImageFilter( diff --git a/lib/web_ui/lib/src/engine/scene_builder.dart b/lib/web_ui/lib/src/engine/scene_builder.dart index 37d29e66f7fdd..9fe4c17c57711 100644 --- a/lib/web_ui/lib/src/engine/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/scene_builder.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:math' as math; import 'dart:typed_data'; import 'package:ui/src/engine.dart'; @@ -62,17 +63,119 @@ class EngineScene implements ui.Scene { final ui.Rect canvasRect = ui.Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); final ui.Canvas canvas = ui.Canvas(recorder, canvasRect); - // Only rasterizes the picture slices. - for (final PictureSlice slice in rootLayer.slices.whereType()) { - canvas.drawPicture(slice.picture); + // Only rasterizes the pictures. + for (final LayerSlice? slice in rootLayer.slices) { + if (slice != null) { + canvas.drawPicture(slice.picture); + } } return recorder.endRecording().toImageSync(width, height); } } +sealed class OcclusionMapNode { + bool overlaps(ui.Rect rect); + OcclusionMapNode insert(ui.Rect rect); + ui.Rect get boundingBox; +} + +class OcclusionMapEmpty implements OcclusionMapNode { + @override + ui.Rect get boundingBox => ui.Rect.zero; + + @override + OcclusionMapNode insert(ui.Rect rect) => OcclusionMapLeaf(rect); + + @override + bool overlaps(ui.Rect rect) => false; + +} + +class OcclusionMapLeaf implements OcclusionMapNode { + OcclusionMapLeaf(this.rect); + + final ui.Rect rect; + + @override + ui.Rect get boundingBox => rect; + + @override + OcclusionMapNode insert(ui.Rect other) => OcclusionMapBranch(this, OcclusionMapLeaf(other)); + + @override + bool overlaps(ui.Rect other) => rect.overlaps(other); +} + +class OcclusionMapBranch implements OcclusionMapNode { + OcclusionMapBranch(this.left, this.right) + : boundingBox = left.boundingBox.expandToInclude(right.boundingBox); + + final OcclusionMapNode left; + final OcclusionMapNode right; + + @override + final ui.Rect boundingBox; + + double _areaOfUnion(ui.Rect first, ui.Rect second) { + return (math.max(first.right, second.right) - math.min(first.left, second.left)) + * (math.max(first.bottom, second.bottom) - math.max(first.top, second.top)); + } + + @override + OcclusionMapNode insert(ui.Rect other) { + // Try to create nodes with the smallest possible area + final double leftOtherArea = _areaOfUnion(left.boundingBox, other); + final double rightOtherArea = _areaOfUnion(right.boundingBox, other); + final double leftRightArea = boundingBox.width * boundingBox.height; + if (leftOtherArea < rightOtherArea) { + if (leftOtherArea < leftRightArea) { + return OcclusionMapBranch( + left.insert(other), + right, + ); + } + } else { + if (rightOtherArea < leftRightArea) { + return OcclusionMapBranch( + left, + right.insert(other), + ); + } + } + return OcclusionMapBranch(this, OcclusionMapLeaf(other)); + } + + @override + bool overlaps(ui.Rect rect) { + if (!boundingBox.overlaps(rect)) { + return false; + } + return left.overlaps(rect) || right.overlaps(rect); + } +} + +class OcclusionMap { + OcclusionMapNode root = OcclusionMapEmpty(); + + void addRect(ui.Rect rect) => root = root.insert(rect); + + bool overlaps(ui.Rect rect) => root.overlaps(rect); +} + +class SceneSlice { + final OcclusionMap pictureOcclusionMap = OcclusionMap(); + final OcclusionMap platformViewOcclusionMap = OcclusionMap(); +} + class EngineSceneBuilder implements ui.SceneBuilder { LayerBuilder currentBuilder = LayerBuilder.rootLayer(); + final List sceneSlices = [SceneSlice()]; + + // This represents the simplest case with no platform views, which is a fast path + // that allows us to avoid work tracking the pictures themselves. + bool _isSimple = true; + @override void addPerformanceOverlay(int enabledOptions, ui.Rect bounds) { // We don't plan to implement this on the web. @@ -86,15 +189,54 @@ class EngineSceneBuilder implements ui.SceneBuilder { bool isComplexHint = false, bool willChangeHint = false }) { + final int sliceIndex = _placePicture(offset, picture as ScenePicture, currentBuilder.globalPlatformViewStyling); currentBuilder.addPicture( offset, picture, - isComplexHint: - isComplexHint, - willChangeHint: willChangeHint + sliceIndex: sliceIndex, ); } + // This function determines the lowest scene slice that this picture can be placed + // into and adds it to that slice's occlusion map. + // + // The picture is placed in the last slice where it either intersects with a picture + // in the slice or it intersects with a platform view in the preceding slice. If the + // picture intersects with a platform view in the last slice, a new slice is added at + // the end and the picture goes in there. + int _placePicture(ui.Offset offset, ScenePicture picture, PlatformViewStyling styling) { + if (_isSimple) { + // This is the fast path where there are no platform views. The picture should + // just be placed on the bottom (and only) slice. + return 0; + } + final ui.Rect cullRect = picture.cullRect.shift(offset); + final ui.Rect mappedCullRect = styling.mapLocalToGlobal(cullRect); + int sliceIndex = sceneSlices.length; + while (sliceIndex > 0) { + final SceneSlice sliceBelow = sceneSlices[sliceIndex - 1]; + if (sliceBelow.platformViewOcclusionMap.overlaps(mappedCullRect)) { + break; + } + sliceIndex--; + if (sliceBelow.pictureOcclusionMap.overlaps(mappedCullRect)) { + break; + } + } + if (sliceIndex == 0) { + // Don't bother to populate the lowest occlusion map with pictures, since + // we never hit test against pictures in the bottom slice. + return sliceIndex; + } + if (sliceIndex == sceneSlices.length) { + // Insert a new slice. + sceneSlices.add(SceneSlice()); + } + final SceneSlice slice = sceneSlices[sliceIndex]; + slice.pictureOcclusionMap.addRect(mappedCullRect); + return sliceIndex; + } + @override void addPlatformView( int viewId, { @@ -102,17 +244,103 @@ class EngineSceneBuilder implements ui.SceneBuilder { double width = 0.0, double height = 0.0 }) { + final ui.Rect platformViewRect = ui.Rect.fromLTWH(offset.dx, offset.dy, width, height); + final int sliceIndex = _placePlatformView(viewId, platformViewRect, currentBuilder.globalPlatformViewStyling); currentBuilder.addPlatformView( viewId, - offset: offset, - width: width, - height: height + bounds: platformViewRect, + sliceIndex: sliceIndex, ); } + // This function determines the lowest scene slice this platform view can be placed + // into and adds it to that slice's occlusion map. + // + // The platform view is placed into the last slice where it intersects with a picture + // or a platform view. + int _placePlatformView( + int viewId, + ui.Rect rect, + PlatformViewStyling styling, + ) { + // Once we add a platform view, we actually have to do proper occlusion tracking. + _isSimple = false; + + final ui.Rect globalPlatformViewRect = styling.mapLocalToGlobal(rect); + int sliceIndex = sceneSlices.length - 1; + while (sliceIndex > 0) { + final SceneSlice slice = sceneSlices[sliceIndex]; + if (slice.platformViewOcclusionMap.overlaps(globalPlatformViewRect) || + slice.pictureOcclusionMap.overlaps(globalPlatformViewRect)) { + break; + } + sliceIndex--; + } + sliceIndex = 0; + final SceneSlice slice = sceneSlices[sliceIndex]; + slice.platformViewOcclusionMap.addRect(globalPlatformViewRect); + return sliceIndex; + } + @override void addRetained(ui.EngineLayer retainedLayer) { - currentBuilder.mergeLayer(retainedLayer as PictureEngineLayer); + final PictureEngineLayer placedEngineLayer = _placeRetainedLayer(retainedLayer as PictureEngineLayer, currentBuilder.globalPlatformViewStyling); + currentBuilder.mergeLayer(placedEngineLayer); + } + + PictureEngineLayer _placeRetainedLayer(PictureEngineLayer retainedLayer, PlatformViewStyling styling) { + if (_isSimple && retainedLayer.isSimple) { + // There are no platform views, so we don't need to do any occlusion tracking + // and can simply merge the layer. + return retainedLayer; + } + bool needsRebuild = false; + final List revisedDrawCommands = []; + final PlatformViewStyling combinedStyling = PlatformViewStyling.combine(styling, retainedLayer.platformViewStyling); + for (final LayerDrawCommand command in retainedLayer.drawCommands) { + switch (command) { + case PictureDrawCommand(offset: final ui.Offset offset, picture: final ScenePicture picture): + final int sliceIndex = _placePicture(offset, picture, combinedStyling); + if (command.sliceIndex != sliceIndex) { + needsRebuild = true; + } + revisedDrawCommands.add(PictureDrawCommand(offset, picture, sliceIndex)); + case PlatformViewDrawCommand(viewId: final int viewId, bounds: final ui.Rect bounds): + final int sliceIndex = _placePlatformView(viewId, bounds, combinedStyling); + if (command.sliceIndex != sliceIndex) { + needsRebuild = true; + } + revisedDrawCommands.add(PlatformViewDrawCommand(viewId, bounds, sliceIndex)); + case RetainedLayerDrawCommand(layer: final PictureEngineLayer sublayer): + final PictureEngineLayer revisedSublayer = _placeRetainedLayer(sublayer, combinedStyling); + if (sublayer != revisedSublayer) { + needsRebuild = true; + } + revisedDrawCommands.add(RetainedLayerDrawCommand(revisedSublayer)); + } + } + + if (!needsRebuild) { + // No elements changed which slice position they are in, so we can simply + // merge the existing layer down and don't have to redraw individual elements. + return retainedLayer; + } + + // Otherwise, we replace the commands of the layer to create a new one. + currentBuilder = LayerBuilder.childLayer(parent: currentBuilder, layer: retainedLayer.emptyClone()); + for (final LayerDrawCommand command in revisedDrawCommands) { + switch (command) { + case PictureDrawCommand(offset: final ui.Offset offset, picture: final ScenePicture picture): + currentBuilder.addPicture(offset, picture, sliceIndex: command.sliceIndex); + case PlatformViewDrawCommand(viewId: final int viewId, bounds: final ui.Rect bounds): + currentBuilder.addPlatformView(viewId, bounds: bounds, sliceIndex: command.sliceIndex); + case RetainedLayerDrawCommand(layer: final PictureEngineLayer layer): + currentBuilder.mergeLayer(layer); + } + } + final PictureEngineLayer newLayer = currentBuilder.build(); + currentBuilder = currentBuilder.parent!; + return newLayer; } @override @@ -133,30 +361,21 @@ class EngineSceneBuilder implements ui.SceneBuilder { ui.BlendMode blendMode = ui.BlendMode.srcOver, ui.BackdropFilterEngineLayer? oldLayer, int? backdropId, - }) => pushLayer( - BackdropFilterLayer(), - BackdropFilterOperation(filter, blendMode), - ); + }) => pushLayer(BackdropFilterLayer(BackdropFilterOperation(filter, blendMode))); @override ui.ClipPathEngineLayer pushClipPath( ui.Path path, { ui.Clip clipBehavior = ui.Clip.antiAlias, ui.ClipPathEngineLayer? oldLayer - }) => pushLayer( - ClipPathLayer(), - ClipPathOperation(path as ScenePath, clipBehavior), - ); + }) => pushLayer(ClipPathLayer(ClipPathOperation(path as ScenePath, clipBehavior))); @override ui.ClipRRectEngineLayer pushClipRRect( ui.RRect rrect, { required ui.Clip clipBehavior, ui.ClipRRectEngineLayer? oldLayer - }) => pushLayer( - ClipRRectLayer(), - ClipRRectOperation(rrect, clipBehavior) - ); + }) => pushLayer(ClipRRectLayer(ClipRRectOperation(rrect, clipBehavior))); @override ui.ClipRectEngineLayer pushClipRect( @@ -164,20 +383,14 @@ class EngineSceneBuilder implements ui.SceneBuilder { ui.Clip clipBehavior = ui.Clip.antiAlias, ui.ClipRectEngineLayer? oldLayer }) { - return pushLayer( - ClipRectLayer(), - ClipRectOperation(rect, clipBehavior) - ); + return pushLayer(ClipRectLayer(ClipRectOperation(rect, clipBehavior))); } @override ui.ColorFilterEngineLayer pushColorFilter( ui.ColorFilter filter, { ui.ColorFilterEngineLayer? oldLayer - }) => pushLayer( - ColorFilterLayer(), - ColorFilterOperation(filter), - ); + }) => pushLayer(ColorFilterLayer(ColorFilterOperation(filter))); @override ui.ImageFilterEngineLayer pushImageFilter( @@ -185,8 +398,7 @@ class EngineSceneBuilder implements ui.SceneBuilder { ui.Offset offset = ui.Offset.zero, ui.ImageFilterEngineLayer? oldLayer }) => pushLayer( - ImageFilterLayer(), - ImageFilterOperation(filter as SceneImageFilter, offset), + ImageFilterLayer(ImageFilterOperation(filter as SceneImageFilter, offset)), ); @override @@ -194,19 +406,14 @@ class EngineSceneBuilder implements ui.SceneBuilder { double dx, double dy, { ui.OffsetEngineLayer? oldLayer - }) => pushLayer( - OffsetLayer(), - OffsetOperation(dx, dy) - ); + }) => pushLayer(OffsetLayer(OffsetOperation(dx, dy))); @override ui.OpacityEngineLayer pushOpacity(int alpha, { ui.Offset offset = ui.Offset.zero, ui.OpacityEngineLayer? oldLayer - }) => pushLayer( - OpacityLayer(), - OpacityOperation(alpha, offset), - ); + }) => pushLayer(OpacityLayer(OpacityOperation(alpha, offset))); + @override ui.ShaderMaskEngineLayer pushShaderMask( ui.Shader shader, @@ -215,18 +422,14 @@ class EngineSceneBuilder implements ui.SceneBuilder { ui.ShaderMaskEngineLayer? oldLayer, ui.FilterQuality filterQuality = ui.FilterQuality.low }) => pushLayer( - ShaderMaskLayer(), - ShaderMaskOperation(shader, maskRect, blendMode) + ShaderMaskLayer(ShaderMaskOperation(shader, maskRect, blendMode)), ); @override ui.TransformEngineLayer pushTransform( Float64List matrix4, { ui.TransformEngineLayer? oldLayer - }) => pushLayer( - TransformLayer(), - TransformOperation(matrix4), - ); + }) => pushLayer(TransformLayer(TransformOperation(matrix4))); @override void setProperties( @@ -261,11 +464,10 @@ class EngineSceneBuilder implements ui.SceneBuilder { currentBuilder.mergeLayer(layer); } - T pushLayer(T layer, LayerOperation operation) { + T pushLayer(T layer) { currentBuilder = LayerBuilder.childLayer( parent: currentBuilder, layer: layer, - operation: operation ); return layer; } diff --git a/lib/web_ui/lib/src/engine/scene_painting.dart b/lib/web_ui/lib/src/engine/scene_painting.dart index 1ef39d24480cd..4f70633328c67 100644 --- a/lib/web_ui/lib/src/engine/scene_painting.dart +++ b/lib/web_ui/lib/src/engine/scene_painting.dart @@ -4,6 +4,8 @@ import 'package:ui/ui.dart' as ui; +import 'vector_math.dart'; + // These are additional APIs that are not part of the `dart:ui` interface that // are needed internally to properly implement a `SceneBuilder` on top of the // generic Canvas/Picture api. @@ -22,6 +24,10 @@ abstract class SceneImageFilter implements ui.ImageFilter { // gives the maximum draw boundary for a picture with the given input bounds after it // has been processed by the filter. ui.Rect filterBounds(ui.Rect inputBounds); + + // The matrix image filter changes the position of the content, so when positioning + // platform views and calculating occlusion we need to take its transform into account. + Matrix4? get transform; } abstract class ScenePath implements ui.Path { diff --git a/lib/web_ui/lib/src/engine/scene_view.dart b/lib/web_ui/lib/src/engine/scene_view.dart index 53504204a4bcb..bedffaef2d7f8 100644 --- a/lib/web_ui/lib/src/engine/scene_view.dart +++ b/lib/web_ui/lib/src/engine/scene_view.dart @@ -95,23 +95,24 @@ class EngineSceneView { flutterView.physicalSize.width, flutterView.physicalSize.height, ); - final List slices = scene.rootLayer.slices; + final List slices = scene.rootLayer.slices; final List picturesToRender = []; final List originalPicturesToRender = []; - for (final LayerSlice slice in slices) { - if (slice is PictureSlice) { - final ui.Rect clippedRect = slice.picture.cullRect.intersect(screenBounds); - if (clippedRect.isEmpty) { - // This picture is completely offscreen, so don't render it at all - continue; - } else if (clippedRect == slice.picture.cullRect) { - // The picture doesn't need to be clipped, just render the original - originalPicturesToRender.add(slice.picture); - picturesToRender.add(slice.picture); - } else { - originalPicturesToRender.add(slice.picture); - picturesToRender.add(pictureRenderer.clipPicture(slice.picture, clippedRect)); - } + for (final LayerSlice? slice in slices) { + if (slice == null) { + continue; + } + final ui.Rect clippedRect = slice.picture.cullRect.intersect(screenBounds); + if (clippedRect.isEmpty) { + // This picture is completely offscreen, so don't render it at all + continue; + } else if (clippedRect == slice.picture.cullRect) { + // The picture doesn't need to be clipped, just render the original + originalPicturesToRender.add(slice.picture); + picturesToRender.add(slice.picture); + } else { + originalPicturesToRender.add(slice.picture); + picturesToRender.add(pictureRenderer.clipPicture(slice.picture, clippedRect)); } } final Map renderMap; @@ -132,58 +133,52 @@ class EngineSceneView { final List reusableContainers = List.from(containers); final List newContainers = []; - for (final LayerSlice slice in slices) { - switch (slice) { - case PictureSlice(): - final DomImageBitmap? bitmap = renderMap[slice.picture]; - if (bitmap == null) { - // We didn't render this slice because no part of it is visible. - continue; - } - PictureSliceContainer? container; - for (int j = 0; j < reusableContainers.length; j++) { - final SliceContainer? candidate = reusableContainers[j]; - if (candidate is PictureSliceContainer) { - container = candidate; - reusableContainers[j] = null; - break; - } + for (final LayerSlice? slice in slices) { + if (slice == null) { + continue; + } + final DomImageBitmap? bitmap = renderMap[slice.picture]; + if (bitmap != null) { + PictureSliceContainer? container; + for (int j = 0; j < reusableContainers.length; j++) { + final SliceContainer? candidate = reusableContainers[j]; + if (candidate is PictureSliceContainer) { + container = candidate; + reusableContainers[j] = null; + break; } + } - final ui.Rect clippedBounds = slice.picture.cullRect.intersect(screenBounds); - if (container != null) { - container.bounds = clippedBounds; - } else { - container = PictureSliceContainer(clippedBounds); - } - container.updateContents(); - container.renderBitmap(bitmap); - newContainers.add(container); - - case PlatformViewSlice(): - for (final PlatformView view in slice.views) { - // TODO(harryterkelsen): Inject the FlutterView instance from `renderScene`, - // instead of using `EnginePlatformDispatcher...implicitView` directly, - // or make the FlutterView "register" like in canvaskit. - // Ensure the platform view contents are injected in the DOM. - EnginePlatformDispatcher.instance.implicitView?.dom.injectPlatformView(view.viewId); - - // Attempt to reuse a container for the existing view - PlatformViewContainer? container; - for (int j = 0; j < reusableContainers.length; j++) { - final SliceContainer? candidate = reusableContainers[j]; - if (candidate is PlatformViewContainer && candidate.viewId == view.viewId) { - container = candidate; - reusableContainers[j] = null; - break; - } - } - container ??= PlatformViewContainer(view.viewId); - container.bounds = view.bounds; - container.styling = view.styling; - container.updateContents(); - newContainers.add(container); + final ui.Rect clippedBounds = slice.picture.cullRect.intersect(screenBounds); + if (container != null) { + container.bounds = clippedBounds; + } else { + container = PictureSliceContainer(clippedBounds); + } + container.updateContents(); + container.renderBitmap(bitmap); + newContainers.add(container); + } + + for (final PlatformView view in slice.platformViews) { + // Ensure the contents of the platform view are injected into the DOM. + flutterView.dom.injectPlatformView(view.viewId); + + // Attempt to reuse a container for the existing view + PlatformViewContainer? container; + for (int j = 0; j < reusableContainers.length; j++) { + final SliceContainer? candidate = reusableContainers[j]; + if (candidate is PlatformViewContainer && candidate.viewId == view.viewId) { + container = candidate; + reusableContainers[j] = null; + break; } + } + container ??= PlatformViewContainer(view.viewId); + container.bounds = view.bounds; + container.styling = view.styling; + container.updateContents(); + newContainers.add(container); } } diff --git a/lib/web_ui/lib/src/engine/semantics.dart b/lib/web_ui/lib/src/engine/semantics.dart index 6bfca5a58571a..036faceca171b 100644 --- a/lib/web_ui/lib/src/engine/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics.dart @@ -5,6 +5,7 @@ export 'semantics/accessibility.dart'; export 'semantics/checkable.dart'; export 'semantics/focusable.dart'; +export 'semantics/header.dart'; export 'semantics/heading.dart'; export 'semantics/image.dart'; export 'semantics/incrementable.dart'; diff --git a/lib/web_ui/lib/src/engine/semantics/focusable.dart b/lib/web_ui/lib/src/engine/semantics/focusable.dart index 54bc0d4e55392..745b646d134c0 100644 --- a/lib/web_ui/lib/src/engine/semantics/focusable.dart +++ b/lib/web_ui/lib/src/engine/semantics/focusable.dart @@ -206,6 +206,7 @@ class AccessibilityFocusManager { // shifting focus. if (_lastEvent != AccessibilityFocusManagerEvent.requestedFocus) { EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + _owner.viewId, target.semanticsNodeId, ui.SemanticsAction.focus, null, diff --git a/lib/web_ui/lib/src/engine/semantics/header.dart b/lib/web_ui/lib/src/engine/semantics/header.dart new file mode 100644 index 0000000000000..9de9e3d4d271f --- /dev/null +++ b/lib/web_ui/lib/src/engine/semantics/header.dart @@ -0,0 +1,44 @@ +// 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 '../dom.dart'; +import 'label_and_value.dart'; +import 'semantics.dart'; + +/// Renders a semantic header. +/// +/// A header is a group of nodes that together introduce the content of the +/// current screen or page. +/// +/// Uses the `
` element, which implies ARIA role "banner". +/// +/// See also: +/// * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header +/// * https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/banner_role +class SemanticHeader extends SemanticRole { + SemanticHeader(SemanticsObject semanticsObject) : super.withBasics( + SemanticRoleKind.header, + semanticsObject, + + // Why use sizedSpan? + // + // On an empty
aria-label alone will read the label but also add + // "empty banner". Additionally, if the label contains information that's + // meant to be crawlable, it will be lost by moving into aria-label, because + // most crawlers ignore ARIA labels. + // + // Using DOM text, such as
DOM text
causes the browser to + // generate two a11y nodes, one for the
element, and one for the + // "DOM text" text node. The text node is sized according to the text size, + // and does not match the size of the
element, which is the same + // issue as https://github.com/flutter/flutter/issues/146774. + preferredLabelRepresentation: LabelRepresentation.sizedSpan, + ); + + @override + DomElement createElement() => createDomElement('header'); + + @override + bool focusAsRouteDefault() => focusable?.focusAsRouteDefault() ?? false; +} diff --git a/lib/web_ui/lib/src/engine/semantics/heading.dart b/lib/web_ui/lib/src/engine/semantics/heading.dart index 3f00837db34b3..051b6829c2508 100644 --- a/lib/web_ui/lib/src/engine/semantics/heading.dart +++ b/lib/web_ui/lib/src/engine/semantics/heading.dart @@ -20,7 +20,7 @@ class SemanticHeading extends SemanticRole { @override DomElement createElement() { - final element = createDomElement('h${semanticsObject.headingLevel}'); + final element = createDomElement('h${semanticsObject.effectiveHeadingLevel}'); element.style // Browser adds default non-zero margins/paddings to tags, which // affects the size of the element. As the element size is fully defined diff --git a/lib/web_ui/lib/src/engine/semantics/incrementable.dart b/lib/web_ui/lib/src/engine/semantics/incrementable.dart index c1b6cbd4dc5d6..ef351e660cb2b 100644 --- a/lib/web_ui/lib/src/engine/semantics/incrementable.dart +++ b/lib/web_ui/lib/src/engine/semantics/incrementable.dart @@ -43,11 +43,11 @@ class SemanticIncrementable extends SemanticRole { if (newInputValue > _currentSurrogateValue) { _currentSurrogateValue += 1; EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsObject.id, ui.SemanticsAction.increase, null); + viewId, semanticsObject.id, ui.SemanticsAction.increase, null); } else if (newInputValue < _currentSurrogateValue) { _currentSurrogateValue -= 1; EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsObject.id, ui.SemanticsAction.decrease, null); + viewId, semanticsObject.id, ui.SemanticsAction.decrease, null); } })); diff --git a/lib/web_ui/lib/src/engine/semantics/scrollable.dart b/lib/web_ui/lib/src/engine/semantics/scrollable.dart index 9679b85f37a21..508e5e6bd954f 100644 --- a/lib/web_ui/lib/src/engine/semantics/scrollable.dart +++ b/lib/web_ui/lib/src/engine/semantics/scrollable.dart @@ -76,20 +76,20 @@ class SemanticScrollable extends SemanticRole { if (doScrollForward) { if (semanticsObject.isVerticalScrollContainer) { EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsId, ui.SemanticsAction.scrollUp, null); + viewId, semanticsId, ui.SemanticsAction.scrollUp, null); } else { assert(semanticsObject.isHorizontalScrollContainer); EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsId, ui.SemanticsAction.scrollLeft, null); + viewId, semanticsId, ui.SemanticsAction.scrollLeft, null); } } else { if (semanticsObject.isVerticalScrollContainer) { EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsId, ui.SemanticsAction.scrollDown, null); + viewId, semanticsId, ui.SemanticsAction.scrollDown, null); } else { assert(semanticsObject.isHorizontalScrollContainer); EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsId, ui.SemanticsAction.scrollRight, null); + viewId, semanticsId, ui.SemanticsAction.scrollRight, null); } } } diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart index ea31b6356e3b1..d35c9c1e69f36 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -21,6 +21,7 @@ import '../window.dart'; import 'accessibility.dart'; import 'checkable.dart'; import 'focusable.dart'; +import 'header.dart'; import 'heading.dart'; import 'image.dart'; import 'incrementable.dart'; @@ -396,14 +397,17 @@ enum SemanticRoleKind { /// The node's role is to host a platform view. platformView, + /// Contains a link. + link, + + /// Denotes a header. + header, + /// A role used when a more specific role cannot be assigend to /// a [SemanticsObject]. /// /// Provides a label or a value. generic, - - /// Contains a link. - link, } /// Responsible for setting the `role` ARIA attribute, for attaching @@ -440,6 +444,9 @@ abstract class SemanticRole { /// The semantics object managed by this role. final SemanticsObject semanticsObject; + /// The ID of the Flutter View that this [SemanticRole] belongs to. + int get viewId => semanticsObject.owner.viewId; + /// Whether this role accepts pointer events. /// /// This boolean decides whether to set the `pointer-events` CSS property to @@ -688,13 +695,11 @@ final class GenericRole extends SemanticRole { return; } - // Assign one of three roles to the element: group, heading, text. + // Assign one of two roles to the element: group or text. // // - "group" is used when the node has children, irrespective of whether the // node is marked as a header or not. This is because marking a group // as a "heading" will prevent the AT from reaching its children. - // - "heading" is used when the framework explicitly marks the node as a - // heading and the node does not have children. // - If a node has a label and no children, assume is a paragraph of text. // In HTML text has no ARIA role. It's just a DOM node with text inside // it. Previously, role="text" was used, but it was only supported by @@ -702,9 +707,6 @@ final class GenericRole extends SemanticRole { if (semanticsObject.hasChildren) { labelAndValue!.preferredRepresentation = LabelRepresentation.ariaLabel; setAriaRole('group'); - } else if (semanticsObject.hasFlag(ui.SemanticsFlag.isHeader)) { - labelAndValue!.preferredRepresentation = LabelRepresentation.domText; - setAriaRole('heading'); } else { labelAndValue!.preferredRepresentation = LabelRepresentation.sizedSpan; removeAttribute('role'); @@ -765,6 +767,9 @@ abstract class SemanticBehavior { final SemanticRole owner; + /// The ID of the Flutter View that this [SemanticBehavior] belongs to. + int get viewId => semanticsObject.owner.viewId; + /// Whether this role accepts pointer events. /// /// This boolean decides whether to set the `pointer-events` CSS property to @@ -1123,10 +1128,24 @@ class SemanticsObject { _dirtyFields |= _platformViewIdIndex; } - /// See [ui.SemanticsUpdateBuilder.updateNode]. - int get headingLevel => _headingLevel; + // This field is not exposed publicly because code that applies heading levels + // should use [effectiveHeadingLevel] instead. int _headingLevel = 0; + /// The effective heading level value to be used when rendering this node as + /// a heading. + /// + /// If a heading is rendered from a header, uses heading level 2. + int get effectiveHeadingLevel { + if (_headingLevel != 0) { + return _headingLevel; + } else { + // This branch may be taken when a heading is rendered from a header, + // where the heading level is not provided. + return 2; + } + } + static const int _headingLevelIndex = 1 << 24; /// Whether the [headingLevel] field has been updated but has not been @@ -1136,6 +1155,36 @@ class SemanticsObject { _dirtyFields |= _headingLevelIndex; } + /// Whether this object represents a heading. + /// + /// Typically, a heading is a prominent piece of text that provides a title + /// for a section in the UI. + /// + /// Labeled empty headers are treated as headings too. + /// + /// See also: + /// + /// * [isHeader], which also describes the rest of the screen, and is + /// sometimes presented to the user as a heading. + bool get isHeading => _headingLevel != 0 || isHeader && hasLabel && !hasChildren; + + /// Whether this object represents a header. + /// + /// A header is used for one of two purposes: + /// + /// * Introduce the content of the main screen or a page. In this case, the + /// header is a, possibly labeled, container of widgets that together + /// provide the description of the screen. + /// * Provide a heading (like [isHeading]). Native mobile apps do not have a + /// notion of "heading". It is common to mark headings as headers instead + /// and the screen readers will announce "heading". Labeled empty headers + /// are treated as heading by the web engine. + /// + /// See also: + /// + /// * [isHeading], which determines whether this node represents a heading. + bool get isHeader => hasFlag(ui.SemanticsFlag.isHeader); + /// See [ui.SemanticsUpdateBuilder.updateNode]. String? get identifier => _identifier; String? _identifier; @@ -1271,10 +1320,7 @@ class SemanticsObject { /// Whether this object represents an editable text field. bool get isTextField => hasFlag(ui.SemanticsFlag.isTextField); - /// Whether this object represents a heading element. - bool get isHeading => headingLevel != 0; - - /// Whether this object represents an editable text field. + /// Whether this object represents an interactive link. bool get isLink => hasFlag(ui.SemanticsFlag.isLink); /// Whether this object needs screen readers attention right away. @@ -1673,6 +1719,8 @@ class SemanticsObject { if (isPlatformView) { return SemanticRoleKind.platformView; } else if (isHeading) { + // IMPORTANT: because headings also cover certain kinds of headers, the + // `heading` role has precedence over the `header` role. return SemanticRoleKind.heading; } else if (isTextField) { return SemanticRoleKind.textField; @@ -1690,6 +1738,8 @@ class SemanticsObject { return SemanticRoleKind.route; } else if (isLink) { return SemanticRoleKind.link; + } else if (isHeader) { + return SemanticRoleKind.header; } else { return SemanticRoleKind.generic; } @@ -1707,6 +1757,7 @@ class SemanticsObject { SemanticRoleKind.platformView => SemanticPlatformView(this), SemanticRoleKind.link => SemanticLink(this), SemanticRoleKind.heading => SemanticHeading(this), + SemanticRoleKind.header => SemanticHeader(this), SemanticRoleKind.generic => GenericRole(this), }; } @@ -1987,7 +2038,9 @@ class SemanticsObject { void dispose() { assert(!_isDisposed); _isDisposed = true; - element.remove(); + + EnginePlatformDispatcher.instance.viewManager.safeRemoveSync(element); + _parent = null; semanticRole?.dispose(); semanticRole = null; @@ -2334,12 +2387,15 @@ class EngineSemantics { /// The top-level service that manages everything semantics-related. class EngineSemanticsOwner { - EngineSemanticsOwner(this.semanticsHost) { + EngineSemanticsOwner(this.viewId, this.semanticsHost) { registerHotRestartListener(() { _rootSemanticsElement?.remove(); }); } + /// The ID of the Flutter View that this semantics owner belongs to. + final int viewId; + /// The permanent element in the view's DOM structure that hosts the semantics /// tree. /// diff --git a/lib/web_ui/lib/src/engine/semantics/tappable.dart b/lib/web_ui/lib/src/engine/semantics/tappable.dart index 544ad1475a45b..aea41d9e9ddad 100644 --- a/lib/web_ui/lib/src/engine/semantics/tappable.dart +++ b/lib/web_ui/lib/src/engine/semantics/tappable.dart @@ -46,6 +46,7 @@ class Tappable extends SemanticBehavior { _clickListener = createDomEventListener((DomEvent click) { PointerBinding.clickDebouncer.onClick( click, + viewId, semanticsObject.id, _isListening, ); diff --git a/lib/web_ui/lib/src/engine/semantics/text_field.dart b/lib/web_ui/lib/src/engine/semantics/text_field.dart index 1ad9cecf839bb..6c58d3b102f27 100644 --- a/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -114,16 +114,7 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy { } subscriptions.clear(); lastEditingState = null; - - // If the text element still has focus, remove focus from the editable - // element to cause the on-screen keyboard, if any, to hide (e.g. on iOS, - // Android). - // Otherwise, the keyboard stays on screen even when the user navigates to - // a different screen (e.g. by hitting the "back" button). - // Keep this consistent with how DefaultTextEditingStrategy does it. As of - // right now, the only difference is that semantic text fields do not - // participate in form autofill. - DefaultTextEditingStrategy.scheduleFocusFlutterView(activeDomElement, activeDomElementView); + EnginePlatformDispatcher.instance.viewManager.safeBlur(activeDomElement); domElement = null; activeTextField = null; _queuedStyle = null; @@ -287,7 +278,7 @@ class SemanticTextField extends SemanticRole { // IMPORTANT: because this event listener can be triggered by either or // both a "focus" and a "click" DOM events, this code must be idempotent. EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsObject.id, ui.SemanticsAction.focus, null); + viewId, semanticsObject.id, ui.SemanticsAction.focus, null); })); editableElement.addEventListener('click', createDomEventListener((DomEvent event) { editableElement.focusWithoutScroll(); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart index 9bbb98ff244f5..2c918a11890dc 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart @@ -32,28 +32,41 @@ class SkwasmCanvas implements SceneCanvas { final paintHandle = (paint as SkwasmPaint).toRawPaint(); if (bounds != null) { withStackScope((StackScope s) { - canvasSaveLayer(_handle, s.convertRectToNative(bounds), paintHandle, nullptr); + canvasSaveLayer(_handle, s.convertRectToNative(bounds), paintHandle, nullptr, + ui.TileMode.clamp.index); }); } else { - canvasSaveLayer(_handle, nullptr, paintHandle, nullptr); + canvasSaveLayer(_handle, nullptr, paintHandle, nullptr, ui.TileMode.clamp.index); } paintDispose(paintHandle); } @override void saveLayerWithFilter(ui.Rect? bounds, ui.Paint paint, ui.ImageFilter imageFilter) { + // There are 2 ImageFilter objects applied here. The filter in the paint + // object is applied to the contents and its default tile mode is decal + // (automatically applied by toSkPaint). + // The filter supplied as an argument to this function [nativeFilter] will + // be applied to the backdrop and its default tile mode will be mirror. + // We also pass in the blur tile mode as an argument to saveLayer because + // that operation will not adopt the tile mode from the backdrop filter + // and instead needs it supplied to the saveLayer call itself as a + // separate argument. final SkwasmImageFilter nativeFilter = SkwasmImageFilter.fromUiFilter(imageFilter); - final paintHandle = (paint as SkwasmPaint).toRawPaint(); + final ui.TileMode? backdropTileMode = nativeFilter.backdropTileMode; + final paintHandle = (paint as SkwasmPaint).toRawPaint(/*ui.TileMode.decal*/); if (bounds != null) { withStackScope((StackScope s) { nativeFilter.withRawImageFilter((nativeFilterHandle) { - canvasSaveLayer(_handle, s.convertRectToNative(bounds), paintHandle, nativeFilterHandle); - }); + canvasSaveLayer(_handle, s.convertRectToNative(bounds), paintHandle, nativeFilterHandle, + (backdropTileMode ?? ui.TileMode.mirror).index); + }, defaultBlurTileMode: ui.TileMode.mirror); }); } else { nativeFilter.withRawImageFilter((nativeFilterHandle) { - canvasSaveLayer(_handle, nullptr, paintHandle, nativeFilterHandle); - }); + canvasSaveLayer(_handle, nullptr, paintHandle, nativeFilterHandle, + (backdropTileMode ?? ui.TileMode.mirror).index); + }, defaultBlurTileMode: ui.TileMode.mirror); } paintDispose(paintHandle); } @@ -212,7 +225,9 @@ class SkwasmCanvas implements SceneCanvas { @override void drawImage(ui.Image image, ui.Offset offset, ui.Paint paint) { - final paintHandle = (paint as SkwasmPaint).toRawPaint(); + final paintHandle = (paint as SkwasmPaint).toRawPaint( + defaultBlurTileMode: ui.TileMode.clamp, + ); canvasDrawImage( _handle, (image as SkwasmImage).handle, @@ -234,7 +249,9 @@ class SkwasmCanvas implements SceneCanvas { withStackScope((StackScope scope) { final Pointer sourceRect = scope.convertRectToNative(src); final Pointer destRect = scope.convertRectToNative(dst); - final paintHandle = (paint as SkwasmPaint).toRawPaint(); + final paintHandle = (paint as SkwasmPaint).toRawPaint( + defaultBlurTileMode: ui.TileMode.clamp, + ); canvasDrawImageRect( _handle, (image as SkwasmImage).handle, @@ -257,7 +274,9 @@ class SkwasmCanvas implements SceneCanvas { withStackScope((StackScope scope) { final Pointer centerRect = scope.convertIRectToNative(center); final Pointer destRect = scope.convertRectToNative(dst); - final paintHandle = (paint as SkwasmPaint).toRawPaint(); + final paintHandle = (paint as SkwasmPaint).toRawPaint( + defaultBlurTileMode: ui.TileMode.clamp, + ); canvasDrawImageNine( _handle, (image as SkwasmImage).handle, @@ -355,7 +374,9 @@ class SkwasmCanvas implements SceneCanvas { final RawRect rawCullRect = cullRect != null ? scope.convertRectToNative(cullRect) : nullptr; - final paintHandle = (paint as SkwasmPaint).toRawPaint(); + final paintHandle = (paint as SkwasmPaint).toRawPaint( + defaultBlurTileMode: ui.TileMode.clamp, + ); canvasDrawAtlas( _handle, (atlas as SkwasmImage).handle, @@ -388,7 +409,9 @@ class SkwasmCanvas implements SceneCanvas { final RawRect rawCullRect = cullRect != null ? scope.convertRectToNative(cullRect) : nullptr; - final paintHandle = (paint as SkwasmPaint).toRawPaint(); + final paintHandle = (paint as SkwasmPaint).toRawPaint( + defaultBlurTileMode: ui.TileMode.clamp, + ); canvasDrawAtlas( _handle, (atlas as SkwasmImage).handle, diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/codecs.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/codecs.dart index 52b1c0f9ec724..ed317f45223cd 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/codecs.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/codecs.dart @@ -17,8 +17,8 @@ class SkwasmImageDecoder extends BrowserImageDecoder { @override ui.Image generateImageFromVideoFrame(VideoFrame frame) { - final int width = frame.codedWidth.toInt(); - final int height = frame.codedHeight.toInt(); + final int width = frame.displayWidth.toInt(); + final int height = frame.displayHeight.toInt(); final SkwasmSurface surface = (renderer as SkwasmRenderer).surface; return SkwasmImage(imageCreateFromTextureSource( frame as JSObject, diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart index 7c611d110baef..615a0285cdffd 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart @@ -16,7 +16,7 @@ abstract class SkwasmImageFilter implements SceneImageFilter { factory SkwasmImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0, - ui.TileMode tileMode = ui.TileMode.clamp, + ui.TileMode? tileMode, }) => SkwasmBlurFilter(sigmaX, sigmaY, tileMode); factory SkwasmImageFilter.dilate({ @@ -58,9 +58,16 @@ abstract class SkwasmImageFilter implements SceneImageFilter { /// Creates a temporary [ImageFilterHandle] and passes it to the [borrow] /// function. /// + /// If (and only if) the filter is a blur ImageFilter, then the indicated + /// [defaultBlurTileMode] is used in place of a missing (null) tile mode. + /// /// The handle is deleted immediately after [borrow] returns. The [borrow] /// function must not store the handle to avoid dangling pointer bugs. - void withRawImageFilter(ImageFilterHandleBorrow borrow); + void withRawImageFilter(ImageFilterHandleBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }); + + ui.TileMode? get backdropTileMode => ui.TileMode.clamp; @override ui.Rect filterBounds(ui.Rect inputBounds) => withStackScope((StackScope scope) { @@ -77,17 +84,25 @@ class SkwasmBlurFilter extends SkwasmImageFilter { final double sigmaX; final double sigmaY; - final ui.TileMode tileMode; + final ui.TileMode? tileMode; @override - void withRawImageFilter(ImageFilterHandleBorrow borrow) { - final rawImageFilter = imageFilterCreateBlur(sigmaX, sigmaY, tileMode.index); + ui.TileMode? get backdropTileMode => tileMode; + + @override + void withRawImageFilter(ImageFilterHandleBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { + final rawImageFilter = imageFilterCreateBlur(sigmaX, sigmaY, (tileMode ?? defaultBlurTileMode).index); borrow(rawImageFilter); imageFilterDispose(rawImageFilter); } @override String toString() => 'ImageFilter.blur($sigmaX, $sigmaY, ${tileModeString(tileMode)})'; + + @override + Matrix4? get transform => null; } class SkwasmDilateFilter extends SkwasmImageFilter { @@ -97,7 +112,9 @@ class SkwasmDilateFilter extends SkwasmImageFilter { final double radiusY; @override - void withRawImageFilter(ImageFilterHandleBorrow borrow) { + void withRawImageFilter(ImageFilterHandleBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { final rawImageFilter = imageFilterCreateDilate(radiusX, radiusY); borrow(rawImageFilter); imageFilterDispose(rawImageFilter); @@ -105,6 +122,9 @@ class SkwasmDilateFilter extends SkwasmImageFilter { @override String toString() => 'ImageFilter.dilate($radiusX, $radiusY)'; + + @override + Matrix4? get transform => null; } class SkwasmErodeFilter extends SkwasmImageFilter { @@ -114,7 +134,9 @@ class SkwasmErodeFilter extends SkwasmImageFilter { final double radiusY; @override - void withRawImageFilter(ImageFilterHandleBorrow borrow) { + void withRawImageFilter(ImageFilterHandleBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { final rawImageFilter = imageFilterCreateErode(radiusX, radiusY); borrow(rawImageFilter); imageFilterDispose(rawImageFilter); @@ -122,6 +144,9 @@ class SkwasmErodeFilter extends SkwasmImageFilter { @override String toString() => 'ImageFilter.erode($radiusX, $radiusY)'; + + @override + Matrix4? get transform => null; } class SkwasmMatrixFilter extends SkwasmImageFilter { @@ -131,7 +156,9 @@ class SkwasmMatrixFilter extends SkwasmImageFilter { final ui.FilterQuality filterQuality; @override - void withRawImageFilter(ImageFilterHandleBorrow borrow) { + void withRawImageFilter(ImageFilterHandleBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { withStackScope((scope) { final rawImageFilter = imageFilterCreateMatrix( scope.convertMatrix4toSkMatrix(matrix4), @@ -144,6 +171,9 @@ class SkwasmMatrixFilter extends SkwasmImageFilter { @override String toString() => 'ImageFilter.matrix($matrix4, $filterQuality)'; + + @override + Matrix4? get transform => Matrix4.fromFloat32List(toMatrix32(matrix4)); } class SkwasmColorImageFilter extends SkwasmImageFilter { @@ -152,7 +182,9 @@ class SkwasmColorImageFilter extends SkwasmImageFilter { final SkwasmColorFilter filter; @override - void withRawImageFilter(ImageFilterHandleBorrow borrow) { + void withRawImageFilter(ImageFilterHandleBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { filter.withRawColorFilter((colroFilterHandle) { final rawImageFilter = imageFilterCreateFromColorFilter(colroFilterHandle); borrow(rawImageFilter); @@ -162,6 +194,9 @@ class SkwasmColorImageFilter extends SkwasmImageFilter { @override String toString() => filter.toString(); + + @override + Matrix4? get transform => null; } class SkwasmComposedImageFilter extends SkwasmImageFilter { @@ -171,18 +206,30 @@ class SkwasmComposedImageFilter extends SkwasmImageFilter { final SkwasmImageFilter inner; @override - void withRawImageFilter(ImageFilterHandleBorrow borrow) { + void withRawImageFilter(ImageFilterHandleBorrow borrow, { + ui.TileMode defaultBlurTileMode = ui.TileMode.clamp, + }) { outer.withRawImageFilter((outerHandle) { inner.withRawImageFilter((innerHandle) { final rawImageFilter = imageFilterCompose(outerHandle, innerHandle); borrow(rawImageFilter); imageFilterDispose(rawImageFilter); - }); - }); + }, defaultBlurTileMode: defaultBlurTileMode); + }, defaultBlurTileMode: defaultBlurTileMode); } @override String toString() => 'ImageFilter.compose($outer, $inner)'; + + @override + Matrix4? get transform { + final outerTransform = outer.transform; + final innerTransform = inner.transform; + if (outerTransform != null && innerTransform != null) { + return outerTransform.multiplied(innerTransform); + } + return outerTransform ?? innerTransform; + } } typedef ColorFilterHandleBorrow = void Function(ColorFilterHandle handle); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/font_collection.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/font_collection.dart index 4f561e4183734..cd1d862532968 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/font_collection.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/font_collection.dart @@ -18,7 +18,7 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web; // Roboto font. The API reference is here: // https://developers.google.com/fonts/docs/developer_api String _robotoUrl = - '${configuration.fontFallbackBaseUrl}roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf'; + '${configuration.fontFallbackBaseUrl}roboto/v32/KFOmCnqEu92Fr1Me4GZLCzYlKw.woff2'; class SkwasmTypeface extends SkwasmObjectWrapper { SkwasmTypeface(SkDataHandle data) : super(typefaceCreate(data), _registry); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart index a6eb94d1b9b87..1d8ab97c44de8 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart @@ -16,7 +16,7 @@ class SkwasmPaint implements ui.Paint { /// /// It is the responsibility of the caller to dispose of the returned handle /// when it's no longer needed. - PaintHandle toRawPaint() { + PaintHandle toRawPaint({ui.TileMode defaultBlurTileMode = ui.TileMode.decal}) { final rawPaint = paintCreate( isAntiAlias, blendMode.index, @@ -47,7 +47,7 @@ class SkwasmPaint implements ui.Paint { final skwasmImageFilter = SkwasmImageFilter.fromUiFilter(filter); skwasmImageFilter.withRawImageFilter((nativeHandle) { paintSetImageFilter(rawPaint, nativeHandle); - }); + }, defaultBlurTileMode: defaultBlurTileMode); } return rawPaint; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart index 599085ea6c146..391e18a82dcd1 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart @@ -21,12 +21,14 @@ external void canvasSave(CanvasHandle canvas); RawRect, PaintHandle, ImageFilterHandle, + Int )>(symbol: 'canvas_saveLayer', isLeaf: true) external void canvasSaveLayer( CanvasHandle canvas, RawRect rect, PaintHandle paint, ImageFilterHandle handle, + int backdropTileMode, ); @Native(symbol: 'canvas_restore', isLeaf: true) diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_memory.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_memory.dart index 9b63df3e12439..4cb95aa99489a 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_memory.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_memory.dart @@ -15,13 +15,13 @@ final class Stack extends Opaque {} typedef StackPointer = Pointer; /// Generic linear memory allocation -@Native(symbol: 'stackAlloc', isLeaf: true) +@Native(symbol: '_emscripten_stack_alloc', isLeaf: true) external StackPointer stackAlloc(int length); -@Native(symbol: 'stackSave', isLeaf: true) +@Native(symbol: 'emscripten_stack_get_current', isLeaf: true) external StackPointer stackSave(); -@Native(symbol: 'stackRestore', isLeaf: true) +@Native(symbol: '_emscripten_stack_restore', isLeaf: true) external void stackRestore(StackPointer pointer); class StackScope { diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart index d162f190dbe70..c1ec710a65330 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart @@ -2,7 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +@DefaultAsset('skwasm') +library skwasm_impl; + import 'dart:_wasm'; +import 'dart:ffi'; import 'dart:js_interop'; @JS() @@ -26,3 +30,6 @@ external SkwasmInstance get skwasmInstance; @pragma('wasm:import', 'skwasmWrapper.addFunction') external WasmI32 addFunction(WasmFuncRef function); + +@Native(symbol: 'skwasm_isMultiThreaded', isLeaf: true) +external bool skwasmIsMultiThreaded(); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart index c73c63d0060d9..27d29b1418210 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart @@ -16,6 +16,8 @@ class SkwasmRenderer implements Renderer { late SkwasmSurface surface; final Map _sceneViews = {}; + bool get isMultiThreaded => skwasmIsMultiThreaded(); + @override final SkwasmFontCollection fontCollection = SkwasmFontCollection(); @@ -58,7 +60,7 @@ class SkwasmRenderer implements Renderer { ui.ImageFilter createBlurImageFilter({ double sigmaX = 0.0, double sigmaY = 0.0, - ui.TileMode tileMode = ui.TileMode.clamp + ui.TileMode? tileMode, }) => SkwasmImageFilter.blur( sigmaX: sigmaX, sigmaY: sigmaY, diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart index 2a2e6b5137a32..5e48b42f5f325 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_stub/renderer.dart @@ -12,6 +12,8 @@ import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; class SkwasmRenderer implements Renderer { + bool get isMultiThreaded => false; + @override ui.Path combinePaths(ui.PathOperation op, ui.Path path1, ui.Path path2) { throw UnimplementedError('Skwasm not implemented on this platform.'); @@ -28,7 +30,7 @@ class SkwasmRenderer implements Renderer { } @override - ui.ImageFilter createBlurImageFilter({double sigmaX = 0.0, double sigmaY = 0.0, ui.TileMode tileMode = ui.TileMode.clamp}) { + ui.ImageFilter createBlurImageFilter({double sigmaX = 0.0, double sigmaY = 0.0, ui.TileMode? tileMode}) { throw UnimplementedError('Skwasm not implemented on this platform.'); } diff --git a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index e5082a09e25af..edba5f73afe7c 100644 --- a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -1411,9 +1411,9 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements inputConfiguration.autofillGroup?.formElement != null) { _styleAutofillElements(activeDomElement, isOffScreen: true); inputConfiguration.autofillGroup?.storeForm(); - scheduleFocusFlutterView(activeDomElement, activeDomElementView); + EnginePlatformDispatcher.instance.viewManager.safeBlur(activeDomElement); } else { - scheduleFocusFlutterView(activeDomElement, activeDomElementView, removeElement: true); + EnginePlatformDispatcher.instance.viewManager.safeRemove(activeDomElement); } domElement = null; } @@ -1573,29 +1573,6 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements void moveFocusToActiveDomElement() { activeDomElement.focusWithoutScroll(); } - - /// Move the focus to the given [EngineFlutterView] in the next timer event. - /// - /// The timer gives the engine the opportunity to focus on another element. - /// Shifting focus immediately can cause the keyboard to jump. - static void scheduleFocusFlutterView( - DomElement element, - EngineFlutterView? view, { - bool removeElement = false, - }) { - Timer(Duration.zero, () { - // If by the time the timer fired the focused element is no longer the - // editing element whose editing session was disabled, there's no need to - // move the focus, as it is likely that another widget already took the - // focus. - if (element == domDocument.activeElement) { - view?.dom.rootElement.focusWithoutScroll(); - } - if (removeElement) { - element.remove(); - } - }); - } } /// IOS/Safari behaviour for text editing. diff --git a/lib/web_ui/lib/src/engine/util.dart b/lib/web_ui/lib/src/engine/util.dart index 9dbf6a3ce3180..3734ec541fd3f 100644 --- a/lib/web_ui/lib/src/engine/util.dart +++ b/lib/web_ui/lib/src/engine/util.dart @@ -887,7 +887,7 @@ class LruCache { } /// Returns the VM-compatible string for the tile mode. -String tileModeString(ui.TileMode tileMode) { +String tileModeString(ui.TileMode? tileMode) { switch (tileMode) { case ui.TileMode.clamp: return 'clamp'; @@ -897,6 +897,8 @@ String tileModeString(ui.TileMode tileMode) { return 'repeated'; case ui.TileMode.decal: return 'decal'; + case null: + return 'unspecified'; } } diff --git a/lib/web_ui/lib/src/engine/view_embedder/flutter_view_manager.dart b/lib/web_ui/lib/src/engine/view_embedder/flutter_view_manager.dart index 685e940a53cf1..16a16d5192781 100644 --- a/lib/web_ui/lib/src/engine/view_embedder/flutter_view_manager.dart +++ b/lib/web_ui/lib/src/engine/view_embedder/flutter_view_manager.dart @@ -108,9 +108,86 @@ class FlutterViewManager { const String viewRootSelector = '${DomManager.flutterViewTagName}[${GlobalHtmlAttributes.flutterViewIdAttributeName}]'; final DomElement? viewRoot = element?.closest(viewRootSelector); - final String? viewIdAttribute = viewRoot?.getAttribute(GlobalHtmlAttributes.flutterViewIdAttributeName); - final int? viewId = viewIdAttribute == null ? null : int.parse(viewIdAttribute); - return viewId == null ? null : _viewData[viewId]; + if (viewRoot == null) { + // `element` is not inside any flutter view. + return null; + } + + final String? viewIdAttribute = viewRoot.getAttribute(GlobalHtmlAttributes.flutterViewIdAttributeName); + assert(viewIdAttribute != null, 'Located Flutter view is missing its id attribute.'); + + final int? viewId = int.tryParse(viewIdAttribute!); + assert(viewId != null, 'Flutter view id must be a valid int.'); + + return _viewData[viewId]; + } + + /// Blurs [element] by transferring its focus to its [EngineFlutterView] + /// `rootElement`. + /// + /// This operation is asynchronous, but happens as soon as possible + /// (see [Timer.run]). + Future safeBlur(DomElement element) { + return Future(() { + _transferFocusToViewRoot(element); + }); + } + + /// Removes [element] after transferring its focus to its [EngineFlutterView] + /// `rootElement`. + /// + /// This operation is asynchronous, but happens as soon as possible + /// (see [Timer.run]). + /// + /// There's a synchronous version of this method: [safeRemoveSync]. + Future safeRemove(DomElement element) { + return Future(() => safeRemoveSync(element)); + } + + /// Synchronously removes [element] after transferring its focus to its + /// [EngineFlutterView] `rootElement`. + /// + /// This is the synchronous version of [safeRemove]. + void safeRemoveSync(DomElement element) { + _transferFocusToViewRoot(element, removeElement: true); + } + + /// Blurs (removes focus) from [element] by transferring its focus to its + /// [EngineFlutterView] DOM's `rootElement` before (optionally) removing it. + /// + /// By default, blurring a focused `element` (or removing it from the DOM) + /// transfers its focus to the `body` element of the page. + /// + /// This method achieves two things when blurring/removing `element`: + /// + /// * Ensures the focus is preserved within the Flutter View when blurring + /// elements that are part of the internal DOM structure of the Flutter + /// app. + /// * Prevents the Flutter engine from reporting bogus "blur" events from the + /// Flutter View. + /// + /// When [removeElement] is true, `element` will be removed from the DOM after + /// its focus (or that of any of its children) is transferred to the root of + /// the view. + /// + /// See: https://jsfiddle.net/ditman/1e2swpno for a JS focus transfer demo. + void _transferFocusToViewRoot( + DomElement element, { + bool removeElement = false, + }) { + final DomElement? activeElement = domDocument.activeElement; + // If the element we're operating on is not active anymore (it can happen + // when this method is called asynchronously), OR the element that we want + // to remove *contains* the `activeElement`. + if (element == activeElement || removeElement && element.contains(activeElement)) { + // Transfer the browser focus to the `rootElement` of the + // [EngineFlutterView] that contains `element` + final EngineFlutterView? view = findViewForElement(element); + view?.dom.rootElement.focusWithoutScroll(); + } + if (removeElement) { + element.remove(); + } } void dispose() { diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index f11d92166f5dc..0281aa7b7c287 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -157,7 +157,7 @@ class EngineFlutterView implements ui.FlutterView { final JsViewConstraints? _jsViewConstraints; - late final EngineSemanticsOwner semantics = EngineSemanticsOwner(dom.semanticsHost); + late final EngineSemanticsOwner semantics = EngineSemanticsOwner(viewId, dom.semanticsHost); @override ui.Size get physicalSize { diff --git a/lib/web_ui/pubspec.yaml b/lib/web_ui/pubspec.yaml index 15c763467e4f9..06f585c97dedd 100644 --- a/lib/web_ui/pubspec.yaml +++ b/lib/web_ui/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none # Keep the SDK version range in sync with pubspecs under web_sdk environment: - sdk: '>=3.6.0-0 <4.0.0' + sdk: ^3.7.0-0 dependencies: js: ^0.7.0 @@ -21,7 +21,6 @@ dev_dependencies: archive: 3.6.1 args: any async: any - csslib: 1.0.0 convert: any crypto: any html: 0.15.4 diff --git a/lib/web_ui/skwasm/BUILD.gn b/lib/web_ui/skwasm/BUILD.gn index 44219af9697ec..cdacdcddde6b7 100644 --- a/lib/web_ui/skwasm/BUILD.gn +++ b/lib/web_ui/skwasm/BUILD.gn @@ -4,72 +4,98 @@ import("//build/toolchain/wasm.gni") -wasm_lib("skwasm") { - public_configs = [ "//flutter:config" ] +template("skwasm_variant") { + wasm_lib(target_name) { + public_configs = [ "//flutter:config" ] - sources = [ - "canvas.cpp", - "contour_measure.cpp", - "data.cpp", - "export.h", - "filters.cpp", - "fonts.cpp", - "helpers.h", - "image.cpp", - "paint.cpp", - "path.cpp", - "picture.cpp", - "shaders.cpp", - "skwasm_support.h", - "string.cpp", - "surface.cpp", - "text/line_metrics.cpp", - "text/paragraph.cpp", - "text/paragraph_builder.cpp", - "text/paragraph_style.cpp", - "text/strut_style.cpp", - "text/text_style.cpp", - "vertices.cpp", - "wrappers.h", - ] + sources = [ + "canvas.cpp", + "contour_measure.cpp", + "data.cpp", + "export.h", + "filters.cpp", + "fonts.cpp", + "helpers.h", + "image.cpp", + "paint.cpp", + "path.cpp", + "picture.cpp", + "shaders.cpp", + "skwasm_support.h", + "string.cpp", + "surface.cpp", + "text/line_metrics.cpp", + "text/paragraph.cpp", + "text/paragraph_builder.cpp", + "text/paragraph_style.cpp", + "text/strut_style.cpp", + "text/text_style.cpp", + "vertices.cpp", + "wrappers.h", + ] + + cflags = [ "-mreference-types" ] + + ldflags = [ + "-std=c++20", + "-lGL", + "-sUSE_WEBGL2=1", + "-sMAX_WEBGL_VERSION=2", + "-sOFFSCREENCANVAS_SUPPORT", + "-sALLOW_MEMORY_GROWTH", + "-sALLOW_TABLE_GROWTH", + "-lexports.js", + "-sEXPORTED_FUNCTIONS=[stackAlloc]", + "-sEXPORTED_RUNTIME_METHODS=[addFunction,wasmExports,wasmMemory,stackAlloc]", + "-sSHARED_MEMORY=1", + "-sINCOMING_MODULE_JS_API=[instantiateWasm,noExitRuntime,mainScriptUrlOrBlob]", + "-sUSE_ES6_IMPORT_META=0", + "--js-library", + rebase_path("library_skwasm_support.js"), + ] - cflags = [ - "-mreference-types", - "-pthread", - ] + inputs = [ rebase_path("library_skwasm_support.js") ] - ldflags = [ - "-std=c++20", - "-lGL", - "-sUSE_WEBGL2=1", - "-sMAX_WEBGL_VERSION=2", - "-sOFFSCREENCANVAS_SUPPORT", - "-sPTHREAD_POOL_SIZE=1", - "-sALLOW_MEMORY_GROWTH", - "-sALLOW_TABLE_GROWTH", - "-lexports.js", - "-sEXPORTED_FUNCTIONS=[stackAlloc]", - "-sEXPORTED_RUNTIME_METHODS=[addFunction,wasmExports]", - "-Wno-pthreads-mem-growth", - "--js-library", - rebase_path("library_skwasm_support.js"), - ] + if (invoker.multi_threaded) { + sources += [ "surface_mt.cpp" ] + ldflags += [ + "-sPTHREAD_POOL_SIZE=1", + "-Wno-pthreads-mem-growth", + "--js-library", + rebase_path("library_skwasm_multi_threaded.js"), + ] + inputs += [ rebase_path("library_skwasm_multi_threaded.js") ] + } else { + sources += [ "surface_st.cpp" ] + ldflags += [ + "--js-library", + rebase_path("library_skwasm_single_threaded.js"), + ] + inputs += [ rebase_path("library_skwasm_single_threaded.js") ] + } - inputs = [ rebase_path("library_skwasm_support.js") ] + if (is_debug) { + ldflags += [ + "-sDEMANGLE_SUPPORT=1", + "-sASSERTIONS=1", + "-sGL_ASSERTIONS=1", + ] + } else { + ldflags += [ "--closure=1" ] + } - if (is_debug) { - ldflags += [ - "-sDEMANGLE_SUPPORT=1", - "-sASSERTIONS=1", - "-sGL_ASSERTIONS=1", + deps = [ + "//flutter/skia", + "//flutter/skia/modules/skparagraph", + "//flutter/skia/modules/skunicode", ] - } else { - ldflags += [ "--closure=1" ] } +} + +skwasm_variant("skwasm") { + multi_threaded = true +} - deps = [ - "//flutter/skia", - "//flutter/skia/modules/skparagraph", - "//flutter/skia/modules/skunicode", - ] +skwasm_variant("skwasm_st") { + multi_threaded = false } diff --git a/lib/web_ui/skwasm/canvas.cpp b/lib/web_ui/skwasm/canvas.cpp index 9a8da8ad06b6e..cf53f5e2bd100 100644 --- a/lib/web_ui/skwasm/canvas.cpp +++ b/lib/web_ui/skwasm/canvas.cpp @@ -33,8 +33,10 @@ constexpr SkScalar kShadowLightYOffset = -450; SKWASM_EXPORT void canvas_saveLayer(SkCanvas* canvas, SkRect* rect, SkPaint* paint, - SkImageFilter* backdrop) { - canvas->saveLayer(SkCanvas::SaveLayerRec(rect, paint, backdrop, 0)); + SkImageFilter* backdrop, + SkTileMode backdropTileMode) { + canvas->saveLayer(SkCanvas::SaveLayerRec(rect, paint, backdrop, + backdropTileMode, nullptr, 0)); } SKWASM_EXPORT void canvas_save(SkCanvas* canvas) { diff --git a/lib/web_ui/skwasm/library_skwasm_multi_threaded.js b/lib/web_ui/skwasm/library_skwasm_multi_threaded.js new file mode 100644 index 0000000000000..237ccf38bed10 --- /dev/null +++ b/lib/web_ui/skwasm/library_skwasm_multi_threaded.js @@ -0,0 +1,57 @@ +// 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. + +// This file adds JavaScript APIs that are accessible to the C++ layer. +// See: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#implement-a-c-api-in-javascript + +mergeInto(LibraryManager.library, { + $skwasm_threading_setup__postset: 'skwasm_threading_setup();', + $skwasm_threading_setup: function() { + // This value represents the difference between the time origin of the main + // thread and whichever web worker this code is running on. This is so that + // when we report frame timings, that they are in the same time domain + // regardless of whether they are captured on the main thread or the web + // worker. + let timeOriginDelta = 0; + skwasm_registerMessageListener = function(threadId, listener) { + const eventListener = function({data}) { + const skwasmMessage = data.skwasmMessage; + if (!skwasmMessage) { + return; + } + if (skwasmMessage == 'syncTimeOrigin') { + timeOriginDelta = performance.timeOrigin - data.timeOrigin; + return; + } + listener(data); + }; + if (!threadId) { + addEventListener("message", eventListener); + } else { + PThread.pthreads[threadId].addEventListener("message", eventListener); + PThread.pthreads[threadId].postMessage({ + skwasmMessage: 'syncTimeOrigin', + timeOrigin: performance.timeOrigin, + }); + } + }; + skwasm_getCurrentTimestamp = function() { + return performance.now() + timeOriginDelta; + }; + skwasm_postMessage = function(message, transfers, threadId) { + if (threadId) { + PThread.pthreads[threadId].postMessage(message, transfers); + } else { + postMessage(message, transfers); + } + }; + }, + $skwasm_threading_setup__deps: ['$skwasm_registerMessageListener', '$skwasm_getCurrentTimestamp', '$skwasm_postMessage'], + $skwasm_registerMessageListener: function() {}, + $skwasm_registerMessageListener__deps: ['$skwasm_threading_setup'], + $skwasm_getCurrentTimestamp: function () {}, + $skwasm_getCurrentTimestamp__deps: ['$skwasm_threading_setup'], + $skwasm_postMessage: function () {}, + $skwasm_postMessage__deps: ['$skwasm_threading_setup'], +}); diff --git a/lib/web_ui/skwasm/library_skwasm_single_threaded.js b/lib/web_ui/skwasm/library_skwasm_single_threaded.js new file mode 100644 index 0000000000000..6b6221ad8850f --- /dev/null +++ b/lib/web_ui/skwasm/library_skwasm_single_threaded.js @@ -0,0 +1,31 @@ +// 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. + +// This file adds JavaScript APIs that are accessible to the C++ layer. +// See: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#implement-a-c-api-in-javascript + +mergeInto(LibraryManager.library, { + $skwasm_threading_setup__postset: 'skwasm_threading_setup();', + $skwasm_threading_setup: function() { + let messageListener; + skwasm_registerMessageListener = function(threadId, listener) { + messageListener = listener; + }; + skwasm_getCurrentTimestamp = function() { + return performance.now(); + }; + skwasm_postMessage = function(message, transfers, threadId) { + queueMicrotask(() => { + messageListener(message); + }) + }; + }, + $skwasm_threading_setup__deps: ['$skwasm_registerMessageListener', '$skwasm_getCurrentTimestamp', '$skwasm_postMessage'], + $skwasm_registerMessageListener: function() {}, + $skwasm_registerMessageListener__deps: ['$skwasm_threading_setup'], + $skwasm_getCurrentTimestamp: function () {}, + $skwasm_getCurrentTimestamp__deps: ['$skwasm_threading_setup'], + $skwasm_postMessage: function () {}, + $skwasm_postMessage__deps: ['$skwasm_threading_setup'], +}); diff --git a/lib/web_ui/skwasm/library_skwasm_support.js b/lib/web_ui/skwasm/library_skwasm_support.js index 61e8b47179f54..0a93e682dc191 100644 --- a/lib/web_ui/skwasm/library_skwasm_support.js +++ b/lib/web_ui/skwasm/library_skwasm_support.js @@ -11,45 +11,30 @@ mergeInto(LibraryManager.library, { const handleToCanvasMap = new Map(); const associatedObjectsMap = new Map(); - // This value represents the difference between the time origin of the main - // thread and whichever web worker this code is running on. This is so that - // when we report frame timings, that they are in the same time domain - // regardless of whether they are captured on the main thread or the web - // worker. - let timeOriginDelta; _skwasm_setAssociatedObjectOnThread = function(threadId, pointer, object) { - PThread.pthreads[threadId].postMessage({ + skwasm_postMessage({ skwasmMessage: 'setAssociatedObject', pointer, object, - }, [object]); + }, [object], threadId); }; _skwasm_getAssociatedObject = function(pointer) { return associatedObjectsMap.get(pointer); }; - _skwasm_syncTimeOriginForThread = function(threadId) { - PThread.pthreads[threadId].postMessage({ - skwasmMessage: 'syncTimeOrigin', - timeOrigin: performance.timeOrigin, - }); - } - _skwasm_registerMessageListener = function(threadId) { - const eventListener = function({data}) { + _skwasm_connectThread = function(threadId) { + const eventListener = function(data) { const skwasmMessage = data.skwasmMessage; if (!skwasmMessage) { return; } switch (skwasmMessage) { - case 'syncTimeOrigin': - timeOriginDelta = performance.timeOrigin - data.timeOrigin; - return; case 'renderPictures': _surface_renderPicturesOnWorker( data.surface, data.pictures, data.pictureCount, data.callbackId, - performance.now() + timeOriginDelta); + skwasm_getCurrentTimestamp()); return; case 'onRenderComplete': _surface_onRenderComplete( @@ -94,20 +79,16 @@ mergeInto(LibraryManager.library, { console.warn(`unrecognized skwasm message: ${skwasmMessage}`); } }; - if (!threadId) { - addEventListener("message", eventListener); - } else { - PThread.pthreads[threadId].addEventListener("message", eventListener); - } + skwasm_registerMessageListener(threadId, eventListener); }; _skwasm_dispatchRenderPictures = function(threadId, surfaceHandle, pictures, pictureCount, callbackId) { - PThread.pthreads[threadId].postMessage({ + skwasm_postMessage({ skwasmMessage: 'renderPictures', surface: surfaceHandle, pictures, pictureCount, callbackId, - }); + }, [], threadId); }; _skwasm_createOffscreenCanvas = function(width, height) { const canvas = new OffscreenCanvas(width, height); @@ -140,8 +121,8 @@ mergeInto(LibraryManager.library, { }; _skwasm_resolveAndPostImages = async function(surfaceHandle, imagePromises, rasterStart, callbackId) { const imageBitmaps = imagePromises ? await Promise.all(imagePromises) : []; - const rasterEnd = performance.now() + timeOriginDelta; - postMessage({ + const rasterEnd = skwasm_getCurrentTimestamp(); + skwasm_postMessage({ skwasmMessage: 'onRenderComplete', surface: surfaceHandle, callbackId, @@ -166,28 +147,28 @@ mergeInto(LibraryManager.library, { return textureId; }; _skwasm_disposeAssociatedObjectOnThread = function(threadId, pointer) { - PThread.pthreads[threadId].postMessage({ + skwasm_postMessage({ skwasmMessage: 'disposeAssociatedObject', pointer, - }); + }, [], threadId); }; _skwasm_dispatchDisposeSurface = function(threadId, surface) { - PThread.pthreads[threadId].postMessage({ + skwasm_postMessage({ skwasmMessage: 'disposeSurface', surface, - }); + }, [], threadId); } _skwasm_dispatchRasterizeImage = function(threadId, surface, image, format, callbackId) { - PThread.pthreads[threadId].postMessage({ + skwasm_postMessage({ skwasmMessage: 'rasterizeImage', surface, image, format, callbackId, - }); + }, [], threadId); } _skwasm_postRasterizeResult = function(surface, data, callbackId) { - postMessage({ + skwasm_postMessage({ skwasmMessage: 'onRasterizeComplete', surface, data, @@ -195,16 +176,15 @@ mergeInto(LibraryManager.library, { }); } }, + $skwasm_support_setup__deps: [ '$skwasm_threading_setup'], skwasm_setAssociatedObjectOnThread: function () {}, skwasm_setAssociatedObjectOnThread__deps: ['$skwasm_support_setup'], skwasm_getAssociatedObject: function () {}, skwasm_getAssociatedObject__deps: ['$skwasm_support_setup'], skwasm_disposeAssociatedObjectOnThread: function () {}, skwasm_disposeAssociatedObjectOnThread__deps: ['$skwasm_support_setup'], - skwasm_syncTimeOriginForThread: function() {}, - skwasm_syncTimeOriginForThread__deps: ['$skwasm_support_setup'], - skwasm_registerMessageListener: function() {}, - skwasm_registerMessageListener__deps: ['$skwasm_support_setup'], + skwasm_connectThread: function() {}, + skwasm_connectThread__deps: ['$skwasm_support_setup'], skwasm_dispatchRenderPictures: function() {}, skwasm_dispatchRenderPictures__deps: ['$skwasm_support_setup'], skwasm_createOffscreenCanvas: function () {}, diff --git a/lib/web_ui/skwasm/skwasm_support.h b/lib/web_ui/skwasm/skwasm_support.h index a77bd7726aa5f..157611ec5d428 100644 --- a/lib/web_ui/skwasm/skwasm_support.h +++ b/lib/web_ui/skwasm/skwasm_support.h @@ -19,8 +19,7 @@ extern void skwasm_setAssociatedObjectOnThread(unsigned long threadId, extern SkwasmObject skwasm_getAssociatedObject(void* pointer); extern void skwasm_disposeAssociatedObjectOnThread(unsigned long threadId, void* pointer); -extern void skwasm_registerMessageListener(pthread_t threadId); -extern void skwasm_syncTimeOriginForThread(pthread_t threadId); +extern void skwasm_connectThread(pthread_t threadId); extern void skwasm_dispatchRenderPictures(unsigned long threadId, Skwasm::Surface* surface, sk_sp* pictures, diff --git a/lib/web_ui/skwasm/surface.cpp b/lib/web_ui/skwasm/surface.cpp index 09958b60c8db3..2f1dcfd1073d6 100644 --- a/lib/web_ui/skwasm/surface.cpp +++ b/lib/web_ui/skwasm/surface.cpp @@ -14,27 +14,6 @@ using namespace Skwasm; -Surface::Surface() { - assert(emscripten_is_main_browser_thread()); - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - - pthread_create( - &_thread, &attr, - [](void* context) -> void* { - static_cast(context)->_runWorker(); - return nullptr; - }, - this); - // Listen to messages from the worker - skwasm_registerMessageListener(_thread); - - // Synchronize the time origin for the worker thread - skwasm_syncTimeOriginForThread(_thread); -} - // Worker thread only void Surface::dispose() { delete this; @@ -88,7 +67,7 @@ void Surface::_runWorker() { // Worker thread only void Surface::_init() { // Listen to messages from the main thread - skwasm_registerMessageListener(0); + skwasm_connectThread(0); _glContext = skwasm_createOffscreenCanvas(256, 256); if (!_glContext) { printf("Failed to create context!\n"); diff --git a/lib/web_ui/skwasm/surface_mt.cpp b/lib/web_ui/skwasm/surface_mt.cpp new file mode 100644 index 0000000000000..cd02dc6d9bb36 --- /dev/null +++ b/lib/web_ui/skwasm/surface_mt.cpp @@ -0,0 +1,31 @@ +// 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. + +#include "surface.h" + +#include "skwasm_support.h" + +using namespace Skwasm; + +Surface::Surface() { + assert(emscripten_is_main_browser_thread()); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + pthread_create( + &_thread, &attr, + [](void* context) -> void* { + static_cast(context)->_runWorker(); + return nullptr; + }, + this); + // Listen to messages from the worker + skwasm_connectThread(_thread); +} + +SKWASM_EXPORT bool skwasm_isMultiThreaded() { + return true; +} diff --git a/lib/web_ui/skwasm/surface_st.cpp b/lib/web_ui/skwasm/surface_st.cpp new file mode 100644 index 0000000000000..86c7b6b22eed5 --- /dev/null +++ b/lib/web_ui/skwasm/surface_st.cpp @@ -0,0 +1,15 @@ +// 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. + +#include "surface.h" + +using namespace Skwasm; + +Surface::Surface() : _thread(0) { + _init(); +} + +SKWASM_EXPORT bool skwasm_isMultiThreaded() { + return false; +} diff --git a/lib/web_ui/test/canvaskit/canvas_golden_test.dart b/lib/web_ui/test/canvaskit/canvas_golden_test.dart index 70570582e915e..66f12821ab367 100644 --- a/lib/web_ui/test/canvaskit/canvas_golden_test.dart +++ b/lib/web_ui/test/canvaskit/canvas_golden_test.dart @@ -34,7 +34,7 @@ void testMain() { expect(canvas.runtimeType, CkCanvas); drawTestPicture(canvas); await matchPictureGolden( - 'canvaskit_picture.png', + 'canvaskit_weakref_picture.png', recorder.endRecording(), region: kDefaultRegion, ); @@ -450,7 +450,7 @@ void drawTestPicture(CkCanvas canvas) { canvas.drawCircle(const ui.Offset(30, 30), 10, CkPaint()); { canvas.saveLayerWithFilter( - kDefaultRegion, ui.ImageFilter.blur(sigmaX: 5, sigmaY: 10)); + kDefaultRegion, ui.ImageFilter.blur(sigmaX: 5, sigmaY: 10, tileMode: ui.TileMode.clamp)); canvas.drawCircle(const ui.Offset(10, 10), 10, CkPaint()); canvas.drawCircle(const ui.Offset(50, 50), 10, CkPaint()); canvas.restore(); diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index f574f9c2a6880..b70007019b21b 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -1126,11 +1126,12 @@ void _canvasTests() { toSkRect(const ui.Rect.fromLTRB(0, 0, 100, 100)), null, null, + canvasKit.TileMode.Clamp, ); }); test('saveLayer without bounds', () { - canvas.saveLayer(SkPaint(), null, null, null); + canvas.saveLayer(SkPaint(), null, null, null, canvasKit.TileMode.Clamp); }); test('saveLayer with filter', () { @@ -1139,6 +1140,7 @@ void _canvasTests() { toSkRect(const ui.Rect.fromLTRB(0, 0, 100, 100)), canvasKit.ImageFilter.MakeBlur(1, 2, canvasKit.TileMode.Repeat, null), 0, + canvasKit.TileMode.Repeat, ); }); diff --git a/lib/web_ui/test/canvaskit/layer_test.dart b/lib/web_ui/test/canvaskit/layer_test.dart index 180599dc7c79c..6b8ecdbe99372 100644 --- a/lib/web_ui/test/canvaskit/layer_test.dart +++ b/lib/web_ui/test/canvaskit/layer_test.dart @@ -69,7 +69,7 @@ void testMain() { final CkCanvas canvas = recorder.beginRecording(kDefaultRegion); canvas.drawImage(testImage as CkImage, ui.Offset.zero, CkPaint()); await matchPictureGolden( - 'canvaskit_picture.png', + 'canvaskit_null_viewembedder_with_platformview.png', recorder.endRecording(), region: kDefaultRegion, ); diff --git a/lib/web_ui/test/canvaskit/surface_test.dart b/lib/web_ui/test/canvaskit/surface_test.dart index dacb9b8952bc2..a1242c7a4e6d3 100644 --- a/lib/web_ui/test/canvaskit/surface_test.dart +++ b/lib/web_ui/test/canvaskit/surface_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:js_interop'; import 'dart:js_util' as js_util; import 'package:test/bootstrap/browser.dart'; @@ -292,6 +293,29 @@ void testMain() { }, skip: !Surface.offscreenCanvasSupported, ); + + test('can recover from MakeSWCanvasSurface failure', () async { + debugOverrideJsConfiguration({ + 'canvasKitForceCpuOnly': true, + }.jsify() as JsFlutterConfiguration?); + addTearDown(() => debugOverrideJsConfiguration(null)); + + final Surface surface = Surface(); + surface.debugThrowOnSoftwareSurfaceCreation = true; + expect( + () => surface.createOrUpdateSurface(const BitmapSize(12, 34)), + throwsA(isA()), + ); + await Future.delayed(Duration.zero); + + expect(surface.debugForceNewContext, isFalse); + + surface.debugThrowOnSoftwareSurfaceCreation = false; + final ckSurface = surface.createOrUpdateSurface(const BitmapSize(12, 34)); + + expect(ckSurface.surface.width(), 12); + expect(ckSurface.surface.height(), 34); + }); }); } diff --git a/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart b/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart index fe0f9c5479ea3..7e7754836c1e8 100644 --- a/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart +++ b/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart @@ -68,28 +68,6 @@ void testMain() { expect(view2.dom.rootElement.getAttribute('tabindex'), '0'); }); - test('never marks the views as focusable with semantincs enabled', () async { - EngineSemantics.instance.semanticsEnabled = true; - - final EngineFlutterView view1 = createAndRegisterView(dispatcher); - final EngineFlutterView view2 = createAndRegisterView(dispatcher); - - expect(view1.dom.rootElement.getAttribute('tabindex'), isNull); - expect(view2.dom.rootElement.getAttribute('tabindex'), isNull); - - view1.dom.rootElement.focusWithoutScroll(); - expect(view1.dom.rootElement.getAttribute('tabindex'), isNull); - expect(view2.dom.rootElement.getAttribute('tabindex'), isNull); - - view2.dom.rootElement.focusWithoutScroll(); - expect(view1.dom.rootElement.getAttribute('tabindex'), isNull); - expect(view2.dom.rootElement.getAttribute('tabindex'), isNull); - - view2.dom.rootElement.blur(); - expect(view1.dom.rootElement.getAttribute('tabindex'), isNull); - expect(view2.dom.rootElement.getAttribute('tabindex'), isNull); - }); - test('fires a focus event - a view was focused', () async { final EngineFlutterView view = createAndRegisterView(dispatcher); diff --git a/lib/web_ui/test/engine/pointer_binding/event_position_helper_test.dart b/lib/web_ui/test/engine/pointer_binding/event_position_helper_test.dart index eca204cd6e613..14f41dccbbf9a 100644 --- a/lib/web_ui/test/engine/pointer_binding/event_position_helper_test.dart +++ b/lib/web_ui/test/engine/pointer_binding/event_position_helper_test.dart @@ -32,6 +32,7 @@ void doTests() { group('computeEventOffsetToTarget', () { setUp(() { view = EngineFlutterView(EnginePlatformDispatcher.instance, domDocument.body!); + EnginePlatformDispatcher.instance.viewManager.registerView(view); rootElement = view.dom.rootElement; eventSource = createDomElement('div-event-source'); rootElement.append(eventSource); @@ -58,6 +59,7 @@ void doTests() { }); tearDown(() { + EnginePlatformDispatcher.instance.viewManager.unregisterView(view.viewId); view.dispose(); }); @@ -101,6 +103,36 @@ void doTests() { expect(offset.dy, 110); }); + test('eventTarget takes precedence', () async { + final input = view.dom.textEditingHost.appendChild(createDomElement('input')); + + textEditing.strategy.enable( + InputConfiguration(viewId: view.viewId), + onChange: (_, __) {}, + onAction: (_) {}, + ); + + addTearDown(() { + textEditing.strategy.disable(); + }); + + final moveEvent = createDomPointerEvent('pointermove', { + 'bubbles': true, + 'clientX': 10, + 'clientY': 20, + }); + + expect( + () => computeEventOffsetToTarget(moveEvent, view), + throwsA(anything), + ); + + expect( + () => computeEventOffsetToTarget(moveEvent, view, eventTarget: input), + returnsNormally, + ); + }); + test('Event dispatched by TalkBack gets a computed offset', () async { // Fill this in to test _computeOffsetForTalkbackEvent }, skip: 'To be implemented!'); diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index f765c492f7226..9757758ffcb8f 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -2358,6 +2358,89 @@ void testMain() { }, ); + // STYLUS + + test( + 'handles stylus touches', + () { + // Repeated stylus touches use different pointerIds. + + final _PointerEventContext context = _PointerEventContext(); + + final List packets = []; + ui.PlatformDispatcher.instance.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + rootElement.dispatchEvent(context.stylusTouchDown( + pointerId: 100, + buttons: 1, + clientX: 5.0, + clientY: 100.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, isTrue); + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].synthesized, isFalse); + expect(packets[0].data[1].buttons, equals(1)); + expect(packets[0].data[1].physicalX, equals(5.0 * dpi)); + expect(packets[0].data[1].physicalY, equals(100.0 * dpi)); + packets.clear(); + + rootElement.dispatchEvent(context.stylusTouchUp( + pointerId: 100, + buttons: 0, + clientX: 5.0, + clientY: 100.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, isFalse); + expect(packets[0].data[0].buttons, equals(0)); + expect(packets[0].data[0].physicalX, equals(5.0 * dpi)); + expect(packets[0].data[0].physicalY, equals(100.0 * dpi)); + packets.clear(); + + rootElement.dispatchEvent(context.stylusTouchDown( + pointerId: 101, + buttons: 1, + clientX: 5.0, + clientY: 150.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[0].data[0].synthesized, isTrue); + expect(packets[0].data[0].buttons, equals(0)); + expect(packets[0].data[0].physicalX, equals(5.0 * dpi)); + expect(packets[0].data[0].physicalY, equals(150.0 * dpi)); + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].synthesized, isFalse); + expect(packets[0].data[1].buttons, equals(1)); + expect(packets[0].data[1].physicalX, equals(5.0 * dpi)); + expect(packets[0].data[1].physicalY, equals(150.0 * dpi)); + packets.clear(); + + rootElement.dispatchEvent(context.stylusTouchUp( + pointerId: 101, + buttons: 0, + clientX: 5.0, + clientY: 150.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, isFalse); + expect(packets[0].data[0].buttons, equals(0)); + expect(packets[0].data[0].physicalX, equals(5.0 * dpi)); + expect(packets[0].data[0].physicalY, equals(150.0 * dpi)); + packets.clear(); + }, + ); + // MULTIPOINTER ADAPTERS test( @@ -2526,6 +2609,88 @@ void testMain() { }, ); + test('ignores pointerId on coalesced events', () { + final _MultiPointerEventMixin context = _PointerEventContext(); + final List packets = []; + List data; + ui.PlatformDispatcher.instance.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + context.multiTouchDown(const <_TouchDetails>[ + _TouchDetails(pointer: 52, clientX: 100, clientY: 101), + ]).forEach(rootElement.dispatchEvent); + expect(packets.length, 1); + + data = packets.single.data; + expect(data, hasLength(2)); + expect(data[0].change, equals(ui.PointerChange.add)); + expect(data[0].synthesized, isTrue); + expect(data[0].device, equals(52)); + expect(data[0].physicalX, equals(100 * dpi)); + expect(data[0].physicalY, equals(101 * dpi)); + + expect(data[1].change, equals(ui.PointerChange.down)); + expect(data[1].device, equals(52)); + expect(data[1].buttons, equals(1)); + expect(data[1].physicalX, equals(100 * dpi)); + expect(data[1].physicalY, equals(101 * dpi)); + expect(data[1].physicalDeltaX, equals(0)); + expect(data[1].physicalDeltaY, equals(0)); + packets.clear(); + + // Pointer move with coaleasced events + context.multiTouchMove(const <_TouchDetails>[ + _TouchDetails(pointer: 52, coalescedEvents: <_CoalescedTouchDetails>[ + _CoalescedTouchDetails(pointer: 0, clientX: 301, clientY: 302), + _CoalescedTouchDetails(pointer: 0, clientX: 401, clientY: 402), + ]), + ]).forEach(rootElement.dispatchEvent); + expect(packets.length, 1); + + data = packets.single.data; + expect(data, hasLength(2)); + expect(data[0].change, equals(ui.PointerChange.move)); + expect(data[0].device, equals(52)); + expect(data[0].buttons, equals(1)); + expect(data[0].physicalX, equals(301 * dpi)); + expect(data[0].physicalY, equals(302 * dpi)); + expect(data[0].physicalDeltaX, equals(201 * dpi)); + expect(data[0].physicalDeltaY, equals(201 * dpi)); + + expect(data[1].change, equals(ui.PointerChange.move)); + expect(data[1].device, equals(52)); + expect(data[1].buttons, equals(1)); + expect(data[1].physicalX, equals(401 * dpi)); + expect(data[1].physicalY, equals(402 * dpi)); + expect(data[1].physicalDeltaX, equals(100 * dpi)); + expect(data[1].physicalDeltaY, equals(100 * dpi)); + packets.clear(); + + // Pointer up + context.multiTouchUp(const <_TouchDetails>[ + _TouchDetails(pointer: 52, clientX: 401, clientY: 402), + ]).forEach(rootElement.dispatchEvent); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].device, equals(52)); + expect(packets[0].data[0].buttons, equals(0)); + expect(packets[0].data[0].physicalX, equals(401 * dpi)); + expect(packets[0].data[0].physicalY, equals(402 * dpi)); + expect(packets[0].data[0].physicalDeltaX, equals(0)); + expect(packets[0].data[0].physicalDeltaY, equals(0)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.remove)); + expect(packets[0].data[1].device, equals(52)); + expect(packets[0].data[1].buttons, equals(0)); + expect(packets[0].data[1].physicalX, equals(401 * dpi)); + expect(packets[0].data[1].physicalY, equals(402 * dpi)); + expect(packets[0].data[1].physicalDeltaX, equals(0)); + expect(packets[0].data[1].physicalDeltaY, equals(0)); + packets.clear(); + }); + test( 'correctly parses cancel event', () { @@ -2938,7 +3103,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { } ); - PointerBinding.clickDebouncer.onClick(click, 42, true); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, true); expect(PointerBinding.clickDebouncer.isDebouncing, false); expect(pointerPackets, isEmpty); expect(semanticsActions, [ @@ -2963,7 +3128,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { } ); - PointerBinding.clickDebouncer.onClick(click, 42, true); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, true); expect(pointerPackets, isEmpty); expect(semanticsActions, [ (type: ui.SemanticsAction.tap, nodeId: 42) @@ -2987,7 +3152,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { } ); - PointerBinding.clickDebouncer.onClick(click, 42, false); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, false); expect( reason: 'When tappable declares that it is not listening to click events ' 'the debouncer flushes the pointer events to the framework and ' @@ -3046,7 +3211,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { 'clientY': testElement.getBoundingClientRect().y, } ); - PointerBinding.clickDebouncer.onClick(click, 42, true); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, true); expect( reason: 'Because the DOM click event was deduped.', @@ -3107,7 +3272,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { 'clientY': testElement.getBoundingClientRect().y, } ); - PointerBinding.clickDebouncer.onClick(click, 42, true); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, true); expect( reason: 'Because the DOM click event was deduped.', @@ -3162,7 +3327,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { 'clientY': testElement.getBoundingClientRect().y, } ); - PointerBinding.clickDebouncer.onClick(click, 42, true); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, true); expect( reason: 'The DOM click should still be sent to the framework because it ' @@ -3336,7 +3501,26 @@ mixin _ButtonedEventMixin on _BasicEventContext { } class _TouchDetails { - const _TouchDetails({this.pointer, this.clientX, this.clientY}); + const _TouchDetails({ + this.pointer, + this.clientX, + this.clientY, + this.coalescedEvents, + }); + + final int? pointer; + final double? clientX; + final double? clientY; + + final List<_CoalescedTouchDetails>? coalescedEvents; +} + +class _CoalescedTouchDetails { + const _CoalescedTouchDetails({ + this.pointer, + this.clientX, + this.clientY, + }); final int? pointer; final double? clientX; @@ -3395,6 +3579,10 @@ class _PointerEventContext extends _BasicEventContext @override List multiTouchDown(List<_TouchDetails> touches) { + assert( + touches.every((_TouchDetails details) => details.coalescedEvents == null), + 'Coalesced events are not allowed for pointerdown events.', + ); return touches .map((_TouchDetails details) => _downWithFullDetails( pointer: details.pointer, @@ -3458,6 +3646,7 @@ class _PointerEventContext extends _BasicEventContext clientX: details.clientX, clientY: details.clientY, pointerType: 'touch', + coalescedEvents: details.coalescedEvents, )) .toList(); } @@ -3487,8 +3676,9 @@ class _PointerEventContext extends _BasicEventContext int? buttons, int? pointer, String? pointerType, + List<_CoalescedTouchDetails>? coalescedEvents, }) { - return createDomPointerEvent('pointermove', { + final event = createDomPointerEvent('pointermove', { 'bubbles': true, 'pointerId': pointer, 'button': button, @@ -3497,6 +3687,26 @@ class _PointerEventContext extends _BasicEventContext 'clientY': clientY, 'pointerType': pointerType, }); + + if (coalescedEvents != null) { + // There's no JS API for setting coalesced events, so we need to + // monkey-patch the `getCoalescedEvents` method to return what we want. + final coalescedEventJs = coalescedEvents + .map((_CoalescedTouchDetails details) => _moveWithFullDetails( + pointer: details.pointer, + button: button, + buttons: buttons, + clientX: details.clientX, + clientY: details.clientY, + pointerType: 'touch', + )).toJSAnyDeep; + + js_util.setProperty(event, 'getCoalescedEvents', js_util.allowInterop(() { + return coalescedEventJs; + })); + } + + return event; } @override @@ -3537,6 +3747,10 @@ class _PointerEventContext extends _BasicEventContext @override List multiTouchUp(List<_TouchDetails> touches) { + assert( + touches.every((_TouchDetails details) => details.coalescedEvents == null), + 'Coalesced events are not allowed for pointerup events.', + ); return touches .map((_TouchDetails details) => _upWithFullDetails( pointer: details.pointer, @@ -3587,6 +3801,10 @@ class _PointerEventContext extends _BasicEventContext @override List multiTouchCancel(List<_TouchDetails> touches) { + assert( + touches.every((_TouchDetails details) => details.coalescedEvents == null), + 'Coalesced events are not allowed for pointercancel events.', + ); return touches .map((_TouchDetails details) => createDomPointerEvent('pointercancel', { @@ -3600,6 +3818,40 @@ class _PointerEventContext extends _BasicEventContext })) .toList(); } + + // STYLUSES + + DomEvent stylusTouchDown({ + double? clientX, + double? clientY, + int? buttons, + int? pointerId = 1000, + }) { + return _downWithFullDetails( + pointer: pointerId, + buttons: buttons, + button: 0, + clientX: clientX, + clientY: clientY, + pointerType: 'pen', + ); + } + + DomEvent stylusTouchUp({ + double? clientX, + double? clientY, + int? buttons, + int? pointerId = 1000, + }) { + return _upWithFullDetails( + pointer: pointerId, + buttons: buttons, + button: 0, + clientX: clientX, + clientY: clientY, + pointerType: 'pen', + ); + } } class MockPointerSupportDetector implements PointerSupportDetector { diff --git a/lib/web_ui/test/engine/scene_builder_test.dart b/lib/web_ui/test/engine/scene_builder_test.dart index d8826e8da6033..3d39a22f71342 100644 --- a/lib/web_ui/test/engine/scene_builder_test.dart +++ b/lib/web_ui/test/engine/scene_builder_test.dart @@ -16,7 +16,7 @@ void main() { void testMain() { setUpAll(() { - LayerBuilder.debugRecorderFactory = (ui.Rect rect) { + LayerSliceBuilder.debugRecorderFactory = (ui.Rect rect) { final StubSceneCanvas canvas = StubSceneCanvas(); final StubPictureRecorder recorder = StubPictureRecorder(canvas); return (recorder, canvas); @@ -24,7 +24,7 @@ void testMain() { }); tearDownAll(() { - LayerBuilder.debugRecorderFactory = null; + LayerSliceBuilder.debugRecorderFactory = null; }); group('EngineSceneBuilder', () { @@ -35,23 +35,23 @@ void testMain() { sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect)); final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; + final List slices = scene.rootLayer.slices; expect(slices.length, 1); - expect(slices[0], pictureSliceWithRect(pictureRect)); + expect(slices[0], layerSlice(withPictureRect: pictureRect)); }); test('two pictures', () { final EngineSceneBuilder sceneBuilder = EngineSceneBuilder(); const ui.Rect pictureRect1 = ui.Rect.fromLTRB(100, 100, 200, 200); - const ui.Rect pictureRect2 = ui.Rect.fromLTRB(300, 400, 400, 400); + const ui.Rect pictureRect2 = ui.Rect.fromLTRB(300, 300, 400, 400); sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect1)); sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect2)); final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; + final List slices = scene.rootLayer.slices; expect(slices.length, 1); - expect(slices[0], pictureSliceWithRect(const ui.Rect.fromLTRB(100, 100, 400, 400))); + expect(slices[0], layerSlice(withPictureRect: const ui.Rect.fromLTRB(100, 100, 400, 400))); }); test('picture + platform view (overlapping)', () { @@ -68,10 +68,11 @@ void testMain() { ); final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - expect(slices.length, 2); - expect(slices[0], pictureSliceWithRect(pictureRect)); - expect(slices[1], platformViewSliceWithViews([ + final List slices = scene.rootLayer.slices; + expect(slices.length, 1); + expect(slices[0], layerSlice( + withPictureRect: pictureRect, + withPlatformViews: [ PlatformView(1, platformViewRect, const PlatformViewStyling()) ])); }); @@ -90,12 +91,12 @@ void testMain() { sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect)); final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; + final List slices = scene.rootLayer.slices; expect(slices.length, 2); - expect(slices[0], platformViewSliceWithViews([ + expect(slices[0], layerSlice(withPlatformViews: [ PlatformView(1, platformViewRect, const PlatformViewStyling()) ])); - expect(slices[1], pictureSliceWithRect(pictureRect)); + expect(slices[1], layerSlice(withPictureRect: pictureRect)); }); test('platform view sandwich (overlapping)', () { @@ -114,13 +115,14 @@ void testMain() { sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect2)); final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - expect(slices.length, 3); - expect(slices[0], pictureSliceWithRect(pictureRect1)); - expect(slices[1], platformViewSliceWithViews([ + final List slices = scene.rootLayer.slices; + expect(slices.length, 2); + expect(slices[0], layerSlice( + withPictureRect: pictureRect1, + withPlatformViews: [ PlatformView(1, platformViewRect, const PlatformViewStyling()) ])); - expect(slices[2], pictureSliceWithRect(pictureRect2)); + expect(slices[1], layerSlice(withPictureRect: pictureRect2)); }); test('platform view sandwich (non-overlapping)', () { @@ -139,14 +141,15 @@ void testMain() { sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect2)); final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; + final List slices = scene.rootLayer.slices; // The top picture does not overlap with the platform view, so it should // be grouped into the slice below it to reduce the number of canvases we // need. - expect(slices.length, 2); - expect(slices[0], pictureSliceWithRect(const ui.Rect.fromLTRB(50, 50, 200, 200))); - expect(slices[1], platformViewSliceWithViews([ + expect(slices.length, 1); + expect(slices[0], layerSlice( + withPictureRect: const ui.Rect.fromLTRB(50, 50, 200, 200), + withPlatformViews: [ PlatformView(1, platformViewRect, const PlatformViewStyling()) ])); }); @@ -169,34 +172,99 @@ void testMain() { sceneBuilder.addPicture(ui.Offset.zero, StubPicture(const ui.Rect.fromLTRB(0, 0, 100, 100))); final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - expect(slices.length, 3); - expect(slices[0], pictureSliceWithRect(pictureRect1)); - expect(slices[1], platformViewSliceWithViews([ + final List slices = scene.rootLayer.slices; + expect(slices.length, 2); + expect(slices[0], layerSlice( + withPictureRect: pictureRect1, + withPlatformViews: [ PlatformView(1, platformViewRect, const PlatformViewStyling(position: PlatformViewPosition.offset(ui.Offset(150, 150)))) ])); - expect(slices[2], pictureSliceWithRect(const ui.Rect.fromLTRB(200, 200, 300, 300))); + expect(slices[1], layerSlice(withPictureRect: const ui.Rect.fromLTRB(200, 200, 300, 300))); + }); + + test('grid view test', () { + // This test case covers a grid of elements, where each element is a platform + // view that has flutter content underneath it and on top of it. + // See a detailed explanation of this use-case in the following flutter issue: + // https://github.com/flutter/flutter/issues/149863 + final EngineSceneBuilder sceneBuilder = EngineSceneBuilder(); + + const double padding = 10; + const double tileSize = 50; + final List expectedPlatformViews = []; + for (int x = 0; x < 10; x++) { + for (int y = 0; y < 10; y++) { + final ui.Offset offset = ui.Offset( + padding + (tileSize + padding) * x, + padding + (tileSize + padding) * y, + ); + sceneBuilder.pushOffset(offset.dx, offset.dy); + sceneBuilder.addPicture( + ui.Offset.zero, + StubPicture(const ui.Rect.fromLTWH(0, 0, tileSize, tileSize)) + ); + sceneBuilder.addPlatformView( + 1, + offset: const ui.Offset(5, 5), + width: tileSize - 10, + height: tileSize - 10, + ); + sceneBuilder.addPicture( + const ui.Offset(10, 10), + StubPicture(const ui.Rect.fromLTWH(0, 0, tileSize - 20, tileSize - 20)), + ); + sceneBuilder.pop(); + expectedPlatformViews.add(PlatformView( + 1, + const ui.Rect.fromLTRB(5.0, 5.0, tileSize - 5.0, tileSize - 5.0), + PlatformViewStyling(position: PlatformViewPosition.offset(offset)) + )); + } + } + + final EngineScene scene = sceneBuilder.build() as EngineScene; + final List slices = scene.rootLayer.slices; + + // It is important that the optimizations of the scene builder result in + // there only being two scene slices. + expect(slices.length, 2); + expect(slices[0], layerSlice( + withPictureRect: const ui.Rect.fromLTRB( + padding, + padding, + 10 * (padding + tileSize), + 10 * (padding + tileSize) + ), + withPlatformViews: expectedPlatformViews, + )); + expect(slices[1], layerSlice(withPictureRect: const ui.Rect.fromLTRB( + padding + 10, + padding + 10, + 10 * (padding + tileSize) - 10, + 10 * (padding + tileSize) - 10, + ))); }); }); } -PictureSliceMatcher pictureSliceWithRect(ui.Rect rect) => PictureSliceMatcher(rect); -PlatformViewSliceMatcher platformViewSliceWithViews(List views) - => PlatformViewSliceMatcher(views); +LayerSliceMatcher layerSlice({ + ui.Rect withPictureRect = ui.Rect.zero, + List withPlatformViews = const [], +}) => LayerSliceMatcher(withPictureRect, withPlatformViews); +class LayerSliceMatcher extends Matcher { + LayerSliceMatcher(this.expectedPictureRect, this.expectedPlatformViews); -class PictureSliceMatcher extends Matcher { - PictureSliceMatcher(this.expectedRect); - - final ui.Rect expectedRect; + final ui.Rect expectedPictureRect; + final List expectedPlatformViews; @override Description describe(Description description) { - return description.add(''); + return description.add(''); } @override bool matches(dynamic item, Map matchState) { - if (item is! PictureSlice) { + if (item is! LayerSlice) { return false; } final ScenePicture picture = item.picture; @@ -204,50 +272,28 @@ class PictureSliceMatcher extends Matcher { return false; } - if (picture.cullRect != expectedRect) { + if (picture.cullRect != expectedPictureRect) { return false; } - return true; - } -} - -class PlatformViewSliceMatcher extends Matcher { - PlatformViewSliceMatcher(this.expectedPlatformViews); - - final List expectedPlatformViews; - - @override - Description describe(Description description) { - return description.add(''); - } - - @override - bool matches(dynamic item, Map matchState) { - if (item is! PlatformViewSlice) { + if (item.platformViews.length != expectedPlatformViews.length) { return false; } - if (item.views.length != expectedPlatformViews.length) { - return false; - } - - for (int i = 0; i < item.views.length; i++) { + for (int i = 0; i < item.platformViews.length; i++) { final PlatformView expectedView = expectedPlatformViews[i]; - final PlatformView actualView = item.views[i]; + final PlatformView actualView = item.platformViews[i]; if (expectedView.viewId != actualView.viewId) { - print('viewID mismatch'); return false; } if (expectedView.bounds != actualView.bounds) { - print('bounds mismatch'); return false; } if (expectedView.styling != actualView.styling) { - print('styling mismatch'); return false; } } + return true; } } diff --git a/lib/web_ui/test/engine/scene_builder_utils.dart b/lib/web_ui/test/engine/scene_builder_utils.dart index 033177c8d730b..ec014e70548ff 100644 --- a/lib/web_ui/test/engine/scene_builder_utils.dart +++ b/lib/web_ui/test/engine/scene_builder_utils.dart @@ -36,8 +36,12 @@ class StubPicture implements ScenePicture { class StubCompositePicture extends StubPicture { StubCompositePicture(this.children) : super( children.fold(null, (ui.Rect? previousValue, StubPicture child) { + final ui.Rect childRect = child.cullRect; + if (childRect.isEmpty) { + return previousValue; + } return previousValue?.expandToInclude(child.cullRect) ?? child.cullRect; - })! + }) ?? ui.Rect.zero ); final List children; diff --git a/lib/web_ui/test/engine/scene_view_test.dart b/lib/web_ui/test/engine/scene_view_test.dart index 80093bb0313f9..76e3b250d3dc3 100644 --- a/lib/web_ui/test/engine/scene_view_test.dart +++ b/lib/web_ui/test/engine/scene_view_test.dart @@ -54,6 +54,10 @@ class StubPictureRenderer implements PictureRenderer { } class StubFlutterView implements EngineFlutterView { + // Overridden in some tests + @override + DomManager dom = StubDomManager(); + @override double get devicePixelRatio => throw UnimplementedError(); @@ -128,9 +132,6 @@ class StubFlutterView implements EngineFlutterView { throw UnimplementedError(); } - @override - DomManager get dom => throw UnimplementedError(); - @override EmbeddingStrategy get embeddingStrategy => throw UnimplementedError(); @@ -172,7 +173,7 @@ void testMain() { 120, )); final EngineRootLayer rootLayer = EngineRootLayer(); - rootLayer.slices.add(PictureSlice(picture)); + rootLayer.slices.add(LayerSlice(picture, [])); final EngineScene scene = EngineScene(rootLayer); await sceneView.renderScene(scene, null); @@ -205,7 +206,7 @@ void testMain() { const ui.Rect.fromLTWH(50, 80, 100, 120), const PlatformViewStyling()); final EngineRootLayer rootLayer = EngineRootLayer(); - rootLayer.slices.add(PlatformViewSlice([platformView], null)); + rootLayer.slices.add(LayerSlice(StubPicture(ui.Rect.zero), [platformView])); final EngineScene scene = EngineScene(rootLayer); await sceneView.renderScene(scene, null); @@ -246,7 +247,7 @@ void testMain() { )); pictures.add(picture); final EngineRootLayer rootLayer = EngineRootLayer(); - rootLayer.slices.add(PictureSlice(picture)); + rootLayer.slices.add(LayerSlice(picture, [])); final EngineScene scene = EngineScene(rootLayer); renderFutures.add(sceneView.renderScene(scene, null)); } @@ -267,11 +268,69 @@ void testMain() { )); final EngineRootLayer rootLayer = EngineRootLayer(); - rootLayer.slices.add(PictureSlice(picture)); + rootLayer.slices.add(LayerSlice(picture, [])); final EngineScene scene = EngineScene(rootLayer); await sceneView.renderScene(scene, null); expect(stubPictureRenderer.renderedPictures.length, 1); expect(stubPictureRenderer.clipRequests.containsKey(picture), true); }); + + test('SceneView places platform view contents in the DOM', () async { + const int expectedPlatformViewId = 1234; + + int? injectedViewId; + final DomManager stubDomManager = StubDomManager() + ..injectPlatformViewOverride = (int viewId) { + injectedViewId = viewId; + }; + sceneView = EngineSceneView( + stubPictureRenderer, + StubFlutterView()..dom = stubDomManager, + ); + + final PlatformView platformView = PlatformView(expectedPlatformViewId, + const ui.Rect.fromLTWH(50, 80, 100, 120), const PlatformViewStyling()); + + final EngineRootLayer rootLayer = EngineRootLayer(); + rootLayer.slices.add( + LayerSlice(StubPicture(ui.Rect.zero), [platformView])); + final EngineScene scene = EngineScene(rootLayer); + await sceneView.renderScene(scene, null); + + expect( + injectedViewId, + expectedPlatformViewId, + reason: 'SceneView should call injectPlatformView on its flutterView.dom', + ); + }); +} + +class StubDomManager implements DomManager { + void Function(int platformViewId) injectPlatformViewOverride = (int id) {}; + @override + void injectPlatformView(int platformViewId) { + injectPlatformViewOverride(platformViewId); + } + + @override + DomElement get platformViewsHost => throw UnimplementedError(); + + @override + DomShadowRoot get renderingHost => throw UnimplementedError(); + + @override + DomElement get rootElement => throw UnimplementedError(); + + @override + DomElement get sceneHost => throw UnimplementedError(); + + @override + DomElement get semanticsHost => throw UnimplementedError(); + + @override + void setScene(DomElement sceneElement) {} + + @override + DomElement get textEditingHost => throw UnimplementedError(); } diff --git a/lib/web_ui/test/engine/semantics/semantics_api_test.dart b/lib/web_ui/test/engine/semantics/semantics_api_test.dart index 8df41a7cae384..e56242639b44c 100644 --- a/lib/web_ui/test/engine/semantics/semantics_api_test.dart +++ b/lib/web_ui/test/engine/semantics/semantics_api_test.dart @@ -29,7 +29,7 @@ void testMain() { }); // This must match the number of actions in lib/ui/semantics.dart - const int numSemanticsActions = 23; + const int numSemanticsActions = 24; test('SemanticsAction.values refers to all actions.', () async { expect(SemanticsAction.values.length, equals(numSemanticsActions)); for (int index = 0; index < numSemanticsActions; ++index) { diff --git a/lib/web_ui/test/engine/semantics/semantics_test.dart b/lib/web_ui/test/engine/semantics/semantics_test.dart index dd9cf23b9dea7..5975b9f6a2581 100644 --- a/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -25,6 +25,9 @@ EngineSemanticsOwner owner() => EnginePlatformDispatcher.instance.implicitView!. DomElement get platformViewsHost => EnginePlatformDispatcher.instance.implicitView!.dom.platformViewsHost; +DomElement get flutterViewRoot => + EnginePlatformDispatcher.instance.implicitView!.dom.rootElement; + void main() { internalBootstrapBrowserTest(() { return testMain; @@ -739,7 +742,7 @@ class MockSemanticsEnabler implements SemanticsEnabler { } void _testHeader() { - test('renders heading role for headers', () { + test('renders an empty labeled header as a heading with a label and uses a sized span for label', () { semantics() ..debugOverrideTimestampFunction(() => _testTime) ..semanticsEnabled = true; @@ -754,20 +757,32 @@ void _testHeader() { ); owner().updateSemantics(builder.build()); - expectSemanticsTree(owner(), ''' -Header of the page -'''); + expectSemanticsTree(owner(), '

Header of the page

'); + + semantics().semanticsEnabled = false; + }); + + // This is a useless case, but we should at least not crash if it happens. + test('renders an empty unlabeled header', () { + semantics() + ..debugOverrideTimestampFunction(() => _testTime) + ..semanticsEnabled = true; + + final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder(); + updateNode( + builder, + flags: 0 | ui.SemanticsFlag.isHeader.index, + transform: Matrix4.identity().toFloat64(), + rect: const ui.Rect.fromLTRB(0, 0, 100, 50), + ); + + owner().updateSemantics(builder.build()); + expectSemanticsTree(owner(), '
'); semantics().semanticsEnabled = false; }); - // When a header has child elements, role="heading" prevents AT from reaching - // child elements. To fix that role="group" is used, even though that causes - // the heading to not be announced as a heading. If the app really needs the - // heading to be announced as a heading, the developer can restructure the UI - // such that the heading is not a parent node, but a side-note, e.g. preceding - // the child list. - test('uses group role for headers when children are present', () { + test('renders a header with children and uses aria-label', () { semantics() ..debugOverrideTimestampFunction(() => _testTime) ..semanticsEnabled = true; @@ -791,7 +806,7 @@ void _testHeader() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' - +
'''); semantics().semanticsEnabled = false; @@ -3567,7 +3582,7 @@ void _testRoute() { tester.apply(); expect(capturedActions, isEmpty); - expect(domDocument.activeElement, domDocument.body); + expect(domDocument.activeElement, flutterViewRoot); semantics().semanticsEnabled = false; }); diff --git a/lib/web_ui/test/engine/semantics/text_field_test.dart b/lib/web_ui/test/engine/semantics/text_field_test.dart index c93dc3bf4a86f..38cef56fa80e3 100644 --- a/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -9,7 +9,6 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart' hide window; import 'package:ui/ui.dart' as ui; -import 'package:ui/ui_web/src/ui_web.dart' as ui_web; import '../../common/test_initialization.dart'; import 'semantics_tester.dart'; @@ -24,8 +23,8 @@ final InputConfiguration multilineConfig = InputConfiguration( ); EngineSemantics semantics() => EngineSemantics.instance; -EngineSemanticsOwner owner() => - EnginePlatformDispatcher.instance.implicitView!.semantics; +EngineFlutterView get flutterView => EnginePlatformDispatcher.instance.implicitView!; +EngineSemanticsOwner owner() => flutterView.semantics; const MethodCodec codec = JSONMethodCodec(); @@ -88,6 +87,8 @@ void testMain() { tearDown(() { semantics().semanticsEnabled = false; + // Most tests in this file expect to start with nothing focused. + domDocument.activeElement?.blur(); }); test('renders a text field', () { @@ -156,8 +157,7 @@ void testMain() { expect( owner().semanticsHost.ownerDocument?.activeElement, isNot(textField)); - // TODO(yjbanov): https://github.com/flutter/flutter/issues/46638 - }, skip: ui_web.browser.browserEngine == ui_web.BrowserEngine.firefox); + }); test('Syncs semantic state from framework', () async { expect( @@ -226,7 +226,9 @@ void testMain() { await Future.delayed(Duration.zero); expect( owner().semanticsHost.ownerDocument?.activeElement, - EnginePlatformDispatcher.instance.implicitView!.dom.rootElement, + flutterView.dom.rootElement, + reason: 'Focus should be returned to the root element of the Flutter view ' + 'after housekeeping DOM operations (blur/remove)', ); // There was no user interaction with the element, @@ -367,7 +369,9 @@ void testMain() { await Future.delayed(Duration.zero); expect( owner().semanticsHost.ownerDocument?.activeElement, - EnginePlatformDispatcher.instance.implicitView!.dom.rootElement, + flutterView.dom.rootElement, + reason: 'Focus should be returned to the root element of the Flutter view ' + 'after housekeeping DOM operations (blur/remove)', ); }); diff --git a/lib/web_ui/test/engine/surface/scene_builder_test.dart b/lib/web_ui/test/engine/surface/scene_builder_test.dart index d2c47c17d8339..a058051b4017f 100644 --- a/lib/web_ui/test/engine/surface/scene_builder_test.dart +++ b/lib/web_ui/test/engine/surface/scene_builder_test.dart @@ -13,7 +13,6 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -import 'package:ui/ui_web/src/ui_web.dart' as ui_web; import '../../common/matchers.dart'; import '../../common/rendering.dart'; @@ -182,8 +181,7 @@ void testMain() { expect(picture.buildCount, 1); expect(picture.updateCount, 0); expect(picture.applyPaintCount, 2); - }, // TODO(yjbanov): https://github.com/flutter/flutter/issues/46638 - skip: ui_web.browser.browserEngine == ui_web.BrowserEngine.firefox); + }); }); group('Compositing order', () { diff --git a/lib/web_ui/test/engine/window_test.dart b/lib/web_ui/test/engine/window_test.dart index 522cc5742c477..299490dc00c66 100644 --- a/lib/web_ui/test/engine/window_test.dart +++ b/lib/web_ui/test/engine/window_test.dart @@ -239,7 +239,8 @@ Future testMain() async { expect(ui.PlatformDispatcher.instance.onSemanticsActionEvent, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnSemanticsAction(0, ui.SemanticsAction.tap, null); + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + myWindow.viewId, 0, ui.SemanticsAction.tap, null); }); test('onAccessibilityFeaturesChanged preserves the zone', () { @@ -347,12 +348,10 @@ Future testMain() async { 'orientation': { 'lock': (String lockType) { lockCalls.add(lockType); - return futureToPromise(() async { - if (simulateError) { - throw Error(); - } - return 0.toJS; - }()); + if (simulateError) { + throw Error(); + } + return Future.value(0.toJS).toJS; }.toJS, 'unlock': () { unlockCount += 1; diff --git a/lib/web_ui/test/fallbacks/fallbacks_test.dart b/lib/web_ui/test/fallbacks/fallbacks_test.dart index 2a259c56585e4..36f71df6319ad 100644 --- a/lib/web_ui/test/fallbacks/fallbacks_test.dart +++ b/lib/web_ui/test/fallbacks/fallbacks_test.dart @@ -3,6 +3,8 @@ // found in the LICENSE file. +import 'dart:js_interop'; + import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; @@ -15,6 +17,9 @@ void main() { internalBootstrapBrowserTest(() => testMain); } +@JS() +external JSBoolean get crossOriginIsolated; + Future testMain() async { setUpUnitTests( setUpTestViewDimensions: false, @@ -24,6 +29,8 @@ Future testMain() async { if (ui_web.browser.browserEngine == ui_web.BrowserEngine.blink) { expect(isWasm, isTrue); expect(isSkwasm, isTrue); + final bool shouldBeMultiThreaded = crossOriginIsolated.toDart && !configuration.forceSingleThreadedSkwasm; + expect(isMultiThreaded, shouldBeMultiThreaded); } else { expect(isWasm, isFalse); expect(isCanvasKit, isTrue); diff --git a/lib/web_ui/test/felt_config.yaml b/lib/web_ui/test/felt_config.yaml index 2e20a355c3244..b081865921f76 100644 --- a/lib/web_ui/test/felt_config.yaml +++ b/lib/web_ui/test/felt_config.yaml @@ -17,10 +17,6 @@ compile-configs: compiler: dart2wasm renderer: html - - name: dart2wasm-canvaskit - compiler: dart2wasm - renderer: canvaskit - - name: dart2wasm-skwasm compiler: dart2wasm renderer: skwasm @@ -71,22 +67,6 @@ test-bundles: test-set: engine compile-configs: dart2wasm-html - - name: dart2wasm-html-html - test-set: html - compile-configs: dart2wasm-html - - - name: dart2wasm-html-ui - test-set: ui - compile-configs: dart2wasm-html - - - name: dart2wasm-canvaskit-canvaskit - test-set: canvaskit - compile-configs: dart2wasm-canvaskit - - - name: dart2wasm-canvaskit-ui - test-set: ui - compile-configs: dart2wasm-canvaskit - - name: dart2wasm-skwasm-ui test-set: ui compile-configs: dart2wasm-skwasm @@ -102,6 +82,17 @@ run-configs: browser: chrome canvaskit-variant: chromium + - name: chrome-coi + browser: chrome + canvaskit-variant: chromium + cross-origin-isolated: true + + - name: chrome-force-st + browser: chrome + canvaskit-variant: chromium + cross-origin-isolated: true + force-single-threaded-skwasm: true + - name: chrome-full browser: chrome canvaskit-variant: full @@ -235,44 +226,31 @@ test-suites: test-bundle: dart2wasm-html-engine run-config: chrome - - name: chrome-dart2wasm-html-html - test-bundle: dart2wasm-html-html - run-config: chrome - - - name: chrome-dart2wasm-html-ui - test-bundle: dart2wasm-html-ui - run-config: chrome - - - name: chrome-dart2wasm-canvaskit-canvaskit - test-bundle: dart2wasm-canvaskit-canvaskit - run-config: chrome - artifact-deps: [ canvaskit_chromium ] - - - name: chrome-dart2wasm-canvaskit-ui - test-bundle: dart2wasm-canvaskit-ui - run-config: chrome - artifact-deps: [ canvaskit_chromium ] - - - name: chrome-dart2wasm-skwasm-ui + - name: chrome-coi-dart2wasm-skwasm-ui test-bundle: dart2wasm-skwasm-ui - run-config: chrome + run-config: chrome-coi artifact-deps: [ skwasm ] - - name: chrome-full-dart2wasm-canvaskit-canvaskit - test-bundle: dart2wasm-canvaskit-canvaskit - run-config: chrome-full - artifact-deps: [ canvaskit ] - - - name: chrome-full-dart2wasm-canvaskit-ui - test-bundle: dart2wasm-canvaskit-ui - run-config: chrome-full - artifact-deps: [ canvaskit ] + - name: chrome-force-st-dart2wasm-skwasm-ui + test-bundle: dart2wasm-skwasm-ui + run-config: chrome-force-st + artifact-deps: [ skwasm ] - name: chrome-fallbacks test-bundle: fallbacks run-config: chrome artifact-deps: [ canvaskit, skwasm ] + - name: chrome-coi-fallbacks + test-bundle: fallbacks + run-config: chrome-coi + artifact-deps: [ canvaskit, skwasm ] + + - name: chrome-force-st-fallbacks + test-bundle: fallbacks + run-config: chrome-force-st + artifact-deps: [ canvaskit, skwasm ] + - name: firefox-fallbacks test-bundle: fallbacks run-config: firefox diff --git a/lib/web_ui/test/ui/fallback_fonts_golden_test.dart b/lib/web_ui/test/ui/fallback_fonts_golden_test.dart index 6d869d2c592c0..c97678683b6f7 100644 --- a/lib/web_ui/test/ui/fallback_fonts_golden_test.dart +++ b/lib/web_ui/test/ui/fallback_fonts_golden_test.dart @@ -222,6 +222,49 @@ void testMain() { expect(downloadedFontFamilies, isEmpty); } + /// Asserts that a given [partialFontFamilyName] is downloaded to render + /// a given [charCode]. + /// + /// The match on [partialFontFamilyName] is "starts with", so this method + /// supports split fonts, without hardcoding the shard number (which we + /// don't own). + Future checkDownloadedFamilyForCharCode( + int charCode, + String partialFontFamilyName, { + String? userPreferredLanguage, + }) async { + // downloadedFontFamilies.clear(); + // renderer.fontCollection.debugResetFallbackFonts(); + + final fallbackManager = renderer.fontCollection.fontFallbackManager!; + final oldLanguage = fallbackManager.debugUserPreferredLanguage; + if (userPreferredLanguage != null) { + fallbackManager.debugUserPreferredLanguage = userPreferredLanguage; + } + + // Try rendering text that requires fallback fonts, initially before the fonts are loaded. + final ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle()); + pb.addText(String.fromCharCode(charCode)); + pb.build().layout(const ui.ParagraphConstraints(width: 1000)); + + await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); + if (userPreferredLanguage != null) { + fallbackManager.debugUserPreferredLanguage = oldLanguage; + } + + expect( + downloadedFontFamilies, + hasLength(1), + reason: + 'Downloaded more than one font family for character: 0x${charCode.toRadixString(16)}' + '${userPreferredLanguage == null ? '' : ' (userPreferredLanguage: $userPreferredLanguage)'}', + ); + expect( + downloadedFontFamilies.first, + startsWith(partialFontFamilyName), + ); + } + // Regression test for https://github.com/flutter/flutter/issues/75836 // When we had this bug our font fallback resolution logic would end up in an // infinite loop and this test would freeze and time out. @@ -229,7 +272,7 @@ void testMain() { 'can find fonts for two adjacent unmatched code points from different fonts', () async { await checkDownloadedFamiliesForString('ヽಠ', [ - 'Noto Sans SC', + 'Noto Sans SC 68', 'Noto Sans Kannada', ]); }); @@ -261,6 +304,62 @@ void testMain() { ]); }); + // https://github.com/flutter/flutter/issues/157763 + test('prioritizes Noto Color Emoji over Noto Sans Symbols', () async { + await checkDownloadedFamilyForCharCode(0x1f3d5, 'Noto Color Emoji'); + }); + + // 0x700b is a CJK Unified Ideograph code point that exists in all of our + // CJK fonts. + + // Simplified Chinese + test('prioritizes Noto Sans SC for lang=zh', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans SC', userPreferredLanguage: 'zh'); + }); + test('prioritizes Noto Sans SC for lang=zh-Hans', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans SC', userPreferredLanguage: 'zh-Hans'); + }); + test('prioritizes Noto Sans SC for lang=zh-CN', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans SC', userPreferredLanguage: 'zh-CN'); + }); + test('prioritizes Noto Sans SC for lang=zh-SG', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans SC', userPreferredLanguage: 'zh-SG'); + }); + test('prioritizes Noto Sans SC for lang=zh-MY', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans SC', userPreferredLanguage: 'zh-MY'); + }); + + // Simplified Chinese is prioritized when preferred language is non-CJK. + test('prioritizes Noto Sans SC for lang=en-US', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans SC', userPreferredLanguage: 'en-US'); + }); + + // Traditional Chinese + test('prioritizes Noto Sans TC for lang=zh-Hant', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans TC', userPreferredLanguage: 'zh-Hant'); + }); + test('prioritizes Noto Sans TC for lang=zh-TW', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans TC', userPreferredLanguage: 'zh-TW'); + }); + test('prioritizes Noto Sans TC for lang=zh-MO', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans TC', userPreferredLanguage: 'zh-MO'); + }); + + // Hong Kong + test('prioritizes Noto Sans HK for lang=zh-HK', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans HK', userPreferredLanguage: 'zh-HK'); + }); + + // Japanese + test('prioritizes Noto Sans JP for lang=ja', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans JP', userPreferredLanguage: 'ja'); + }); + + // Korean + test('prioritizes Noto Sans KR for lang=ko', () async { + await checkDownloadedFamilyForCharCode(0x700b, 'Noto Sans KR', userPreferredLanguage: 'ko'); + }); + test('findMinimumFontsForCodePoints for all supported code points', () async { // Collect all supported code points from all fallback fonts in the Noto @@ -279,25 +378,20 @@ void testMain() { expect( supportedUniqueCodePoints.length, greaterThan(10000)); // sanity check - expect( - testedFonts, - unorderedEquals({ - 'Noto Color Emoji 0', - 'Noto Color Emoji 1', - 'Noto Color Emoji 2', - 'Noto Color Emoji 3', - 'Noto Color Emoji 4', - 'Noto Color Emoji 5', - 'Noto Color Emoji 6', - 'Noto Color Emoji 7', - 'Noto Color Emoji 8', - 'Noto Color Emoji 9', - 'Noto Color Emoji 10', - 'Noto Color Emoji 11', + final allFonts = { + ...[for (int i = 0; i <= 11; i++) 'Noto Color Emoji $i'], + ...[for (int i = 0; i <= 5; i++) 'Noto Sans Symbols 2 $i'], + ...[for (int i = 0; i <= 2; i++) 'Noto Sans Cuneiform $i'], + ...[for (int i = 0; i <= 2; i++) 'Noto Sans Duployan $i'], + ...[for (int i = 0; i <= 2; i++) 'Noto Sans Egyptian Hieroglyphs $i'], + ...[for (int i = 0; i <= 108; i++) 'Noto Sans HK $i'], + ...[for (int i = 0; i <= 123; i++) 'Noto Sans JP $i'], + ...[for (int i = 0; i <= 123; i++) 'Noto Sans KR $i'], + ...[for (int i = 0; i <= 100; i++) 'Noto Sans SC $i'], + ...[for (int i = 0; i <= 104; i++) 'Noto Sans TC $i'], 'Noto Music', 'Noto Sans', 'Noto Sans Symbols', - 'Noto Sans Symbols 2', 'Noto Sans Adlam', 'Noto Sans Anatolian Hieroglyphs', 'Noto Sans Arabic', @@ -319,12 +413,9 @@ void testMain() { 'Noto Sans Cham', 'Noto Sans Cherokee', 'Noto Sans Coptic', - 'Noto Sans Cuneiform', 'Noto Sans Cypriot', 'Noto Sans Deseret', 'Noto Sans Devanagari', - 'Noto Sans Duployan', - 'Noto Sans Egyptian Hieroglyphs', 'Noto Sans Elbasan', 'Noto Sans Elymaic', 'Noto Sans Ethiopic', @@ -335,7 +426,6 @@ void testMain() { 'Noto Sans Gujarati', 'Noto Sans Gunjala Gondi', 'Noto Sans Gurmukhi', - 'Noto Sans HK', 'Noto Sans Hanunoo', 'Noto Sans Hatran', 'Noto Sans Hebrew', @@ -343,9 +433,7 @@ void testMain() { 'Noto Sans Indic Siyaq Numbers', 'Noto Sans Inscriptional Pahlavi', 'Noto Sans Inscriptional Parthian', - 'Noto Sans JP', 'Noto Sans Javanese', - 'Noto Sans KR', 'Noto Sans Kaithi', 'Noto Sans Kannada', 'Noto Sans Kayah Li', @@ -404,7 +492,6 @@ void testMain() { 'Noto Sans Psalter Pahlavi', 'Noto Sans Rejang', 'Noto Sans Runic', - 'Noto Sans SC', 'Noto Sans Saurashtra', 'Noto Sans Sharada', 'Noto Sans Shavian', @@ -416,7 +503,6 @@ void testMain() { 'Noto Sans Sundanese', 'Noto Sans Syloti Nagri', 'Noto Sans Syriac', - 'Noto Sans TC', 'Noto Sans Tagalog', 'Noto Sans Tagbanwa', 'Noto Sans Tai Le', @@ -437,7 +523,14 @@ void testMain() { 'Noto Sans Yi', 'Noto Sans Zanabazar Square', 'Noto Serif Tibetan', - })); + }; + expect( + testedFonts, + unorderedEquals(allFonts), + reason: 'Found mismatch in fonts.\n' + 'Missing fonts: ${allFonts.difference(testedFonts)}\n' + 'Extra fonts: ${testedFonts.difference(allFonts)}', + ); // Construct random paragraphs out of supported code points. final math.Random random = math.Random(0); @@ -501,6 +594,19 @@ void testMain() { expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, isNot(contains('Noto Color Emoji 9'))); }); + + test('only woff2 fonts are used for fallback', () { + final fonts = getFallbackFontList(); + + for (final font in fonts) { + expect( + font.url, + endsWith('.woff2'), + reason: 'Expected all fallback fonts to be WOFF2, but found ' + '"${font.name}" was not a WOFF2 font: ${font.url}', + ); + } + }); }, // HTML renderer doesn't use the fallback font manager. skip: isHtml, diff --git a/lib/web_ui/test/ui/filters_test.dart b/lib/web_ui/test/ui/filters_test.dart index fc7ce4aa604aa..980e53e9bb615 100644 --- a/lib/web_ui/test/ui/filters_test.dart +++ b/lib/web_ui/test/ui/filters_test.dart @@ -189,4 +189,244 @@ Future testMain() async { await drawTestImageWithPaint(ui.Paint()..maskFilter = maskFilter); await matchGoldenFile('ui_filter_blur_maskfilter.png', region: region); }); + + ui.Image makeCheckerBoard(int width, int height) { + final recorder = ui.PictureRecorder(); + final canvas = ui.Canvas(recorder); + + const double left = 0; + final double centerX = width * 0.5; + final double right = width.toDouble(); + + const double top = 0; + final double centerY = height * 0.5; + final double bottom = height.toDouble(); + + canvas.drawRect(ui.Rect.fromLTRB(left, top, centerX, centerY), + ui.Paint()..color = const ui.Color.fromARGB(255, 0, 255, 0)); + canvas.drawRect(ui.Rect.fromLTRB(centerX, top, right, centerY), + ui.Paint()..color = const ui.Color.fromARGB(255, 255, 255, 0)); + canvas.drawRect(ui.Rect.fromLTRB(left, centerY, centerX, bottom), + ui.Paint()..color = const ui.Color.fromARGB(255, 0, 0, 255)); + canvas.drawRect(ui.Rect.fromLTRB(centerX, centerY, right, bottom), + ui.Paint()..color = const ui.Color.fromARGB(255, 255, 0, 0)); + + final picture = recorder.endRecording(); + return picture.toImageSync(width, height); + } + + Future renderingOpsWithTileMode(ui.TileMode? tileMode) async { + final recorder = ui.PictureRecorder(); + final canvas = ui.Canvas(recorder); + canvas.drawColor(const ui.Color.fromARGB(255, 224, 224, 224), ui.BlendMode.src); + + const ui.Rect zone = ui.Rect.fromLTWH(15, 15, 20, 20); + final ui.Rect arena = zone.inflate(15); + const ui.Rect ovalZone = ui.Rect.fromLTWH(20, 15, 10, 20); + + final gradient = ui.Gradient.linear( + zone.topLeft, + zone.bottomRight, + [ + const ui.Color.fromARGB(255, 0, 255, 0), + const ui.Color.fromARGB(255, 0, 0, 255), + ], + [0, 1], + ); + final filter = ui.ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0, tileMode: tileMode); + final ui.Paint white = ui.Paint()..color = const ui.Color.fromARGB(255, 255, 255, 255); + final ui.Paint grey = ui.Paint()..color = const ui.Color.fromARGB(255, 127, 127, 127); + final ui.Paint unblurredFill = ui.Paint()..shader = gradient; + final ui.Paint blurredFill = ui.Paint.from(unblurredFill) + ..imageFilter = filter; + final ui.Paint unblurredStroke = ui.Paint.from(unblurredFill) + ..style = ui.PaintingStyle.stroke + ..strokeCap = ui.StrokeCap.round + ..strokeJoin = ui.StrokeJoin.round + ..strokeWidth = 10; + final ui.Paint blurredStroke = ui.Paint.from(unblurredStroke) + ..imageFilter = filter; + final ui.Image image = makeCheckerBoard(20, 20); + const ui.Rect imageBounds = ui.Rect.fromLTRB(0, 0, 20, 20); + const ui.Rect imageCenter = ui.Rect.fromLTRB(5, 5, 9, 9); + final points = [ + zone.topLeft, + zone.topCenter, + zone.topRight, + zone.centerLeft, + zone.center, + zone.centerRight, + zone.bottomLeft, + zone.bottomCenter, + zone.bottomRight, + ]; + final vertices = ui.Vertices( + ui.VertexMode.triangles, + [ + zone.topLeft, + zone.bottomRight, + zone.topRight, + zone.topLeft, + zone.bottomRight, + zone.bottomLeft, + ], + colors: [ + const ui.Color.fromARGB(255, 0, 255, 0), + const ui.Color.fromARGB(255, 255, 0, 0), + const ui.Color.fromARGB(255, 255, 255, 0), + const ui.Color.fromARGB(255, 0, 255, 0), + const ui.Color.fromARGB(255, 255, 0, 0), + const ui.Color.fromARGB(255, 0, 0, 255), + ], + ); + final atlasXforms = [ + ui.RSTransform.fromComponents( + rotation: 0.0, + scale: 1.0, + anchorX: 0, + anchorY: 0, + translateX: zone.topLeft.dx, + translateY: zone.topLeft.dy, + ), + ui.RSTransform.fromComponents( + rotation: math.pi / 2, + scale: 1.0, + anchorX: 0, + anchorY: 0, + translateX: zone.topRight.dx, + translateY: zone.topRight.dy, + ), + ui.RSTransform.fromComponents( + rotation: math.pi, + scale: 1.0, + anchorX: 0, + anchorY: 0, + translateX: zone.bottomRight.dx, + translateY: zone.bottomRight.dy, + ), + ui.RSTransform.fromComponents( + rotation: math.pi * 3 / 2, + scale: 1.0, + anchorX: 0, + anchorY: 0, + translateX: zone.bottomLeft.dx, + translateY: zone.bottomLeft.dy, + ), + ui.RSTransform.fromComponents( + rotation: math.pi / 4, + scale: 1.0, + anchorX: 4, + anchorY: 4, + translateX: zone.center.dx, + translateY: zone.center.dy, + ), + ]; + const atlasRects = [ + ui.Rect.fromLTRB(6, 6, 14, 14), + ui.Rect.fromLTRB(6, 6, 14, 14), + ui.Rect.fromLTRB(6, 6, 14, 14), + ui.Rect.fromLTRB(6, 6, 14, 14), + ui.Rect.fromLTRB(6, 6, 14, 14), + ]; + + const double pad = 10; + final double offset = arena.width + pad; + const int columns = 5; + final ui.Rect pairArena = ui.Rect.fromLTRB(arena.left - 3, arena.top - 3, + arena.right + 3, arena.bottom + offset + 3); + + final List renderers = [ + (canvas, fill, stroke) { + canvas.saveLayer(zone.inflate(5), fill); + canvas.drawLine(zone.topLeft, zone.bottomRight, unblurredStroke); + canvas.drawLine(zone.topRight, zone.bottomLeft, unblurredStroke); + canvas.restore(); + }, + (canvas, fill, stroke) => canvas.drawLine(zone.topLeft, zone.bottomRight, stroke), + (canvas, fill, stroke) => canvas.drawRect(zone, fill), + (canvas, fill, stroke) => canvas.drawOval(ovalZone, fill), + (canvas, fill, stroke) => canvas.drawCircle(zone.center, zone.width * 0.5, fill), + (canvas, fill, stroke) => canvas.drawRRect(ui.RRect.fromRectXY(zone, 4.0, 4.0), fill), + (canvas, fill, stroke) => canvas.drawDRRect( + ui.RRect.fromRectXY(zone, 4.0, 4.0), + ui.RRect.fromRectXY(zone.deflate(4), 4.0, 4.0), + fill), + (canvas, fill, stroke) => canvas.drawArc(zone, math.pi / 4, math.pi * 3 / 2, true, fill), + (canvas, fill, stroke) => canvas.drawPath(ui.Path() + ..moveTo(zone.left, zone.top) + ..lineTo(zone.right, zone.top) + ..lineTo(zone.left, zone.bottom) + ..lineTo(zone.right, zone.bottom), + stroke), + (canvas, fill, stroke) => canvas.drawImage(image, zone.topLeft, fill), + (canvas, fill, stroke) => canvas.drawImageRect(image, imageBounds, zone.inflate(2), fill), + (canvas, fill, stroke) => canvas.drawImageNine(image, imageCenter, zone.inflate(2), fill), + (canvas, fill, stroke) => canvas.drawPoints(ui.PointMode.points, points, stroke), + (canvas, fill, stroke) => canvas.drawVertices(vertices, ui.BlendMode.dstOver, fill), + (canvas, fill, stroke) => canvas.drawAtlas(image, atlasXforms, atlasRects, + null, null, null, fill), + ]; + + canvas.save(); + canvas.translate(pad, pad); + int renderIndex = 0; + int rows = 0; + while (renderIndex < renderers.length) { + rows += 2; + canvas.save(); + for (int col = 0; col < columns && renderIndex < renderers.length; col++) { + final renderer = renderers[renderIndex++]; + canvas.drawRect(pairArena, grey); + canvas.drawRect(arena, white); + renderer(canvas, unblurredFill, unblurredStroke); + canvas.save(); + canvas.translate(0, offset); + canvas.drawRect(arena, white); + renderer(canvas, blurredFill, blurredStroke); + canvas.restore(); + canvas.translate(offset, 0); + } + canvas.restore(); + canvas.translate(0, offset * 2); + } + canvas.restore(); + + await drawPictureUsingCurrentRenderer(recorder.endRecording()); + return ui.Rect.fromLTWH(0, 0, offset * columns + pad, offset * rows + pad); + } + + test('Rendering ops with ImageFilter blur with default tile mode', () async { + final region = await renderingOpsWithTileMode(null); + await matchGoldenFile('ui_filter_blurred_rendering_with_default_tile_mode.png', region: region); + }, + // HTML renderer doesn't have tile modes + skip: isHtml); + + test('Rendering ops with ImageFilter blur with clamp tile mode', () async { + final region = await renderingOpsWithTileMode(ui.TileMode.clamp); + await matchGoldenFile('ui_filter_blurred_rendering_with_clamp_tile_mode.png', region: region); + }, + // HTML renderer doesn't have tile modes + skip: isHtml); + + test('Rendering ops with ImageFilter blur with decal tile mode', () async { + final region = await renderingOpsWithTileMode(ui.TileMode.decal); + await matchGoldenFile('ui_filter_blurred_rendering_with_decal_tile_mode.png', region: region); + }, + // HTML renderer doesn't have tile modes + skip: isHtml); + + test('Rendering ops with ImageFilter blur with mirror tile mode', () async { + final region = await renderingOpsWithTileMode(ui.TileMode.mirror); + await matchGoldenFile('ui_filter_blurred_rendering_with_mirror_tile_mode.png', region: region); + }, + // HTML renderer doesn't have tile modes + skip: isHtml); + + test('Rendering ops with ImageFilter blur with repeated tile mode', () async { + final region = await renderingOpsWithTileMode(ui.TileMode.repeated); + await matchGoldenFile('ui_filter_blurred_rendering_with_repeated_tile_mode.png', region: region); + }, + // HTML renderer doesn't have tile modes + skip: isHtml); } diff --git a/lib/web_ui/test/ui/image_golden_test.dart b/lib/web_ui/test/ui/image_golden_test.dart index 2b1f31277b699..0ac49d2ab09d5 100644 --- a/lib/web_ui/test/ui/image_golden_test.dart +++ b/lib/web_ui/test/ui/image_golden_test.dart @@ -385,6 +385,19 @@ Future testMain() async { return info.image; }); + test('decode rotated jpeg', () async { + // This image (from skia's test images) has a rotated orientation in its exif data. + // This should result in a 3024x4032 image, not 4032x3024 image. + final ui.Codec codec = await renderer.instantiateImageCodecFromUrl( + Uri(path: '/test_images/iphone_15.jpeg') + ); + expect(codec.frameCount, 1); + + final ui.FrameInfo info = await codec.getNextFrame(); + expect(info.image.width, 3024); + expect(info.image.height, 4032); + }); + // This API doesn't work in headless Firefox due to requiring WebGL // See https://github.com/flutter/flutter/issues/109265 if (!isFirefox) { @@ -414,8 +427,9 @@ Future testMain() async { expect(bitmap.height.toDartInt, 150); final ui.Image uiImage = await renderer.createImageFromImageBitmap(bitmap); - if (isSkwasm) { - // Skwasm transfers the bitmap to the web worker, so it should be disposed/consumed. + if (isSkwasm && isMultiThreaded) { + // Multi-threaded skwasm transfers the bitmap to the web worker, so it should be + // disposed/consumed. expect(bitmap.width.toDartInt, 0); expect(bitmap.height.toDartInt, 0); } diff --git a/lib/web_ui/test/ui/scene_builder_test.dart b/lib/web_ui/test/ui/scene_builder_test.dart index af17bf98eb3ba..3ffe8e0ccdf88 100644 --- a/lib/web_ui/test/ui/scene_builder_test.dart +++ b/lib/web_ui/test/ui/scene_builder_test.dart @@ -77,6 +77,23 @@ Future testMain() async { region: region); }); + test('Devtools rendering regression test', () async { + // This is a regression test for https://github.com/flutter/devtools/issues/8401 + final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); + + sceneBuilder.pushClipRect(const ui.Rect.fromLTRB(12.0, 0.0, 300.0, 27.0)); + sceneBuilder.addPicture(ui.Offset.zero, drawPicture((ui.Canvas canvas) { + canvas.drawOval( + const ui.Rect.fromLTRB(15.0, 5.0, 64.0, 21.0), + ui.Paint()..color = const ui.Color(0xFF0000FF), + ); + })); + + await renderScene(sceneBuilder.build()); + await matchGoldenFile('scene_builder_oval_clip_rect.png', + region: region); + }); + test('Test clipRRect layer', () async { final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); sceneBuilder.pushClipRRect( @@ -194,9 +211,6 @@ Future testMain() async { }); test('empty backdrop filter layer with clip', () async { - // Note that this test does not actually render properly in skwasm due to - // a Skia bug. See https://g-issues.skia.org/issues/362552959 and - // https://github.com/flutter/flutter/issues/152026 final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); sceneBuilder.addPicture(ui.Offset.zero, drawPicture((ui.Canvas canvas) { @@ -224,7 +238,7 @@ Future testMain() async { region: region); }); - test('image filter layer', () async { + test('blur image filter layer', () async { final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); sceneBuilder.pushImageFilter(ui.ImageFilter.blur( sigmaX: 5.0, @@ -240,6 +254,23 @@ Future testMain() async { await matchGoldenFile('scene_builder_image_filter.png', region: region); }); + test('matrix image filter layer', () async { + final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); + sceneBuilder.pushOffset(50.0, 50.0); + + final Matrix4 matrix = Matrix4.rotationZ(math.pi / 18); + final ui.ImageFilter matrixFilter = ui.ImageFilter.matrix(toMatrix64(matrix.storage)); + sceneBuilder.pushImageFilter(matrixFilter); + sceneBuilder.addPicture(ui.Offset.zero, drawPicture((ui.Canvas canvas) { + canvas.drawRect( + region, + ui.Paint()..color = const ui.Color(0xFF00FF00) + ); + })); + await renderScene(sceneBuilder.build()); + await matchGoldenFile('scene_builder_matrix_image_filter.png', region: region); + }); + // Regression test for https://github.com/flutter/flutter/issues/154303 test('image filter layer with offset', () async { final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); @@ -467,6 +498,61 @@ Future testMain() async { 'scene_builder_opacity_layer_with_transformed_children.png', region: region); }); + + test('backdrop layer with default blur tile mode', () async { + final scene = backdropBlurWithTileMode(null, 10, 50); + await renderScene(scene); + + await matchGoldenFile( + 'scene_builder_backdrop_filter_blur_default_tile_mode.png', + region: const ui.Rect.fromLTWH(0, 0, 10*50, 10*50)); + }, + // HTML renderer doesn't have tile modes + skip: isHtml); + + test('backdrop layer with clamp blur tile mode', () async { + final scene = backdropBlurWithTileMode(ui.TileMode.clamp, 10, 50); + await renderScene(scene); + + await matchGoldenFile( + 'scene_builder_backdrop_filter_blur_clamp_tile_mode.png', + region: const ui.Rect.fromLTWH(0, 0, 10*50, 10*50)); + }, + // HTML renderer doesn't have tile modes + skip: isHtml); + + test('backdrop layer with mirror blur tile mode', () async { + final scene = backdropBlurWithTileMode(ui.TileMode.mirror, 10, 50); + await renderScene(scene); + + await matchGoldenFile( + 'scene_builder_backdrop_filter_blur_mirror_tile_mode.png', + region: const ui.Rect.fromLTWH(0, 0, 10*50, 10*50)); + }, + // HTML renderer doesn't have tile modes + skip: isHtml); + + test('backdrop layer with repeated blur tile mode', () async { + final scene = backdropBlurWithTileMode(ui.TileMode.repeated, 10, 50); + await renderScene(scene); + + await matchGoldenFile( + 'scene_builder_backdrop_filter_blur_repeated_tile_mode.png', + region: const ui.Rect.fromLTWH(0, 0, 10*50, 10*50)); + }, + // HTML renderer doesn't have tile modes + skip: isHtml); + + test('backdrop layer with decal blur tile mode', () async { + final scene = backdropBlurWithTileMode(ui.TileMode.decal, 10, 50); + await renderScene(scene); + + await matchGoldenFile( + 'scene_builder_backdrop_filter_blur_decal_tile_mode.png', + region: const ui.Rect.fromLTWH(0, 0, 10*50, 10*50)); + }, + // HTML renderer doesn't have tile modes + skip: isHtml); }); } @@ -476,3 +562,58 @@ ui.Picture drawPicture(void Function(ui.Canvas) drawCommands) { drawCommands(canvas); return recorder.endRecording(); } + +ui.Scene backdropBlurWithTileMode(ui.TileMode? tileMode, + final double rectSize, + final int count) { + final double imgSize = rectSize * count; + + const ui.Color white = ui.Color(0xFFFFFFFF); + const ui.Color purple = ui.Color(0xFFFF00FF); + const ui.Color blue = ui.Color(0xFF0000FF); + const ui.Color green = ui.Color(0xFF00FF00); + const ui.Color yellow = ui.Color(0xFFFFFF00); + const ui.Color red = ui.Color(0xFFFF0000); + + final ui.Picture blueGreenGridPicture = drawPicture((ui.Canvas canvas) { + canvas.drawColor(white, ui.BlendMode.src); + for (int i = 0; i < count; i++) { + for (int j = 0; j < count; j++) { + final bool rectOdd = (i + j) & 1 == 0; + final ui.Color fg = (i < count / 2) + ? ((j < count / 2) ? green : blue) + : ((j < count / 2) ? yellow : red); + canvas.drawRect(ui.Rect.fromLTWH(i * rectSize, j * rectSize, rectSize, rectSize), + ui.Paint()..color = rectOdd ? fg : white); + } + } + canvas.drawRect(ui.Rect.fromLTWH(0, 0, imgSize, 1), ui.Paint()..color = purple); + canvas.drawRect(ui.Rect.fromLTWH(0, 0, 1, imgSize), ui.Paint()..color = purple); + canvas.drawRect(ui.Rect.fromLTWH(0, imgSize - 1, imgSize, 1), ui.Paint()..color = purple); + canvas.drawRect(ui.Rect.fromLTWH(imgSize - 1, 0, 1, imgSize), ui.Paint()..color = purple); + }); + + final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); + // We push a clipRect layer with the SaveLayer behavior so that it creates + // a layer of predetermined size in which the backdrop filter will apply + // its filter to show the edge effects on predictable edges. + sceneBuilder.pushClipRect(ui.Rect.fromLTWH(0, 0, imgSize, imgSize), + clipBehavior: ui.Clip.antiAliasWithSaveLayer); + sceneBuilder.addPicture(ui.Offset.zero, blueGreenGridPicture); + sceneBuilder.pushBackdropFilter(ui.ImageFilter.blur(sigmaX: 20, sigmaY: 20, tileMode: tileMode)); + // The following picture prevents the saveLayer in the backdrop filter from + // being completely ignored on the skwasm backend due to an interaction with + // SkPictureRecorder eliminating saveLayer entries with no content even if + // they have a backdrop filter. It draws nothing because the pixels below + // it are opaque and dstOver is a NOP in that case, but it is unlikely that + // a recording process would be able to figure that out without extensive + // analysis between the pictures and layers. + sceneBuilder.addPicture(ui.Offset.zero, drawPicture((ui.Canvas canvas) { + canvas.drawRect(ui.Rect.fromLTWH(imgSize * 0.5 - 10, imgSize * 0.5 - 10, 20, 20), + ui.Paint()..color = purple..blendMode = ui.BlendMode.dstOver); + })); + sceneBuilder.pop(); + sceneBuilder.pop(); + + return sceneBuilder.build(); +} diff --git a/lib/web_ui/test/ui/utils.dart b/lib/web_ui/test/ui/utils.dart index 62e142de8a22b..e160d07dcd674 100644 --- a/lib/web_ui/test/ui/utils.dart +++ b/lib/web_ui/test/ui/utils.dart @@ -35,3 +35,5 @@ bool get isCanvasKit => renderer is CanvasKitRenderer; bool get isHtml => renderer is HtmlRenderer; bool get isSkwasm => renderer is SkwasmRenderer; + +bool get isMultiThreaded => isSkwasm && (renderer as SkwasmRenderer).isMultiThreaded; diff --git a/pubspec.yaml b/pubspec.yaml index bece8d638daba..0eb81c032e285 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,7 +35,7 @@ # # # Required for workspace support. # environment: -# sdk: ^3.5.0-294.0.dev +# sdk: ^3.7.0-0 # # # This package is managed as part of the engine workspace. # resolution: workspace @@ -76,7 +76,7 @@ name: _engine_workspace # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # Declare all packages that are part of the workspace. workspace: @@ -174,21 +174,21 @@ dependency_overrides: archive: path: ./third_party/pkg/archive args: - path: ./third_party/dart/third_party/pkg/args + path: ./third_party/dart/third_party/pkg/core/pkgs/args async: - path: ./third_party/dart/third_party/pkg/async + path: ./third_party/dart/third_party/pkg/core/pkgs/async async_helper: path: ./third_party/dart/pkg/async_helper boolean_selector: - path: ./third_party/dart/third_party/pkg/boolean_selector + path: ./third_party/dart/third_party/pkg/tools/pkgs/boolean_selector collection: - path: ./third_party/dart/third_party/pkg/collection + path: ./third_party/dart/third_party/pkg/core/pkgs/collection convert: - path: ./third_party/dart/third_party/pkg/convert + path: ./third_party/dart/third_party/pkg/core/pkgs/convert coverage: path: ./third_party/pkg/coverage crypto: - path: ./third_party/dart/third_party/pkg/crypto + path: ./third_party/dart/third_party/pkg/core/pkgs/crypto equatable: path: ./third_party/pkg/equatable expect: @@ -198,7 +198,7 @@ dependency_overrides: ffi: path: ./third_party/dart/third_party/pkg/native/pkgs/ffi fixnum: - path: ./third_party/dart/third_party/pkg/fixnum + path: ./third_party/dart/third_party/pkg/core/pkgs/fixnum frontend_server_client: path: ./third_party/dart/third_party/pkg/webdev/frontend_server_client gcloud: @@ -214,7 +214,7 @@ dependency_overrides: http_multi_server: path: ./third_party/dart/third_party/pkg/http_multi_server http_parser: - path: ./third_party/dart/third_party/pkg/http_parser + path: ./third_party/dart/third_party/pkg/http/pkgs/http_parser io: path: ./third_party/pkg/io js: @@ -222,11 +222,11 @@ dependency_overrides: kernel: path: ./third_party/dart/pkg/kernel logging: - path: ./third_party/dart/third_party/pkg/logging + path: ./third_party/dart/third_party/pkg/core/pkgs/logging macros: path: ./third_party/dart/pkg/macros matcher: - path: ./third_party/dart/third_party/pkg/matcher + path: ./third_party/dart/third_party/pkg/test/pkgs/matcher meta: path: ./third_party/dart/pkg/meta metrics_center: @@ -238,9 +238,9 @@ dependency_overrides: package_config: path: ./third_party/dart/third_party/pkg/package_config path: - path: ./third_party/dart/third_party/pkg/path + path: ./third_party/dart/third_party/pkg/core/pkgs/path platform: - path: ./third_party/pkg/platform + path: ./third_party/dart/third_party/pkg/core/pkgs/platform pool: path: ./third_party/dart/third_party/pkg/pool process: @@ -284,7 +284,7 @@ dependency_overrides: test_core: path: ./third_party/dart/third_party/pkg/test/pkgs/test_core typed_data: - path: ./third_party/dart/third_party/pkg/typed_data + path: ./third_party/dart/third_party/pkg/core/pkgs/typed_data vector_math: path: ./third_party/pkg/vector_math vm_service: diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index 80e5c372a4062..5c75d2f4cb2bc 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -29,7 +29,7 @@ group("libdart") { public_deps = [] if (flutter_runtime_mode == "profile" || flutter_runtime_mode == "release") { - public_deps += [ "$dart_src/runtime:libdart_precompiled_runtime" ] + public_deps += [ "$dart_src/runtime:libdart_aotruntime" ] } else { public_deps += [ "$dart_src/runtime:libdart_jit", diff --git a/runtime/dart_isolate.cc b/runtime/dart_isolate.cc index 4af4758d6154a..1761147761161 100644 --- a/runtime/dart_isolate.cc +++ b/runtime/dart_isolate.cc @@ -5,7 +5,6 @@ #include "flutter/runtime/dart_isolate.h" #include -#include #include #include "flutter/fml/logging.h" @@ -14,14 +13,13 @@ #include "flutter/lib/io/dart_io.h" #include "flutter/lib/ui/dart_runtime_hooks.h" #include "flutter/lib/ui/dart_ui.h" -#include "flutter/lib/ui/window/platform_isolate.h" #include "flutter/runtime/dart_isolate_group_data.h" #include "flutter/runtime/dart_plugin_registrant.h" #include "flutter/runtime/dart_service_isolate.h" #include "flutter/runtime/dart_vm.h" #include "flutter/runtime/dart_vm_lifecycle.h" #include "flutter/runtime/isolate_configuration.h" -#include "flutter/runtime/runtime_controller.h" +#include "flutter/runtime/platform_isolate_manager.h" #include "fml/message_loop_task_queues.h" #include "fml/task_source.h" #include "fml/time/time_point.h" @@ -33,7 +31,6 @@ #include "third_party/tonic/dart_class_provider.h" #include "third_party/tonic/dart_message_handler.h" #include "third_party/tonic/dart_state.h" -#include "third_party/tonic/file_loader/file_loader.h" #include "third_party/tonic/logging/dart_invoke.h" #include "third_party/tonic/scopes/dart_api_scope.h" #include "third_party/tonic/scopes/dart_isolate_scope.h" @@ -100,7 +97,8 @@ std::weak_ptr DartIsolate::CreateRunningRootIsolate( const std::vector& dart_entrypoint_args, std::unique_ptr isolate_configuration, const UIDartState::Context& context, - const DartIsolate* spawning_isolate) { + const DartIsolate* spawning_isolate, + std::shared_ptr native_assets_manager) { if (!isolate_snapshot) { FML_LOG(ERROR) << "Invalid isolate snapshot."; return {}; @@ -122,7 +120,8 @@ std::weak_ptr DartIsolate::CreateRunningRootIsolate( isolate_create_callback, // isolate_shutdown_callback, // context, // - spawning_isolate // + spawning_isolate, // + std::move(native_assets_manager) // ) .lock(); @@ -196,7 +195,8 @@ std::weak_ptr DartIsolate::CreateRootIsolate( const fml::closure& isolate_create_callback, const fml::closure& isolate_shutdown_callback, const UIDartState::Context& context, - const DartIsolate* spawning_isolate) { + const DartIsolate* spawning_isolate, + std::shared_ptr native_assets_manager) { TRACE_EVENT0("flutter", "DartIsolate::CreateRootIsolate"); // Only needed if this is the main isolate for the group. @@ -247,7 +247,8 @@ std::weak_ptr DartIsolate::CreateRootIsolate( context.advisory_script_entrypoint, // advisory entrypoint nullptr, // child isolate preparer isolate_create_callback, // isolate create callback - isolate_shutdown_callback // isolate shutdown callback + isolate_shutdown_callback, // isolate shutdown callback + std::move(native_assets_manager) // ))); isolate_maker = [](std::shared_ptr* isolate_group_data, @@ -1181,15 +1182,73 @@ static void* NativeAssetsDlopenRelative(const char* path, char** error) { error); } +static void* NativeAssetsDlopen(const char* asset_id, char** error) { + auto* isolate_group_data = + static_cast*>( + Dart_CurrentIsolateGroupData()); + auto native_assets_manager = (*isolate_group_data)->GetNativeAssetsManager(); + if (native_assets_manager == nullptr) { + return nullptr; + } + + std::vector asset_path = + native_assets_manager->LookupNativeAsset(asset_id); + if (asset_path.size() == 0) { + // The asset id was not in the mapping. + return nullptr; + } + + auto& path_type = asset_path[0]; + std::string path; + static constexpr const char* kAbsolute = "absolute"; + static constexpr const char* kExecutable = "executable"; + static constexpr const char* kProcess = "process"; + static constexpr const char* kRelative = "relative"; + static constexpr const char* kSystem = "system"; + if (path_type == kAbsolute || path_type == kRelative || + path_type == kSystem) { + path = asset_path[1]; + } + + if (path_type == kAbsolute) { + return dart::bin::NativeAssets::DlopenAbsolute(path.c_str(), error); + } else if (path_type == kRelative) { + return NativeAssetsDlopenRelative(path.c_str(), error); + } else if (path_type == kSystem) { + return dart::bin::NativeAssets::DlopenSystem(path.c_str(), error); + } else if (path_type == kProcess) { + return dart::bin::NativeAssets::DlopenProcess(error); + } else if (path_type == kExecutable) { + return dart::bin::NativeAssets::DlopenExecutable(error); + } + + return nullptr; +} + +static char* NativeAssetsAvailableAssets() { + auto* isolate_group_data = + static_cast*>( + Dart_CurrentIsolateGroupData()); + auto native_assets_manager = (*isolate_group_data)->GetNativeAssetsManager(); + FML_DCHECK(native_assets_manager != nullptr); + auto available_assets = native_assets_manager->AvailableNativeAssets(); + auto* result = fml::strdup(available_assets.c_str()); + return result; +} + static void InitDartFFIForIsolateGroup() { NativeAssetsApi native_assets; memset(&native_assets, 0, sizeof(native_assets)); + // TODO(dacoharkes): Remove after flutter_tools stops kernel embedding. native_assets.dlopen_absolute = &dart::bin::NativeAssets::DlopenAbsolute; native_assets.dlopen_relative = &NativeAssetsDlopenRelative; native_assets.dlopen_system = &dart::bin::NativeAssets::DlopenSystem; native_assets.dlopen_executable = &dart::bin::NativeAssets::DlopenExecutable; native_assets.dlopen_process = &dart::bin::NativeAssets::DlopenProcess; + // TODO(dacoharkes): End todo. native_assets.dlsym = &dart::bin::NativeAssets::Dlsym; + native_assets.dlopen = &NativeAssetsDlopen; + native_assets.available_assets = &NativeAssetsAvailableAssets; Dart_InitializeNativeAssetsResolver(&native_assets); }; diff --git a/runtime/dart_isolate.h b/runtime/dart_isolate.h index e22a0132c55df..4b2116ed70ab4 100644 --- a/runtime/dart_isolate.h +++ b/runtime/dart_isolate.h @@ -7,21 +7,16 @@ #include #include -#include #include #include -#include "flutter/common/task_runners.h" +#include "assets/native_assets.h" #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" -#include "flutter/lib/ui/io_manager.h" -#include "flutter/lib/ui/snapshot_delegate.h" #include "flutter/lib/ui/ui_dart_state.h" #include "flutter/lib/ui/window/platform_configuration.h" #include "flutter/runtime/dart_snapshot.h" -#include "flutter/runtime/isolate_configuration.h" #include "third_party/dart/runtime/include/dart_api.h" -#include "third_party/tonic/dart_state.h" namespace flutter { @@ -221,7 +216,8 @@ class DartIsolate : public UIDartState { const std::vector& dart_entrypoint_args, std::unique_ptr isolate_configuration, const UIDartState::Context& context, - const DartIsolate* spawning_isolate = nullptr); + const DartIsolate* spawning_isolate = nullptr, + std::shared_ptr native_assets_manager = nullptr); // |UIDartState| ~DartIsolate() override; @@ -447,7 +443,8 @@ class DartIsolate : public UIDartState { const fml::closure& isolate_create_callback, const fml::closure& isolate_shutdown_callback, const UIDartState::Context& context, - const DartIsolate* spawning_isolate = nullptr); + const DartIsolate* spawning_isolate = nullptr, + std::shared_ptr native_assets_manager = nullptr); DartIsolate(const Settings& settings, bool is_root_isolate, diff --git a/runtime/dart_isolate_group_data.cc b/runtime/dart_isolate_group_data.cc index 8c1c09b257786..0e844d3e3c417 100644 --- a/runtime/dart_isolate_group_data.cc +++ b/runtime/dart_isolate_group_data.cc @@ -17,14 +17,16 @@ DartIsolateGroupData::DartIsolateGroupData( std::string advisory_script_entrypoint, const ChildIsolatePreparer& child_isolate_preparer, const fml::closure& isolate_create_callback, - const fml::closure& isolate_shutdown_callback) + const fml::closure& isolate_shutdown_callback, + std::shared_ptr native_assets_manager) : settings_(settings), isolate_snapshot_(std::move(isolate_snapshot)), advisory_script_uri_(std::move(advisory_script_uri)), advisory_script_entrypoint_(std::move(advisory_script_entrypoint)), child_isolate_preparer_(child_isolate_preparer), isolate_create_callback_(isolate_create_callback), - isolate_shutdown_callback_(isolate_shutdown_callback) { + isolate_shutdown_callback_(isolate_shutdown_callback), + native_assets_manager_(std::move(native_assets_manager)) { FML_DCHECK(isolate_snapshot_) << "Must contain a valid isolate snapshot."; } @@ -66,6 +68,11 @@ void DartIsolateGroupData::SetChildIsolatePreparer( child_isolate_preparer_ = value; } +std::shared_ptr +DartIsolateGroupData::GetNativeAssetsManager() const { + return native_assets_manager_; +} + void DartIsolateGroupData::SetPlatformMessageHandler( int64_t root_isolate_token, std::weak_ptr handler) { diff --git a/runtime/dart_isolate_group_data.h b/runtime/dart_isolate_group_data.h index 8b862564d21e2..8ff29272150ab 100644 --- a/runtime/dart_isolate_group_data.h +++ b/runtime/dart_isolate_group_data.h @@ -9,6 +9,7 @@ #include #include +#include "assets/native_assets.h" #include "flutter/common/settings.h" #include "flutter/fml/closure.h" #include "flutter/fml/memory/ref_ptr.h" @@ -30,13 +31,15 @@ using ChildIsolatePreparer = std::function; // group cleanup callback on any thread. class DartIsolateGroupData : public PlatformMessageHandlerStorage { public: - DartIsolateGroupData(const Settings& settings, - fml::RefPtr isolate_snapshot, - std::string advisory_script_uri, - std::string advisory_script_entrypoint, - const ChildIsolatePreparer& child_isolate_preparer, - const fml::closure& isolate_create_callback, - const fml::closure& isolate_shutdown_callback); + DartIsolateGroupData( + const Settings& settings, + fml::RefPtr isolate_snapshot, + std::string advisory_script_uri, + std::string advisory_script_entrypoint, + const ChildIsolatePreparer& child_isolate_preparer, + const fml::closure& isolate_create_callback, + const fml::closure& isolate_shutdown_callback, + std::shared_ptr native_assets_manager = nullptr); ~DartIsolateGroupData(); @@ -56,6 +59,8 @@ class DartIsolateGroupData : public PlatformMessageHandlerStorage { void SetChildIsolatePreparer(const ChildIsolatePreparer& value); + std::shared_ptr GetNativeAssetsManager() const; + /// Adds a kernel buffer mapping to the kernels loaded for this isolate group. void AddKernelBuffer(const std::shared_ptr& buffer); @@ -82,6 +87,7 @@ class DartIsolateGroupData : public PlatformMessageHandlerStorage { ChildIsolatePreparer child_isolate_preparer_; const fml::closure isolate_create_callback_; const fml::closure isolate_shutdown_callback_; + std::shared_ptr native_assets_manager_; std::map> platform_message_handlers_; mutable std::mutex platform_message_handlers_mutex_; diff --git a/runtime/dart_service_isolate.h b/runtime/dart_service_isolate.h index a6fc32d4297af..439b49ce5884c 100644 --- a/runtime/dart_service_isolate.h +++ b/runtime/dart_service_isolate.h @@ -6,6 +6,7 @@ #define FLUTTER_RUNTIME_DART_SERVICE_ISOLATE_H_ #include +#include #include #include #include diff --git a/runtime/dart_snapshot.h b/runtime/dart_snapshot.h index 96cd3c764166a..62125facd509f 100644 --- a/runtime/dart_snapshot.h +++ b/runtime/dart_snapshot.h @@ -6,7 +6,6 @@ #define FLUTTER_RUNTIME_DART_SNAPSHOT_H_ #include -#include #include "flutter/common/settings.h" #include "flutter/fml/macros.h" diff --git a/runtime/isolate_configuration.h b/runtime/isolate_configuration.h index 05e2270b266d7..20aee674d8a92 100644 --- a/runtime/isolate_configuration.h +++ b/runtime/isolate_configuration.h @@ -7,14 +7,12 @@ #include #include -#include #include "flutter/assets/asset_manager.h" #include "flutter/assets/asset_resolver.h" #include "flutter/common/settings.h" #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" -#include "flutter/fml/memory/weak_ptr.h" #include "flutter/runtime/dart_isolate.h" namespace flutter { diff --git a/runtime/platform_isolate_manager_unittests.cc b/runtime/platform_isolate_manager_unittests.cc index 312beff516999..7c604e07ef9dc 100644 --- a/runtime/platform_isolate_manager_unittests.cc +++ b/runtime/platform_isolate_manager_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/runtime/dart_vm.h" #include "flutter/runtime/dart_vm_lifecycle.h" +#include "flutter/runtime/isolate_configuration.h" #include "flutter/runtime/platform_isolate_manager.h" #include "flutter/testing/fixture_test.h" #include "flutter/testing/testing.h" diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 51613d6430ada..27193cdf73db3 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -6,9 +6,7 @@ #include -#include "flutter/common/constants.h" #include "flutter/common/settings.h" -#include "flutter/fml/message_loop.h" #include "flutter/fml/trace_event.h" #include "flutter/lib/ui/compositing/scene.h" #include "flutter/lib/ui/ui_dart_state.h" @@ -511,6 +509,14 @@ bool RuntimeController::HasLivePorts() { return Dart_HasLivePorts(); } +bool RuntimeController::HasPendingMicrotasks() { + std::shared_ptr root_isolate = root_isolate_.lock(); + if (!root_isolate) { + return false; + } + return root_isolate->HasPendingMicrotasks(); +} + tonic::DartErrorHandleType RuntimeController::GetLastError() { std::shared_ptr root_isolate = root_isolate_.lock(); return root_isolate ? root_isolate->GetLastError() : tonic::kNoError; @@ -522,7 +528,8 @@ bool RuntimeController::LaunchRootIsolate( std::optional dart_entrypoint, std::optional dart_entrypoint_library, const std::vector& dart_entrypoint_args, - std::unique_ptr isolate_configuration) { + std::unique_ptr isolate_configuration, + std::shared_ptr native_assets_manager) { if (root_isolate_.lock()) { FML_LOG(ERROR) << "Root isolate was already running."; return false; @@ -542,7 +549,8 @@ bool RuntimeController::LaunchRootIsolate( dart_entrypoint_args, // std::move(isolate_configuration), // context_, // - spawning_isolate_.lock().get()) // + spawning_isolate_.lock().get(), + std::move(native_assets_manager)) // .lock(); if (!strong_root_isolate) { diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index c19f856644caf..3ed05aa945461 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -8,9 +8,9 @@ #include #include +#include "assets/native_assets.h" #include "flutter/assets/asset_manager.h" #include "flutter/common/task_runners.h" -#include "flutter/flow/layers/layer_tree.h" #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" #include "flutter/lib/ui/io_manager.h" @@ -23,8 +23,6 @@ #include "flutter/runtime/dart_vm.h" #include "flutter/runtime/platform_data.h" #include "flutter/runtime/platform_isolate_manager.h" -#include "rapidjson/document.h" -#include "rapidjson/stringbuffer.h" namespace flutter { @@ -167,7 +165,8 @@ class RuntimeController : public PlatformConfigurationClient, std::optional dart_entrypoint, std::optional dart_entrypoint_library, const std::vector& dart_entrypoint_args, - std::unique_ptr isolate_configuration); + std::unique_ptr isolate_configuration, + std::shared_ptr native_assets_manager); //---------------------------------------------------------------------------- /// @brief Clone the runtime controller. Launching an isolate with a @@ -523,6 +522,15 @@ class RuntimeController : public PlatformConfigurationClient, /// bool HasLivePorts(); + //---------------------------------------------------------------------------- + /// @brief Returns if the root isolate has any pending microtasks. + /// + /// @return True if there are microtasks that have been queued but not + /// run, False otherwise. Return False if the root isolate is not + /// running as well. + /// + bool HasPendingMicrotasks(); + //---------------------------------------------------------------------------- /// @brief Get the last error encountered by the microtask queue. /// diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index 8e2d9f2174b3a..ddceabbf3fb69 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -296,6 +296,9 @@ if (enable_unittests) { } if (test_enable_metal) { + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + sources += [ "shell_test_platform_view_metal.h", "shell_test_platform_view_metal.mm", diff --git a/shell/common/animator_unittests.cc b/shell/common/animator_unittests.cc index a4467371aba3b..1f8bb22db642c 100644 --- a/shell/common/animator_unittests.cc +++ b/shell/common/animator_unittests.cc @@ -88,8 +88,8 @@ TEST_F(ShellTest, VSyncTargetTime) { flutter::PlatformData(), task_runners, settings, [vsync_clock, &create_vsync_waiter](Shell& shell) { return ShellTestPlatformView::Create( - shell, shell.GetTaskRunners(), vsync_clock, create_vsync_waiter, - ShellTestPlatformView::BackendType::kDefaultBackend, nullptr, + ShellTestPlatformView::DefaultBackendType(), shell, + shell.GetTaskRunners(), vsync_clock, create_vsync_waiter, nullptr, shell.GetIsGpuDisabledSyncSwitch()); }, [](Shell& shell) { return std::make_unique(shell); }); diff --git a/shell/common/dl_op_spy.cc b/shell/common/dl_op_spy.cc index 34746ae4698b3..f9671ccab4a73 100644 --- a/shell/common/dl_op_spy.cc +++ b/shell/common/dl_op_spy.cc @@ -11,6 +11,7 @@ bool DlOpSpy::did_draw() { } void DlOpSpy::setColor(DlColor color) { + color_ = color; if (color.isTransparent()) { will_draw_ = false; } else { @@ -19,11 +20,8 @@ void DlOpSpy::setColor(DlColor color) { } void DlOpSpy::setColorSource(const DlColorSource* source) { if (!source) { - return; - } - const DlColorColorSource* color_source = source->asColor(); - if (color_source && color_source->color().isTransparent()) { - will_draw_ = false; + // Restore settings based on previously set color + setColor(color_); return; } will_draw_ = true; diff --git a/shell/common/dl_op_spy.h b/shell/common/dl_op_spy.h index 3c2c63d75f8c9..27fe39554f3b5 100644 --- a/shell/common/dl_op_spy.h +++ b/shell/common/dl_op_spy.h @@ -106,6 +106,9 @@ class DlOpSpy final : public virtual DlOpReceiver, bool transparent_occluder, DlScalar dpr) override; + // Most recently set color, used when color_source goes to null + DlColor color_; + // Indicates if the attributes are set to values that will modify the // destination. For now, the test only checks if there is a non-transparent // color set. diff --git a/shell/common/dl_op_spy_unittests.cc b/shell/common/dl_op_spy_unittests.cc index b04c6d6f176f1..8dc48734da129 100644 --- a/shell/common/dl_op_spy_unittests.cc +++ b/shell/common/dl_op_spy_unittests.cc @@ -84,29 +84,26 @@ TEST(DlOpSpy, SetColorSource) { dl->Dispatch(dl_op_spy); ASSERT_DID_DRAW(dl_op_spy, dl); } - { // Set transparent color. - DisplayListBuilder builder; - DlPaint paint; - auto color = DlColor::kTransparent(); - DlColorColorSource color_source_transparent(color); - paint.setColorSource(color_source_transparent.shared()); - builder.DrawRect(SkRect::MakeWH(5, 5), paint); - sk_sp dl = builder.Build(); + { // setColorSource(null) restores previous color visibility DlOpSpy dl_op_spy; - dl->Dispatch(dl_op_spy); - ASSERT_NO_DRAW(dl_op_spy, dl); - } - { // Set black color. - DisplayListBuilder builder; - DlPaint paint; - auto color = DlColor::kBlack(); - DlColorColorSource color_source_transparent(color); - paint.setColorSource(color_source_transparent.shared()); - builder.DrawRect(SkRect::MakeWH(5, 5), paint); - sk_sp dl = builder.Build(); - DlOpSpy dl_op_spy; - dl->Dispatch(dl_op_spy); - ASSERT_DID_DRAW(dl_op_spy, dl); + DlOpReceiver* receiver = &dl_op_spy; + receiver->setColor(DlColor::kTransparent()); + receiver->drawRect(DlRect::MakeWH(5, 5)); + ASSERT_FALSE(dl_op_spy.did_draw()); + DlColor colors[2] = { + DlColor::kGreen(), + DlColor::kBlue(), + }; + float stops[2] = { + 0.0f, + 1.0f, + }; + auto color_source = DlColorSource::MakeLinear({0, 0}, {10, 10}, 2, colors, + stops, DlTileMode::kClamp); + receiver->setColorSource(color_source.get()); + receiver->setColorSource(nullptr); + receiver->drawRect(DlRect::MakeWH(5, 5)); + ASSERT_FALSE(dl_op_spy.did_draw()); } } diff --git a/shell/common/engine.cc b/shell/common/engine.cc index e524230fb8122..770f778759925 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -10,17 +10,12 @@ #include #include +#include "flutter/assets/native_assets.h" #include "flutter/common/settings.h" -#include "flutter/fml/make_copyable.h" #include "flutter/fml/trace_event.h" -#include "flutter/lib/snapshot/snapshot.h" #include "flutter/lib/ui/text/font_collection.h" #include "flutter/shell/common/animator.h" -#include "flutter/shell/common/platform_view.h" -#include "flutter/shell/common/shell.h" -#include "impeller/runtime_stage/runtime_stage.h" #include "rapidjson/document.h" -#include "third_party/dart/runtime/include/dart_tools_api.h" namespace flutter { @@ -195,6 +190,11 @@ bool Engine::UpdateAssetManager( font_collection_->RegisterTestFonts(); } + if (native_assets_manager_ == nullptr) { + native_assets_manager_ = std::make_shared(); + } + native_assets_manager_->RegisterNativeAssets(asset_manager_); + return true; } @@ -244,7 +244,8 @@ Engine::RunStatus Engine::Run(RunConfiguration configuration) { configuration.GetEntrypoint(), // configuration.GetEntrypointLibrary(), // configuration.GetEntrypointArgs(), // - configuration.TakeIsolateConfiguration()) // + configuration.TakeIsolateConfiguration(), // + native_assets_manager_) // ) { return RunStatus::Failure; } @@ -293,6 +294,10 @@ bool Engine::UIIsolateHasLivePorts() { return runtime_controller_->HasLivePorts(); } +bool Engine::UIIsolateHasPendingMicrotasks() { + return runtime_controller_->HasPendingMicrotasks(); +} + tonic::DartErrorHandleType Engine::GetUIIsolateLastError() { return runtime_controller_->GetLastError(); } diff --git a/shell/common/engine.h b/shell/common/engine.h index 2530b7cd2949f..754539ee825e1 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -25,11 +25,8 @@ #include "flutter/runtime/runtime_controller.h" #include "flutter/runtime/runtime_delegate.h" #include "flutter/shell/common/animator.h" -#include "flutter/shell/common/display_manager.h" -#include "flutter/shell/common/platform_view.h" #include "flutter/shell/common/pointer_data_dispatcher.h" #include "flutter/shell/common/run_configuration.h" -#include "flutter/shell/common/shell_io_manager.h" namespace flutter { @@ -682,6 +679,15 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// bool UIIsolateHasLivePorts(); + /// @brief Another signal of liveness is the presence of microtasks that + /// have been queued by the application but have not yet been + /// executed. Embedders may want to check for pending microtasks + /// and ensure that the microtask queue has been drained before + /// the embedder terminates. + /// + /// @return Check if the root isolate has any pending microtasks. + bool UIIsolateHasPendingMicrotasks(); + //---------------------------------------------------------------------------- /// @brief Errors that are unhandled on the Dart message loop are kept /// for further inspection till the next unhandled error comes @@ -1046,6 +1052,7 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { std::string initial_route_; std::shared_ptr asset_manager_; std::shared_ptr font_collection_; + std::shared_ptr native_assets_manager_; const std::unique_ptr image_decoder_; ImageGeneratorRegistry image_generator_registry_; TaskRunners task_runners_; diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart index 9826d58b95a65..24dc227aa8f40 100644 --- a/shell/common/fixtures/shell_test.dart +++ b/shell/common/fixtures/shell_test.dart @@ -4,6 +4,7 @@ // ignore_for_file: avoid_print +import 'dart:async'; import 'dart:convert' show json, utf8; import 'dart:isolate'; import 'dart:typed_data'; @@ -629,3 +630,23 @@ void renderWarmUpView1and2() { } }); } + +@pragma('vm:entry-point') +void testSemanticsActions() { + PlatformDispatcher.instance.onSemanticsActionEvent = (SemanticsActionEvent action) async { + await null; + Future.value().then((_) { + notifyNative(); + }); + }; +} + +@pragma('vm:entry-point') +void testPointerActions() { + PlatformDispatcher.instance.onPointerDataPacket = (PointerDataPacket pointer) async { + await null; + Future.value().then((_) { + notifyNative(); + }); + }; +} diff --git a/shell/common/shell.cc b/shell/common/shell.cc index e634941db1660..9cc5c13f3199b 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "fml/task_runner.h" #define RAPIDJSON_HAS_STDSTRING 1 #include "flutter/shell/common/shell.h" @@ -28,7 +29,6 @@ #include "flutter/shell/common/skia_event_tracer_impl.h" #include "flutter/shell/common/switches.h" #include "flutter/shell/common/vsync_waiter.h" -#include "impeller/runtime_stage/runtime_stage.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" #include "third_party/dart/runtime/include/dart_tools_api.h" @@ -199,14 +199,7 @@ static impeller::RuntimeStageBackend DetermineRuntimeStageBackend( if (!impeller_context) { return impeller::RuntimeStageBackend::kSkSL; } - switch (impeller_context->GetBackendType()) { - case impeller::Context::BackendType::kMetal: - return impeller::RuntimeStageBackend::kMetal; - case impeller::Context::BackendType::kOpenGLES: - return impeller::RuntimeStageBackend::kOpenGLES; - case impeller::Context::BackendType::kVulkan: - return impeller::RuntimeStageBackend::kVulkan; - } + return impeller_context->GetRuntimeStageBackend(); } std::unique_ptr Shell::CreateShellOnPlatformThread( @@ -710,6 +703,17 @@ bool Shell::EngineHasLivePorts() const { return weak_engine_->UIIsolateHasLivePorts(); } +bool Shell::EngineHasPendingMicrotasks() const { + FML_DCHECK(is_set_up_); + FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); + + if (!weak_engine_) { + return false; + } + + return weak_engine_->UIIsolateHasPendingMicrotasks(); +} + bool Shell::IsSetup() const { return is_set_up_; } @@ -1025,7 +1029,7 @@ void Shell::OnPlatformViewSetViewportMetrics(int64_t view_id, } }); - fml::TaskRunner::RunNowOrPostTask( + fml::TaskRunner::RunNowAndFlushMessages( task_runners_.GetUITaskRunner(), [engine = engine_->GetWeakPtr(), view_id, metrics]() { if (engine) { @@ -1064,24 +1068,16 @@ void Shell::OnPlatformViewDispatchPlatformMessage( } #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG - if (task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()) { - engine_->DispatchPlatformMessage(std::move(message)); - - // Post an empty task to make the UI message loop run its task observers. - // The observers will execute any Dart microtasks queued by the platform - // message handler. - task_runners_.GetUITaskRunner()->PostTask([] {}); - } else { - // The static leak checker gets confused by the use of fml::MakeCopyable. - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - task_runners_.GetUITaskRunner()->PostTask( - fml::MakeCopyable([engine = engine_->GetWeakPtr(), - message = std::move(message)]() mutable { - if (engine) { - engine->DispatchPlatformMessage(std::move(message)); - } - })); - } + // The static leak checker gets confused by the use of fml::MakeCopyable. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + fml::TaskRunner::RunNowAndFlushMessages( + task_runners_.GetUITaskRunner(), + fml::MakeCopyable([engine = engine_->GetWeakPtr(), + message = std::move(message)]() mutable { + if (engine) { + engine->DispatchPlatformMessage(std::move(message)); + } + })); } // |PlatformView::Delegate| @@ -1093,7 +1089,7 @@ void Shell::OnPlatformViewDispatchPointerDataPacket( TRACE_FLOW_BEGIN("flutter", "PointerEvent", next_pointer_flow_id_); FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); - fml::TaskRunner::RunNowOrPostTask( + fml::TaskRunner::RunNowAndFlushMessages( task_runners_.GetUITaskRunner(), fml::MakeCopyable([engine = weak_engine_, packet = std::move(packet), flow_id = next_pointer_flow_id_]() mutable { @@ -1111,7 +1107,7 @@ void Shell::OnPlatformViewDispatchSemanticsAction(int32_t node_id, FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); - fml::TaskRunner::RunNowOrPostTask( + fml::TaskRunner::RunNowAndFlushMessages( task_runners_.GetUITaskRunner(), fml::MakeCopyable([engine = engine_->GetWeakPtr(), node_id, action, args = std::move(args)]() mutable { @@ -1126,12 +1122,13 @@ void Shell::OnPlatformViewSetSemanticsEnabled(bool enabled) { FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); - fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), - [engine = engine_->GetWeakPtr(), enabled] { - if (engine) { - engine->SetSemanticsEnabled(enabled); - } - }); + fml::TaskRunner::RunNowAndFlushMessages( + task_runners_.GetUITaskRunner(), + [engine = engine_->GetWeakPtr(), enabled] { + if (engine) { + engine->SetSemanticsEnabled(enabled); + } + }); } // |PlatformView::Delegate| @@ -1139,12 +1136,12 @@ void Shell::OnPlatformViewSetAccessibilityFeatures(int32_t flags) { FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); - fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), - [engine = engine_->GetWeakPtr(), flags] { - if (engine) { - engine->SetAccessibilityFeatures(flags); - } - }); + fml::TaskRunner::RunNowAndFlushMessages( + task_runners_.GetUITaskRunner(), [engine = engine_->GetWeakPtr(), flags] { + if (engine) { + engine->SetAccessibilityFeatures(flags); + } + }); } // |PlatformView::Delegate| diff --git a/shell/common/shell.h b/shell/common/shell.h index 0032403deaf69..0685bb7a9b5f2 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -358,6 +358,17 @@ class Shell final : public PlatformView::Delegate, /// bool EngineHasLivePorts() const; + //---------------------------------------------------------------------------- + /// @brief Used by embedders to check if the Engine is running and has + /// any microtasks that have been queued but have not yet run. + /// The Flutter tester uses this as a signal that a test is still + /// running. + /// + /// @return Returns if the shell has an engine and the engine has pending + /// microtasks. + /// + bool EngineHasPendingMicrotasks() const; + //---------------------------------------------------------------------------- /// @brief Accessor for the disable GPU SyncSwitch. // |Rasterizer::Delegate| diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 48a18b830f9ce..800d3d11c6d5c 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -65,6 +65,14 @@ void ShellTest::SendPlatformMessage(Shell* shell, shell->OnPlatformViewDispatchPlatformMessage(std::move(message)); } +void ShellTest::SendSemanticsAction(Shell* shell, + int32_t node_id, + SemanticsAction action, + fml::MallocMapping args) { + shell->OnPlatformViewDispatchSemanticsAction(node_id, action, + std::move(args)); +} + void ShellTest::SendEnginePlatformMessage( Shell* shell, std::unique_ptr message) { diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h index 4fb758e55b90b..e980474364ad3 100644 --- a/shell/common/shell_test.h +++ b/shell/common/shell_test.h @@ -90,6 +90,11 @@ class ShellTest : public FixtureTest { void SendPlatformMessage(Shell* shell, std::unique_ptr message); + void SendSemanticsAction(Shell* shell, + int32_t node_id, + SemanticsAction action, + fml::MallocMapping args); + void SendEnginePlatformMessage(Shell* shell, std::unique_ptr message); diff --git a/shell/common/shell_test_external_view_embedder.cc b/shell/common/shell_test_external_view_embedder.cc index dd351b5dfc605..7e0d3b4c410a0 100644 --- a/shell/common/shell_test_external_view_embedder.cc +++ b/shell/common/shell_test_external_view_embedder.cc @@ -76,7 +76,7 @@ void ShellTestExternalViewEmbedder::PushVisitedPlatformView(int64_t view_id) { // |ExternalViewEmbedder| void ShellTestExternalViewEmbedder::PushFilterToVisitedPlatformViews( - const std::shared_ptr& filter, + const std::shared_ptr& filter, const SkRect& filter_rect) { for (int64_t id : visited_platform_views_) { EmbeddedViewParams params = current_composition_params_[id]; diff --git a/shell/common/shell_test_external_view_embedder.h b/shell/common/shell_test_external_view_embedder.h index b000c239435fb..ef63189c92593 100644 --- a/shell/common/shell_test_external_view_embedder.h +++ b/shell/common/shell_test_external_view_embedder.h @@ -72,7 +72,7 @@ class ShellTestExternalViewEmbedder final : public ExternalViewEmbedder { // |ExternalViewEmbedder| void PushFilterToVisitedPlatformViews( - const std::shared_ptr& filter, + const std::shared_ptr& filter, const SkRect& filter_rect) override; // |ExternalViewEmbedder| diff --git a/shell/common/shell_test_platform_view.cc b/shell/common/shell_test_platform_view.cc index 248448f567c7f..6f6a69aed9567 100644 --- a/shell/common/shell_test_platform_view.cc +++ b/shell/common/shell_test_platform_view.cc @@ -4,59 +4,79 @@ #include "flutter/shell/common/shell_test_platform_view.h" -#ifdef SHELL_ENABLE_GL -#include "flutter/shell/common/shell_test_platform_view_gl.h" -#endif // SHELL_ENABLE_GL -#ifdef SHELL_ENABLE_VULKAN -#include "flutter/shell/common/shell_test_platform_view_vulkan.h" -#endif // SHELL_ENABLE_VULKAN -#ifdef SHELL_ENABLE_METAL -#include "flutter/shell/common/shell_test_platform_view_metal.h" -#endif // SHELL_ENABLE_METAL +#include #include "flutter/shell/common/vsync_waiter_fallback.h" -namespace flutter { -namespace testing { +namespace flutter::testing { std::unique_ptr ShellTestPlatformView::Create( + BackendType backend, PlatformView::Delegate& delegate, const TaskRunners& task_runners, const std::shared_ptr& vsync_clock, const CreateVsyncWaiter& create_vsync_waiter, - BackendType backend, const std::shared_ptr& shell_test_external_view_embedder, const std::shared_ptr& is_gpu_disabled_sync_switch) { // TODO(gw280): https://github.com/flutter/flutter/issues/50298 // Make this fully runtime configurable switch (backend) { - case BackendType::kDefaultBackend: -#ifdef SHELL_ENABLE_GL case BackendType::kGLBackend: - return std::make_unique( - delegate, task_runners, vsync_clock, create_vsync_waiter, - shell_test_external_view_embedder); -#endif // SHELL_ENABLE_GL -#ifdef SHELL_ENABLE_VULKAN - case BackendType::kVulkanBackend: - return std::make_unique( - delegate, task_runners, vsync_clock, create_vsync_waiter, - shell_test_external_view_embedder); -#endif // SHELL_ENABLE_VULKAN -#ifdef SHELL_ENABLE_METAL + return CreateGL(delegate, task_runners, vsync_clock, create_vsync_waiter, + shell_test_external_view_embedder, + is_gpu_disabled_sync_switch); case BackendType::kMetalBackend: - return std::make_unique( + return CreateMetal(delegate, task_runners, vsync_clock, + create_vsync_waiter, shell_test_external_view_embedder, + is_gpu_disabled_sync_switch); + case BackendType::kVulkanBackend: + return CreateVulkan( delegate, task_runners, vsync_clock, create_vsync_waiter, shell_test_external_view_embedder, is_gpu_disabled_sync_switch); -#endif // SHELL_ENABLE_METAL - - default: - FML_LOG(FATAL) << "No backends supported for ShellTestPlatformView"; - return nullptr; } } +#ifndef SHELL_ENABLE_GL +std::unique_ptr ShellTestPlatformView::CreateGL( + PlatformView::Delegate& delegate, + const TaskRunners& task_runners, + const std::shared_ptr& vsync_clock, + const CreateVsyncWaiter& create_vsync_waiter, + const std::shared_ptr& + shell_test_external_view_embedder, + const std::shared_ptr& is_gpu_disabled_sync_switch) { + FML_LOG(FATAL) << "OpenGL backend not enabled in this build"; + return nullptr; +} +#endif // SHELL_ENABLE_GL +#ifndef SHELL_ENABLE_METAL +std::unique_ptr ShellTestPlatformView::CreateMetal( + PlatformView::Delegate& delegate, + const TaskRunners& task_runners, + const std::shared_ptr& vsync_clock, + const CreateVsyncWaiter& create_vsync_waiter, + const std::shared_ptr& + shell_test_external_view_embedder, + const std::shared_ptr& is_gpu_disabled_sync_switch) { + FML_LOG(FATAL) << "Metal backend not enabled in this build"; + return nullptr; +} +#endif // SHELL_ENABLE_METAL +#ifndef SHELL_ENABLE_VULKAN +std::unique_ptr ShellTestPlatformView::CreateVulkan( + PlatformView::Delegate& delegate, + const TaskRunners& task_runners, + const std::shared_ptr& vsync_clock, + const CreateVsyncWaiter& create_vsync_waiter, + const std::shared_ptr& + shell_test_external_view_embedder, + const std::shared_ptr& is_gpu_disabled_sync_switch) { + FML_LOG(FATAL) << "Vulkan backend not enabled in this build"; + return nullptr; +} +#endif // SHELL_ENABLE_VULKAN + ShellTestPlatformViewBuilder::ShellTestPlatformViewBuilder(Config config) : config_(std::move(config)) {} @@ -76,15 +96,14 @@ std::unique_ptr ShellTestPlatformViewBuilder::operator()( } }; return ShellTestPlatformView::Create( + config_.rendering_backend, // shell, // task_runners, // vsync_clock, // create_vsync_waiter, // - config_.rendering_backend, // config_.shell_test_external_view_embedder, // shell.GetIsGpuDisabledSyncSwitch() // ); } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/shell/common/shell_test_platform_view.h b/shell/common/shell_test_platform_view.h index 459476c668bf2..de179fdbb2e84 100644 --- a/shell/common/shell_test_platform_view.h +++ b/shell/common/shell_test_platform_view.h @@ -5,28 +5,41 @@ #ifndef FLUTTER_SHELL_COMMON_SHELL_TEST_PLATFORM_VIEW_H_ #define FLUTTER_SHELL_COMMON_SHELL_TEST_PLATFORM_VIEW_H_ +#include + #include "flutter/shell/common/platform_view.h" #include "flutter/shell/common/shell_test_external_view_embedder.h" #include "flutter/shell/common/vsync_waiters_test.h" -namespace flutter { -namespace testing { +namespace flutter::testing { class ShellTestPlatformView : public PlatformView { public: enum class BackendType { - kDefaultBackend = 0, kGLBackend, kVulkanBackend, kMetalBackend, }; + static BackendType DefaultBackendType() { +#if defined(SHELL_ENABLE_GL) + return BackendType::kGLBackend; +#elif defined(SHELL_ENABLE_METAL) + return BackendType::kMetalBackend; +#elif defined(SHELL_ENABLE_VULKAN) + return BackendType::kVulkanBackend; +#else + FML_LOG(FATAL) << "No backend is enabled in this build."; + std::terminate(); +#endif + } + static std::unique_ptr Create( + BackendType backend, PlatformView::Delegate& delegate, const TaskRunners& task_runners, const std::shared_ptr& vsync_clock, const CreateVsyncWaiter& create_vsync_waiter, - BackendType backend, const std::shared_ptr& shell_test_external_view_embedder, const std::shared_ptr& @@ -39,6 +52,34 @@ class ShellTestPlatformView : public PlatformView { const TaskRunners& task_runners) : PlatformView(delegate, task_runners) {} + static std::unique_ptr CreateGL( + PlatformView::Delegate& delegate, + const TaskRunners& task_runners, + const std::shared_ptr& vsync_clock, + const CreateVsyncWaiter& create_vsync_waiter, + const std::shared_ptr& + shell_test_external_view_embedder, + const std::shared_ptr& + is_gpu_disabled_sync_switch); + static std::unique_ptr CreateMetal( + PlatformView::Delegate& delegate, + const TaskRunners& task_runners, + const std::shared_ptr& vsync_clock, + const CreateVsyncWaiter& create_vsync_waiter, + const std::shared_ptr& + shell_test_external_view_embedder, + const std::shared_ptr& + is_gpu_disabled_sync_switch); + static std::unique_ptr CreateVulkan( + PlatformView::Delegate& delegate, + const TaskRunners& task_runners, + const std::shared_ptr& vsync_clock, + const CreateVsyncWaiter& create_vsync_waiter, + const std::shared_ptr& + shell_test_external_view_embedder, + const std::shared_ptr& + is_gpu_disabled_sync_switch); + FML_DISALLOW_COPY_AND_ASSIGN(ShellTestPlatformView); }; @@ -50,7 +91,7 @@ class ShellTestPlatformViewBuilder { std::shared_ptr shell_test_external_view_embedder = nullptr; ShellTestPlatformView::BackendType rendering_backend = - ShellTestPlatformView::BackendType::kDefaultBackend; + ShellTestPlatformView::DefaultBackendType(); }; explicit ShellTestPlatformViewBuilder(Config config); @@ -63,7 +104,6 @@ class ShellTestPlatformViewBuilder { Config config_; }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_SHELL_COMMON_SHELL_TEST_PLATFORM_VIEW_H_ diff --git a/shell/common/shell_test_platform_view_gl.cc b/shell/common/shell_test_platform_view_gl.cc index 7c9dc06d8d854..6719673f665b9 100644 --- a/shell/common/shell_test_platform_view_gl.cc +++ b/shell/common/shell_test_platform_view_gl.cc @@ -22,6 +22,19 @@ static std::vector> ShaderLibraryMappings() { }; } +std::unique_ptr ShellTestPlatformView::CreateGL( + PlatformView::Delegate& delegate, + const TaskRunners& task_runners, + const std::shared_ptr& vsync_clock, + const CreateVsyncWaiter& create_vsync_waiter, + const std::shared_ptr& + shell_test_external_view_embedder, + const std::shared_ptr& is_gpu_disabled_sync_switch) { + return std::make_unique( + delegate, task_runners, vsync_clock, create_vsync_waiter, + shell_test_external_view_embedder); +} + ShellTestPlatformViewGL::ShellTestPlatformViewGL( PlatformView::Delegate& delegate, const TaskRunners& task_runners, diff --git a/shell/common/shell_test_platform_view_metal.h b/shell/common/shell_test_platform_view_metal.h index c5c0a8d454d04..7f3204ddceccf 100644 --- a/shell/common/shell_test_platform_view_metal.h +++ b/shell/common/shell_test_platform_view_metal.h @@ -5,14 +5,16 @@ #ifndef FLUTTER_SHELL_COMMON_SHELL_TEST_PLATFORM_VIEW_METAL_H_ #define FLUTTER_SHELL_COMMON_SHELL_TEST_PLATFORM_VIEW_METAL_H_ -#include "flutter/fml/macros.h" #include "flutter/shell/common/shell_test_platform_view.h" -#include "flutter/shell/gpu/gpu_surface_metal_delegate.h" -namespace flutter { -namespace testing { +#import + +#include "flutter/fml/macros.h" +#include "flutter/shell/gpu/gpu_surface_metal_delegate.h" +#include "flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.h" +#include "flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.h" -class DarwinContextMetal; +namespace flutter::testing { class ShellTestPlatformViewMetal final : public ShellTestPlatformView, public GPUSurfaceMetalDelegate { @@ -30,7 +32,9 @@ class ShellTestPlatformViewMetal final : public ShellTestPlatformView, virtual ~ShellTestPlatformViewMetal() override; private: - const std::unique_ptr metal_context_; + FlutterDarwinContextMetalSkia* skia_context_; + FlutterDarwinContextMetalImpeller* impeller_context_; + id offscreen_texture_; const CreateVsyncWaiter create_vsync_waiter_; const std::shared_ptr vsync_clock_; const std::shared_ptr @@ -70,7 +74,6 @@ class ShellTestPlatformViewMetal final : public ShellTestPlatformView, FML_DISALLOW_COPY_AND_ASSIGN(ShellTestPlatformViewMetal); }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_SHELL_COMMON_SHELL_TEST_PLATFORM_VIEW_METAL_H_ diff --git a/shell/common/shell_test_platform_view_metal.mm b/shell/common/shell_test_platform_view_metal.mm index 94345527bcc31..4639b2a46b597 100644 --- a/shell/common/shell_test_platform_view_metal.mm +++ b/shell/common/shell_test_platform_view_metal.mm @@ -4,67 +4,26 @@ #include "flutter/shell/common/shell_test_platform_view_metal.h" -#import - #include -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/shell/gpu/gpu_surface_metal_impeller.h" #include "flutter/shell/gpu/gpu_surface_metal_skia.h" -#include "flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.h" -#include "flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.h" -namespace flutter { -namespace testing { +FLUTTER_ASSERT_ARC -static fml::scoped_nsprotocol> CreateOffscreenTexture(id device) { - auto descriptor = - [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm - width:800 - height:600 - mipmapped:NO]; - descriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead; - return fml::scoped_nsprotocol>{[device newTextureWithDescriptor:descriptor]}; -} - -// This is out of the header so that shell_test_platform_view_metal.h can be included in -// non-Objective-C TUs. -class DarwinContextMetal { - public: - explicit DarwinContextMetal( - bool impeller, - const std::shared_ptr& is_gpu_disabled_sync_switch) - : context_(impeller ? nil : [[FlutterDarwinContextMetalSkia alloc] initWithDefaultMTLDevice]), - impeller_context_( - impeller ? [[FlutterDarwinContextMetalImpeller alloc] init:is_gpu_disabled_sync_switch] - : nil), - offscreen_texture_(CreateOffscreenTexture( - impeller ? [impeller_context_ context]->GetMTLDevice() : [context_ device])) {} - - ~DarwinContextMetal() = default; - - fml::scoped_nsobject impeller_context() const { - return impeller_context_; - } +namespace flutter::testing { - fml::scoped_nsobject context() const { return context_; } - - fml::scoped_nsprotocol> offscreen_texture() const { return offscreen_texture_; } - - GPUMTLTextureInfo offscreen_texture_info() const { - GPUMTLTextureInfo info = {}; - info.texture_id = 0; - info.texture = reinterpret_cast(offscreen_texture_.get()); - return info; - } - - private: - const fml::scoped_nsobject context_; - const fml::scoped_nsobject impeller_context_; - const fml::scoped_nsprotocol> offscreen_texture_; - - FML_DISALLOW_COPY_AND_ASSIGN(DarwinContextMetal); -}; +std::unique_ptr ShellTestPlatformView::CreateMetal( + PlatformView::Delegate& delegate, + const TaskRunners& task_runners, + const std::shared_ptr& vsync_clock, + const CreateVsyncWaiter& create_vsync_waiter, + const std::shared_ptr& shell_test_external_view_embedder, + const std::shared_ptr& is_gpu_disabled_sync_switch) { + return std::make_unique( + delegate, task_runners, vsync_clock, create_vsync_waiter, shell_test_external_view_embedder, + is_gpu_disabled_sync_switch); +} ShellTestPlatformViewMetal::ShellTestPlatformViewMetal( PlatformView::Delegate& delegate, @@ -75,16 +34,27 @@ GPUMTLTextureInfo offscreen_texture_info() const { const std::shared_ptr& is_gpu_disabled_sync_switch) : ShellTestPlatformView(delegate, task_runners), GPUSurfaceMetalDelegate(MTLRenderTargetType::kMTLTexture), - metal_context_(std::make_unique(GetSettings().enable_impeller, - is_gpu_disabled_sync_switch)), create_vsync_waiter_(std::move(create_vsync_waiter)), vsync_clock_(std::move(vsync_clock)), shell_test_external_view_embedder_(std::move(shell_test_external_view_embedder)) { + id device = nil; if (GetSettings().enable_impeller) { - FML_CHECK([metal_context_->impeller_context() context] != nil); + impeller_context_ = + [[FlutterDarwinContextMetalImpeller alloc] init:is_gpu_disabled_sync_switch]; + FML_CHECK(impeller_context_.context); + device = impeller_context_.context->GetMTLDevice(); } else { - FML_CHECK([metal_context_->context() mainContext] != nil); + skia_context_ = [[FlutterDarwinContextMetalSkia alloc] initWithDefaultMTLDevice]; + FML_CHECK(skia_context_.mainContext); + device = skia_context_.device; } + auto descriptor = + [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm + width:800 + height:600 + mipmapped:NO]; + descriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead; + offscreen_texture_ = [device newTextureWithDescriptor:descriptor]; } ShellTestPlatformViewMetal::~ShellTestPlatformViewMetal() = default; @@ -113,16 +83,16 @@ GPUMTLTextureInfo offscreen_texture_info() const { // |PlatformView| std::unique_ptr ShellTestPlatformViewMetal::CreateRenderingSurface() { if (GetSettings().enable_impeller) { - auto context = [metal_context_->impeller_context() context]; + auto context = impeller_context_.context; return std::make_unique( this, std::make_shared(context, nullptr)); } - return std::make_unique(this, [metal_context_->context() mainContext]); + return std::make_unique(this, skia_context_.mainContext); } // |PlatformView| std::shared_ptr ShellTestPlatformViewMetal::GetImpellerContext() const { - return [metal_context_->impeller_context() context]; + return impeller_context_.context; } // |GPUSurfaceMetalDelegate| @@ -141,7 +111,10 @@ GPUMTLTextureInfo offscreen_texture_info() const { // |GPUSurfaceMetalDelegate| GPUMTLTextureInfo ShellTestPlatformViewMetal::GetMTLTexture(const SkISize& frame_info) const { - return metal_context_->offscreen_texture_info(); + return { + .texture_id = 0, + .texture = (__bridge GPUMTLTextureHandle)offscreen_texture_, + }; } // |GPUSurfaceMetalDelegate| @@ -150,5 +123,4 @@ GPUMTLTextureInfo offscreen_texture_info() const { return true; } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/shell/common/shell_test_platform_view_vulkan.cc b/shell/common/shell_test_platform_view_vulkan.cc index f0989007931b1..27129ed907c35 100644 --- a/shell/common/shell_test_platform_view_vulkan.cc +++ b/shell/common/shell_test_platform_view_vulkan.cc @@ -23,8 +23,20 @@ #include "flutter/vulkan/swiftshader_path.h" #endif -namespace flutter { -namespace testing { +namespace flutter::testing { + +std::unique_ptr ShellTestPlatformView::CreateVulkan( + PlatformView::Delegate& delegate, + const TaskRunners& task_runners, + const std::shared_ptr& vsync_clock, + const CreateVsyncWaiter& create_vsync_waiter, + const std::shared_ptr& + shell_test_external_view_embedder, + const std::shared_ptr& is_gpu_disabled_sync_switch) { + return std::make_unique( + delegate, task_runners, vsync_clock, create_vsync_waiter, + shell_test_external_view_embedder); +} ShellTestPlatformViewVulkan::ShellTestPlatformViewVulkan( PlatformView::Delegate& delegate, @@ -232,5 +244,4 @@ SkMatrix ShellTestPlatformViewVulkan::OffScreenSurface::GetRootTransformation() return matrix; } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/shell/common/shell_test_platform_view_vulkan.h b/shell/common/shell_test_platform_view_vulkan.h index 7ee3f3ed0420d..5ab27fe975407 100644 --- a/shell/common/shell_test_platform_view_vulkan.h +++ b/shell/common/shell_test_platform_view_vulkan.h @@ -15,8 +15,7 @@ #include "third_party/skia/include/gpu/vk/VulkanMemoryAllocator.h" #include "third_party/skia/include/gpu/vk/VulkanTypes.h" -namespace flutter { -namespace testing { +namespace flutter::testing { class ShellTestPlatformViewVulkan : public ShellTestPlatformView { public: @@ -93,7 +92,6 @@ class ShellTestPlatformViewVulkan : public ShellTestPlatformView { FML_DISALLOW_COPY_AND_ASSIGN(ShellTestPlatformViewVulkan); }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_SHELL_COMMON_SHELL_TEST_PLATFORM_VIEW_VULKAN_H_ diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 2149c25f5a279..a38994120cba2 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -5,11 +5,9 @@ #define FML_USED_ON_EMBEDDER #include -#include #include #include #include -#include #include #include #include @@ -21,6 +19,7 @@ #include "assets/asset_resolver.h" #include "assets/directory_asset_bundle.h" #include "common/graphics/persistent_cache.h" +#include "flutter/display_list/effects/dl_image_filter.h" #include "flutter/flow/layers/backdrop_filter_layer.h" #include "flutter/flow/layers/clip_rect_layer.h" #include "flutter/flow/layers/display_list_layer.h" @@ -29,7 +28,6 @@ #include "flutter/flow/layers/transform_layer.h" #include "flutter/fml/backtrace.h" #include "flutter/fml/command_line.h" -#include "flutter/fml/make_copyable.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/synchronization/count_down_latch.h" #include "flutter/fml/synchronization/waitable_event.h" @@ -45,8 +43,10 @@ #include "flutter/shell/common/vsync_waiters_test.h" #include "flutter/shell/version/version.h" #include "flutter/testing/testing.h" +#include "fml/mapping.h" #include "gmock/gmock.h" #include "impeller/core/runtime_types.h" +#include "lib/ui/semantics/semantics_node.h" #include "third_party/rapidjson/include/rapidjson/writer.h" #include "third_party/skia/include/codec/SkCodecAnimation.h" #include "third_party/tonic/converter/dart_converter.h" @@ -314,7 +314,8 @@ class ThreadCheckingAssetResolver : public AssetResolver { // |AssetResolver| std::unique_ptr GetAsMapping( const std::string& asset_name) const override { - if (asset_name == "FontManifest.json") { + if (asset_name == "FontManifest.json" || + asset_name == "NativeAssetsManifest.json") { // This file is loaded directly by the engine. return nullptr; } @@ -499,13 +500,13 @@ TEST_F(ShellTest, // vsync mechanism. We should have better DI in the tests. const auto vsync_clock = std::make_shared(); return ShellTestPlatformView::Create( - shell, shell.GetTaskRunners(), vsync_clock, + ShellTestPlatformView::DefaultBackendType(), shell, + shell.GetTaskRunners(), vsync_clock, [task_runners = shell.GetTaskRunners()]() { return static_cast>( std::make_unique(task_runners)); }, - ShellTestPlatformView::BackendType::kDefaultBackend, nullptr, - shell.GetIsGpuDisabledSyncSwitch()); + nullptr, shell.GetIsGpuDisabledSyncSwitch()); }, [](Shell& shell) { return std::make_unique(shell); }); ASSERT_TRUE(ValidateShell(shell.get())); @@ -988,7 +989,7 @@ TEST_F(ShellTest, PushBackdropFilterToVisitedPlatformViews) { auto clip_rect_layer = std::make_shared( SkRect::MakeLTRB(0, 0, 30, 30), Clip::kHardEdge); transform_layer->Add(clip_rect_layer); - auto filter = std::make_shared(5, 5, DlTileMode::kClamp); + auto filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); auto backdrop_filter_layer = std::make_shared(filter, DlBlendMode::kSrcOver); clip_rect_layer->Add(backdrop_filter_layer); @@ -1003,10 +1004,10 @@ TEST_F(ShellTest, PushBackdropFilterToVisitedPlatformViews) { ASSERT_TRUE(stack_75.is_empty()); ASSERT_FALSE(stack_50.is_empty()); - auto filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp); + auto filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp); auto mutator = *stack_50.Begin(); ASSERT_EQ(mutator->GetType(), MutatorType::kBackdropFilter); - ASSERT_EQ(mutator->GetFilterMutation().GetFilter(), filter); + ASSERT_EQ(mutator->GetFilterMutation().GetFilter(), *filter); // Make sure the filterRect is in global coordinates (contains the (1,1) // translation). ASSERT_EQ(mutator->GetFilterMutation().GetFilterRect(), @@ -4311,6 +4312,71 @@ TEST_F(ShellTest, NavigationMessageDispachedImmediately) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); } +// Verifies a semantics Action will flush the dart event loop. +TEST_F(ShellTest, SemanticsActionsFlushMessageLoop) { + Settings settings = CreateSettingsForFixture(); + ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::kPlatform); + auto task_runner = thread_host.platform_thread->GetTaskRunner(); + TaskRunners task_runners("test", task_runner, task_runner, task_runner, + task_runner); + + EXPECT_EQ(task_runners.GetPlatformTaskRunner(), + task_runners.GetUITaskRunner()); + auto shell = CreateShell(settings, task_runners); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("testSemanticsActions"); + + RunEngine(shell.get(), std::move(configuration)); + fml::CountDownLatch latch(1); + AddNativeCallback( + // The Dart native function names aren't very consistent but this is + // just the native function name of the second vm entrypoint in the + // fixture. + "NotifyNative", + CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); })); + + task_runners.GetPlatformTaskRunner()->PostTask([&] { + SendSemanticsAction(shell.get(), 0, SemanticsAction::kTap, + fml::MallocMapping(nullptr, 0)); + }); + latch.Wait(); + + DestroyShell(std::move(shell), task_runners); + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); +} + +// Verifies a pointer event will flush the dart event loop. +TEST_F(ShellTest, PointerPacketFlushMessageLoop) { + Settings settings = CreateSettingsForFixture(); + ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::kPlatform); + auto task_runner = thread_host.platform_thread->GetTaskRunner(); + TaskRunners task_runners("test", task_runner, task_runner, task_runner, + task_runner); + + EXPECT_EQ(task_runners.GetPlatformTaskRunner(), + task_runners.GetUITaskRunner()); + auto shell = CreateShell(settings, task_runners); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("testPointerActions"); + + RunEngine(shell.get(), std::move(configuration)); + fml::CountDownLatch latch(1); + AddNativeCallback( + // The Dart native function names aren't very consistent but this is + // just the native function name of the second vm entrypoint in the + // fixture. + "NotifyNative", + CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); })); + + DispatchFakePointerData(shell.get(), 23); + latch.Wait(); + + DestroyShell(std::move(shell), task_runners); + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); +} + TEST_F(ShellTest, DiesIfSoftwareRenderingAndImpellerAreEnabledDeathTest) { #if defined(OS_FUCHSIA) GTEST_SKIP() << "Fuchsia"; @@ -4810,7 +4876,7 @@ TEST_F(ShellTest, RuntimeStageBackendWithImpeller) { EXPECT_EQ(backend, impeller::RuntimeStageBackend::kMetal); break; case impeller::Context::BackendType::kOpenGLES: - EXPECT_EQ(backend, impeller::RuntimeStageBackend::kOpenGLES); + EXPECT_EQ(backend, impeller::RuntimeStageBackend::kOpenGLES3); break; case impeller::Context::BackendType::kVulkan: EXPECT_EQ(backend, impeller::RuntimeStageBackend::kVulkan); diff --git a/shell/common/snapshot_controller_impeller.cc b/shell/common/snapshot_controller_impeller.cc index 3da0f1961910f..5bfbd2113e39b 100644 --- a/shell/common/snapshot_controller_impeller.cc +++ b/shell/common/snapshot_controller_impeller.cc @@ -18,6 +18,7 @@ namespace flutter { namespace { + sk_sp DoMakeRasterSnapshot( const sk_sp& display_list, SkISize size, @@ -53,6 +54,27 @@ sk_sp DoMakeRasterSnapshot( DlImage::OwningContext::kRaster); } +sk_sp DoMakeRasterSnapshot( + const sk_sp& display_list, + SkISize size, + const SnapshotController::Delegate& delegate) { + // Ensure that the current thread has a rendering context. This must be done + // before calling GetAiksContext because constructing the AiksContext may + // invoke graphics APIs. + std::unique_ptr pbuffer_surface; + if (delegate.GetSurface()) { + delegate.GetSurface()->MakeRenderContextCurrent(); + } else if (delegate.GetSnapshotSurfaceProducer()) { + pbuffer_surface = + delegate.GetSnapshotSurfaceProducer()->CreateSnapshotSurface(); + if (pbuffer_surface) { + pbuffer_surface->MakeRenderContextCurrent(); + } + } + + return DoMakeRasterSnapshot(display_list, size, delegate.GetAiksContext()); +} + sk_sp DoMakeRasterSnapshot( sk_sp display_list, SkISize picture_size, @@ -112,16 +134,14 @@ void SnapshotControllerImpeller::MakeRasterSnapshot( } #endif callback(DoMakeRasterSnapshot(display_list, picture_size, - GetDelegate().GetAiksContext())); + GetDelegate())); })); } sk_sp SnapshotControllerImpeller::MakeRasterSnapshotSync( sk_sp display_list, SkISize picture_size) { - return DoMakeRasterSnapshot(display_list, picture_size, - GetDelegate().GetIsGpuDisabledSyncSwitch(), - GetDelegate().GetAiksContext()); + return DoMakeRasterSnapshot(display_list, picture_size, GetDelegate()); } void SnapshotControllerImpeller::CacheRuntimeStage( diff --git a/shell/common/switches.cc b/shell/common/switches.cc index b3bac173623e2..9aa9b1528f0d6 100644 --- a/shell/common/switches.cc +++ b/shell/common/switches.cc @@ -443,7 +443,7 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { settings.use_asset_fonts = !command_line.HasOption(FlagForSwitch(Switch::DisableAssetFonts)); -#if FML_OS_IOS && !FML_OS_IOS_SIMULATOR +#if FML_OS_IOS || FML_OS_IOS_SIMULATOR // On these configurations, the Impeller flags are completely ignored with the // default taking hold. #else // FML_OS_IOS && !FML_OS_IOS_SIMULATOR diff --git a/shell/common/vsync_waiter.cc b/shell/common/vsync_waiter.cc index 80074fa3ef982..fdc8c648466e9 100644 --- a/shell/common/vsync_waiter.cc +++ b/shell/common/vsync_waiter.cc @@ -127,7 +127,6 @@ void VsyncWaiter::FireCallback(fml::TimePoint frame_start_time, fml::TaskQueueId ui_task_queue_id = task_runners_.GetUITaskRunner()->GetTaskQueueId(); - task_runners_.GetUITaskRunner()->PostTask( [ui_task_queue_id, callback, flow_identifier, frame_start_time, frame_target_time, pause_secondary_tasks]() { diff --git a/shell/gpu/BUILD.gn b/shell/gpu/BUILD.gn index ebf3bc45e1f71..ace6788674950 100644 --- a/shell/gpu/BUILD.gn +++ b/shell/gpu/BUILD.gn @@ -69,31 +69,39 @@ source_set("gpu_surface_vulkan") { } } -source_set("gpu_surface_metal") { - sources = [ - "gpu_surface_metal_delegate.cc", - "gpu_surface_metal_delegate.h", - "gpu_surface_metal_skia.h", - "gpu_surface_metal_skia.mm", - "gpu_surface_noop.h", - "gpu_surface_noop.mm", - ] +if (shell_enable_metal) { + source_set("gpu_surface_metal") { + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc - public_deps = gpu_common_deps - - if (impeller_enable_metal) { - sources += [ - "gpu_surface_metal_impeller.h", - "gpu_surface_metal_impeller.mm", + sources = [ + "gpu_surface_metal_delegate.cc", + "gpu_surface_metal_delegate.h", + "gpu_surface_metal_skia.h", + "gpu_surface_metal_skia.mm", + "gpu_surface_noop.h", + "gpu_surface_noop.mm", ] - public_deps += [ "//flutter/impeller" ] + public_deps = gpu_common_deps + + if (impeller_enable_metal) { + sources += [ + "gpu_surface_metal_impeller.h", + "gpu_surface_metal_impeller.mm", + ] + + public_deps += [ "//flutter/impeller" ] + } } } if (is_mac) { impeller_component("gpu_surface_metal_unittests") { testonly = true + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + target_type = "executable" sources = [ "gpu_surface_metal_impeller_unittests.mm", diff --git a/shell/gpu/gpu_surface_metal_impeller.h b/shell/gpu/gpu_surface_metal_impeller.h index 02407fd63971b..9236e91367d9b 100644 --- a/shell/gpu/gpu_surface_metal_impeller.h +++ b/shell/gpu/gpu_surface_metal_impeller.h @@ -9,7 +9,6 @@ #include "flutter/flow/surface.h" #include "flutter/fml/macros.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/impeller/display_list/aiks_context.h" #include "flutter/impeller/renderer/backend/metal/context_mtl.h" #include "flutter/shell/gpu/gpu_surface_metal_delegate.h" @@ -36,7 +35,7 @@ class IMPELLER_CA_METAL_LAYER_AVAILABLE GPUSurfaceMetalImpeller const GPUSurfaceMetalDelegate* delegate_; const MTLRenderTargetType render_target_type_; std::shared_ptr aiks_context_; - fml::scoped_nsprotocol> last_texture_; + id last_texture_; // TODO(38466): Refactor GPU surface APIs take into account the fact that an // external view embedder may want to render to the root surface. This is a // hack to make avoid allocating resources for the root surface when an @@ -45,8 +44,8 @@ class IMPELLER_CA_METAL_LAYER_AVAILABLE GPUSurfaceMetalImpeller bool disable_partial_repaint_ = false; // Accumulated damage for each framebuffer; Key is address of underlying // MTLTexture for each drawable - std::shared_ptr> damage_ = - std::make_shared>(); + std::shared_ptr> damage_ = + std::make_shared>(); // |Surface| std::unique_ptr AcquireFrame( diff --git a/shell/gpu/gpu_surface_metal_impeller.mm b/shell/gpu/gpu_surface_metal_impeller.mm index 5ec05628f42bf..b35d509f148ba 100644 --- a/shell/gpu/gpu_surface_metal_impeller.mm +++ b/shell/gpu/gpu_surface_metal_impeller.mm @@ -18,7 +18,7 @@ #include "impeller/renderer/backend/metal/surface_mtl.h" #include "impeller/typographer/backends/skia/typographer_context_skia.h" -static_assert(!__has_feature(objc_arc), "ARC must be disabled."); +static_assert(__has_feature(objc_arc), "ARC must be enabled."); namespace flutter { @@ -80,40 +80,41 @@ std::unique_ptr GPUSurfaceMetalImpeller::AcquireFrameFromCAMetalLayer( const SkISize& frame_size) { - auto layer = delegate_->GetCAMetalLayer(frame_size); - + CAMetalLayer* layer = (__bridge CAMetalLayer*)delegate_->GetCAMetalLayer(frame_size); if (!layer) { FML_LOG(ERROR) << "Invalid CAMetalLayer given by the embedder."; return nullptr; } - auto* mtl_layer = (CAMetalLayer*)layer; - - auto drawable = - impeller::SurfaceMTL::GetMetalDrawableAndValidate(aiks_context_->GetContext(), mtl_layer); + id drawable = + impeller::SurfaceMTL::GetMetalDrawableAndValidate(aiks_context_->GetContext(), layer); if (!drawable) { return nullptr; } if (Settings::kSurfaceDataAccessible) { - last_texture_.reset([drawable.texture retain]); + last_texture_ = drawable.texture; } #ifdef IMPELLER_DEBUG impeller::ContextMTL::Cast(*aiks_context_->GetContext()).GetCaptureManager()->StartCapture(); #endif // IMPELLER_DEBUG - id last_texture = static_cast>(last_texture_); - + __weak id weak_last_texture = last_texture_; + __weak CAMetalLayer* weak_layer = layer; SurfaceFrame::EncodeCallback encode_callback = fml::MakeCopyable([damage = damage_, disable_partial_repaint = disable_partial_repaint_, // aiks_context = aiks_context_, // drawable, // - last_texture, // - mtl_layer // + weak_last_texture, // + weak_layer // ](SurfaceFrame& surface_frame, DlCanvas* canvas) mutable -> bool { - mtl_layer.presentsWithTransaction = surface_frame.submit_info().present_with_transaction; - + id strong_last_texture = weak_last_texture; + CAMetalLayer* strong_layer = weak_layer; + if (!strong_last_texture || !strong_layer) { + return false; + } + strong_layer.presentsWithTransaction = surface_frame.submit_info().present_with_transaction; if (!aiks_context) { return false; } @@ -125,8 +126,7 @@ } if (!disable_partial_repaint && damage) { - uintptr_t texture = reinterpret_cast(last_texture); - + void* texture = (__bridge void*)strong_last_texture; for (auto& entry : *damage) { if (entry.first != texture) { // Accumulate damage for other framebuffers @@ -192,7 +192,7 @@ if (!disable_partial_repaint_) { // Provide accumulated damage to rasterizer (area in current framebuffer that lags behind // front buffer) - uintptr_t texture = reinterpret_cast(drawable.texture); + void* texture = (__bridge void*)drawable.texture; auto i = damage_->find(texture); if (i != damage_->end()) { framebuffer_info.existing_damage = i->second; @@ -214,31 +214,34 @@ std::unique_ptr GPUSurfaceMetalImpeller::AcquireFrameFromMTLTexture( const SkISize& frame_size) { GPUMTLTextureInfo texture_info = delegate_->GetMTLTexture(frame_size); - id mtl_texture = (id)(texture_info.texture); - + id mtl_texture = (__bridge id)texture_info.texture; if (!mtl_texture) { FML_LOG(ERROR) << "Invalid MTLTexture given by the embedder."; return nullptr; } if (Settings::kSurfaceDataAccessible) { - last_texture_.reset([mtl_texture retain]); + last_texture_ = mtl_texture; } #ifdef IMPELLER_DEBUG impeller::ContextMTL::Cast(*aiks_context_->GetContext()).GetCaptureManager()->StartCapture(); #endif // IMPELLER_DEBUG + __weak id weak_texture = mtl_texture; SurfaceFrame::EncodeCallback encode_callback = fml::MakeCopyable([disable_partial_repaint = disable_partial_repaint_, // damage = damage_, aiks_context = aiks_context_, // - mtl_texture // + weak_texture // ](SurfaceFrame& surface_frame, DlCanvas* canvas) mutable -> bool { + id strong_texture = weak_texture; + if (!strong_texture) { + return false; + } if (!aiks_context) { return false; } - auto display_list = surface_frame.BuildDisplayList(); if (!display_list) { FML_LOG(ERROR) << "Could not build display list for surface frame."; @@ -246,8 +249,7 @@ } if (!disable_partial_repaint && damage) { - uintptr_t texture_ptr = reinterpret_cast(mtl_texture); - + void* texture_ptr = (__bridge void*)strong_texture; for (auto& entry : *damage) { if (entry.first != texture_ptr) { // Accumulate damage for other framebuffers @@ -268,7 +270,7 @@ } auto surface = impeller::SurfaceMTL::MakeFromTexture(aiks_context->GetContext(), - mtl_texture, clip_rect); + strong_texture, clip_rect); surface->PresentWithTransaction(surface_frame.submit_info().present_with_transaction); @@ -308,7 +310,7 @@ if (!disable_partial_repaint_) { // Provide accumulated damage to rasterizer (area in current framebuffer that lags behind // front buffer) - uintptr_t texture = reinterpret_cast(mtl_texture); + void* texture = (__bridge void*)mtl_texture; auto i = damage_->find(texture); if (i != damage_->end()) { framebuffer_info.existing_damage = i->second; @@ -362,7 +364,7 @@ if (!(last_texture_ && [last_texture_ conformsToProtocol:@protocol(MTLTexture)])) { return {}; } - id texture = last_texture_.get(); + id texture = last_texture_; int bytesPerPixel = 0; std::string pixel_format; switch (texture.pixelFormat) { diff --git a/shell/gpu/gpu_surface_metal_skia.h b/shell/gpu/gpu_surface_metal_skia.h index 15322c87a41c9..e4fb83127cd87 100644 --- a/shell/gpu/gpu_surface_metal_skia.h +++ b/shell/gpu/gpu_surface_metal_skia.h @@ -40,7 +40,7 @@ class SK_API_AVAILABLE_CA_METAL_LAYER GPUSurfaceMetalSkia : public Surface { // Accumulated damage for each framebuffer; Key is address of underlying // MTLTexture for each drawable - std::map damage_; + std::map damage_; // |Surface| std::unique_ptr AcquireFrame(const SkISize& size) override; diff --git a/shell/gpu/gpu_surface_metal_skia.mm b/shell/gpu/gpu_surface_metal_skia.mm index 085b70419a3df..111c5b244d102 100644 --- a/shell/gpu/gpu_surface_metal_skia.mm +++ b/shell/gpu/gpu_surface_metal_skia.mm @@ -15,7 +15,6 @@ #include "flutter/common/graphics/persistent_cache.h" #include "flutter/fml/make_copyable.h" #include "flutter/fml/platform/darwin/cf_utils.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/fml/trace_event.h" #include "flutter/shell/gpu/gpu_surface_metal_delegate.h" #include "third_party/skia/include/core/SkCanvas.h" @@ -34,7 +33,7 @@ #include "third_party/skia/include/gpu/ganesh/mtl/GrMtlTypes.h" #include "third_party/skia/include/ports/SkCFObject.h" -static_assert(!__has_feature(objc_arc), "ARC must be disabled."); +static_assert(__has_feature(objc_arc), "ARC must be enabled."); namespace flutter { @@ -48,7 +47,7 @@ SkSurfaces::TextureReleaseProc release_proc, SkSurface::ReleaseContext release_context) { GrMtlTextureInfo info; - info.fTexture.reset([texture retain]); + info.fTexture.retain((__bridge GrMTLHandle)texture); GrBackendTexture backend_texture = GrBackendTextures::MakeMtl(texture.width, texture.height, skgpu::Mipmapped::kNo, info); return SkSurfaces::WrapBackendTexture(context, backend_texture, origin, 1, color_type, @@ -127,23 +126,20 @@ std::unique_ptr GPUSurfaceMetalSkia::AcquireFrameFromCAMetalLayer( const SkISize& frame_info) { - auto layer = delegate_->GetCAMetalLayer(frame_info); + CAMetalLayer* layer = (__bridge CAMetalLayer*)delegate_->GetCAMetalLayer(frame_info); if (!layer) { FML_LOG(ERROR) << "Invalid CAMetalLayer given by the embedder."; return nullptr; } - auto* mtl_layer = (CAMetalLayer*)layer; // Get the drawable eagerly, we will need texture object to identify target framebuffer - fml::scoped_nsprotocol> drawable( - reinterpret_cast>([[mtl_layer nextDrawable] retain])); - - if (!drawable.get()) { + id drawable = [layer nextDrawable]; + if (!drawable) { FML_LOG(ERROR) << "Could not obtain drawable from the metal layer."; return nullptr; } - auto surface = CreateSurfaceFromMetalTexture(context_.get(), drawable.get().texture, + auto surface = CreateSurfaceFromMetalTexture(context_.get(), drawable.texture, kTopLeft_GrSurfaceOrigin, // origin kBGRA_8888_SkColorType, // color type nullptr, // colorspace @@ -157,9 +153,10 @@ return nullptr; } + // drawable is a local and needs to be strongly-captured. SurfaceFrame::EncodeCallback encode_callback = - [this, drawable, mtl_layer](const SurfaceFrame& surface_frame, DlCanvas* canvas) -> bool { - mtl_layer.presentsWithTransaction = surface_frame.submit_info().present_with_transaction; + [this, drawable, layer](const SurfaceFrame& surface_frame, DlCanvas* canvas) -> bool { + layer.presentsWithTransaction = surface_frame.submit_info().present_with_transaction; if (canvas == nullptr) { FML_DLOG(ERROR) << "Canvas not available."; return false; @@ -171,7 +168,7 @@ } if (!disable_partial_repaint_) { - uintptr_t texture = reinterpret_cast(drawable.get().texture); + void* texture = (__bridge void*)drawable.texture; for (auto& entry : damage_) { if (entry.first != texture) { // Accumulate damage for other framebuffers @@ -187,10 +184,11 @@ return true; }; + // drawable is a local and needs to be strongly-captured. SurfaceFrame::SubmitCallback submit_callback = [this, drawable](const SurfaceFrame& surface_frame) -> bool { TRACE_EVENT0("flutter", "GPUSurfaceMetal::Submit"); - return delegate_->PresentDrawable(drawable); + return delegate_->PresentDrawable((__bridge GrMTLHandle)drawable); }; SurfaceFrame::FramebufferInfo framebuffer_info; @@ -199,7 +197,7 @@ if (!disable_partial_repaint_) { // Provide accumulated damage to rasterizer (area in current framebuffer that lags behind // front buffer) - uintptr_t texture = reinterpret_cast(drawable.get().texture); + void* texture = (__bridge void*)drawable.texture; auto i = damage_.find(texture); if (i != damage_.end()) { framebuffer_info.existing_damage = i->second; @@ -214,7 +212,7 @@ std::unique_ptr GPUSurfaceMetalSkia::AcquireFrameFromMTLTexture( const SkISize& frame_info) { GPUMTLTextureInfo texture = delegate_->GetMTLTexture(frame_info); - id mtl_texture = (id)(texture.texture); + id mtl_texture = (__bridge id)texture.texture; if (!mtl_texture) { FML_LOG(ERROR) << "Invalid MTLTexture given by the embedder."; diff --git a/shell/gpu/gpu_surface_noop.mm b/shell/gpu/gpu_surface_noop.mm index 31e15d6a43b0f..1b40d51eec858 100644 --- a/shell/gpu/gpu_surface_noop.mm +++ b/shell/gpu/gpu_surface_noop.mm @@ -13,7 +13,7 @@ #include "flutter/fml/mapping.h" #include "flutter/fml/trace_event.h" -static_assert(!__has_feature(objc_arc), "ARC must be disabled."); +static_assert(__has_feature(objc_arc), "ARC must be enabled."); namespace flutter { diff --git a/shell/gpu/gpu_surface_vulkan.cc b/shell/gpu/gpu_surface_vulkan.cc index c6f5c62ca4928..9207bfa506883 100644 --- a/shell/gpu/gpu_surface_vulkan.cc +++ b/shell/gpu/gpu_surface_vulkan.cc @@ -68,18 +68,14 @@ std::unique_ptr GPUSurfaceVulkan::AcquireFrame( return nullptr; } - SurfaceFrame::EncodeCallback encode_callback = - [image = image, delegate = delegate_](const SurfaceFrame&, - DlCanvas* canvas) -> bool { + SurfaceFrame::EncodeCallback encode_callback = [](const SurfaceFrame&, + DlCanvas* canvas) -> bool { if (canvas == nullptr) { FML_DLOG(ERROR) << "Canvas not available."; return false; } - canvas->Flush(); - - return delegate->PresentImage(reinterpret_cast(image.image), - static_cast(image.format)); + return true; }; SurfaceFrame::SubmitCallback submit_callback = diff --git a/shell/gpu/gpu_surface_vulkan_impeller.cc b/shell/gpu/gpu_surface_vulkan_impeller.cc index aea1c9994de88..c0a3fb7344314 100644 --- a/shell/gpu/gpu_surface_vulkan_impeller.cc +++ b/shell/gpu/gpu_surface_vulkan_impeller.cc @@ -4,17 +4,52 @@ #include "flutter/shell/gpu/gpu_surface_vulkan_impeller.h" +#include + #include "flow/surface_frame.h" #include "flutter/fml/make_copyable.h" +#include "fml/trace_event.h" +#include "impeller/core/formats.h" +#include "impeller/core/texture_descriptor.h" #include "impeller/display_list/dl_dispatcher.h" +#include "impeller/renderer/backend/vulkan/command_buffer_vk.h" +#include "impeller/renderer/backend/vulkan/context_vk.h" #include "impeller/renderer/backend/vulkan/surface_context_vk.h" +#include "impeller/renderer/backend/vulkan/swapchain/surface_vk.h" +#include "impeller/renderer/render_target.h" #include "impeller/renderer/surface.h" #include "impeller/typographer/backends/skia/typographer_context_skia.h" namespace flutter { +class WrappedTextureSourceVK : public impeller::TextureSourceVK { + public: + explicit WrappedTextureSourceVK(impeller::vk::Image image, + impeller::vk::ImageView image_view, + impeller::TextureDescriptor desc) + : TextureSourceVK(desc), image_(image), image_view_(image_view) {} + + ~WrappedTextureSourceVK() {} + + private: + impeller::vk::Image GetImage() const override { return image_; } + + impeller::vk::ImageView GetImageView() const override { return image_view_; } + + impeller::vk::ImageView GetRenderTargetView() const override { + return image_view_; + } + + bool IsSwapchainImage() const override { return true; } + + impeller::vk::Image image_; + impeller::vk::ImageView image_view_; +}; + GPUSurfaceVulkanImpeller::GPUSurfaceVulkanImpeller( - std::shared_ptr context) { + GPUSurfaceVulkanDelegate* delegate, + std::shared_ptr context) + : delegate_(delegate) { if (!context || !context->IsValid()) { return; } @@ -27,7 +62,7 @@ GPUSurfaceVulkanImpeller::GPUSurfaceVulkanImpeller( impeller_context_ = std::move(context); aiks_context_ = std::move(aiks_context); - is_valid_ = true; + is_valid_ = !!aiks_context_; } // |Surface| @@ -51,53 +86,193 @@ std::unique_ptr GPUSurfaceVulkanImpeller::AcquireFrame( return nullptr; } - auto& context_vk = impeller::SurfaceContextVK::Cast(*impeller_context_); - std::unique_ptr surface = context_vk.AcquireNextSurface(); + if (delegate_ == nullptr) { + auto& context_vk = impeller::SurfaceContextVK::Cast(*impeller_context_); + std::unique_ptr surface = + context_vk.AcquireNextSurface(); - if (!surface) { - FML_LOG(ERROR) << "No surface available."; - return nullptr; - } + if (!surface) { + FML_LOG(ERROR) << "No surface available."; + return nullptr; + } + + impeller::RenderTarget render_target = surface->GetRenderTarget(); + auto cull_rect = render_target.GetRenderTargetSize(); + + SurfaceFrame::EncodeCallback encode_callback = [aiks_context = + aiks_context_, // + render_target, + cull_rect // + ](SurfaceFrame& surface_frame, DlCanvas* canvas) mutable -> bool { + if (!aiks_context) { + return false; + } + + auto display_list = surface_frame.BuildDisplayList(); + if (!display_list) { + FML_LOG(ERROR) << "Could not build display list for surface frame."; + return false; + } + + SkIRect sk_cull_rect = SkIRect::MakeWH(cull_rect.width, cull_rect.height); + return impeller::RenderToOnscreen(aiks_context->GetContentContext(), // + render_target, // + display_list, // + sk_cull_rect, // + /*reset_host_buffer=*/true // + ); + }; + + return std::make_unique( + nullptr, // surface + SurfaceFrame::FramebufferInfo{}, // framebuffer info + encode_callback, // encode callback + fml::MakeCopyable([surface = std::move(surface)](const SurfaceFrame&) { + return surface->Present(); + }), // submit callback + size, // frame size + nullptr, // context result + true // display list fallback + ); + } else { + FlutterVulkanImage flutter_image = delegate_->AcquireImage(size); + if (!flutter_image.image) { + FML_LOG(ERROR) << "Invalid VkImage given by the embedder."; + return nullptr; + } + impeller::vk::Format vk_format = + static_cast(flutter_image.format); + std::optional format = + impeller::VkFormatToImpellerFormat(vk_format); + if (!format.has_value()) { + FML_LOG(ERROR) << "Unsupported pixel format: " + << impeller::vk::to_string(vk_format); + return nullptr; + } + + impeller::vk::Image vk_image = + impeller::vk::Image(reinterpret_cast(flutter_image.image)); + + impeller::TextureDescriptor desc; + desc.format = format.value(); + desc.size = impeller::ISize{size.width(), size.height()}; + desc.storage_mode = impeller::StorageMode::kDevicePrivate; + desc.mip_count = 1; + desc.compression_type = impeller::CompressionType::kLossless; + desc.usage = impeller::TextureUsage::kRenderTarget; - auto cull_rect = surface->GetRenderTarget().GetRenderTargetSize(); + impeller::ContextVK& context_vk = + impeller::ContextVK::Cast(*impeller_context_); - impeller::RenderTarget render_target = surface->GetRenderTarget(); + impeller::vk::ImageViewCreateInfo view_info = {}; + view_info.viewType = impeller::vk::ImageViewType::e2D; + view_info.format = ToVKImageFormat(desc.format); + view_info.subresourceRange.aspectMask = + impeller::vk::ImageAspectFlagBits::eColor; + view_info.subresourceRange.baseMipLevel = 0u; + view_info.subresourceRange.baseArrayLayer = 0u; + view_info.subresourceRange.levelCount = 1; + view_info.subresourceRange.layerCount = 1; + view_info.image = vk_image; - SurfaceFrame::EncodeCallback encode_callback = [aiks_context = - aiks_context_, // - render_target, - cull_rect // - ](SurfaceFrame& surface_frame, DlCanvas* canvas) mutable -> bool { - if (!aiks_context) { - return false; + auto [result, image_view] = + context_vk.GetDevice().createImageView(view_info); + if (result != impeller::vk::Result::eSuccess) { + FML_LOG(ERROR) << "Failed to create image view for provided image: " + << impeller::vk::to_string(result); + return nullptr; } - auto display_list = surface_frame.BuildDisplayList(); - if (!display_list) { - FML_LOG(ERROR) << "Could not build display list for surface frame."; - return false; + if (transients_ == nullptr) { + transients_ = std::make_shared( + impeller_context_, desc, + /*enable_msaa=*/true); } - SkIRect sk_cull_rect = SkIRect::MakeWH(cull_rect.width, cull_rect.height); - return impeller::RenderToOnscreen(aiks_context->GetContentContext(), // - render_target, // - display_list, // - sk_cull_rect, // - /*reset_host_buffer=*/true // + auto wrapped_onscreen = + std::make_shared(vk_image, image_view, desc); + auto surface = impeller::SurfaceVK::WrapSwapchainImage( + transients_, wrapped_onscreen, [&]() -> bool { return true; }); + impeller::RenderTarget render_target = surface->GetRenderTarget(); + auto cull_rect = render_target.GetRenderTargetSize(); + + SurfaceFrame::EncodeCallback encode_callback = [aiks_context = + aiks_context_, // + render_target, + cull_rect // + ](SurfaceFrame& surface_frame, DlCanvas* canvas) mutable -> bool { + if (!aiks_context) { + return false; + } + + auto display_list = surface_frame.BuildDisplayList(); + if (!display_list) { + FML_LOG(ERROR) << "Could not build display list for surface frame."; + return false; + } + + SkIRect sk_cull_rect = SkIRect::MakeWH(cull_rect.width, cull_rect.height); + return impeller::RenderToOnscreen(aiks_context->GetContentContext(), // + render_target, // + display_list, // + sk_cull_rect, // + /*reset_host_buffer=*/true // + ); + }; + + SurfaceFrame::SubmitCallback submit_callback = + [image = flutter_image, delegate = delegate_, + impeller_context = impeller_context_, + wrapped_onscreen](const SurfaceFrame&) -> bool { + TRACE_EVENT0("flutter", "GPUSurfaceVulkan::PresentImage"); + + { + const auto& context = impeller::ContextVK::Cast(*impeller_context); + + //---------------------------------------------------------------------------- + /// Transition the image to color-attachment-optimal. + /// + auto cmd_buffer = context.CreateCommandBuffer(); + + auto vk_final_cmd_buffer = + impeller::CommandBufferVK::Cast(*cmd_buffer).GetCommandBuffer(); + { + impeller::BarrierVK barrier; + barrier.new_layout = + impeller::vk::ImageLayout::eColorAttachmentOptimal; + barrier.cmd_buffer = vk_final_cmd_buffer; + barrier.src_access = + impeller::vk::AccessFlagBits::eColorAttachmentWrite; + barrier.src_stage = + impeller::vk::PipelineStageFlagBits::eColorAttachmentOutput; + barrier.dst_access = {}; + barrier.dst_stage = + impeller::vk::PipelineStageFlagBits::eBottomOfPipe; + + if (!wrapped_onscreen->SetLayout(barrier).ok()) { + return false; + } + } + if (!context.GetCommandQueue()->Submit({cmd_buffer}).ok()) { + return false; + } + } + + return delegate->PresentImage(reinterpret_cast(image.image), + static_cast(image.format)); + }; + + SurfaceFrame::FramebufferInfo framebuffer_info{.supports_readback = true}; + + return std::make_unique(nullptr, // surface + framebuffer_info, // framebuffer info + encode_callback, // encode callback + submit_callback, + size, // frame size + nullptr, // context result + true // display list fallback ); - }; - - return std::make_unique( - nullptr, // surface - SurfaceFrame::FramebufferInfo{}, // framebuffer info - encode_callback, // encode callback - fml::MakeCopyable([surface = std::move(surface)](const SurfaceFrame&) { - return surface->Present(); - }), // submit callback - size, // frame size - nullptr, // context result - true // display list fallback - ); + } } // |Surface| diff --git a/shell/gpu/gpu_surface_vulkan_impeller.h b/shell/gpu/gpu_surface_vulkan_impeller.h index 41d296a58cb7f..602761db0cfce 100644 --- a/shell/gpu/gpu_surface_vulkan_impeller.h +++ b/shell/gpu/gpu_surface_vulkan_impeller.h @@ -12,12 +12,14 @@ #include "flutter/impeller/display_list/aiks_context.h" #include "flutter/impeller/renderer/context.h" #include "flutter/shell/gpu/gpu_surface_vulkan_delegate.h" +#include "impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.h" namespace flutter { class GPUSurfaceVulkanImpeller final : public Surface { public: - explicit GPUSurfaceVulkanImpeller(std::shared_ptr context); + explicit GPUSurfaceVulkanImpeller(GPUSurfaceVulkanDelegate* delegate, + std::shared_ptr context); // |Surface| ~GPUSurfaceVulkanImpeller() override; @@ -26,8 +28,10 @@ class GPUSurfaceVulkanImpeller final : public Surface { bool IsValid() override; private: + GPUSurfaceVulkanDelegate* delegate_; std::shared_ptr impeller_context_; std::shared_ptr aiks_context_; + std::shared_ptr transients_; bool is_valid_ = false; // |Surface| diff --git a/shell/platform/android/AndroidManifest.xml b/shell/platform/android/AndroidManifest.xml index cbc2aa2e8cde7..1f8e23631865b 100644 --- a/shell/platform/android/AndroidManifest.xml +++ b/shell/platform/android/AndroidManifest.xml @@ -3,28 +3,13 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> - + - - - - - - - - - diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index a0745b4107fe7..4b6ea453c09ed 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -207,13 +207,6 @@ android_java_sources = [ "io/flutter/Build.java", "io/flutter/FlutterInjector.java", "io/flutter/Log.java", - "io/flutter/app/FlutterActivity.java", - "io/flutter/app/FlutterActivityDelegate.java", - "io/flutter/app/FlutterActivityEvents.java", - "io/flutter/app/FlutterApplication.java", - "io/flutter/app/FlutterFragmentActivity.java", - "io/flutter/app/FlutterPlayStoreSplitApplication.java", - "io/flutter/app/FlutterPluginRegistry.java", "io/flutter/embedding/android/AndroidTouchProcessor.java", "io/flutter/embedding/android/ExclusiveAppComponent.java", "io/flutter/embedding/android/FlutterActivity.java", @@ -273,8 +266,6 @@ android_java_sources = [ "io/flutter/embedding/engine/plugins/service/ServiceAware.java", "io/flutter/embedding/engine/plugins/service/ServiceControlSurface.java", "io/flutter/embedding/engine/plugins/service/ServicePluginBinding.java", - "io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java", - "io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java", "io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java", "io/flutter/embedding/engine/renderer/FlutterRenderer.java", "io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java", @@ -356,10 +347,7 @@ android_java_sources = [ "io/flutter/view/AccessibilityBridge.java", "io/flutter/view/AccessibilityViewEmbedder.java", "io/flutter/view/FlutterCallbackInformation.java", - "io/flutter/view/FlutterMain.java", - "io/flutter/view/FlutterNativeView.java", "io/flutter/view/FlutterRunArguments.java", - "io/flutter/view/FlutterView.java", "io/flutter/view/TextureRegistry.java", "io/flutter/view/VsyncWaiter.java", ] diff --git a/shell/platform/android/android_context_gl_impeller.cc b/shell/platform/android/android_context_gl_impeller.cc index 79091a108f9c1..31ad0a5551391 100644 --- a/shell/platform/android/android_context_gl_impeller.cc +++ b/shell/platform/android/android_context_gl_impeller.cc @@ -11,6 +11,8 @@ #include "flutter/impeller/toolkit/egl/surface.h" #include "impeller/entity/gles/entity_shaders_gles.h" #include "impeller/entity/gles/framebuffer_blend_shaders_gles.h" +#include "impeller/entity/gles3/entity_shaders_gles.h" +#include "impeller/entity/gles3/framebuffer_blend_shaders_gles.h" namespace flutter { @@ -55,8 +57,10 @@ static std::shared_ptr CreateImpellerContext( FML_LOG(ERROR) << "Could not create OpenGL proc table."; return nullptr; } + bool is_gles3 = proc_table->GetDescription()->GetGlVersion().IsAtLeast( + impeller::Version{3, 0, 0}); - std::vector> shader_mappings = { + std::vector> gles2_shader_mappings = { std::make_shared( impeller_entity_shaders_gles_data, impeller_entity_shaders_gles_length), @@ -65,8 +69,19 @@ static std::shared_ptr CreateImpellerContext( impeller_framebuffer_blend_shaders_gles_length), }; + std::vector> gles3_shader_mappings = { + std::make_shared( + impeller_entity_shaders_gles3_data, + impeller_entity_shaders_gles3_length), + std::make_shared( + impeller_framebuffer_blend_shaders_gles3_data, + impeller_framebuffer_blend_shaders_gles3_length), + }; + auto context = impeller::ContextGLES::Create( - std::move(proc_table), shader_mappings, enable_gpu_tracing); + std::move(proc_table), + is_gles3 ? gles3_shader_mappings : gles2_shader_mappings, + enable_gpu_tracing); if (!context) { FML_LOG(ERROR) << "Could not create OpenGLES Impeller Context."; return nullptr; @@ -92,15 +107,21 @@ AndroidContextGLImpeller::AndroidContextGLImpeller( } impeller::egl::ConfigDescriptor desc; - desc.api = impeller::egl::API::kOpenGLES2; + desc.api = impeller::egl::API::kOpenGLES3; desc.color_format = impeller::egl::ColorFormat::kRGBA8888; - desc.depth_bits = impeller::egl::DepthBits::kZero; + desc.depth_bits = impeller::egl::DepthBits::kTwentyFour; desc.stencil_bits = impeller::egl::StencilBits::kEight; desc.samples = impeller::egl::Samples::kFour; desc.surface_type = impeller::egl::SurfaceType::kWindow; std::unique_ptr onscreen_config = display_->ChooseConfig(desc); + + if (!onscreen_config) { + desc.api = impeller::egl::API::kOpenGLES2; + onscreen_config = display_->ChooseConfig(desc); + } + if (!onscreen_config) { // Fallback for Android emulator. desc.samples = impeller::egl::Samples::kOne; diff --git a/shell/platform/android/android_context_gl_impeller_unittests.cc b/shell/platform/android/android_context_gl_impeller_unittests.cc index 6d8243e8dd2cb..b74c068de093a 100644 --- a/shell/platform/android/android_context_gl_impeller_unittests.cc +++ b/shell/platform/android/android_context_gl_impeller_unittests.cc @@ -38,6 +38,8 @@ bool GetEGLConfigForSurface(EGLint surface_bit, EGLConfig* result) { EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, EGL_NONE, // clang-format on }; @@ -95,28 +97,38 @@ TEST_F(AndroidContextGLImpellerTest, FallbackForEmulator) { auto display = std::make_unique(); EXPECT_CALL(*display, IsValid).WillRepeatedly(Return(true)); std::unique_ptr first_result; - auto second_result = - std::make_unique(ConfigDescriptor(), window_egl_config); + std::unique_ptr second_result; auto third_result = + std::make_unique(ConfigDescriptor(), window_egl_config); + auto fourth_result = std::make_unique(ConfigDescriptor(), pbuffer_egl_config); EXPECT_CALL( *display, ChooseConfig(Matcher(AllOf( + Field(&ConfigDescriptor::api, impeller::egl::API::kOpenGLES3), Field(&ConfigDescriptor::samples, impeller::egl::Samples::kFour), Field(&ConfigDescriptor::surface_type, impeller::egl::SurfaceType::kWindow))))) .WillOnce(Return(ByMove(std::move(first_result)))); + EXPECT_CALL( + *display, + ChooseConfig(Matcher(AllOf( + Field(&ConfigDescriptor::api, impeller::egl::API::kOpenGLES2), + Field(&ConfigDescriptor::samples, impeller::egl::Samples::kFour), + Field(&ConfigDescriptor::surface_type, + impeller::egl::SurfaceType::kWindow))))) + .WillOnce(Return(ByMove(std::move(second_result)))); EXPECT_CALL( *display, ChooseConfig(Matcher( AllOf(Field(&ConfigDescriptor::samples, impeller::egl::Samples::kOne), Field(&ConfigDescriptor::surface_type, impeller::egl::SurfaceType::kWindow))))) - .WillOnce(Return(ByMove(std::move(second_result)))); + .WillOnce(Return(ByMove(std::move(third_result)))); EXPECT_CALL(*display, ChooseConfig(Matcher( Field(&ConfigDescriptor::surface_type, impeller::egl::SurfaceType::kPBuffer)))) - .WillOnce(Return(ByMove(std::move(third_result)))); + .WillOnce(Return(ByMove(std::move(fourth_result)))); ON_CALL(*display, ChooseConfig(_)) .WillByDefault(Return(ByMove(std::unique_ptr()))); auto context = diff --git a/shell/platform/android/android_context_gl_unittests.cc b/shell/platform/android/android_context_gl_unittests.cc index 5173f59d673db..33416e0182264 100644 --- a/shell/platform/android/android_context_gl_unittests.cc +++ b/shell/platform/android/android_context_gl_unittests.cc @@ -13,6 +13,7 @@ #include "fml/logging.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "impeller/core/runtime_types.h" #include "shell/platform/android/context/android_context.h" namespace flutter { @@ -84,6 +85,10 @@ class TestImpellerContext : public impeller::Context { void Shutdown() override { did_shutdown = true; } + impeller::RuntimeStageBackend GetRuntimeStageBackend() const override { + return impeller::RuntimeStageBackend::kVulkan; + } + bool did_shutdown = false; }; diff --git a/shell/platform/android/android_surface_gl_impeller.cc b/shell/platform/android/android_surface_gl_impeller.cc index b2d0b69d9a9cf..405f7f2711f63 100644 --- a/shell/platform/android/android_surface_gl_impeller.cc +++ b/shell/platform/android/android_surface_gl_impeller.cc @@ -81,7 +81,24 @@ bool AndroidSurfaceGLImpeller::SetNativeWindow( // |AndroidSurface| std::unique_ptr AndroidSurfaceGLImpeller::CreateSnapshotSurface() { - FML_UNREACHABLE(); + if (!onscreen_surface_ || !onscreen_surface_->IsValid()) { + onscreen_surface_ = android_context_->CreateOffscreenSurface(); + if (!onscreen_surface_) { + FML_DLOG(ERROR) << "Could not create offscreen surface for snapshot."; + return nullptr; + } + } + // Make the snapshot surface current because constucting a + // GPUSurfaceGLImpeller and its AiksContext may invoke graphics APIs. + if (!android_context_->OnscreenContextMakeCurrent(onscreen_surface_.get())) { + FML_DLOG(ERROR) << "Could not make snapshot surface current."; + return nullptr; + } + return std::make_unique( + this, // delegate + android_context_->GetImpellerContext(), // context + true // render to surface + ); } // |AndroidSurface| diff --git a/shell/platform/android/android_surface_vk_impeller.cc b/shell/platform/android/android_surface_vk_impeller.cc index 8dc12a78ac479..69d9575128015 100644 --- a/shell/platform/android/android_surface_vk_impeller.cc +++ b/shell/platform/android/android_surface_vk_impeller.cc @@ -25,7 +25,7 @@ AndroidSurfaceVKImpeller::AndroidSurfaceVKImpeller( impeller::ContextVK::Cast(*android_context->GetImpellerContext()); surface_context_vk_ = context_vk.CreateSurfaceContext(); eager_gpu_surface_ = - std::make_unique(surface_context_vk_); + std::make_unique(nullptr, surface_context_vk_); } AndroidSurfaceVKImpeller::~AndroidSurfaceVKImpeller() = default; @@ -57,7 +57,7 @@ std::unique_ptr AndroidSurfaceVKImpeller::CreateGPUSurface( } std::unique_ptr gpu_surface = - std::make_unique(surface_context_vk_); + std::make_unique(nullptr, surface_context_vk_); if (!gpu_surface->IsValid()) { return nullptr; diff --git a/shell/platform/android/build.gradle b/shell/platform/android/build.gradle index 9f8b409965b3f..11dd5f89c4474 100644 --- a/shell/platform/android/build.gradle +++ b/shell/platform/android/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:7.4.2" + classpath "com.android.tools.build:gradle:8.7.2" } } @@ -20,6 +20,7 @@ repositories { apply plugin: "com.android.library" android { + namespace "io.flutter.embedding" compileSdk 35 defaultConfig { @@ -52,7 +53,7 @@ android { implementation "androidx.test:core:1.4.0" implementation "com.google.android.play:core:1.8.0" implementation "com.ibm.icu:icu4j:69.1" - implementation "org.robolectric:robolectric:4.12.1" + implementation "org.robolectric:robolectric:4.14.1" implementation "junit:junit:4.13.2" implementation "androidx.test.ext:junit:1.1.4-alpha07" diff --git a/shell/platform/android/flutter_main.cc b/shell/platform/android/flutter_main.cc index 184a2e98d4bd9..4159d3713c647 100644 --- a/shell/platform/android/flutter_main.cc +++ b/shell/platform/android/flutter_main.cc @@ -240,7 +240,7 @@ AndroidRenderingAPI FlutterMain::SelectedRenderingAPI( return AndroidRenderingAPI::kSoftware; } constexpr AndroidRenderingAPI kVulkanUnsupportedFallback = - AndroidRenderingAPI::kSkiaOpenGLES; + AndroidRenderingAPI::kImpellerOpenGLES; // Debug/Profile only functionality for testing a specific // backend configuration. diff --git a/shell/platform/android/image_external_texture_gl_impeller.cc b/shell/platform/android/image_external_texture_gl_impeller.cc index f14fea8882beb..7b2e5ce706016 100644 --- a/shell/platform/android/image_external_texture_gl_impeller.cc +++ b/shell/platform/android/image_external_texture_gl_impeller.cc @@ -38,8 +38,10 @@ sk_sp ImageExternalTextureGLImpeller::CreateDlImage( static_cast(bounds.height())}; desc.mip_count = 1; auto texture = std::make_shared( - impeller_context_->GetReactor(), desc, - impeller::TextureGLES::IsWrapped::kWrapped); + impeller_context_->GetReactor(), desc); + // The contents will be initialized later in the call to + // `glEGLImageTargetTexture2DOES` instead of by Impeller. + texture->MarkContentsInitialized(); texture->SetCoordinateSystem( impeller::TextureCoordinateSystem::kUploadFromHost); if (!texture->Bind()) { diff --git a/shell/platform/android/io/flutter/app/FlutterActivity.java b/shell/platform/android/io/flutter/app/FlutterActivity.java deleted file mode 100644 index 932ad2c5ada57..0000000000000 --- a/shell/platform/android/io/flutter/app/FlutterActivity.java +++ /dev/null @@ -1,181 +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. - -package io.flutter.app; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.Bundle; -import androidx.annotation.NonNull; -import io.flutter.app.FlutterActivityDelegate.ViewFactory; -import io.flutter.plugin.common.PluginRegistry; -import io.flutter.view.FlutterNativeView; -import io.flutter.view.FlutterView; - -/** - * Deprecated base class for activities that use Flutter. - * - * @deprecated {@link io.flutter.embedding.android.FlutterActivity} is the new API that now replaces - * this class. See https://flutter.dev/go/android-project-migration for more migration details. - */ -@Deprecated -public class FlutterActivity extends Activity - implements FlutterView.Provider, PluginRegistry, ViewFactory { - private static final String TAG = "FlutterActivity"; - - private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this); - - // These aliases ensure that the methods we forward to the delegate adhere - // to relevant interfaces versus just existing in FlutterActivityDelegate. - private final FlutterActivityEvents eventDelegate = delegate; - private final FlutterView.Provider viewProvider = delegate; - private final PluginRegistry pluginRegistry = delegate; - - /** - * Returns the Flutter view used by this activity; will be null before {@link #onCreate(Bundle)} - * is called. - */ - @Override - public FlutterView getFlutterView() { - return viewProvider.getFlutterView(); - } - - /** - * Hook for subclasses to customize the creation of the {@code FlutterView}. - * - *

The default implementation returns {@code null}, which will cause the activity to use a - * newly instantiated full-screen view. - */ - @Override - public FlutterView createFlutterView(Context context) { - return null; - } - - /** - * Hook for subclasses to customize the creation of the {@code FlutterNativeView}. - * - *

The default implementation returns {@code null}, which will cause the activity to use a - * newly instantiated native view object. - */ - @Override - public FlutterNativeView createFlutterNativeView() { - return null; - } - - @Override - public boolean retainFlutterNativeView() { - return false; - } - - @Override - public final boolean hasPlugin(String key) { - return pluginRegistry.hasPlugin(key); - } - - @Override - public final T valuePublishedByPlugin(String pluginKey) { - return pluginRegistry.valuePublishedByPlugin(pluginKey); - } - - @Override - public final Registrar registrarFor(String pluginKey) { - return pluginRegistry.registrarFor(pluginKey); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - eventDelegate.onCreate(savedInstanceState); - } - - @Override - protected void onStart() { - super.onStart(); - eventDelegate.onStart(); - } - - @Override - protected void onResume() { - super.onResume(); - eventDelegate.onResume(); - } - - @Override - protected void onDestroy() { - eventDelegate.onDestroy(); - super.onDestroy(); - } - - @Override - public void onBackPressed() { - if (!eventDelegate.onBackPressed()) { - super.onBackPressed(); - } - } - - @Override - protected void onStop() { - eventDelegate.onStop(); - super.onStop(); - } - - @Override - protected void onPause() { - super.onPause(); - eventDelegate.onPause(); - } - - @Override - protected void onPostResume() { - super.onPostResume(); - eventDelegate.onPostResume(); - } - - // @Override - added in API level 23 - public void onRequestPermissionsResult( - int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - eventDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (!eventDelegate.onActivityResult(requestCode, resultCode, data)) { - super.onActivityResult(requestCode, resultCode, data); - } - } - - @Override - protected void onNewIntent(Intent intent) { - eventDelegate.onNewIntent(intent); - } - - @Override - public void onUserLeaveHint() { - eventDelegate.onUserLeaveHint(); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - eventDelegate.onWindowFocusChanged(hasFocus); - } - - @Override - public void onTrimMemory(int level) { - eventDelegate.onTrimMemory(level); - } - - @Override - public void onLowMemory() { - eventDelegate.onLowMemory(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - eventDelegate.onConfigurationChanged(newConfig); - } -} diff --git a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java deleted file mode 100644 index fcb553bbe8a2f..0000000000000 --- a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java +++ /dev/null @@ -1,495 +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. - -package io.flutter.app; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.app.Activity; -import android.app.Application; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Configuration; -import android.content.res.Resources.NotFoundException; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager.LayoutParams; -import io.flutter.Log; -import io.flutter.plugin.common.PluginRegistry; -import io.flutter.plugin.platform.PlatformPlugin; -import io.flutter.util.Preconditions; -import io.flutter.view.FlutterMain; -import io.flutter.view.FlutterNativeView; -import io.flutter.view.FlutterRunArguments; -import io.flutter.view.FlutterView; -import java.util.ArrayList; - -/** - * Deprecated class that performs the actual work of tying Android {@link android.app.Activity} - * instances to Flutter. - * - *

This exists as a dedicated class (as opposed to being integrated directly into {@link - * FlutterActivity}) to facilitate applications that don't wish to subclass {@code FlutterActivity}. - * The most obvious example of when this may come in handy is if an application wishes to subclass - * the Android v4 support library's {@code FragmentActivity}. - * - *

Usage: - * - *

To wire this class up to your activity, simply forward the events defined in {@link - * FlutterActivityEvents} from your activity to an instance of this class. Optionally, you can make - * your activity implement {@link PluginRegistry} and/or {@link - * io.flutter.view.FlutterView.Provider} and forward those methods to this class as well. - * - * @deprecated {@link io.flutter.embedding.android.FlutterActivity} is the new API that now replaces - * this class and {@link io.flutter.app.FlutterActivity}. See - * https://flutter.dev/go/android-project-migration for more migration details. - */ -@Deprecated -public final class FlutterActivityDelegate - implements FlutterActivityEvents, FlutterView.Provider, PluginRegistry { - private static final String SPLASH_SCREEN_META_DATA_KEY = - "io.flutter.app.android.SplashScreenUntilFirstFrame"; - private static final String TAG = "FlutterActivityDelegate"; - private static final LayoutParams matchParent = - new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - - /** - * Specifies the mechanism by which Flutter views are created during the operation of a {@code - * FlutterActivityDelegate}. - * - *

A delegate's view factory will be consulted during {@link #onCreate(Bundle)}. If it returns - * {@code null}, then the delegate will fall back to instantiating a new full-screen {@code - * FlutterView}. - * - *

A delegate's native view factory will be consulted during {@link #onCreate(Bundle)}. If it - * returns {@code null}, then the delegate will fall back to instantiating a new {@code - * FlutterNativeView}. This is useful for applications to override to reuse the FlutterNativeView - * held e.g. by a pre-existing background service. - */ - public interface ViewFactory { - FlutterView createFlutterView(Context context); - - FlutterNativeView createFlutterNativeView(); - - /** - * Hook for subclasses to indicate that the {@code FlutterNativeView} returned by {@link - * #createFlutterNativeView()} should not be destroyed when this activity is destroyed. - * - * @return Whether the FlutterNativeView is retained. - */ - boolean retainFlutterNativeView(); - } - - private final Activity activity; - private final ViewFactory viewFactory; - private FlutterView flutterView; - private View launchView; - - public FlutterActivityDelegate(Activity activity, ViewFactory viewFactory) { - this.activity = Preconditions.checkNotNull(activity); - this.viewFactory = Preconditions.checkNotNull(viewFactory); - } - - @Override - public FlutterView getFlutterView() { - return flutterView; - } - - // The implementation of PluginRegistry forwards to flutterView. - @Override - public boolean hasPlugin(String key) { - return flutterView.getPluginRegistry().hasPlugin(key); - } - - @Override - @SuppressWarnings("unchecked") - public T valuePublishedByPlugin(String pluginKey) { - return (T) flutterView.getPluginRegistry().valuePublishedByPlugin(pluginKey); - } - - @Override - public Registrar registrarFor(String pluginKey) { - return flutterView.getPluginRegistry().registrarFor(pluginKey); - } - - @Override - public boolean onRequestPermissionsResult( - int requestCode, String[] permissions, int[] grantResults) { - return flutterView - .getPluginRegistry() - .onRequestPermissionsResult(requestCode, permissions, grantResults); - } - - @Override - public boolean onActivityResult(int requestCode, int resultCode, Intent data) { - return flutterView.getPluginRegistry().onActivityResult(requestCode, resultCode, data); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - Window window = activity.getWindow(); - window.addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(0x40000000); - window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI); - - String[] args = getArgsFromIntent(activity.getIntent()); - FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args); - - flutterView = viewFactory.createFlutterView(activity); - if (flutterView == null) { - FlutterNativeView nativeView = viewFactory.createFlutterNativeView(); - flutterView = new FlutterView(activity, null, nativeView); - flutterView.setLayoutParams(matchParent); - activity.setContentView(flutterView); - launchView = createLaunchView(); - if (launchView != null) { - addLaunchView(); - } - } - - if (loadIntent(activity.getIntent())) { - return; - } - - String appBundlePath = FlutterMain.findAppBundlePath(); - if (appBundlePath != null) { - runBundle(appBundlePath); - } - } - - @Override - public void onNewIntent(Intent intent) { - // Only attempt to reload the Flutter Dart code during development. Use - // the debuggable flag as an indicator that we are in development mode. - if (!isDebuggable() || !loadIntent(intent)) { - flutterView.getPluginRegistry().onNewIntent(intent); - } - } - - private boolean isDebuggable() { - return (activity.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - } - - @Override - public void onPause() { - Application app = (Application) activity.getApplicationContext(); - if (app instanceof FlutterApplication) { - FlutterApplication flutterApp = (FlutterApplication) app; - if (activity.equals(flutterApp.getCurrentActivity())) { - flutterApp.setCurrentActivity(null); - } - } - if (flutterView != null) { - flutterView.onPause(); - } - } - - @Override - public void onStart() { - if (flutterView != null) { - flutterView.onStart(); - } - } - - @Override - public void onResume() { - Application app = (Application) activity.getApplicationContext(); - if (app instanceof FlutterApplication) { - FlutterApplication flutterApp = (FlutterApplication) app; - flutterApp.setCurrentActivity(activity); - } - } - - @Override - public void onStop() { - flutterView.onStop(); - } - - @Override - public void onPostResume() { - if (flutterView != null) { - flutterView.onPostResume(); - } - } - - @Override - public void onDestroy() { - Application app = (Application) activity.getApplicationContext(); - if (app instanceof FlutterApplication) { - FlutterApplication flutterApp = (FlutterApplication) app; - if (activity.equals(flutterApp.getCurrentActivity())) { - flutterApp.setCurrentActivity(null); - } - } - if (flutterView != null) { - final boolean detach = - flutterView.getPluginRegistry().onViewDestroy(flutterView.getFlutterNativeView()); - if (detach || viewFactory.retainFlutterNativeView()) { - // Detach, but do not destroy the FlutterView if a plugin - // expressed interest in its FlutterNativeView. - flutterView.detach(); - } else { - flutterView.destroy(); - } - } - } - - @Override - public boolean onBackPressed() { - if (flutterView != null) { - flutterView.popRoute(); - return true; - } - return false; - } - - @Override - public void onUserLeaveHint() { - flutterView.getPluginRegistry().onUserLeaveHint(); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - flutterView.getPluginRegistry().onWindowFocusChanged(hasFocus); - } - - @Override - public void onTrimMemory(int level) { - // Use a trim level delivered while the application is running so the - // framework has a chance to react to the notification. - if (level == TRIM_MEMORY_RUNNING_LOW) { - flutterView.onMemoryPressure(); - } - } - - @Override - public void onLowMemory() { - flutterView.onMemoryPressure(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) {} - - private static String[] getArgsFromIntent(Intent intent) { - // Before adding more entries to this list, consider that arbitrary - // Android applications can generate intents with extra data and that - // there are many security-sensitive args in the binary. - ArrayList args = new ArrayList<>(); - if (intent.getBooleanExtra("trace-startup", false)) { - args.add("--trace-startup"); - } - if (intent.getBooleanExtra("start-paused", false)) { - args.add("--start-paused"); - } - if (intent.getBooleanExtra("disable-service-auth-codes", false)) { - args.add("--disable-service-auth-codes"); - } - if (intent.getBooleanExtra("use-test-fonts", false)) { - args.add("--use-test-fonts"); - } - if (intent.getBooleanExtra("enable-dart-profiling", false)) { - args.add("--enable-dart-profiling"); - } - if (intent.getBooleanExtra("enable-software-rendering", false)) { - args.add("--enable-software-rendering"); - } - if (intent.getBooleanExtra("skia-deterministic-rendering", false)) { - args.add("--skia-deterministic-rendering"); - } - if (intent.getBooleanExtra("trace-skia", false)) { - args.add("--trace-skia"); - } - if (intent.getBooleanExtra("trace-systrace", false)) { - args.add("--trace-systrace"); - } - if (intent.hasExtra("trace-to-file")) { - args.add("--trace-to-file=" + intent.getStringExtra("trace-to-file")); - } - if (intent.getBooleanExtra("dump-skp-on-shader-compilation", false)) { - args.add("--dump-skp-on-shader-compilation"); - } - if (intent.getBooleanExtra("cache-sksl", false)) { - args.add("--cache-sksl"); - } - if (intent.getBooleanExtra("purge-persistent-cache", false)) { - args.add("--purge-persistent-cache"); - } - if (intent.getBooleanExtra("verbose-logging", false)) { - args.add("--verbose-logging"); - } - int vmServicePort = intent.getIntExtra("vm-service-port", 0); - if (vmServicePort > 0) { - args.add("--vm-service-port=" + Integer.toString(vmServicePort)); - } else { - // TODO(bkonyi): remove once flutter_tools no longer uses this option. - // See https://github.com/dart-lang/sdk/issues/50233 - vmServicePort = intent.getIntExtra("observatory-port", 0); - if (vmServicePort > 0) { - args.add("--vm-service-port=" + Integer.toString(vmServicePort)); - } - } - if (intent.getBooleanExtra("endless-trace-buffer", false)) { - args.add("--endless-trace-buffer"); - } - // NOTE: all flags provided with this argument are subject to filtering - // based on a list of allowed flags in shell/common/switches.cc. If any - // flag provided is not allowed, the process will immediately terminate. - if (intent.hasExtra("dart-flags")) { - args.add("--dart-flags=" + intent.getStringExtra("dart-flags")); - } - if (!args.isEmpty()) { - String[] argsArray = new String[args.size()]; - return args.toArray(argsArray); - } - return null; - } - - private boolean loadIntent(Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_RUN.equals(action)) { - String route = intent.getStringExtra("route"); - String appBundlePath = intent.getDataString(); - if (appBundlePath == null) { - // Fall back to the installation path if no bundle path was specified. - appBundlePath = FlutterMain.findAppBundlePath(); - } - if (route != null) { - flutterView.setInitialRoute(route); - } - - runBundle(appBundlePath); - return true; - } - - return false; - } - - private void runBundle(String appBundlePath) { - if (!flutterView.getFlutterNativeView().isApplicationRunning()) { - FlutterRunArguments args = new FlutterRunArguments(); - args.bundlePath = appBundlePath; - args.entrypoint = "main"; - flutterView.runFromBundle(args); - } - } - - /** - * Creates a {@link View} containing the same {@link Drawable} as the one set as the {@code - * windowBackground} of the parent activity for use as a launch splash view. - * - *

Returns null if no {@code windowBackground} is set for the activity. - */ - private View createLaunchView() { - if (!showSplashScreenUntilFirstFrame()) { - return null; - } - final Drawable launchScreenDrawable = getLaunchScreenDrawableFromActivityTheme(); - if (launchScreenDrawable == null) { - return null; - } - final View view = new View(activity); - view.setLayoutParams(matchParent); - view.setBackground(launchScreenDrawable); - return view; - } - - /** - * Extracts a {@link Drawable} from the parent activity's {@code windowBackground}. - * - *

{@code android:windowBackground} is specifically reused instead of a other attributes - * because the Android framework can display it fast enough when launching the app as opposed to - * anything defined in the Activity subclass. - * - *

Returns null if no {@code windowBackground} is set for the activity. - */ - @SuppressWarnings("deprecation") - private Drawable getLaunchScreenDrawableFromActivityTheme() { - TypedValue typedValue = new TypedValue(); - if (!activity.getTheme().resolveAttribute(android.R.attr.windowBackground, typedValue, true)) { - return null; - } - if (typedValue.resourceId == 0) { - return null; - } - try { - return activity.getResources().getDrawable(typedValue.resourceId); - } catch (NotFoundException e) { - Log.e(TAG, "Referenced launch screen windowBackground resource does not exist"); - return null; - } - } - - /** - * Let the user specify whether the activity's {@code windowBackground} is a launch screen and - * should be shown until the first frame via a tag in the activity. - */ - private Boolean showSplashScreenUntilFirstFrame() { - try { - ActivityInfo activityInfo = - activity - .getPackageManager() - .getActivityInfo(activity.getComponentName(), PackageManager.GET_META_DATA); - Bundle metadata = activityInfo.metaData; - return metadata != null && metadata.getBoolean(SPLASH_SCREEN_META_DATA_KEY); - } catch (NameNotFoundException e) { - return false; - } - } - - /** - * Show and then automatically animate out the launch view. - * - *

If a launch screen is defined in the user application's AndroidManifest.xml as the - * activity's {@code windowBackground}, display it on top of the {@link FlutterView} and remove - * the activity's {@code windowBackground}. - * - *

Fade it out and remove it when the {@link FlutterView} renders its first frame. - */ - private void addLaunchView() { - if (launchView == null) { - return; - } - - activity.addContentView(launchView, matchParent); - flutterView.addFirstFrameListener( - new FlutterView.FirstFrameListener() { - @Override - public void onFirstFrame() { - FlutterActivityDelegate.this - .launchView - .animate() - .alpha(0f) - // Use Android's default animation duration. - .setListener( - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Views added to an Activity's addContentView is always added to its - // root FrameLayout. - ((ViewGroup) FlutterActivityDelegate.this.launchView.getParent()) - .removeView(FlutterActivityDelegate.this.launchView); - FlutterActivityDelegate.this.launchView = null; - } - }); - - FlutterActivityDelegate.this.flutterView.removeFirstFrameListener(this); - } - }); - - // Resets the activity theme from the one containing the launch screen in the window - // background to a blank one since the launch screen is now in a view in front of the - // FlutterView. - // - // We can make this configurable if users want it. - activity.setTheme(android.R.style.Theme_Black_NoTitleBar); - } -} diff --git a/shell/platform/android/io/flutter/app/FlutterActivityEvents.java b/shell/platform/android/io/flutter/app/FlutterActivityEvents.java deleted file mode 100644 index abbdec4fe6afa..0000000000000 --- a/shell/platform/android/io/flutter/app/FlutterActivityEvents.java +++ /dev/null @@ -1,73 +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. - -package io.flutter.app; - -import android.content.ComponentCallbacks2; -import android.content.Intent; -import android.os.Bundle; -import io.flutter.plugin.common.PluginRegistry.ActivityResultListener; -import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener; - -/** - * A collection of Android {@code Activity} methods that are relevant to the core operation of - * Flutter applications. - * - *

Application authors that use an activity other than {@link FlutterActivity} should forward all - * events herein from their activity to an instance of {@link FlutterActivityDelegate} in order to - * wire the activity up to the Flutter framework. This forwarding is already provided in {@code - * FlutterActivity}. - */ -public interface FlutterActivityEvents - extends ComponentCallbacks2, ActivityResultListener, RequestPermissionsResultListener { - /** - * @param savedInstanceState If the activity is being re-initialized after previously being shut - * down then this Bundle contains the data it most recently supplied in {@code - * onSaveInstanceState(Bundle)}. - * @see android.app.Activity#onCreate(android.os.Bundle) - */ - void onCreate(Bundle savedInstanceState); - - /** - * @param intent The new intent that was started for the activity. - * @see android.app.Activity#onNewIntent(Intent) - */ - void onNewIntent(Intent intent); - - /** @see android.app.Activity#onPause() */ - void onPause(); - - /** @see android.app.Activity#onStart() */ - void onStart(); - - /** @see android.app.Activity#onResume() */ - void onResume(); - - /** @see android.app.Activity#onPostResume() */ - void onPostResume(); - - /** @see android.app.Activity#onDestroy() */ - void onDestroy(); - - /** @see android.app.Activity#onStop() */ - void onStop(); - - /** - * Invoked when the activity has detected the user's press of the back key. - * - * @return {@code true} if the listener handled the event; {@code false} to let the activity - * continue with its default back button handling. - * @see android.app.Activity#onBackPressed() - */ - boolean onBackPressed(); - - /** @see android.app.Activity#onUserLeaveHint() */ - void onUserLeaveHint(); - - /** - * @param hasFocus True if the current activity window has focus. - * @see android.app.Activity#onWindowFocusChanged(boolean) - */ - void onWindowFocusChanged(boolean hasFocus); -} diff --git a/shell/platform/android/io/flutter/app/FlutterApplication.java b/shell/platform/android/io/flutter/app/FlutterApplication.java deleted file mode 100644 index a211c268548cd..0000000000000 --- a/shell/platform/android/io/flutter/app/FlutterApplication.java +++ /dev/null @@ -1,36 +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. - -package io.flutter.app; - -import android.app.Activity; -import android.app.Application; -import androidx.annotation.CallSuper; -import io.flutter.FlutterInjector; - -/** - * Flutter implementation of {@link android.app.Application}, managing application-level global - * initializations. - * - *

Using this {@link android.app.Application} is not required when using APIs in the package - * {@code io.flutter.embedding.android} since they self-initialize on first use. - */ -public class FlutterApplication extends Application { - @Override - @CallSuper - public void onCreate() { - super.onCreate(); - FlutterInjector.instance().flutterLoader().startInitialization(this); - } - - private Activity mCurrentActivity = null; - - public Activity getCurrentActivity() { - return mCurrentActivity; - } - - public void setCurrentActivity(Activity mCurrentActivity) { - this.mCurrentActivity = mCurrentActivity; - } -} diff --git a/shell/platform/android/io/flutter/app/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/app/FlutterFragmentActivity.java deleted file mode 100644 index 778bab65bcf00..0000000000000 --- a/shell/platform/android/io/flutter/app/FlutterFragmentActivity.java +++ /dev/null @@ -1,181 +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. - -package io.flutter.app; - -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.Bundle; -import androidx.fragment.app.FragmentActivity; -import io.flutter.app.FlutterActivityDelegate.ViewFactory; -import io.flutter.plugin.common.PluginRegistry; -import io.flutter.view.FlutterNativeView; -import io.flutter.view.FlutterView; - -/** - * Deprecated class for activities that use Flutter who also require the use of the Android v4 - * Support library's {@link FragmentActivity}. - * - *

Applications that don't have this need will likely want to use {@link FlutterActivity} - * instead. - * - *

Important! Flutter does not bundle the necessary Android v4 Support library - * classes for this class to work at runtime. It is the responsibility of the app developer using - * this class to ensure that they link against the v4 support library .jar file when creating their - * app to ensure that {@link FragmentActivity} is available at runtime. - * - * @see https://developer.android.com/training/testing/set-up-project - * @deprecated this class is replaced by {@link - * io.flutter.embedding.android.FlutterFragmentActivity}. - */ -@Deprecated -public class FlutterFragmentActivity extends FragmentActivity - implements FlutterView.Provider, PluginRegistry, ViewFactory { - private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this); - - // These aliases ensure that the methods we forward to the delegate adhere - // to relevant interfaces versus just existing in FlutterActivityDelegate. - private final FlutterActivityEvents eventDelegate = delegate; - private final FlutterView.Provider viewProvider = delegate; - private final PluginRegistry pluginRegistry = delegate; - - /** - * Returns the Flutter view used by this activity; will be null before {@link #onCreate(Bundle)} - * is called. - */ - @Override - public FlutterView getFlutterView() { - return viewProvider.getFlutterView(); - } - - /** - * Hook for subclasses to customize the creation of the {@code FlutterView}. - * - *

The default implementation returns {@code null}, which will cause the activity to use a - * newly instantiated full-screen view. - */ - @Override - public FlutterView createFlutterView(Context context) { - return null; - } - - @Override - public FlutterNativeView createFlutterNativeView() { - return null; - } - - @Override - public boolean retainFlutterNativeView() { - return false; - } - - @Override - public final boolean hasPlugin(String key) { - return pluginRegistry.hasPlugin(key); - } - - @Override - public final T valuePublishedByPlugin(String pluginKey) { - return pluginRegistry.valuePublishedByPlugin(pluginKey); - } - - @Override - public final Registrar registrarFor(String pluginKey) { - return pluginRegistry.registrarFor(pluginKey); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - eventDelegate.onCreate(savedInstanceState); - } - - @Override - protected void onDestroy() { - eventDelegate.onDestroy(); - super.onDestroy(); - } - - @Override - public void onBackPressed() { - if (!eventDelegate.onBackPressed()) { - super.onBackPressed(); - } - } - - @Override - protected void onStart() { - super.onStart(); - eventDelegate.onStart(); - } - - @Override - protected void onStop() { - eventDelegate.onStop(); - super.onStop(); - } - - @Override - protected void onPause() { - super.onPause(); - eventDelegate.onPause(); - } - - @Override - protected void onPostResume() { - super.onPostResume(); - eventDelegate.onPostResume(); - } - - @Override - public void onRequestPermissionsResult( - int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - eventDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (!eventDelegate.onActivityResult(requestCode, resultCode, data)) { - super.onActivityResult(requestCode, resultCode, data); - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - eventDelegate.onNewIntent(intent); - } - - @Override - @SuppressWarnings("MissingSuperCall") - public void onUserLeaveHint() { - eventDelegate.onUserLeaveHint(); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - eventDelegate.onWindowFocusChanged(hasFocus); - } - - @Override - public void onTrimMemory(int level) { - super.onTrimMemory(level); - eventDelegate.onTrimMemory(level); - } - - @Override - public void onLowMemory() { - eventDelegate.onLowMemory(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - eventDelegate.onConfigurationChanged(newConfig); - } -} diff --git a/shell/platform/android/io/flutter/app/FlutterPlayStoreSplitApplication.java b/shell/platform/android/io/flutter/app/FlutterPlayStoreSplitApplication.java deleted file mode 100644 index 7a415d71ff5e6..0000000000000 --- a/shell/platform/android/io/flutter/app/FlutterPlayStoreSplitApplication.java +++ /dev/null @@ -1,53 +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. - -package io.flutter.app; - -import androidx.annotation.CallSuper; -import com.google.android.play.core.splitcompat.SplitCompatApplication; -import io.flutter.FlutterInjector; -import io.flutter.embedding.engine.deferredcomponents.PlayStoreDeferredComponentManager; - -/** - * Flutter's extension of {@link SplitCompatApplication} that injects a {@link - * PlayStoreDeferredComponentManager} with {@link FlutterInjector} to enable Split AOT Flutter apps. - * - *

To use this class, either have your custom application class extend - * FlutterPlayStoreSplitApplication or use it directly in the app's AndroidManifest.xml by adding - * the following line: - * - *

{@code
- * 
- *    
- *  
- * }
- * - * This class is meant to be used with the Google Play store. Custom non-play store applications do - * not need to extend SplitCompatApplication and should inject a custom {@link - * io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager} implementation like so: - * - *
{@code
- * FlutterInjector.setInstance(
- *      new FlutterInjector.Builder().setDeferredComponentManager(yourCustomManager).build());
- * }
- */ -public class FlutterPlayStoreSplitApplication extends SplitCompatApplication { - @Override - @CallSuper - public void onCreate() { - super.onCreate(); - // Create and inject a PlayStoreDeferredComponentManager, which is the default manager for - // interacting with the Google Play Store. - PlayStoreDeferredComponentManager deferredComponentManager = - new PlayStoreDeferredComponentManager(this, null); - FlutterInjector.setInstance( - new FlutterInjector.Builder() - .setDeferredComponentManager(deferredComponentManager) - .build()); - } -} diff --git a/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java b/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java deleted file mode 100644 index 887a9118979cd..0000000000000 --- a/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java +++ /dev/null @@ -1,259 +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. - -package io.flutter.app; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.PluginRegistry; -import io.flutter.plugin.platform.PlatformViewRegistry; -import io.flutter.plugin.platform.PlatformViewsController; -import io.flutter.view.FlutterMain; -import io.flutter.view.FlutterNativeView; -import io.flutter.view.FlutterView; -import io.flutter.view.TextureRegistry; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** @deprecated See https://flutter.dev/go/android-project-migration for migration instructions. */ -@Deprecated -public class FlutterPluginRegistry - implements PluginRegistry, - PluginRegistry.RequestPermissionsResultListener, - PluginRegistry.ActivityResultListener, - PluginRegistry.NewIntentListener, - PluginRegistry.WindowFocusChangedListener, - PluginRegistry.UserLeaveHintListener, - PluginRegistry.ViewDestroyListener { - private static final String TAG = "FlutterPluginRegistry"; - - private Activity mActivity; - private Context mAppContext; - private FlutterNativeView mNativeView; - private FlutterView mFlutterView; - - private final PlatformViewsController mPlatformViewsController; - private final Map mPluginMap = new LinkedHashMap<>(0); - private final List mRequestPermissionsResultListeners = - new ArrayList<>(0); - private final List mActivityResultListeners = new ArrayList<>(0); - private final List mNewIntentListeners = new ArrayList<>(0); - private final List mUserLeaveHintListeners = new ArrayList<>(0); - private final List mWindowFocusChangedListeners = new ArrayList<>(0); - private final List mViewDestroyListeners = new ArrayList<>(0); - - public FlutterPluginRegistry(FlutterNativeView nativeView, Context context) { - mNativeView = nativeView; - mAppContext = context; - mPlatformViewsController = new PlatformViewsController(); - } - - public FlutterPluginRegistry(FlutterEngine engine, Context context) { - // TODO(mattcarroll): implement use of engine instead of nativeView. - mAppContext = context; - mPlatformViewsController = new PlatformViewsController(); - } - - @Override - public boolean hasPlugin(String key) { - return mPluginMap.containsKey(key); - } - - @Override - @SuppressWarnings("unchecked") - public T valuePublishedByPlugin(String pluginKey) { - return (T) mPluginMap.get(pluginKey); - } - - @Override - public Registrar registrarFor(String pluginKey) { - if (mPluginMap.containsKey(pluginKey)) { - throw new IllegalStateException("Plugin key " + pluginKey + " is already in use"); - } - mPluginMap.put(pluginKey, null); - return new FlutterRegistrar(pluginKey); - } - - public void attach(FlutterView flutterView, Activity activity) { - mFlutterView = flutterView; - mActivity = activity; - mPlatformViewsController.attach(activity, flutterView, flutterView.getDartExecutor()); - } - - public void detach() { - mPlatformViewsController.detach(); - mPlatformViewsController.onDetachedFromJNI(); - mFlutterView = null; - mActivity = null; - } - - public void onPreEngineRestart() { - mPlatformViewsController.onPreEngineRestart(); - } - - public PlatformViewsController getPlatformViewsController() { - return mPlatformViewsController; - } - - private class FlutterRegistrar implements Registrar { - private final String pluginKey; - - FlutterRegistrar(String pluginKey) { - this.pluginKey = pluginKey; - } - - @Override - public Activity activity() { - return mActivity; - } - - @Override - public Context context() { - return mAppContext; - } - - @Override - public Context activeContext() { - return (mActivity != null) ? mActivity : mAppContext; - } - - @Override - public BinaryMessenger messenger() { - return mNativeView; - } - - @Override - public TextureRegistry textures() { - return mFlutterView; - } - - @Override - public PlatformViewRegistry platformViewRegistry() { - return mPlatformViewsController.getRegistry(); - } - - @Override - public FlutterView view() { - return mFlutterView; - } - - @Override - public String lookupKeyForAsset(String asset) { - return FlutterMain.getLookupKeyForAsset(asset); - } - - @Override - public String lookupKeyForAsset(String asset, String packageName) { - return FlutterMain.getLookupKeyForAsset(asset, packageName); - } - - @Override - public Registrar publish(Object value) { - mPluginMap.put(pluginKey, value); - return this; - } - - @Override - public Registrar addRequestPermissionsResultListener( - RequestPermissionsResultListener listener) { - mRequestPermissionsResultListeners.add(listener); - return this; - } - - @Override - public Registrar addActivityResultListener(ActivityResultListener listener) { - mActivityResultListeners.add(listener); - return this; - } - - @Override - public Registrar addNewIntentListener(NewIntentListener listener) { - mNewIntentListeners.add(listener); - return this; - } - - @Override - public Registrar addUserLeaveHintListener(UserLeaveHintListener listener) { - mUserLeaveHintListeners.add(listener); - return this; - } - - @Override - public Registrar addWindowFocusChangedListener(WindowFocusChangedListener listener) { - mWindowFocusChangedListeners.add(listener); - return this; - } - - @Override - public Registrar addViewDestroyListener(ViewDestroyListener listener) { - mViewDestroyListeners.add(listener); - return this; - } - } - - @Override - public boolean onRequestPermissionsResult( - int requestCode, String[] permissions, int[] grantResults) { - for (RequestPermissionsResultListener listener : mRequestPermissionsResultListeners) { - if (listener.onRequestPermissionsResult(requestCode, permissions, grantResults)) { - return true; - } - } - return false; - } - - @Override - public boolean onActivityResult(int requestCode, int resultCode, Intent data) { - for (ActivityResultListener listener : mActivityResultListeners) { - if (listener.onActivityResult(requestCode, resultCode, data)) { - return true; - } - } - return false; - } - - @Override - public boolean onNewIntent(Intent intent) { - for (NewIntentListener listener : mNewIntentListeners) { - if (listener.onNewIntent(intent)) { - return true; - } - } - return false; - } - - @Override - public void onUserLeaveHint() { - for (UserLeaveHintListener listener : mUserLeaveHintListeners) { - listener.onUserLeaveHint(); - } - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - for (WindowFocusChangedListener listener : mWindowFocusChangedListeners) { - listener.onWindowFocusChanged(hasFocus); - } - } - - @Override - public boolean onViewDestroy(FlutterNativeView view) { - boolean handled = false; - for (ViewDestroyListener listener : mViewDestroyListeners) { - if (listener.onViewDestroy(view)) { - handled = true; - } - } - return handled; - } - - public void destroy() { - mPlatformViewsController.onDetachedFromJNI(); - } -} diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index f5794335b74f4..b46a8e702954f 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -212,7 +212,7 @@ public class FlutterActivity extends Activity implements FlutterActivityAndFragmentDelegate.Host, LifecycleOwner { private static final String TAG = "FlutterActivity"; - private boolean hasRegisteredBackCallback = false; + @VisibleForTesting boolean hasRegisteredBackCallback = false; /** * The ID of the {@code FlutterView} created by this activity. @@ -634,6 +634,13 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + boolean frameworkHandlesBack = + savedInstanceState.getBoolean( + FlutterActivityAndFragmentDelegate.ON_BACK_CALLBACK_ENABLED_KEY); + setFrameworkHandlesBack(frameworkHandlesBack); + } + delegate = new FlutterActivityAndFragmentDelegate(this); delegate.onAttach(this); delegate.onRestoreInstanceState(savedInstanceState); @@ -1477,6 +1484,11 @@ public boolean attachToEngineAutomatically() { return true; } + @Override + public boolean getBackCallbackState() { + return hasRegisteredBackCallback; + } + @Override public boolean popSystemNavigator() { // Hook for subclass. No-op if returns false. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index c576eacda8c0c..630d3dff4f4c9 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -76,6 +76,7 @@ private static final String TAG = "FlutterActivityAndFragmentDelegate"; private static final String FRAMEWORK_RESTORATION_BUNDLE_KEY = "framework"; private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins"; + static final String ON_BACK_CALLBACK_ENABLED_KEY = "enableOnBackInvokedCallbackState"; private static final int FLUTTER_SPLASH_VIEW_FALLBACK_ID = 486947586; /** Factory to obtain a FlutterActivityAndFragmentDelegate instance. */ @@ -691,6 +692,12 @@ void onSaveInstanceState(@Nullable Bundle bundle) { flutterEngine.getActivityControlSurface().onSaveInstanceState(plugins); bundle.putBundle(PLUGINS_RESTORATION_BUNDLE_KEY, plugins); } + + // If using a cached engine, we need to save whether the framework or the system should handle + // backs. + if (host.getCachedEngineId() != null && !host.shouldDestroyEngineWithHost()) { + bundle.putBoolean(ON_BACK_CALLBACK_ENABLED_KEY, host.getBackCallbackState()); + } } @Override @@ -1297,5 +1304,7 @@ PlatformPlugin providePlatformPlugin( *

Defaults to {@code true}. */ boolean attachToEngineAutomatically(); + + boolean getBackCallbackState(); } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index e26d13e80f74a..fd2021b590ec4 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -1009,7 +1009,8 @@ public FlutterActivityAndFragmentDelegate createDelegate( return new FlutterActivityAndFragmentDelegate(host); } - private final OnBackPressedCallback onBackPressedCallback = + @VisibleForTesting + final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { @@ -1071,6 +1072,12 @@ public void onAttach(@NonNull Context context) { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + boolean frameworkHandlesBack = + savedInstanceState.getBoolean( + FlutterActivityAndFragmentDelegate.ON_BACK_CALLBACK_ENABLED_KEY); + onBackPressedCallback.setEnabled(frameworkHandlesBack); + } delegate.onRestoreInstanceState(savedInstanceState); } @@ -1655,6 +1662,11 @@ public boolean attachToEngineAutomatically() { return true; } + @Override + public boolean getBackCallbackState() { + return onBackPressedCallback.isEnabled(); + } + /** * {@inheritDoc} * diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 3f3f0470d8f54..a1eaed6da7f03 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -196,13 +196,7 @@ public void onFlutterUiNoLongerDisplayed() { } }; - private final Consumer windowInfoListener = - new Consumer() { - @Override - public void accept(WindowLayoutInfo layoutInfo) { - setWindowInfoListenerDisplayFeatures(layoutInfo); - } - }; + private Consumer windowInfoListener; /** * Constructs a {@code FlutterView} programmatically, without any XML attributes. @@ -514,6 +508,10 @@ protected void onAttachedToWindow() { this.windowInfoRepo = createWindowInfoRepo(); Activity activity = ViewUtils.getActivity(getContext()); if (windowInfoRepo != null && activity != null) { + // Creating windowInfoListener on-demand instead of at initialization is necessary in order to + // prevent it from capturing the wrong instance of `this` when spying for testing. + // See https://github.com/mockito/mockito/issues/3479 + windowInfoListener = this::setWindowInfoListenerDisplayFeatures; windowInfoRepo.addWindowLayoutInfoListener( activity, ContextCompat.getMainExecutor(getContext()), windowInfoListener); } @@ -526,9 +524,10 @@ protected void onAttachedToWindow() { */ @Override protected void onDetachedFromWindow() { - if (windowInfoRepo != null) { + if (windowInfoRepo != null && windowInfoListener != null) { windowInfoRepo.removeWindowLayoutInfoListener(windowInfoListener); } + windowInfoListener = null; this.windowInfoRepo = null; super.onDetachedFromWindow(); } @@ -539,12 +538,12 @@ protected void onDetachedFromWindow() { */ @TargetApi(API_LEVELS.API_28) protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) { - List displayFeatures = layoutInfo.getDisplayFeatures(); - List result = new ArrayList<>(); + List newDisplayFeatures = layoutInfo.getDisplayFeatures(); + List flutterDisplayFeatures = new ArrayList<>(); // Data from WindowInfoTracker display features. Fold and hinge areas are // populated here. - for (DisplayFeature displayFeature : displayFeatures) { + for (DisplayFeature displayFeature : newDisplayFeatures) { Log.v( TAG, "WindowInfoTracker Display Feature reported with bounds = " @@ -567,31 +566,17 @@ protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) } else { state = DisplayFeatureState.UNKNOWN; } - result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); + flutterDisplayFeatures.add( + new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); } else { - result.add( + flutterDisplayFeatures.add( new FlutterRenderer.DisplayFeature( displayFeature.getBounds(), DisplayFeatureType.UNKNOWN, DisplayFeatureState.UNKNOWN)); } } - - // Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are - // populated here. DisplayCutout was introduced in API 28. - if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) { - WindowInsets insets = getRootWindowInsets(); - if (insets != null) { - DisplayCutout cutout = insets.getDisplayCutout(); - if (cutout != null) { - for (Rect bounds : cutout.getBoundingRects()) { - Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString()); - result.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT)); - } - } - } - } - viewportMetrics.displayFeatures = result; + viewportMetrics.setDisplayFeatures(flutterDisplayFeatures); sendViewportMetricsToFlutter(); } @@ -784,6 +769,22 @@ navigationBarVisible && guessBottomKeyboardInset(insets) == 0 viewportMetrics.viewInsetLeft = 0; } + // Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are + // populated here. DisplayCutout was introduced in API 28. + List displayCutouts = new ArrayList<>(); + if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) { + DisplayCutout cutout = insets.getDisplayCutout(); + if (cutout != null) { + for (Rect bounds : cutout.getBoundingRects()) { + Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString()); + displayCutouts.add( + new FlutterRenderer.DisplayFeature( + bounds, DisplayFeatureType.CUTOUT, DisplayFeatureState.UNKNOWN)); + } + } + } + viewportMetrics.setDisplayCutouts(displayCutouts); + // The caption bar inset is a new addition, and the APIs called to query it utilize a list of // bounding Rects instead of an Insets object, which is a newer API method, as compared to the // existing Insets-based method calls above. diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 389e866829f09..4747abac87ecc 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -1039,8 +1039,7 @@ private native void nativeRunBundleAndSnapshotFromLibrary( * will be dropped (ignored). Therefore, when using {@code FlutterJNI} to integrate a Flutter * context in an app, a {@link PlatformMessageHandler} must be registered for 2-way Java/Dart * communication to operate correctly. Moreover, the handler must be implemented such that - * fundamental platform messages are handled as expected. See {@link - * io.flutter.view.FlutterNativeView} for an example implementation. + * fundamental platform messages are handled as expected. */ @UiThread public void setPlatformMessageHandler(@Nullable PlatformMessageHandler platformMessageHandler) { diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java deleted file mode 100644 index ed41d1d65f307..0000000000000 --- a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java +++ /dev/null @@ -1,153 +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. - -package io.flutter.embedding.engine.plugins.shim; - -import androidx.annotation.NonNull; -import io.flutter.Log; -import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.PluginRegistry; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * A {@link PluginRegistry} that is shimmed to let old plugins use the new Android embedding and - * plugin API behind the scenes. - * - *

The following is an example usage of {@code ShimPluginRegistry} within a {@code - * FlutterActivity}: - * - *

- * // Create the FlutterEngine that will back the Flutter UI.
- * FlutterEngineGroup group = new FlutterEngineGroup(context);
- * FlutterEngine flutterEngine = group.createAndRunDefaultEngine(context);
- *
- * // Create a ShimPluginRegistry and wrap the FlutterEngine with the shim.
- * ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine, platformViewsController);
- *
- * // Use the GeneratedPluginRegistrant to add every plugin that's in the pubspec.
- * GeneratedPluginRegistrant.registerWith(shimPluginRegistry);
- * 
- */ -public class ShimPluginRegistry implements PluginRegistry { - private static final String TAG = "ShimPluginRegistry"; - - private final FlutterEngine flutterEngine; - private final Map pluginMap = new HashMap<>(); - private final ShimRegistrarAggregate shimRegistrarAggregate; - - public ShimPluginRegistry(@NonNull FlutterEngine flutterEngine) { - this.flutterEngine = flutterEngine; - this.shimRegistrarAggregate = new ShimRegistrarAggregate(); - this.flutterEngine.getPlugins().add(shimRegistrarAggregate); - } - - @Override - @NonNull - public Registrar registrarFor(@NonNull String pluginKey) { - Log.v(TAG, "Creating plugin Registrar for '" + pluginKey + "'"); - if (pluginMap.containsKey(pluginKey)) { - throw new IllegalStateException("Plugin key " + pluginKey + " is already in use"); - } - pluginMap.put(pluginKey, null); - ShimRegistrar registrar = new ShimRegistrar(pluginKey, pluginMap); - shimRegistrarAggregate.addPlugin(registrar); - return registrar; - } - - @Override - public boolean hasPlugin(@NonNull String pluginKey) { - return pluginMap.containsKey(pluginKey); - } - - @Override - @SuppressWarnings("unchecked") - public T valuePublishedByPlugin(@NonNull String pluginKey) { - return (T) pluginMap.get(pluginKey); - } - - /** - * Aggregates all {@link ShimRegistrar}s within one single {@link FlutterPlugin}. - * - *

The reason we need this aggregate is because the new embedding uniquely identifies plugins - * by their plugin class, but the plugin shim system represents every plugin with a {@link - * ShimRegistrar}. Therefore, every plugin we would register after the first plugin, would - * overwrite the previous plugin, because they're all {@link ShimRegistrar} instances. - * - *

{@code ShimRegistrarAggregate} multiplexes {@link FlutterPlugin} and {@link ActivityAware} - * calls so that we can register just one {@code ShimRegistrarAggregate} with a {@link - * FlutterEngine}, while forwarding the relevant plugin resources to any number of {@link - * ShimRegistrar}s within this {@code ShimRegistrarAggregate}. - */ - private static class ShimRegistrarAggregate implements FlutterPlugin, ActivityAware { - private final Set shimRegistrars = new HashSet<>(); - private FlutterPluginBinding flutterPluginBinding; - private ActivityPluginBinding activityPluginBinding; - - public void addPlugin(@NonNull ShimRegistrar shimRegistrar) { - shimRegistrars.add(shimRegistrar); - - if (flutterPluginBinding != null) { - shimRegistrar.onAttachedToEngine(flutterPluginBinding); - } - if (activityPluginBinding != null) { - shimRegistrar.onAttachedToActivity(activityPluginBinding); - } - } - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { - flutterPluginBinding = binding; - for (ShimRegistrar shimRegistrar : shimRegistrars) { - shimRegistrar.onAttachedToEngine(binding); - } - } - - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - for (ShimRegistrar shimRegistrar : shimRegistrars) { - shimRegistrar.onDetachedFromEngine(binding); - } - flutterPluginBinding = null; - activityPluginBinding = null; - } - - @Override - public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { - activityPluginBinding = binding; - for (ShimRegistrar shimRegistrar : shimRegistrars) { - shimRegistrar.onAttachedToActivity(binding); - } - } - - @Override - public void onDetachedFromActivityForConfigChanges() { - for (ShimRegistrar shimRegistrar : shimRegistrars) { - shimRegistrar.onDetachedFromActivity(); - } - activityPluginBinding = null; - } - - @Override - public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - activityPluginBinding = binding; - for (ShimRegistrar shimRegistrar : shimRegistrars) { - shimRegistrar.onReattachedToActivityForConfigChanges(binding); - } - } - - @Override - public void onDetachedFromActivity() { - for (ShimRegistrar shimRegistrar : shimRegistrars) { - shimRegistrar.onDetachedFromActivity(); - } - activityPluginBinding = null; - } - } -} diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java deleted file mode 100644 index d27da01dae04b..0000000000000 --- a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java +++ /dev/null @@ -1,234 +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. - -package io.flutter.embedding.engine.plugins.shim; - -import android.app.Activity; -import android.content.Context; -import androidx.annotation.NonNull; -import io.flutter.FlutterInjector; -import io.flutter.Log; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.PluginRegistry; -import io.flutter.plugin.platform.PlatformViewRegistry; -import io.flutter.view.FlutterView; -import io.flutter.view.TextureRegistry; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * A {@link PluginRegistry.Registrar} that is shimmed let old plugins use the new Android embedding - * and plugin API behind the scenes. - * - *

Instances of {@code ShimRegistrar}s are vended internally by a {@link ShimPluginRegistry}. - */ -class ShimRegistrar implements PluginRegistry.Registrar, FlutterPlugin, ActivityAware { - private static final String TAG = "ShimRegistrar"; - - private final Map globalRegistrarMap; - private final String pluginId; - private final Set viewDestroyListeners = new HashSet<>(); - private final Set - requestPermissionsResultListeners = new HashSet<>(); - private final Set activityResultListeners = - new HashSet<>(); - private final Set newIntentListeners = new HashSet<>(); - private final Set userLeaveHintListeners = new HashSet<>(); - private final Set WindowFocusChangedListeners = - new HashSet<>(); - private FlutterPlugin.FlutterPluginBinding pluginBinding; - private ActivityPluginBinding activityPluginBinding; - - public ShimRegistrar(@NonNull String pluginId, @NonNull Map globalRegistrarMap) { - this.pluginId = pluginId; - this.globalRegistrarMap = globalRegistrarMap; - } - - @Override - public Activity activity() { - return activityPluginBinding != null ? activityPluginBinding.getActivity() : null; - } - - @Override - public Context context() { - return pluginBinding != null ? pluginBinding.getApplicationContext() : null; - } - - @Override - public Context activeContext() { - return activityPluginBinding == null ? context() : activity(); - } - - @Override - public BinaryMessenger messenger() { - return pluginBinding != null ? pluginBinding.getBinaryMessenger() : null; - } - - @Override - public TextureRegistry textures() { - return pluginBinding != null ? pluginBinding.getTextureRegistry() : null; - } - - @Override - public PlatformViewRegistry platformViewRegistry() { - return pluginBinding != null ? pluginBinding.getPlatformViewRegistry() : null; - } - - @Override - public FlutterView view() { - throw new UnsupportedOperationException( - "The new embedding does not support the old FlutterView."); - } - - @Override - public String lookupKeyForAsset(String asset) { - return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset); - } - - @Override - public String lookupKeyForAsset(String asset, String packageName) { - return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset, packageName); - } - - @Override - public PluginRegistry.Registrar publish(Object value) { - globalRegistrarMap.put(pluginId, value); - return this; - } - - @Override - public PluginRegistry.Registrar addRequestPermissionsResultListener( - PluginRegistry.RequestPermissionsResultListener listener) { - requestPermissionsResultListeners.add(listener); - - if (activityPluginBinding != null) { - activityPluginBinding.addRequestPermissionsResultListener(listener); - } - - return this; - } - - @Override - public PluginRegistry.Registrar addActivityResultListener( - PluginRegistry.ActivityResultListener listener) { - activityResultListeners.add(listener); - - if (activityPluginBinding != null) { - activityPluginBinding.addActivityResultListener(listener); - } - - return this; - } - - @Override - public PluginRegistry.Registrar addNewIntentListener(PluginRegistry.NewIntentListener listener) { - newIntentListeners.add(listener); - - if (activityPluginBinding != null) { - activityPluginBinding.addOnNewIntentListener(listener); - } - - return this; - } - - @Override - public PluginRegistry.Registrar addUserLeaveHintListener( - PluginRegistry.UserLeaveHintListener listener) { - userLeaveHintListeners.add(listener); - - if (activityPluginBinding != null) { - activityPluginBinding.addOnUserLeaveHintListener(listener); - } - - return this; - } - - @Override - public PluginRegistry.Registrar addWindowFocusChangedListener( - PluginRegistry.WindowFocusChangedListener listener) { - WindowFocusChangedListeners.add(listener); - - if (activityPluginBinding != null) { - activityPluginBinding.addOnWindowFocusChangedListener(listener); - } - - return this; - } - - @Override - @NonNull - public PluginRegistry.Registrar addViewDestroyListener( - @NonNull PluginRegistry.ViewDestroyListener listener) { - viewDestroyListeners.add(listener); - return this; - } - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { - Log.v(TAG, "Attached to FlutterEngine."); - pluginBinding = binding; - } - - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - Log.v(TAG, "Detached from FlutterEngine."); - for (PluginRegistry.ViewDestroyListener listener : viewDestroyListeners) { - // The following invocation might produce unexpected behavior in old plugins because - // we have no FlutterNativeView to pass to onViewDestroy(). This is a limitation of this shim. - listener.onViewDestroy(null); - } - - pluginBinding = null; - activityPluginBinding = null; - } - - @Override - public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { - Log.v(TAG, "Attached to an Activity."); - activityPluginBinding = binding; - addExistingListenersToActivityPluginBinding(); - } - - @Override - public void onDetachedFromActivityForConfigChanges() { - Log.v(TAG, "Detached from an Activity for config changes."); - activityPluginBinding = null; - } - - @Override - public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - Log.v(TAG, "Reconnected to an Activity after config changes."); - activityPluginBinding = binding; - addExistingListenersToActivityPluginBinding(); - } - - @Override - public void onDetachedFromActivity() { - Log.v(TAG, "Detached from an Activity."); - activityPluginBinding = null; - } - - private void addExistingListenersToActivityPluginBinding() { - for (PluginRegistry.RequestPermissionsResultListener listener : - requestPermissionsResultListeners) { - activityPluginBinding.addRequestPermissionsResultListener(listener); - } - for (PluginRegistry.ActivityResultListener listener : activityResultListeners) { - activityPluginBinding.addActivityResultListener(listener); - } - for (PluginRegistry.NewIntentListener listener : newIntentListeners) { - activityPluginBinding.addOnNewIntentListener(listener); - } - for (PluginRegistry.UserLeaveHintListener listener : userLeaveHintListeners) { - activityPluginBinding.addOnUserLeaveHintListener(listener); - } - for (PluginRegistry.WindowFocusChangedListener listener : WindowFocusChangedListeners) { - activityPluginBinding.addOnWindowFocusChangedListener(listener); - } - } -} diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 54434c27d0594..fe1fa6428eb87 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -1157,6 +1157,13 @@ public void stopRenderingToSurface() { } } + private void translateFeatureBounds(int[] displayFeatureBounds, int offset, Rect bounds) { + displayFeatureBounds[offset] = bounds.left; + displayFeatureBounds[offset + 1] = bounds.top; + displayFeatureBounds[offset + 2] = bounds.right; + displayFeatureBounds[offset + 3] = bounds.bottom; + } + /** * Notifies Flutter that the viewport metrics, e.g. window height and width, have changed. * @@ -1207,20 +1214,31 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { + viewportMetrics.systemGestureInsetRight + "\n" + "Display Features: " - + viewportMetrics.displayFeatures.size()); - - int[] displayFeaturesBounds = new int[viewportMetrics.displayFeatures.size() * 4]; - int[] displayFeaturesType = new int[viewportMetrics.displayFeatures.size()]; - int[] displayFeaturesState = new int[viewportMetrics.displayFeatures.size()]; + + viewportMetrics.displayFeatures.size() + + "\n" + + "Display Cutouts: " + + viewportMetrics.displayCutouts.size()); + + int totalFeaturesAndCutouts = + viewportMetrics.displayFeatures.size() + viewportMetrics.displayCutouts.size(); + int[] displayFeaturesBounds = new int[totalFeaturesAndCutouts * 4]; + int[] displayFeaturesType = new int[totalFeaturesAndCutouts]; + int[] displayFeaturesState = new int[totalFeaturesAndCutouts]; for (int i = 0; i < viewportMetrics.displayFeatures.size(); i++) { DisplayFeature displayFeature = viewportMetrics.displayFeatures.get(i); - displayFeaturesBounds[4 * i] = displayFeature.bounds.left; - displayFeaturesBounds[4 * i + 1] = displayFeature.bounds.top; - displayFeaturesBounds[4 * i + 2] = displayFeature.bounds.right; - displayFeaturesBounds[4 * i + 3] = displayFeature.bounds.bottom; + translateFeatureBounds(displayFeaturesBounds, 4 * i, displayFeature.bounds); displayFeaturesType[i] = displayFeature.type.encodedValue; displayFeaturesState[i] = displayFeature.state.encodedValue; } + int cutoutOffset = viewportMetrics.displayFeatures.size() * 4; + for (int i = 0; i < viewportMetrics.displayCutouts.size(); i++) { + DisplayFeature displayCutout = viewportMetrics.displayCutouts.get(i); + translateFeatureBounds(displayFeaturesBounds, cutoutOffset + 4 * i, displayCutout.bounds); + displayFeaturesType[viewportMetrics.displayFeatures.size() + i] = + displayCutout.type.encodedValue; + displayFeaturesState[viewportMetrics.displayFeatures.size() + i] = + displayCutout.state.encodedValue; + } flutterJNI.setViewportMetrics( viewportMetrics.devicePixelRatio, @@ -1335,7 +1353,29 @@ boolean validate() { return width > 0 && height > 0 && devicePixelRatio > 0; } - public List displayFeatures = new ArrayList<>(); + // Features + private final List displayFeatures = new ArrayList<>(); + + // Specifically display cutouts. + private final List displayCutouts = new ArrayList<>(); + + public List getDisplayFeatures() { + return displayFeatures; + } + + public List getDisplayCutouts() { + return displayCutouts; + } + + public void setDisplayFeatures(List newFeatures) { + displayFeatures.clear(); + displayFeatures.addAll(newFeatures); + } + + public void setDisplayCutouts(List newCutouts) { + displayCutouts.clear(); + displayCutouts.addAll(newCutouts); + } } /** @@ -1358,12 +1398,6 @@ public DisplayFeature(Rect bounds, DisplayFeatureType type, DisplayFeatureState this.type = type; this.state = state; } - - public DisplayFeature(Rect bounds, DisplayFeatureType type) { - this.bounds = bounds; - this.type = type; - this.state = DisplayFeatureState.UNKNOWN; - } } /** diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index 2c3cf3307b863..641d3f37cc2a9 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -717,7 +717,8 @@ public enum TextInputType { EMAIL_ADDRESS("TextInputType.emailAddress"), URL("TextInputType.url"), VISIBLE_PASSWORD("TextInputType.visiblePassword"), - NONE("TextInputType.none"); + NONE("TextInputType.none"), + WEB_SEARCH("TextInputType.webSearch"); static TextInputType fromValue(@NonNull String encodedName) throws NoSuchFieldException { for (TextInputType textInputType : TextInputType.values()) { diff --git a/shell/platform/android/io/flutter/plugin/common/PluginRegistry.java b/shell/platform/android/io/flutter/plugin/common/PluginRegistry.java index 4859617d93b29..977b995ecf63a 100644 --- a/shell/platform/android/io/flutter/plugin/common/PluginRegistry.java +++ b/shell/platform/android/io/flutter/plugin/common/PluginRegistry.java @@ -5,353 +5,13 @@ package io.flutter.plugin.common; import android.app.Activity; -import android.content.Context; import android.content.Intent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.platform.PlatformViewRegistry; -import io.flutter.view.FlutterNativeView; -import io.flutter.view.FlutterView; -import io.flutter.view.TextureRegistry; -/** - * Container class for Android API listeners used by {@link ActivityPluginBinding}. - * - *

This class also contains deprecated v1 embedding APIs used for plugin registration. - * - *

In v1 Android applications, an auto-generated and auto-updated plugin registrant class - * (GeneratedPluginRegistrant) makes use of a {@link PluginRegistry} to register contributions from - * each plugin mentioned in the application's pubspec file. The generated registrant class is, again - * by default, called from the application's main {@link android.app.Activity}, which defaults to an - * instance of {@link io.flutter.app.FlutterActivity}, itself a {@link PluginRegistry}. - */ +/** Container class for Android API listeners used by {@link ActivityPluginBinding}. */ public interface PluginRegistry { - /** - * Returns a {@link Registrar} for receiving the registrations pertaining to the specified plugin. - * - * @param pluginKey a unique String identifying the plugin; typically the fully qualified name of - * the plugin's main class. - * @return A {@link Registrar} for receiving the registrations pertianing to the specified plugin. - * @deprecated See https://flutter.dev/go/android-project-migration for migration details. - */ - @Deprecated - @NonNull - Registrar registrarFor(@NonNull String pluginKey); - - /** - * Returns whether the specified plugin is known to this registry. - * - * @param pluginKey a unique String identifying the plugin; typically the fully qualified name of - * the plugin's main class. - * @return true if this registry has handed out a registrar for the specified plugin. - * @deprecated See https://flutter.dev/go/android-project-migration for migration details. - */ - @Deprecated - boolean hasPlugin(@NonNull String pluginKey); - - /** - * Returns the value published by the specified plugin, if any. - * - *

Plugins may publish a single value, such as an instance of the plugin's main class, for - * situations where external control or interaction is needed. Clients are expected to know the - * value's type. - * - * @param The type of the value. - * @param pluginKey a unique String identifying the plugin; typically the fully qualified name of - * the plugin's main class. - * @return the published value, possibly null. - * @deprecated See https://flutter.dev/go/android-project-migration for migration details. - */ - @Deprecated - @Nullable - T valuePublishedByPlugin(@NonNull String pluginKey); - - /** - * Receiver of registrations from a single plugin. - * - * @deprecated This registrar is for Flutter's v1 embedding. For instructions on migrating a - * plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - */ - @Deprecated - interface Registrar { - /** - * Returns the {@link android.app.Activity} that forms the plugin's operating context. - * - *

Plugin authors should not assume the type returned by this method is any specific subclass - * of {@code Activity} (such as {@link io.flutter.app.FlutterActivity} or {@link - * io.flutter.app.FlutterFragmentActivity}), as applications are free to use any activity - * subclass. - * - *

When there is no foreground activity in the application, this will return null. If a - * {@link Context} is needed, use context() to get the application's context. - * - *

This registrar is for Flutter's v1 embedding. To access an {@code Activity} from a plugin - * using the v2 embedding, see {@link ActivityPluginBinding#getActivity()}. To obtain an - * instance of an {@link ActivityPluginBinding} in a Flutter plugin, implement the {@link - * ActivityAware} interface. A binding is provided in {@link - * ActivityAware#onAttachedToActivity(ActivityPluginBinding)} and {@link - * ActivityAware#onReattachedToActivityForConfigChanges(ActivityPluginBinding)}. - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - */ - @Nullable - Activity activity(); - - /** - * Returns the {@link android.app.Application}'s {@link Context}. - * - *

This registrar is for Flutter's v1 embedding. To access a {@code Context} from a plugin - * using the v2 embedding, see {@link - * FlutterPlugin.FlutterPluginBinding#getApplicationContext()} - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - */ - @NonNull - Context context(); - - /** - * Returns the active {@link Context}. - * - *

This registrar is for Flutter's v1 embedding. In the v2 embedding, there is no concept of - * an "active context". Either use the application {@code Context} or an attached {@code - * Activity}. See {@link #context()} and {@link #activity()} for more details. - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - * - * @return the current {@link #activity() Activity}, if not null, otherwise the {@link - * #context() Application}. - */ - @NonNull - Context activeContext(); - - /** - * Returns a {@link BinaryMessenger} which the plugin can use for creating channels for - * communicating with the Dart side. - * - *

This registrar is for Flutter's v1 embedding. To access a {@code BinaryMessenger} from a - * plugin using the v2 embedding, see {@link - * FlutterPlugin.FlutterPluginBinding#getBinaryMessenger()} - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - */ - @NonNull - BinaryMessenger messenger(); - - /** - * Returns a {@link TextureRegistry} which the plugin can use for managing backend textures. - * - *

This registrar is for Flutter's v1 embedding. To access a {@code TextureRegistry} from a - * plugin using the v2 embedding, see {@link - * FlutterPlugin.FlutterPluginBinding#getTextureRegistry()} - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - */ - @NonNull - TextureRegistry textures(); - - /** - * Returns the application's {@link PlatformViewRegistry}. - * - *

Plugins can use the platform registry to register their view factories. - * - *

This registrar is for Flutter's v1 embedding. To access a {@code PlatformViewRegistry} - * from a plugin using the v2 embedding, see {@link - * FlutterPlugin.FlutterPluginBinding#getPlatformViewRegistry()} - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - */ - @NonNull - PlatformViewRegistry platformViewRegistry(); - - /** - * Returns the {@link FlutterView} that's instantiated by this plugin's {@link #activity() - * activity}. - * - *

This registrar is for Flutter's v1 embedding. The {@link FlutterView} referenced by this - * method does not exist in the v2 embedding. Additionally, no {@code View} is exposed to any - * plugins in the v2 embedding. Platform views can access their containing {@code View} using - * the platform views APIs. If you have a use-case that absolutely requires a plugin to access - * an Android {@code View}, please file a ticket on GitHub. - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - */ - @NonNull - FlutterView view(); - - /** - * Returns the file name for the given asset. The returned file name can be used to access the - * asset in the APK through the {@link android.content.res.AssetManager} API. - * - *

TODO(mattcarroll): point this method towards new lookup method. - * - * @param asset the name of the asset. The name can be hierarchical - * @return the filename to be used with {@link android.content.res.AssetManager} - */ - @NonNull - String lookupKeyForAsset(@NonNull String asset); - - /** - * Returns the file name for the given asset which originates from the specified packageName. - * The returned file name can be used to access the asset in the APK through the {@link - * android.content.res.AssetManager} API. - * - *

TODO(mattcarroll): point this method towards new lookup method. - * - * @param asset the name of the asset. The name can be hierarchical - * @param packageName the name of the package from which the asset originates - * @return the file name to be used with {@link android.content.res.AssetManager} - */ - @NonNull - String lookupKeyForAsset(@NonNull String asset, @NonNull String packageName); - - /** - * Publishes a value associated with the plugin being registered. - * - *

The published value is available to interested clients via {@link - * PluginRegistry#valuePublishedByPlugin(String)}. - * - *

Publication should be done only when client code needs to interact with the plugin in a - * way that cannot be accomplished by the plugin registering callbacks with client APIs. - * - *

Overwrites any previously published value. - * - *

This registrar is for Flutter's v1 embedding. The concept of publishing values from - * plugins is not supported in the v2 embedding. - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - * - * @param value the value, possibly null. - * @return this {@link Registrar}. - */ - @NonNull - Registrar publish(@Nullable Object value); - - /** - * Adds a callback allowing the plugin to take part in handling incoming calls to {@code - * Activity#onRequestPermissionsResult(int, String[], int[])} or {@code - * androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, - * String[], int[])}. - * - *

This registrar is for Flutter's v1 embedding. To listen for permission results in the v2 - * embedding, use {@link - * ActivityPluginBinding#addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener)}. - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - * - * @param listener a {@link RequestPermissionsResultListener} callback. - * @return this {@link Registrar}. - */ - @NonNull - Registrar addRequestPermissionsResultListener( - @NonNull RequestPermissionsResultListener listener); - - /** - * Adds a callback allowing the plugin to take part in handling incoming calls to {@link - * Activity#onActivityResult(int, int, Intent)}. - * - *

This registrar is for Flutter's v1 embedding. To listen for {@code Activity} results in - * the v2 embedding, use {@link - * ActivityPluginBinding#addActivityResultListener(PluginRegistry.ActivityResultListener)}. - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - * - * @param listener an {@link ActivityResultListener} callback. - * @return this {@link Registrar}. - */ - @NonNull - Registrar addActivityResultListener(@NonNull ActivityResultListener listener); - - /** - * Adds a callback allowing the plugin to take part in handling incoming calls to {@link - * Activity#onNewIntent(Intent)}. - * - *

This registrar is for Flutter's v1 embedding. To listen for new {@code Intent}s in the v2 - * embedding, use {@link - * ActivityPluginBinding#addOnNewIntentListener(PluginRegistry.NewIntentListener)}. - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - * - * @param listener a {@link NewIntentListener} callback. - * @return this {@link Registrar}. - */ - @NonNull - Registrar addNewIntentListener(@NonNull NewIntentListener listener); - - /** - * Adds a callback allowing the plugin to take part in handling incoming calls to {@link - * Activity#onUserLeaveHint()}. - * - *

This registrar is for Flutter's v1 embedding. To listen for leave hints in the v2 - * embedding, use {@link - * ActivityPluginBinding#addOnUserLeaveHintListener(PluginRegistry.UserLeaveHintListener)}. - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - * - * @param listener a {@link UserLeaveHintListener} callback. - * @return this {@link Registrar}. - */ - @NonNull - Registrar addUserLeaveHintListener(@NonNull UserLeaveHintListener listener); - - /** - * Adds a callback allowing the plugin to take part in handling incoming calls to {@link - * Activity#onWindowFocusChanged(boolean)}. - * - *

This registrar is for Flutter's v1 embedding. To listen for leave hints in the v2 - * embedding, use {@link - * ActivityPluginBinding#addOnWindowFocusChangedListener(PluginRegistry.WindowFocusChangedListener)}. - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - * - * @param listener a {@link WindowFocusChangedListener} callback. - * @return this {@link Registrar}. - */ - @NonNull - Registrar addWindowFocusChangedListener(@NonNull WindowFocusChangedListener listener); - - /** - * Adds a callback allowing the plugin to take part in handling incoming calls to {@link - * Activity#onDestroy()}. - * - *

This registrar is for Flutter's v1 embedding. The concept of {@code View} destruction does - * not exist in the v2 embedding. However, plugins in the v2 embedding can respond to {@link - * ActivityAware#onDetachedFromActivityForConfigChanges()} and {@link - * ActivityAware#onDetachedFromActivity()}, which indicate the loss of a visual context for the - * running Flutter experience. Developers should implement {@link ActivityAware} for their - * {@link FlutterPlugin} if such callbacks are needed. Also, plugins can respond to {@link - * FlutterPlugin#onDetachedFromEngine(FlutterPlugin.FlutterPluginBinding)}, which indicates that - * the given plugin has been completely disconnected from the associated Flutter experience and - * should clean up any resources. - * - *

For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - * - * @param listener a {@link ViewDestroyListener} callback. - * @return this {@link Registrar}. - */ - // TODO(amirh): Add a line in the javadoc above that points to a Platform Views website guide - // when one is available (but not a website API doc) - @NonNull - Registrar addViewDestroyListener(@NonNull ViewDestroyListener listener); - } - /** * Delegate interface for handling result of permissions requests on behalf of the main {@link * Activity}. @@ -412,29 +72,4 @@ interface UserLeaveHintListener { interface WindowFocusChangedListener { void onWindowFocusChanged(boolean hasFocus); } - - /** - * Delegate interface for handling an {@link android.app.Activity}'s onDestroy method being - * called. A plugin that implements this interface can adopt the {@link FlutterNativeView} by - * retaining a reference and returning true. - * - * @deprecated See https://flutter.dev/go/android-project-migration for migration details. - */ - @Deprecated - interface ViewDestroyListener { - boolean onViewDestroy(@NonNull FlutterNativeView view); - } - - /** - * Callback interface for registering plugins with a plugin registry. - * - *

For example, an Application may use this callback interface to provide a background service - * with a callback for calling its GeneratedPluginRegistrant.registerWith method. - * - * @deprecated See https://flutter.dev/go/android-project-migration for migration details. - */ - @Deprecated - interface PluginRegistrantCallback { - void registerWith(@NonNull PluginRegistry registry); - } } diff --git a/shell/platform/android/io/flutter/plugin/editing/ListenableEditingState.java b/shell/platform/android/io/flutter/plugin/editing/ListenableEditingState.java index d887cdb8a8e9b..ea1d75a6a173c 100644 --- a/shell/platform/android/io/flutter/plugin/editing/ListenableEditingState.java +++ b/shell/platform/android/io/flutter/plugin/editing/ListenableEditingState.java @@ -18,7 +18,7 @@ /// The current editing state (text, selection range, composing range) the text input plugin holds. /// /// As the name implies, this class also notifies its listeners when the editing state changes. When -/// there're ongoing batch edits, change notifications will be deferred until all batch edits end +/// there are ongoing batch edits, change notifications will be deferred until all batch edits end /// (i.e. when the outermost batch edit ends). Listeners added during a batch edit will always be /// notified when all batch edits end, even if there's no real change. /// diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 41cd19ed32fc4..4f3adcec4d8a4 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -252,7 +252,8 @@ private static int inputTypeFromTextInputType( textType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE; } else if (type.type == TextInputChannel.TextInputType.EMAIL_ADDRESS) { textType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; - } else if (type.type == TextInputChannel.TextInputType.URL) { + } else if (type.type == TextInputChannel.TextInputType.URL + || type.type == TextInputChannel.TextInputType.WEB_SEARCH) { textType |= InputType.TYPE_TEXT_VARIATION_URI; } else if (type.type == TextInputChannel.TextInputType.VISIBLE_PASSWORD) { textType |= InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index e585a1392b602..e4bf714010efb 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -44,9 +44,8 @@ /** * Manages platform views. * - *

Each {@link io.flutter.embedding.engine.FlutterEngine} or {@link - * io.flutter.app.FlutterPluginRegistry} has a single platform views controller. A platform views - * controller can be attached to at most one Flutter view. + *

Each {@link io.flutter.embedding.engine.FlutterEngine} has a single platform views controller. + * A platform views controller can be attached to at most one Flutter view. */ public class PlatformViewsController implements PlatformViewsAccessibilityDelegate { private static final String TAG = "PlatformViewsController"; diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index 8bfea3ce899bb..51d2070929f3d 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -802,6 +802,15 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { result.addAction(AccessibilityNodeInfo.ACTION_CLICK); result.setClickable(true); } + } else { + // Prevent Slider to receive a regular tap which will change the value. + // + // This is needed because it causes slider to select to middle if it + // doesn't have a semantics tap. + if (semanticsNode.hasFlag(Flag.IS_SLIDER)) { + result.addAction(AccessibilityNodeInfo.ACTION_CLICK); + result.setClickable(true); + } } if (semanticsNode.hasAction(Action.LONG_PRESS)) { if (semanticsNode.onLongPressOverride != null) { @@ -2111,7 +2120,8 @@ public enum Action { MOVE_CURSOR_FORWARD_BY_WORD(1 << 19), MOVE_CURSOR_BACKWARD_BY_WORD(1 << 20), SET_TEXT(1 << 21), - FOCUS(1 << 22); + FOCUS(1 << 22), + SCROLL_TO_OFFSET(1 << 23); public final int value; diff --git a/shell/platform/android/io/flutter/view/FlutterMain.java b/shell/platform/android/io/flutter/view/FlutterMain.java deleted file mode 100644 index 2092830111fa1..0000000000000 --- a/shell/platform/android/io/flutter/view/FlutterMain.java +++ /dev/null @@ -1,132 +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. - -package io.flutter.view; - -import android.content.Context; -import android.os.Handler; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import io.flutter.FlutterInjector; -import io.flutter.embedding.engine.loader.FlutterLoader; - -/** - * A legacy class to initialize the Flutter engine. - * - * @deprecated Replaced by {@link io.flutter.embedding.engine.loader.FlutterLoader}. - */ -@Deprecated -public class FlutterMain { - - public static class Settings { - private String logTag; - - @Nullable - public String getLogTag() { - return logTag; - } - - /** - * Set the tag associated with Flutter app log messages. - * - * @param tag Log tag. - */ - public void setLogTag(String tag) { - logTag = tag; - } - } - - /** - * Starts initialization of the native system. - * - * @param applicationContext The Android application context. - */ - public static void startInitialization(@NonNull Context applicationContext) { - FlutterInjector.instance().flutterLoader().startInitialization(applicationContext); - } - - /** - * Starts initialization of the native system. - * - *

This loads the Flutter engine's native library to enable subsequent JNI calls. This also - * starts locating and unpacking Dart resources packaged in the app's APK. - * - *

Calling this method multiple times has no effect. - * - * @param applicationContext The Android application context. - * @param settings Configuration settings. - */ - public static void startInitialization( - @NonNull Context applicationContext, @NonNull Settings settings) { - FlutterLoader.Settings newSettings = new FlutterLoader.Settings(); - newSettings.setLogTag(settings.getLogTag()); - FlutterInjector.instance().flutterLoader().startInitialization(applicationContext, newSettings); - } - - /** - * Blocks until initialization of the native system has completed. - * - *

Calling this method multiple times has no effect. - * - * @param applicationContext The Android application context. - * @param args Flags sent to the Flutter runtime. - */ - public static void ensureInitializationComplete( - @NonNull Context applicationContext, @Nullable String[] args) { - FlutterInjector.instance() - .flutterLoader() - .ensureInitializationComplete(applicationContext, args); - } - - /** - * Same as {@link #ensureInitializationComplete(Context, String[])} but waiting on a background - * thread, then invoking {@code callback} on the {@code callbackHandler}. - */ - public static void ensureInitializationCompleteAsync( - @NonNull Context applicationContext, - @Nullable String[] args, - @NonNull Handler callbackHandler, - @NonNull Runnable callback) { - FlutterInjector.instance() - .flutterLoader() - .ensureInitializationCompleteAsync(applicationContext, args, callbackHandler, callback); - } - - @NonNull - public static String findAppBundlePath() { - return FlutterInjector.instance().flutterLoader().findAppBundlePath(); - } - - @Deprecated - @Nullable - public static String findAppBundlePath(@NonNull Context applicationContext) { - return FlutterInjector.instance().flutterLoader().findAppBundlePath(); - } - - /** - * Returns the file name for the given asset. The returned file name can be used to access the - * asset in the APK through the {@link android.content.res.AssetManager} API. - * - * @param asset the name of the asset. The name can be hierarchical - * @return the filename to be used with {@link android.content.res.AssetManager} - */ - @NonNull - public static String getLookupKeyForAsset(@NonNull String asset) { - return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset); - } - - /** - * Returns the file name for the given asset which originates from the specified packageName. The - * returned file name can be used to access the asset in the APK through the {@link - * android.content.res.AssetManager} API. - * - * @param asset the name of the asset. The name can be hierarchical - * @param packageName the name of the package from which the asset originates - * @return the file name to be used with {@link android.content.res.AssetManager} - */ - @NonNull - public static String getLookupKeyForAsset(@NonNull String asset, @NonNull String packageName) { - return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset, packageName); - } -} diff --git a/shell/platform/android/io/flutter/view/FlutterNativeView.java b/shell/platform/android/io/flutter/view/FlutterNativeView.java deleted file mode 100644 index 2f027695b5ad3..0000000000000 --- a/shell/platform/android/io/flutter/view/FlutterNativeView.java +++ /dev/null @@ -1,204 +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. - -package io.flutter.view; - -import android.app.Activity; -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.UiThread; -import io.flutter.Log; -import io.flutter.app.FlutterPluginRegistry; -import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener; -import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.dart.DartExecutor; -import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; -import io.flutter.plugin.common.*; -import java.nio.ByteBuffer; - -/** - * @deprecated {@link io.flutter.embedding.android.FlutterView} is the new API that now replaces - * this class. See https://flutter.dev/go/android-project-migration for more migration details. - */ -@Deprecated -public class FlutterNativeView implements BinaryMessenger { - private static final String TAG = "FlutterNativeView"; - - private final FlutterPluginRegistry mPluginRegistry; - private final DartExecutor dartExecutor; - private FlutterView mFlutterView; - private final FlutterJNI mFlutterJNI; - private final Context mContext; - private boolean applicationIsRunning; - - private final FlutterUiDisplayListener flutterUiDisplayListener = - new FlutterUiDisplayListener() { - @Override - public void onFlutterUiDisplayed() { - if (mFlutterView == null) { - return; - } - mFlutterView.onFirstFrame(); - } - - @Override - public void onFlutterUiNoLongerDisplayed() { - // no-op - } - }; - - public FlutterNativeView(@NonNull Context context) { - this(context, false); - } - - public FlutterNativeView(@NonNull Context context, boolean isBackgroundView) { - if (isBackgroundView) { - Log.w(TAG, "'isBackgroundView' is no longer supported and will be ignored"); - } - mContext = context; - mPluginRegistry = new FlutterPluginRegistry(this, context); - mFlutterJNI = new FlutterJNI(); - mFlutterJNI.addIsDisplayingFlutterUiListener(flutterUiDisplayListener); - this.dartExecutor = new DartExecutor(mFlutterJNI, context.getAssets()); - mFlutterJNI.addEngineLifecycleListener(new EngineLifecycleListenerImpl()); - attach(this); - assertAttached(); - } - - public void detachFromFlutterView() { - mPluginRegistry.detach(); - mFlutterView = null; - } - - public void destroy() { - mPluginRegistry.destroy(); - dartExecutor.onDetachedFromJNI(); - mFlutterView = null; - mFlutterJNI.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener); - mFlutterJNI.detachFromNativeAndReleaseResources(); - applicationIsRunning = false; - } - - @NonNull - public DartExecutor getDartExecutor() { - return dartExecutor; - } - - @NonNull - public FlutterPluginRegistry getPluginRegistry() { - return mPluginRegistry; - } - - public void attachViewAndActivity(FlutterView flutterView, Activity activity) { - mFlutterView = flutterView; - mPluginRegistry.attach(flutterView, activity); - } - - public boolean isAttached() { - return mFlutterJNI.isAttached(); - } - - public void assertAttached() { - if (!isAttached()) throw new AssertionError("Platform view is not attached"); - } - - public void runFromBundle(FlutterRunArguments args) { - if (args.entrypoint == null) { - throw new AssertionError("An entrypoint must be specified"); - } - assertAttached(); - if (applicationIsRunning) - throw new AssertionError("This Flutter engine instance is already running an application"); - mFlutterJNI.runBundleAndSnapshotFromLibrary( - args.bundlePath, - args.entrypoint, - args.libraryPath, - mContext.getResources().getAssets(), - null); - - applicationIsRunning = true; - } - - public boolean isApplicationRunning() { - return applicationIsRunning; - } - - @Deprecated - public static String getObservatoryUri() { - return FlutterJNI.getVMServiceUri(); - } - - public static String getVMServiceUri() { - return FlutterJNI.getVMServiceUri(); - } - - @Override - @UiThread - public TaskQueue makeBackgroundTaskQueue(TaskQueueOptions options) { - return dartExecutor.getBinaryMessenger().makeBackgroundTaskQueue(options); - } - - @Override - @UiThread - public void send(String channel, ByteBuffer message) { - dartExecutor.getBinaryMessenger().send(channel, message); - } - - @Override - @UiThread - public void send(String channel, ByteBuffer message, BinaryReply callback) { - if (!isAttached()) { - Log.d(TAG, "FlutterView.send called on a detached view, channel=" + channel); - return; - } - - dartExecutor.getBinaryMessenger().send(channel, message, callback); - } - - @Override - @UiThread - public void setMessageHandler(String channel, BinaryMessageHandler handler) { - dartExecutor.getBinaryMessenger().setMessageHandler(channel, handler); - } - - @Override - @UiThread - public void setMessageHandler(String channel, BinaryMessageHandler handler, TaskQueue taskQueue) { - dartExecutor.getBinaryMessenger().setMessageHandler(channel, handler, taskQueue); - } - - @Override - public void enableBufferingIncomingMessages() {} - - @Override - public void disableBufferingIncomingMessages() {} - - /*package*/ FlutterJNI getFlutterJNI() { - return mFlutterJNI; - } - - private void attach(FlutterNativeView view) { - mFlutterJNI.attachToNative(); - dartExecutor.onAttachedToJNI(); - } - - private final class EngineLifecycleListenerImpl implements EngineLifecycleListener { - // Called by native to notify right before the engine is restarted (cold reload). - @SuppressWarnings("unused") - public void onPreEngineRestart() { - if (mFlutterView != null) { - mFlutterView.resetAccessibilityTree(); - } - if (mPluginRegistry == null) { - return; - } - mPluginRegistry.onPreEngineRestart(); - } - - public void onEngineWillDestroy() { - // The old embedding doesn't actually have a FlutterEngine. It interacts with the JNI - // directly. - } - } -} diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java deleted file mode 100644 index d20ea56f1f9db..0000000000000 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ /dev/null @@ -1,997 +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. - -package io.flutter.view; - -import static io.flutter.Build.API_LEVELS; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.Insets; -import android.graphics.PixelFormat; -import android.graphics.SurfaceTexture; -import android.os.Build; -import android.os.Handler; -import android.text.format.DateFormat; -import android.util.AttributeSet; -import android.util.SparseArray; -import android.view.DisplayCutout; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.PointerIcon; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewStructure; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityNodeProvider; -import android.view.autofill.AutofillValue; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; -import android.window.BackEvent; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.annotation.UiThread; -import io.flutter.Log; -import io.flutter.app.FlutterPluginRegistry; -import io.flutter.embedding.android.AndroidTouchProcessor; -import io.flutter.embedding.android.KeyboardManager; -import io.flutter.embedding.engine.dart.DartExecutor; -import io.flutter.embedding.engine.renderer.FlutterRenderer; -import io.flutter.embedding.engine.renderer.SurfaceTextureWrapper; -import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; -import io.flutter.embedding.engine.systemchannels.BackGestureChannel; -import io.flutter.embedding.engine.systemchannels.LifecycleChannel; -import io.flutter.embedding.engine.systemchannels.LocalizationChannel; -import io.flutter.embedding.engine.systemchannels.MouseCursorChannel; -import io.flutter.embedding.engine.systemchannels.NavigationChannel; -import io.flutter.embedding.engine.systemchannels.PlatformChannel; -import io.flutter.embedding.engine.systemchannels.ScribeChannel; -import io.flutter.embedding.engine.systemchannels.SettingsChannel; -import io.flutter.embedding.engine.systemchannels.SystemChannel; -import io.flutter.embedding.engine.systemchannels.TextInputChannel; -import io.flutter.plugin.common.ActivityLifecycleListener; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.editing.TextInputPlugin; -import io.flutter.plugin.localization.LocalizationPlugin; -import io.flutter.plugin.mouse.MouseCursorPlugin; -import io.flutter.plugin.platform.PlatformPlugin; -import io.flutter.plugin.platform.PlatformViewsController; -import io.flutter.util.ViewUtils; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; - -/** - * Deprecated Android view containing a Flutter app. - * - * @deprecated {@link io.flutter.embedding.android.FlutterView} is the new API that now replaces - * this class. See https://flutter.dev/go/android-project-migration for more migration details. - */ -@Deprecated -public class FlutterView extends SurfaceView - implements BinaryMessenger, - TextureRegistry, - MouseCursorPlugin.MouseCursorViewDelegate, - KeyboardManager.ViewDelegate { - /** - * Interface for those objects that maintain and expose a reference to a {@code FlutterView} (such - * as a full-screen Flutter activity). - * - *

This indirection is provided to support applications that use an activity other than {@link - * io.flutter.app.FlutterActivity} (e.g. Android v4 support library's {@code FragmentActivity}). - * It allows Flutter plugins to deal in this interface and not require that the activity be a - * subclass of {@code FlutterActivity}. - */ - public interface Provider { - /** - * Returns a reference to the Flutter view maintained by this object. This may be {@code null}. - * - * @return a reference to the Flutter view maintained by this object. - */ - FlutterView getFlutterView(); - } - - private static final String TAG = "FlutterView"; - - static final class ViewportMetrics { - float devicePixelRatio = 1.0f; - int physicalWidth = 0; - int physicalHeight = 0; - int physicalViewPaddingTop = 0; - int physicalViewPaddingRight = 0; - int physicalViewPaddingBottom = 0; - int physicalViewPaddingLeft = 0; - int physicalViewInsetTop = 0; - int physicalViewInsetRight = 0; - int physicalViewInsetBottom = 0; - int physicalViewInsetLeft = 0; - int systemGestureInsetTop = 0; - int systemGestureInsetRight = 0; - int systemGestureInsetBottom = 0; - int systemGestureInsetLeft = 0; - int physicalTouchSlop = -1; - } - - private final DartExecutor dartExecutor; - private final FlutterRenderer flutterRenderer; - private final NavigationChannel navigationChannel; - private final BackGestureChannel backGestureChannel; - private final LifecycleChannel lifecycleChannel; - private final LocalizationChannel localizationChannel; - private final PlatformChannel platformChannel; - private final SettingsChannel settingsChannel; - private final SystemChannel systemChannel; - private final InputMethodManager mImm; - private final TextInputPlugin mTextInputPlugin; - private final LocalizationPlugin mLocalizationPlugin; - private final MouseCursorPlugin mMouseCursorPlugin; - private final KeyboardManager mKeyboardManager; - private final AndroidTouchProcessor androidTouchProcessor; - private AccessibilityBridge mAccessibilityNodeProvider; - private final SurfaceHolder.Callback mSurfaceCallback; - private final ViewportMetrics mMetrics; - private final List mActivityLifecycleListeners; - private final List mFirstFrameListeners; - private final AtomicLong nextTextureId = new AtomicLong(0L); - private FlutterNativeView mNativeView; - private boolean mIsSoftwareRenderingEnabled = false; // using the software renderer or not - private boolean didRenderFirstFrame = false; - - private final AccessibilityBridge.OnAccessibilityChangeListener onAccessibilityChangeListener = - new AccessibilityBridge.OnAccessibilityChangeListener() { - @Override - public void onAccessibilityChanged( - boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) { - resetWillNotDraw(isAccessibilityEnabled, isTouchExplorationEnabled); - } - }; - - public FlutterView(Context context) { - this(context, null); - } - - public FlutterView(Context context, AttributeSet attrs) { - this(context, attrs, null); - } - - public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) { - super(context, attrs); - - Activity activity = ViewUtils.getActivity(getContext()); - if (activity == null) { - throw new IllegalArgumentException("Bad context"); - } - - if (nativeView == null) { - mNativeView = new FlutterNativeView(activity.getApplicationContext()); - } else { - mNativeView = nativeView; - } - - dartExecutor = mNativeView.getDartExecutor(); - flutterRenderer = new FlutterRenderer(mNativeView.getFlutterJNI()); - mIsSoftwareRenderingEnabled = mNativeView.getFlutterJNI().getIsSoftwareRenderingEnabled(); - mMetrics = new ViewportMetrics(); - mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density; - mMetrics.physicalTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - setFocusable(true); - setFocusableInTouchMode(true); - - mNativeView.attachViewAndActivity(this, activity); - - mSurfaceCallback = - new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(SurfaceHolder holder) { - assertAttached(); - mNativeView.getFlutterJNI().onSurfaceCreated(holder.getSurface()); - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - assertAttached(); - mNativeView.getFlutterJNI().onSurfaceChanged(width, height); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - assertAttached(); - mNativeView.getFlutterJNI().onSurfaceDestroyed(); - } - }; - getHolder().addCallback(mSurfaceCallback); - - mActivityLifecycleListeners = new ArrayList<>(); - mFirstFrameListeners = new ArrayList<>(); - - // Create all platform channels - navigationChannel = new NavigationChannel(dartExecutor); - backGestureChannel = new BackGestureChannel(dartExecutor); - lifecycleChannel = new LifecycleChannel(dartExecutor); - localizationChannel = new LocalizationChannel(dartExecutor); - platformChannel = new PlatformChannel(dartExecutor); - systemChannel = new SystemChannel(dartExecutor); - settingsChannel = new SettingsChannel(dartExecutor); - - // Create and set up plugins - PlatformPlugin platformPlugin = new PlatformPlugin(activity, platformChannel); - addActivityLifecycleListener( - new ActivityLifecycleListener() { - @Override - public void onPostResume() { - platformPlugin.updateSystemUiOverlays(); - } - }); - mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - PlatformViewsController platformViewsController = - mNativeView.getPluginRegistry().getPlatformViewsController(); - mTextInputPlugin = - new TextInputPlugin( - this, - new TextInputChannel(dartExecutor), - new ScribeChannel(dartExecutor), - platformViewsController); - mKeyboardManager = new KeyboardManager(this); - - if (Build.VERSION.SDK_INT >= API_LEVELS.API_24) { - mMouseCursorPlugin = new MouseCursorPlugin(this, new MouseCursorChannel(dartExecutor)); - } else { - mMouseCursorPlugin = null; - } - mLocalizationPlugin = new LocalizationPlugin(context, localizationChannel); - androidTouchProcessor = - new AndroidTouchProcessor(flutterRenderer, /*trackMotionEvents=*/ false); - platformViewsController.attachToFlutterRenderer(flutterRenderer); - mNativeView - .getPluginRegistry() - .getPlatformViewsController() - .attachTextInputPlugin(mTextInputPlugin); - mNativeView.getFlutterJNI().setLocalizationPlugin(mLocalizationPlugin); - - // Send initial platform information to Dart - mLocalizationPlugin.sendLocalesToFlutter(getResources().getConfiguration()); - sendUserPlatformSettingsToDart(); - } - - @NonNull - public DartExecutor getDartExecutor() { - return dartExecutor; - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - Log.e(TAG, "dispatchKeyEvent: " + event.toString()); - if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { - // Tell Android to start tracking this event. - getKeyDispatcherState().startTracking(event, this); - } else if (event.getAction() == KeyEvent.ACTION_UP) { - // Stop tracking the event. - getKeyDispatcherState().handleUpEvent(event); - } - // If the key processor doesn't handle it, then send it on to the - // superclass. The key processor will typically handle all events except - // those where it has re-dispatched the event after receiving a reply from - // the framework that the framework did not handle it. - return (isAttached() && mKeyboardManager.handleEvent(event)) || super.dispatchKeyEvent(event); - } - - public FlutterNativeView getFlutterNativeView() { - return mNativeView; - } - - public FlutterPluginRegistry getPluginRegistry() { - return mNativeView.getPluginRegistry(); - } - - public String getLookupKeyForAsset(String asset) { - return FlutterMain.getLookupKeyForAsset(asset); - } - - public String getLookupKeyForAsset(String asset, String packageName) { - return FlutterMain.getLookupKeyForAsset(asset, packageName); - } - - public void addActivityLifecycleListener(ActivityLifecycleListener listener) { - mActivityLifecycleListeners.add(listener); - } - - public void onStart() { - lifecycleChannel.appIsInactive(); - } - - public void onPause() { - lifecycleChannel.appIsInactive(); - } - - public void onPostResume() { - for (ActivityLifecycleListener listener : mActivityLifecycleListeners) { - listener.onPostResume(); - } - lifecycleChannel.appIsResumed(); - } - - public void onStop() { - lifecycleChannel.appIsPaused(); - } - - public void onMemoryPressure() { - mNativeView.getFlutterJNI().notifyLowMemoryWarning(); - systemChannel.sendMemoryPressureWarning(); - } - - /** - * Returns true if the Flutter experience associated with this {@code FlutterView} has rendered - * its first frame, or false otherwise. - */ - public boolean hasRenderedFirstFrame() { - return didRenderFirstFrame; - } - - /** - * Provide a listener that will be called once when the FlutterView renders its first frame to the - * underlaying SurfaceView. - */ - public void addFirstFrameListener(FirstFrameListener listener) { - mFirstFrameListeners.add(listener); - } - - /** Remove an existing first frame listener. */ - public void removeFirstFrameListener(FirstFrameListener listener) { - mFirstFrameListeners.remove(listener); - } - - @Override - public void enableBufferingIncomingMessages() {} - - @Override - public void disableBufferingIncomingMessages() {} - - /** - * Reverts this back to the {@link SurfaceView} defaults, at the back of its window and opaque. - */ - public void disableTransparentBackground() { - setZOrderOnTop(false); - getHolder().setFormat(PixelFormat.OPAQUE); - } - - public void setInitialRoute(String route) { - navigationChannel.setInitialRoute(route); - } - - public void pushRoute(String route) { - navigationChannel.pushRoute(route); - } - - public void popRoute() { - navigationChannel.popRoute(); - } - - @TargetApi(API_LEVELS.API_34) - @RequiresApi(API_LEVELS.API_34) - public void startBackGesture(@NonNull BackEvent backEvent) { - backGestureChannel.startBackGesture(backEvent); - } - - @TargetApi(API_LEVELS.API_34) - @RequiresApi(API_LEVELS.API_34) - public void updateBackGestureProgress(@NonNull BackEvent backEvent) { - backGestureChannel.updateBackGestureProgress(backEvent); - } - - @TargetApi(API_LEVELS.API_34) - @RequiresApi(API_LEVELS.API_34) - public void commitBackGesture() { - backGestureChannel.commitBackGesture(); - } - - @TargetApi(API_LEVELS.API_34) - @RequiresApi(API_LEVELS.API_34) - public void cancelBackGesture() { - backGestureChannel.cancelBackGesture(); - } - - private void sendUserPlatformSettingsToDart() { - // Lookup the current brightness of the Android OS. - boolean isNightModeOn = - (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - SettingsChannel.PlatformBrightness brightness = - isNightModeOn - ? SettingsChannel.PlatformBrightness.dark - : SettingsChannel.PlatformBrightness.light; - - settingsChannel - .startMessage() - .setTextScaleFactor(getResources().getConfiguration().fontScale) - .setUse24HourFormat(DateFormat.is24HourFormat(getContext())) - .setPlatformBrightness(brightness) - .send(); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mLocalizationPlugin.sendLocalesToFlutter(newConfig); - sendUserPlatformSettingsToDart(); - } - - float getDevicePixelRatio() { - return mMetrics.devicePixelRatio; - } - - public FlutterNativeView detach() { - if (!isAttached()) return null; - getHolder().removeCallback(mSurfaceCallback); - mNativeView.detachFromFlutterView(); - - FlutterNativeView view = mNativeView; - mNativeView = null; - return view; - } - - public void destroy() { - if (!isAttached()) return; - - getHolder().removeCallback(mSurfaceCallback); - releaseAccessibilityNodeProvider(); - - mNativeView.destroy(); - mNativeView = null; - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - return mTextInputPlugin.createInputConnection(this, mKeyboardManager, outAttrs); - } - - @Override - public boolean checkInputConnectionProxy(View view) { - return mNativeView - .getPluginRegistry() - .getPlatformViewsController() - .checkInputConnectionProxy(view); - } - - @Override - public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { - super.onProvideAutofillVirtualStructure(structure, flags); - mTextInputPlugin.onProvideAutofillVirtualStructure(structure, flags); - } - - @Override - public void autofill(SparseArray values) { - mTextInputPlugin.autofill(values); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (!isAttached()) { - return super.onTouchEvent(event); - } - - requestUnbufferedDispatch(event); - - return androidTouchProcessor.onTouchEvent(event); - } - - @Override - public boolean onHoverEvent(MotionEvent event) { - if (!isAttached()) { - return super.onHoverEvent(event); - } - - boolean handled = mAccessibilityNodeProvider.onAccessibilityHoverEvent(event); - if (!handled) { - // TODO(ianh): Expose hover events to the platform, - // implementing ADD, REMOVE, etc. - } - return handled; - } - - /** - * Invoked by Android when a generic motion event occurs, e.g., joystick movement, mouse hover, - * track pad touches, scroll wheel movements, etc. - * - *

Flutter handles all of its own gesture detection and processing, therefore this method - * forwards all {@link MotionEvent} data from Android to Flutter. - */ - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - boolean handled = - isAttached() && androidTouchProcessor.onGenericMotionEvent(event, getContext()); - return handled ? true : super.onGenericMotionEvent(event); - } - - @Override - protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { - mMetrics.physicalWidth = width; - mMetrics.physicalHeight = height; - updateViewportMetrics(); - super.onSizeChanged(width, height, oldWidth, oldHeight); - } - - // TODO(garyq): Add support for notch cutout API - // Decide if we want to zero the padding of the sides. When in Landscape orientation, - // android may decide to place the software navigation bars on the side. When the nav - // bar is hidden, the reported insets should be removed to prevent extra useless space - // on the sides. - private enum ZeroSides { - NONE, - LEFT, - RIGHT, - BOTH - } - - private ZeroSides calculateShouldZeroSides() { - // We get both orientation and rotation because rotation is all 4 - // rotations relative to default rotation while orientation is portrait - // or landscape. By combining both, we can obtain a more precise measure - // of the rotation. - Context context = getContext(); - int orientation = context.getResources().getConfiguration().orientation; - int rotation = - ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) - .getDefaultDisplay() - .getRotation(); - - if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - if (rotation == Surface.ROTATION_90) { - return ZeroSides.RIGHT; - } else if (rotation == Surface.ROTATION_270) { - // In android API >= 23, the nav bar always appears on the "bottom" (USB) side. - return Build.VERSION.SDK_INT >= API_LEVELS.API_23 ? ZeroSides.LEFT : ZeroSides.RIGHT; - } - // Ambiguous orientation due to landscape left/right default. Zero both sides. - else if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { - return ZeroSides.BOTH; - } - } - // Square orientation deprecated in API 16, we will not check for it and return false - // to be safe and not remove any unique padding for the devices that do use it. - return ZeroSides.NONE; - } - - // TODO(garyq): Use new Android R getInsets API - // TODO(garyq): The keyboard detection may interact strangely with - // https://github.com/flutter/flutter/issues/22061 - - // Uses inset heights and screen heights as a heuristic to determine if the insets should - // be padded. When the on-screen keyboard is detected, we want to include the full inset - // but when the inset is just the hidden nav bar, we want to provide a zero inset so the space - // can be used. - - private int guessBottomKeyboardInset(WindowInsets insets) { - int screenHeight = getRootView().getHeight(); - // Magic number due to this being a heuristic. This should be replaced, but we have not - // found a clean way to do it yet (Sept. 2018) - final double keyboardHeightRatioHeuristic = 0.18; - if (insets.getSystemWindowInsetBottom() < screenHeight * keyboardHeightRatioHeuristic) { - // Is not a keyboard, so return zero as inset. - return 0; - } else { - // Is a keyboard, so return the full inset. - return insets.getSystemWindowInsetBottom(); - } - } - - // This callback is not present in API < 20, which means lower API devices will see - // the wider than expected padding when the status and navigation bars are hidden. - // The annotations to suppress "InlinedApi" and "NewApi" lints prevent lint warnings - // caused by usage of Android Q APIs. These calls are safe because they are - // guarded. - @Override - @SuppressLint({"InlinedApi", "NewApi"}) - public final WindowInsets onApplyWindowInsets(WindowInsets insets) { - // getSystemGestureInsets() was introduced in API 29 and immediately deprecated in 30. - if (Build.VERSION.SDK_INT == API_LEVELS.API_29) { - Insets systemGestureInsets = insets.getSystemGestureInsets(); - mMetrics.systemGestureInsetTop = systemGestureInsets.top; - mMetrics.systemGestureInsetRight = systemGestureInsets.right; - mMetrics.systemGestureInsetBottom = systemGestureInsets.bottom; - mMetrics.systemGestureInsetLeft = systemGestureInsets.left; - } - - boolean statusBarVisible = (SYSTEM_UI_FLAG_FULLSCREEN & getWindowSystemUiVisibility()) == 0; - boolean navigationBarVisible = - (SYSTEM_UI_FLAG_HIDE_NAVIGATION & getWindowSystemUiVisibility()) == 0; - - if (Build.VERSION.SDK_INT >= API_LEVELS.API_30) { - int mask = 0; - if (navigationBarVisible) { - mask = mask | android.view.WindowInsets.Type.navigationBars(); - } - if (statusBarVisible) { - mask = mask | android.view.WindowInsets.Type.statusBars(); - } - Insets uiInsets = insets.getInsets(mask); - mMetrics.physicalViewPaddingTop = uiInsets.top; - mMetrics.physicalViewPaddingRight = uiInsets.right; - mMetrics.physicalViewPaddingBottom = uiInsets.bottom; - mMetrics.physicalViewPaddingLeft = uiInsets.left; - - Insets imeInsets = insets.getInsets(android.view.WindowInsets.Type.ime()); - mMetrics.physicalViewInsetTop = imeInsets.top; - mMetrics.physicalViewInsetRight = imeInsets.right; - mMetrics.physicalViewInsetBottom = imeInsets.bottom; // Typically, only bottom is non-zero - mMetrics.physicalViewInsetLeft = imeInsets.left; - - Insets systemGestureInsets = - insets.getInsets(android.view.WindowInsets.Type.systemGestures()); - mMetrics.systemGestureInsetTop = systemGestureInsets.top; - mMetrics.systemGestureInsetRight = systemGestureInsets.right; - mMetrics.systemGestureInsetBottom = systemGestureInsets.bottom; - mMetrics.systemGestureInsetLeft = systemGestureInsets.left; - - // TODO(garyq): Expose the full rects of the display cutout. - - // Take the max of the display cutout insets and existing padding to merge them - DisplayCutout cutout = insets.getDisplayCutout(); - if (cutout != null) { - Insets waterfallInsets = cutout.getWaterfallInsets(); - mMetrics.physicalViewPaddingTop = - Math.max( - Math.max(mMetrics.physicalViewPaddingTop, waterfallInsets.top), - cutout.getSafeInsetTop()); - mMetrics.physicalViewPaddingRight = - Math.max( - Math.max(mMetrics.physicalViewPaddingRight, waterfallInsets.right), - cutout.getSafeInsetRight()); - mMetrics.physicalViewPaddingBottom = - Math.max( - Math.max(mMetrics.physicalViewPaddingBottom, waterfallInsets.bottom), - cutout.getSafeInsetBottom()); - mMetrics.physicalViewPaddingLeft = - Math.max( - Math.max(mMetrics.physicalViewPaddingLeft, waterfallInsets.left), - cutout.getSafeInsetLeft()); - } - } else { - // We zero the left and/or right sides to prevent the padding the - // navigation bar would have caused. - ZeroSides zeroSides = ZeroSides.NONE; - if (!navigationBarVisible) { - zeroSides = calculateShouldZeroSides(); - } - - // Status bar (top), navigation bar (bottom) and left/right system insets should - // partially obscure the content (padding). - mMetrics.physicalViewPaddingTop = statusBarVisible ? insets.getSystemWindowInsetTop() : 0; - mMetrics.physicalViewPaddingRight = - zeroSides == ZeroSides.RIGHT || zeroSides == ZeroSides.BOTH - ? 0 - : insets.getSystemWindowInsetRight(); - mMetrics.physicalViewPaddingBottom = - navigationBarVisible && guessBottomKeyboardInset(insets) == 0 - ? insets.getSystemWindowInsetBottom() - : 0; - mMetrics.physicalViewPaddingLeft = - zeroSides == ZeroSides.LEFT || zeroSides == ZeroSides.BOTH - ? 0 - : insets.getSystemWindowInsetLeft(); - - // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset). - mMetrics.physicalViewInsetTop = 0; - mMetrics.physicalViewInsetRight = 0; - mMetrics.physicalViewInsetBottom = guessBottomKeyboardInset(insets); - mMetrics.physicalViewInsetLeft = 0; - } - - updateViewportMetrics(); - return super.onApplyWindowInsets(insets); - } - - private boolean isAttached() { - return mNativeView != null && mNativeView.isAttached(); - } - - void assertAttached() { - if (!isAttached()) throw new AssertionError("Platform view is not attached"); - } - - private void preRun() { - resetAccessibilityTree(); - } - - void resetAccessibilityTree() { - if (mAccessibilityNodeProvider != null) { - mAccessibilityNodeProvider.reset(); - } - } - - private void postRun() {} - - public void runFromBundle(FlutterRunArguments args) { - assertAttached(); - preRun(); - mNativeView.runFromBundle(args); - postRun(); - } - - /** - * Return the most recent frame as a bitmap. - * - * @return A bitmap. - */ - public Bitmap getBitmap() { - assertAttached(); - return mNativeView.getFlutterJNI().getBitmap(); - } - - private void updateViewportMetrics() { - if (!isAttached()) return; - mNativeView - .getFlutterJNI() - .setViewportMetrics( - mMetrics.devicePixelRatio, - mMetrics.physicalWidth, - mMetrics.physicalHeight, - mMetrics.physicalViewPaddingTop, - mMetrics.physicalViewPaddingRight, - mMetrics.physicalViewPaddingBottom, - mMetrics.physicalViewPaddingLeft, - mMetrics.physicalViewInsetTop, - mMetrics.physicalViewInsetRight, - mMetrics.physicalViewInsetBottom, - mMetrics.physicalViewInsetLeft, - mMetrics.systemGestureInsetTop, - mMetrics.systemGestureInsetRight, - mMetrics.systemGestureInsetBottom, - mMetrics.systemGestureInsetLeft, - mMetrics.physicalTouchSlop, - new int[0], - new int[0], - new int[0]); - } - - // Called by FlutterNativeView to notify first Flutter frame rendered. - public void onFirstFrame() { - didRenderFirstFrame = true; - - // Allow listeners to remove themselves when they are called. - List listeners = new ArrayList<>(mFirstFrameListeners); - for (FirstFrameListener listener : listeners) { - listener.onFirstFrame(); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - PlatformViewsController platformViewsController = - getPluginRegistry().getPlatformViewsController(); - mAccessibilityNodeProvider = - new AccessibilityBridge( - this, - new AccessibilityChannel(dartExecutor, getFlutterNativeView().getFlutterJNI()), - (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE), - getContext().getContentResolver(), - platformViewsController); - mAccessibilityNodeProvider.setOnAccessibilityChangeListener(onAccessibilityChangeListener); - - resetWillNotDraw( - mAccessibilityNodeProvider.isAccessibilityEnabled(), - mAccessibilityNodeProvider.isTouchExplorationEnabled()); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - releaseAccessibilityNodeProvider(); - } - - // TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise - // add comments. - private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) { - if (!mIsSoftwareRenderingEnabled) { - setWillNotDraw(!(isAccessibilityEnabled || isTouchExplorationEnabled)); - } else { - setWillNotDraw(false); - } - } - - @Override - public AccessibilityNodeProvider getAccessibilityNodeProvider() { - if (mAccessibilityNodeProvider != null && mAccessibilityNodeProvider.isAccessibilityEnabled()) { - return mAccessibilityNodeProvider; - } else { - // TODO(goderbauer): when a11y is off this should return a one-off snapshot of - // the a11y - // tree. - return null; - } - } - - private void releaseAccessibilityNodeProvider() { - if (mAccessibilityNodeProvider != null) { - mAccessibilityNodeProvider.release(); - mAccessibilityNodeProvider = null; - } - } - - // -------- Start: Mouse ------- - - @Override - @TargetApi(API_LEVELS.API_24) - @RequiresApi(API_LEVELS.API_24) - @NonNull - public PointerIcon getSystemPointerIcon(int type) { - return PointerIcon.getSystemIcon(getContext(), type); - } - - // -------- End: Mouse ------- - - // -------- Start: Keyboard ------- - - @Override - public BinaryMessenger getBinaryMessenger() { - return this; - } - - @Override - public boolean onTextInputKeyEvent(@NonNull KeyEvent keyEvent) { - return mTextInputPlugin.handleKeyEvent(keyEvent); - } - - @Override - public void redispatch(@NonNull KeyEvent keyEvent) { - getRootView().dispatchKeyEvent(keyEvent); - } - - // -------- End: Keyboard ------- - - @Override - @UiThread - public TaskQueue makeBackgroundTaskQueue(TaskQueueOptions options) { - return null; - } - - @Override - @UiThread - public void send(String channel, ByteBuffer message) { - send(channel, message, null); - } - - @Override - @UiThread - public void send(String channel, ByteBuffer message, BinaryReply callback) { - if (!isAttached()) { - Log.d(TAG, "FlutterView.send called on a detached view, channel=" + channel); - return; - } - mNativeView.send(channel, message, callback); - } - - @Override - @UiThread - public void setMessageHandler(@NonNull String channel, @NonNull BinaryMessageHandler handler) { - mNativeView.setMessageHandler(channel, handler); - } - - @Override - @UiThread - public void setMessageHandler( - @NonNull String channel, - @NonNull BinaryMessageHandler handler, - @NonNull TaskQueue taskQueue) { - mNativeView.setMessageHandler(channel, handler, taskQueue); - } - - /** Listener will be called on the Android UI thread once when Flutter renders the first frame. */ - public interface FirstFrameListener { - void onFirstFrame(); - } - - @Override - @NonNull - public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() { - final SurfaceTexture surfaceTexture = new SurfaceTexture(0); - return registerSurfaceTexture(surfaceTexture); - } - - @Override - @NonNull - public ImageTextureEntry createImageTexture() { - throw new UnsupportedOperationException("Image textures are not supported in this mode."); - } - - @NonNull - @Override - public SurfaceProducer createSurfaceProducer() { - throw new UnsupportedOperationException( - "SurfaceProducer textures are not supported in this mode."); - } - - @Override - @NonNull - public TextureRegistry.SurfaceTextureEntry registerSurfaceTexture( - @NonNull SurfaceTexture surfaceTexture) { - surfaceTexture.detachFromGLContext(); - final SurfaceTextureRegistryEntry entry = - new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture); - mNativeView.getFlutterJNI().registerTexture(entry.id(), entry.textureWrapper()); - return entry; - } - - final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextureEntry { - private final long id; - private final SurfaceTextureWrapper textureWrapper; - private boolean released; - - SurfaceTextureRegistryEntry(long id, SurfaceTexture surfaceTexture) { - this.id = id; - this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture); - - // The callback relies on being executed on the UI thread (unsynchronised read of - // mNativeView - // and also the engine code check for platform thread in - // Shell::OnPlatformViewMarkTextureFrameAvailable), - // so we explicitly pass a Handler for the current thread. - this.surfaceTexture().setOnFrameAvailableListener(onFrameListener, new Handler()); - } - - private SurfaceTexture.OnFrameAvailableListener onFrameListener = - new SurfaceTexture.OnFrameAvailableListener() { - @Override - public void onFrameAvailable(SurfaceTexture texture) { - if (released || mNativeView == null) { - // Even though we make sure to unregister the callback before releasing, as of Android - // O - // SurfaceTexture has a data race when accessing the callback, so the callback may - // still be called by a stale reference after released==true and mNativeView==null. - return; - } - - mNativeView - .getFlutterJNI() - .markTextureFrameAvailable(SurfaceTextureRegistryEntry.this.id); - } - }; - - public SurfaceTextureWrapper textureWrapper() { - return textureWrapper; - } - - @NonNull - @Override - public SurfaceTexture surfaceTexture() { - return textureWrapper.surfaceTexture(); - } - - @Override - public long id() { - return id; - } - - @Override - public void release() { - if (released) { - return; - } - released = true; - - // The ordering of the next 3 calls is important: - // First we remove the frame listener, then we release the SurfaceTexture, and only after we - // unregister - // the texture which actually deletes the GL texture. - - // Otherwise onFrameAvailableListener might be called after mNativeView==null - // (https://github.com/flutter/flutter/issues/20951). See also the check in onFrameAvailable. - surfaceTexture().setOnFrameAvailableListener(null); - textureWrapper.release(); - mNativeView.getFlutterJNI().unregisterTexture(id); - } - } -} diff --git a/shell/platform/android/io/flutter/view/TextureRegistry.java b/shell/platform/android/io/flutter/view/TextureRegistry.java index 23991b09813f5..1f21312cfcfd3 100644 --- a/shell/platform/android/io/flutter/view/TextureRegistry.java +++ b/shell/platform/android/io/flutter/view/TextureRegistry.java @@ -13,8 +13,8 @@ // TODO(mattcarroll): re-evalute docs in this class and add nullability annotations. /** - * Registry of backend textures used with a single {@link FlutterView} instance. Entries may be - * embedded into the Flutter view using the Texture widget. */ public interface TextureRegistry { diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 2b884fdab669f..7ad6f075115e0 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -1584,8 +1584,11 @@ PlatformViewAndroidJNIImpl::ImageProducerTextureEntryAcquireLatestImage( JavaLocalRef r = JavaLocalRef( env, env->CallObjectMethod(image_producer_texture_entry_local_ref.obj(), g_acquire_latest_image_method)); - FML_CHECK(fml::jni::CheckException(env)); - return r; + if (fml::jni::CheckException(env)) { + return r; + } + // Return null. + return JavaLocalRef(); } JavaLocalRef PlatformViewAndroidJNIImpl::ImageGetHardwareBuffer( diff --git a/shell/platform/android/platform_view_android_unittests.cc b/shell/platform/android/platform_view_android_unittests.cc index 3d7ed4f492355..fc1c2d9d1b17d 100644 --- a/shell/platform/android/platform_view_android_unittests.cc +++ b/shell/platform/android/platform_view_android_unittests.cc @@ -22,7 +22,7 @@ TEST(AndroidPlatformView, SelectsVulkanBasedOnApiLevel) { AndroidRenderingAPI::kImpellerVulkan); } else { EXPECT_EQ(FlutterMain::SelectedRenderingAPI(settings), - AndroidRenderingAPI::kSkiaOpenGLES); + AndroidRenderingAPI::kImpellerOpenGLES); } } diff --git a/shell/platform/android/surface_texture_external_texture.cc b/shell/platform/android/surface_texture_external_texture.cc index 31a14d37a716b..ed019b9101a16 100644 --- a/shell/platform/android/surface_texture_external_texture.cc +++ b/shell/platform/android/surface_texture_external_texture.cc @@ -52,7 +52,10 @@ void SurfaceTextureExternalTexture::Paint(PaintContext& context, if (should_process_frame) { ProcessFrame(context, bounds); } - FML_CHECK(state_ == AttachmentState::kAttached); + // If process frame failed, this may not be in attached state. + if (state_ != AttachmentState::kAttached) { + return; + } if (!dl_image_) { FML_LOG(WARNING) @@ -67,7 +70,7 @@ void SurfaceTextureExternalTexture::DrawFrame( PaintContext& context, const SkRect& bounds, const DlImageSampling sampling) const { - auto transform = GetCurrentUVTransformation().asM33(); + auto transform = ToDlMatrix(GetCurrentUVTransformation()); // Android's SurfaceTexture transform matrix works on texture coordinate // lookups in the range 0.0-1.0, while Skia's Shader transform matrix works on @@ -76,19 +79,18 @@ void SurfaceTextureExternalTexture::DrawFrame( // texture) is the same as a Skia transform by 2.0 (scaling 50% of the image // outside of the virtual "clip rect"), so we invert the incoming matrix. - SkMatrix inverted; - if (!transform.invert(&inverted)) { - FML_LOG(FATAL) - << "Invalid (not invertable) SurfaceTexture transformation matrix"; - } - transform = inverted; - - if (transform.isIdentity()) { + if (transform.IsIdentity()) { context.canvas->DrawImage(dl_image_, SkPoint{0, 0}, sampling, context.paint); return; } + if (!transform.IsInvertible()) { + FML_LOG(FATAL) + << "Invalid (not invertable) SurfaceTexture transformation matrix"; + } + transform = transform.Invert(); + DlAutoCanvasRestore autoRestore(context.canvas, true); // The incoming texture is vertically flipped, so we flip it @@ -97,14 +99,14 @@ void SurfaceTextureExternalTexture::DrawFrame( context.canvas->Translate(bounds.x(), bounds.y() + bounds.height()); context.canvas->Scale(bounds.width(), -bounds.height()); - DlImageColorSource source(dl_image_, DlTileMode::kClamp, DlTileMode::kClamp, - sampling, &transform); + auto source = DlColorSource::MakeImage( + dl_image_, DlTileMode::kClamp, DlTileMode::kClamp, sampling, &transform); DlPaint paintWithShader; if (context.paint) { paintWithShader = *context.paint; } - paintWithShader.setColorSource(&source); + paintWithShader.setColorSource(source); context.canvas->DrawRect(SkRect::MakeWH(1, 1), paintWithShader); } diff --git a/shell/platform/android/surface_texture_external_texture_gl_impeller.cc b/shell/platform/android/surface_texture_external_texture_gl_impeller.cc index 227610a524889..69f4b5e2c6906 100644 --- a/shell/platform/android/surface_texture_external_texture_gl_impeller.cc +++ b/shell/platform/android/surface_texture_external_texture_gl_impeller.cc @@ -33,8 +33,10 @@ void SurfaceTextureExternalTextureGLImpeller::ProcessFrame( static_cast(bounds.height())}; desc.mip_count = 1; texture_ = std::make_shared( - impeller_context_->GetReactor(), desc, - impeller::TextureGLES::IsWrapped::kWrapped); + impeller_context_->GetReactor(), desc); + // The contents will be initialized later in the call to `Attach` instead of + // by Impeller. + texture_->MarkContentsInitialized(); texture_->SetCoordinateSystem( impeller::TextureCoordinateSystem::kUploadFromHost); auto maybe_handle = texture_->GetGLHandle(); diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java index 8833b7a96dbe3..d3686a6dbcf11 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java @@ -5,6 +5,7 @@ package io.flutter.embedding.android; import static io.flutter.Build.API_LEVELS; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -34,6 +35,7 @@ import androidx.annotation.RequiresApi; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; +import androidx.test.core.app.ActivityScenario; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.FlutterInjector; @@ -94,6 +96,48 @@ public void flutterViewHasId() { assertTrue(activity.findViewById(FlutterActivity.FLUTTER_VIEW_ID) instanceof FlutterView); } + @Test + @Config(minSdk = API_LEVELS.API_34) + @TargetApi(API_LEVELS.API_34) + public void whenUsingCachedEngine_predictiveBackStateIsSaved() { + FlutterLoader mockFlutterLoader = mock(FlutterLoader.class); + FlutterJNI mockFlutterJni = mock(FlutterJNI.class); + when(mockFlutterJni.isAttached()).thenReturn(true); + FlutterEngine cachedEngine = new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni); + FlutterEngineCache.getInstance().put("my_cached_engine", cachedEngine); + + ActivityScenario flutterActivityScenario = + ActivityScenario.launch(FlutterActivity.class); + + // Set to framework handling and then recreate the activity and check the state is preserved. + flutterActivityScenario.onActivity(activity -> activity.setFrameworkHandlesBack(true)); + flutterActivityScenario.onActivity( + activity -> activity.getIntent().putExtra(EXTRA_CACHED_ENGINE_ID, "my_cached_engine")); + + flutterActivityScenario.recreate(); + flutterActivityScenario.onActivity(activity -> assertTrue(activity.hasRegisteredBackCallback)); + + // Clean up. + flutterActivityScenario.close(); + } + + @Test + @Config(minSdk = API_LEVELS.API_34) + @TargetApi(API_LEVELS.API_34) + public void whenNotUsingCachedEngine_predictiveBackStateIsNotSaved() { + ActivityScenario flutterActivityScenario = + ActivityScenario.launch(FlutterActivity.class); + + // Set to framework handling and then recreate the activity and check the state is preserved. + flutterActivityScenario.onActivity(activity -> activity.setFrameworkHandlesBack(true)); + + flutterActivityScenario.recreate(); + flutterActivityScenario.onActivity(activity -> assertFalse(activity.hasRegisteredBackCallback)); + + // Clean up. + flutterActivityScenario.close(); + } + // TODO(garyq): Robolectric does not yet support android api 33 yet. Switch to a robolectric // test that directly exercises the OnBackInvoked APIs when API 33 is supported. @Test diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java index 8e8c77619f887..901ac61b2a002 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java @@ -401,6 +401,11 @@ public boolean attachToEngineAutomatically() { return true; } + @Override + public boolean getBackCallbackState() { + return false; + } + @Override public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {} diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java index 97701775adc8e..73274cbce9766 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java @@ -5,6 +5,8 @@ package io.flutter.embedding.android; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY; +import static io.flutter.embedding.android.FlutterFragment.ARG_CACHED_ENGINE_ID; +import static io.flutter.embedding.android.FlutterFragment.ARG_DESTROY_ENGINE_WITH_FRAGMENT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -13,6 +15,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -25,9 +28,11 @@ import androidx.test.core.app.ActivityScenario; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.flutter.Build; import io.flutter.FlutterInjector; import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode; import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.FlutterEngineCache; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.plugins.GeneratedPluginRegistrant; @@ -254,6 +259,63 @@ public void itHandlesNewFragmentRecreationDuringRestoreWhenActivityIsRecreated() assertEquals(0, activity.numberOfEnginesCreated); } + @Test + @Config(minSdk = Build.API_LEVELS.API_34) + @TargetApi(Build.API_LEVELS.API_34) + public void whenUsingCachedEngine_predictiveBackStateIsSaved() { + FlutterLoader mockFlutterLoader = mock(FlutterLoader.class); + FlutterJNI mockFlutterJni = mock(FlutterJNI.class); + when(mockFlutterJni.isAttached()).thenReturn(true); + FlutterEngine cachedEngine = new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni); + FlutterEngineCache.getInstance().put("my_cached_engine", cachedEngine); + + ActivityScenario flutterFragmentActivityActivityScenario = + ActivityScenario.launch(FlutterFragmentActivity.class); + + // Set to framework handling and then recreate the activity and check the state is preserved. + flutterFragmentActivityActivityScenario.onActivity( + activity -> { + FlutterFragment flutterFragment = activity.retrieveExistingFlutterFragmentIfPossible(); + flutterFragment.setFrameworkHandlesBack(true); + Bundle bundle = flutterFragment.getArguments(); + bundle.putString(ARG_CACHED_ENGINE_ID, "my_cached_engine"); + bundle.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, false); + FlutterEngineCache.getInstance().put("my_cached_engine", cachedEngine); + flutterFragment.setArguments(bundle); + }); + + flutterFragmentActivityActivityScenario.recreate(); + + flutterFragmentActivityActivityScenario.onActivity( + activity -> { + assertTrue( + activity + .retrieveExistingFlutterFragmentIfPossible() + .onBackPressedCallback + .isEnabled()); + }); + + // Clean up. + flutterFragmentActivityActivityScenario.close(); + } + + @Test + @Config(minSdk = Build.API_LEVELS.API_34) + @TargetApi(Build.API_LEVELS.API_34) + public void whenNotUsingCachedEngine_predictiveBackStateIsNotSaved() { + ActivityScenario flutterActivityScenario = + ActivityScenario.launch(FlutterActivity.class); + + // Set to framework handling and then recreate the activity and check the state is preserved. + flutterActivityScenario.onActivity(activity -> activity.setFrameworkHandlesBack(true)); + + flutterActivityScenario.recreate(); + flutterActivityScenario.onActivity(activity -> assertFalse(activity.hasRegisteredBackCallback)); + + // Clean up. + flutterActivityScenario.close(); + } + static class FlutterFragmentActivityWithProvidedEngine extends FlutterFragmentActivity { int numberOfEnginesCreated = 0; diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index dc62638fb2e32..7247873ef1e0d 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -55,6 +55,8 @@ import io.flutter.plugin.platform.PlatformViewsController; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.junit.Before; import org.junit.Test; @@ -634,21 +636,103 @@ public void systemInsetDisplayCutoutSimple() { when(windowInsets.getSystemGestureInsets()).thenReturn(systemGestureInsets); when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); - Insets waterfallInsets = Insets.of(200, 0, 200, 0); + Insets waterfallInsets = Insets.of(200, 0, 250, 0); when(displayCutout.getWaterfallInsets()).thenReturn(waterfallInsets); - when(displayCutout.getSafeInsetTop()).thenReturn(150); - when(displayCutout.getSafeInsetBottom()).thenReturn(150); - when(displayCutout.getSafeInsetLeft()).thenReturn(150); - when(displayCutout.getSafeInsetRight()).thenReturn(150); + when(displayCutout.getSafeInsetLeft()).thenReturn(110); + when(displayCutout.getSafeInsetTop()).thenReturn(120); + when(displayCutout.getSafeInsetRight()).thenReturn(130); + when(displayCutout.getSafeInsetBottom()).thenReturn(140); flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - validateViewportMetricPadding(viewportMetricsCaptor, 200, 150, 200, 150); + // Each dimension of the viewport metric paddings should be the maximum of the corresponding + // dimension from the display cutout's safe insets and waterfall insets. + validateViewportMetricPadding(viewportMetricsCaptor, 200, 120, 250, 140); assertEquals(100, viewportMetricsCaptor.getValue().viewInsetTop); } + @SuppressWarnings("deprecation") + @Test + @Config(minSdk = 28) + public void onApplyWindowInsetsSetsDisplayCutouts() { + // Use an Activity context so that FlutterView.onAttachedToWindow completes. + Context context = Robolectric.setupActivity(Activity.class); + FlutterView flutterView = spy(new FlutterView(context)); + assertEquals(0, flutterView.getSystemUiVisibility()); + when(flutterView.getWindowSystemUiVisibility()).thenReturn(0); + when(flutterView.getContext()).thenReturn(context); + + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); + when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + + // When we attach a new FlutterView to the engine without any system insets, + // the viewport metrics default to 0. + flutterView.attachToFlutterEngine(flutterEngine); + ArgumentCaptor viewportMetricsCaptor = + ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); + + // Capture flutterView.setWindowInfoListenerDisplayFeatures. + WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = + mock(WindowInfoRepositoryCallbackAdapterWrapper.class); + doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); + ArgumentCaptor> consumerCaptor = + ArgumentCaptor.forClass(Consumer.class); + flutterView.onAttachedToWindow(); + verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), consumerCaptor.capture()); + Consumer consumer = consumerCaptor.getValue(); + + // Set display features in flutterView to ensure they are not overridden by display cutouts. + FoldingFeature displayFeature = mock(FoldingFeature.class); + Rect featureBounds = new Rect(10, 20, 30, 40); + when(displayFeature.getBounds()).thenReturn(featureBounds); + when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL); + when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT); + WindowLayoutInfo windowLayout = new WindowLayoutInfo(Collections.singletonList(displayFeature)); + clearInvocations(flutterRenderer); + consumer.accept(windowLayout); + + // Assert the display feature is set. + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + List features = + viewportMetricsCaptor.getValue().getDisplayFeatures(); + assertEquals(1, features.size()); + assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type); + assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state); + assertEquals(featureBounds, features.get(0).bounds); + + // Then we simulate the system applying a window inset. + List cutoutBoundingRects = + Arrays.asList(new Rect(0, 200, 300, 400), new Rect(150, 0, 300, 150)); + WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects); + + clearInvocations(flutterRenderer); + flutterView.onApplyWindowInsets(windowInsets); + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + + features = viewportMetricsCaptor.getValue().getDisplayFeatures(); + + // Assert the old display feature is still present. + assertEquals(1, features.size()); + assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type); + assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state); + assertEquals(featureBounds, features.get(0).bounds); + + List cutouts = + viewportMetricsCaptor.getValue().getDisplayCutouts(); + // Asserts for display cutouts. + assertEquals(2, cutouts.size()); + for (int i = 0; i < 2; i++) { + assertEquals(cutoutBoundingRects.get(i), cutouts.get(i).bounds); + assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutouts.get(i).type); + assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutouts.get(i).state); + } + } + @SuppressWarnings("deprecation") // Robolectric.setupActivity // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 @@ -694,36 +778,59 @@ public void itSendsHingeDisplayFeatureToFlutter() { FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + // Display features should be empty on attaching to engine. + flutterView.attachToFlutterEngine(flutterEngine); + ArgumentCaptor viewportMetricsCaptor = + ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + assertEquals(Collections.emptyList(), viewportMetricsCaptor.getValue().getDisplayFeatures()); + clearInvocations(flutterRenderer); + + // Test that display features do not override cutouts. + List cutoutBoundingRects = Collections.singletonList(new Rect(0, 200, 300, 400)); + WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects); + flutterView.onApplyWindowInsets(windowInsets); + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size()); + assertEquals( + cutoutBoundingRects.get(0), + viewportMetricsCaptor.getValue().getDisplayCutouts().get(0).bounds); + clearInvocations(flutterRenderer); + FoldingFeature displayFeature = mock(FoldingFeature.class); - when(displayFeature.getBounds()).thenReturn(new Rect(0, 0, 100, 100)); + Rect featureRect = new Rect(0, 0, 100, 100); + when(displayFeature.getBounds()).thenReturn(featureRect); when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL); when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT); - WindowLayoutInfo testWindowLayout = new WindowLayoutInfo(Arrays.asList(displayFeature)); + WindowLayoutInfo testWindowLayout = + new WindowLayoutInfo(Collections.singletonList(displayFeature)); // When FlutterView is attached to the engine and window, and a hinge display feature exists - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(Arrays.asList(), viewportMetricsCaptor.getValue().displayFeatures); flutterView.onAttachedToWindow(); ArgumentCaptor> wmConsumerCaptor = - ArgumentCaptor.forClass((Class) Consumer.class); + ArgumentCaptor.forClass(Consumer.class); verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), wmConsumerCaptor.capture()); Consumer wmConsumer = wmConsumerCaptor.getValue(); + clearInvocations(flutterRenderer); wmConsumer.accept(testWindowLayout); // Then the Renderer receives the display feature verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals( - FlutterRenderer.DisplayFeatureType.HINGE, - viewportMetricsCaptor.getValue().displayFeatures.get(0).type); - assertEquals( - FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, - viewportMetricsCaptor.getValue().displayFeatures.get(0).state); - assertEquals( - new Rect(0, 0, 100, 100), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); + assertEquals(1, viewportMetricsCaptor.getValue().getDisplayFeatures().size()); + FlutterRenderer.DisplayFeature feature = + viewportMetricsCaptor.getValue().getDisplayFeatures().get(0); + assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, feature.type); + assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, feature.state); + assertEquals(featureRect, feature.bounds); + + // Assert the display cutout is unaffected. + assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size()); + FlutterRenderer.DisplayFeature cutout = + viewportMetricsCaptor.getValue().getDisplayCutouts().get(0); + assertEquals(cutoutBoundingRects.get(0), cutout.bounds); + assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutout.type); + assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutout.state); } @Test @@ -1173,6 +1280,34 @@ private void mockSystemGestureInsetsIfNeed(WindowInsets windowInsets) { } } + @SuppressWarnings("deprecation") + private WindowInsets setupMockDisplayCutout(List boundingRects) { + WindowInsets windowInsets = mock(WindowInsets.class); + DisplayCutout displayCutout = mock(DisplayCutout.class); + when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); + when(displayCutout.getBoundingRects()).thenReturn(boundingRects); + // The following mocked methods are necessary to avoid a NullPointerException when calling + // onApplyWindowInsets, but are irrelevant to the behavior this test concerns. + Insets unusedInsets = Insets.of(100, 100, 100, 100); + // WindowInsets::getSystemGestureInsets was added in API 29, deprecated in API 30. + if (Build.VERSION.SDK_INT == 29) { + when(windowInsets.getSystemGestureInsets()).thenReturn(unusedInsets); + } + // WindowInsets::getInsets was added in API 30. + if (Build.VERSION.SDK_INT >= 30) { + when(windowInsets.getInsets(anyInt())).thenReturn(unusedInsets); + } + // DisplayCutout::getWaterfallInsets was added in API 30. + if (Build.VERSION.SDK_INT >= 30) { + when(displayCutout.getWaterfallInsets()).thenReturn(unusedInsets); + } + when(displayCutout.getSafeInsetTop()).thenReturn(100); + when(displayCutout.getSafeInsetLeft()).thenReturn(100); + when(displayCutout.getSafeInsetBottom()).thenReturn(100); + when(displayCutout.getSafeInsetRight()).thenReturn(100); + return windowInsets; + } + /* * A custom shadow that reports fullscreen flag for system UI visibility */ diff --git a/shell/platform/android/test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java b/shell/platform/android/test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java deleted file mode 100644 index 421f857856cde..0000000000000 --- a/shell/platform/android/test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java +++ /dev/null @@ -1,157 +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. - -package io.flutter.embedding.engine.plugins.shim; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.Activity; -import android.content.Context; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding; -import io.flutter.embedding.engine.plugins.PluginRegistry; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.annotation.Config; - -@Config(manifest = Config.NONE) -@RunWith(AndroidJUnit4.class) -public class ShimPluginRegistryTest { - - @Mock private FlutterEngine mockFlutterEngine; - @Mock private FlutterPluginBinding mockFlutterPluginBinding; - @Mock private ActivityPluginBinding mockActivityPluginBinding; - @Mock private PluginRegistry mockPluginRegistry; - @Mock private Context mockApplicationContext; - @Mock private Activity mockActivity; - - @Before - public void setup() { - MockitoAnnotations.openMocks(this); - when(mockFlutterEngine.getPlugins()).thenReturn(mockPluginRegistry); - when(mockFlutterPluginBinding.getApplicationContext()).thenReturn(mockApplicationContext); - when(mockActivityPluginBinding.getActivity()).thenReturn(mockActivity); - } - - @SuppressWarnings("deprecation") - // Test is intentionally verifying deprecated behavior. - @Test - public void itSuppliesOldAPIsViaTheNewFlutterPluginBinding() { - ShimPluginRegistry registryUnderTest = new ShimPluginRegistry(mockFlutterEngine); - // Fully qualifed name because imports can not have deprecation supression. - // This is the consumption side of the old plugins. - io.flutter.plugin.common.PluginRegistry.Registrar registrarUnderTest = - registryUnderTest.registrarFor("test"); - - ArgumentCaptor shimAggregateCaptor = - ArgumentCaptor.forClass(FlutterPlugin.class); - // A single shim aggregate was added as a new plugin to the FlutterEngine's PluginRegistry. - verify(mockPluginRegistry).add(shimAggregateCaptor.capture()); - // This is really a ShimRegistrarAggregate acting as a FlutterPlugin which is the - // intermediate consumption side of the new plugin inside the shim. - FlutterPlugin shimAggregateUnderTest = shimAggregateCaptor.getValue(); - // The FlutterPluginBinding is the supply side of the new plugin. - shimAggregateUnderTest.onAttachedToEngine(mockFlutterPluginBinding); - - // Consume something from the old plugin API. - assertEquals(mockApplicationContext, registrarUnderTest.context()); - // Check that the value comes from the supply side of the new plugin. - verify(mockFlutterPluginBinding).getApplicationContext(); - } - - @SuppressWarnings("deprecation") - // Test is intentionally verifying deprecated behavior. - @Test - public void itSuppliesMultipleOldPlugins() { - ShimPluginRegistry registryUnderTest = new ShimPluginRegistry(mockFlutterEngine); - // Fully qualifed name because imports can not have deprecation supression. - io.flutter.plugin.common.PluginRegistry.Registrar registrarUnderTest1 = - registryUnderTest.registrarFor("test1"); - io.flutter.plugin.common.PluginRegistry.Registrar registrarUnderTest2 = - registryUnderTest.registrarFor("test2"); - - ArgumentCaptor shimAggregateCaptor = - ArgumentCaptor.forClass(FlutterPlugin.class); - verify(mockPluginRegistry).add(shimAggregateCaptor.capture()); - // There's only one aggregate for many old plugins. - FlutterPlugin shimAggregateUnderTest = shimAggregateCaptor.getValue(); - - // The FlutterPluginBinding is the supply side of the new plugin. - shimAggregateUnderTest.onAttachedToEngine(mockFlutterPluginBinding); - - // Since the 2 old plugins are supplied by the same intermediate FlutterPlugin, they should - // get the same value. - assertEquals(registrarUnderTest1.context(), registrarUnderTest2.context()); - verify(mockFlutterPluginBinding, times(2)).getApplicationContext(); - } - - @SuppressWarnings("deprecation") - // Test is intentionally verifying deprecated behavior. - @Test - public void itCanOnlySupplyActivityBindingWhenUpstreamActivityIsAttached() { - ShimPluginRegistry registryUnderTest = new ShimPluginRegistry(mockFlutterEngine); - io.flutter.plugin.common.PluginRegistry.Registrar registrarUnderTest = - registryUnderTest.registrarFor("test"); - - ArgumentCaptor shimAggregateCaptor = - ArgumentCaptor.forClass(FlutterPlugin.class); - verify(mockPluginRegistry).add(shimAggregateCaptor.capture()); - FlutterPlugin shimAggregateAsPlugin = shimAggregateCaptor.getValue(); - ActivityAware shimAggregateAsActivityAware = (ActivityAware) shimAggregateCaptor.getValue(); - - // Nothing is retrievable when nothing is attached. - assertNull(registrarUnderTest.context()); - assertNull(registrarUnderTest.activity()); - - shimAggregateAsPlugin.onAttachedToEngine(mockFlutterPluginBinding); - - assertEquals(mockApplicationContext, registrarUnderTest.context()); - assertNull(registrarUnderTest.activity()); - - shimAggregateAsActivityAware.onAttachedToActivity(mockActivityPluginBinding); - - // Now context is the activity context. - assertEquals(mockActivity, registrarUnderTest.activeContext()); - assertEquals(mockActivity, registrarUnderTest.activity()); - - shimAggregateAsActivityAware.onDetachedFromActivityForConfigChanges(); - - assertEquals(mockApplicationContext, registrarUnderTest.activeContext()); - assertNull(registrarUnderTest.activity()); - - shimAggregateAsActivityAware.onReattachedToActivityForConfigChanges(mockActivityPluginBinding); - assertEquals(mockActivity, registrarUnderTest.activeContext()); - assertEquals(mockActivity, registrarUnderTest.activity()); - - shimAggregateAsActivityAware.onDetachedFromActivity(); - - assertEquals(mockApplicationContext, registrarUnderTest.activeContext()); - assertNull(registrarUnderTest.activity()); - - // Attach an activity again. - shimAggregateAsActivityAware.onAttachedToActivity(mockActivityPluginBinding); - - assertEquals(mockActivity, registrarUnderTest.activeContext()); - assertEquals(mockActivity, registrarUnderTest.activity()); - - // Now rip out the whole engine. - shimAggregateAsPlugin.onDetachedFromEngine(mockFlutterPluginBinding); - - // And everything should have been made unavailable. - assertNull(registrarUnderTest.activeContext()); - assertNull(registrarUnderTest.activity()); - } -} diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index eaada1ef26fee..862d00a064aac 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -288,14 +288,20 @@ public void itConvertsDisplayFeatureArrayToPrimitiveArrays() { metrics.width = 1000; metrics.height = 1000; metrics.devicePixelRatio = 2; - metrics.displayFeatures.add( - new FlutterRenderer.DisplayFeature( - new Rect(10, 20, 30, 40), - FlutterRenderer.DisplayFeatureType.FOLD, - FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED)); - metrics.displayFeatures.add( - new FlutterRenderer.DisplayFeature( - new Rect(50, 60, 70, 80), FlutterRenderer.DisplayFeatureType.CUTOUT)); + metrics + .getDisplayFeatures() + .add( + new FlutterRenderer.DisplayFeature( + new Rect(10, 20, 30, 40), + FlutterRenderer.DisplayFeatureType.FOLD, + FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED)); + metrics + .getDisplayCutouts() + .add( + new FlutterRenderer.DisplayFeature( + new Rect(50, 60, 70, 80), + FlutterRenderer.DisplayFeatureType.CUTOUT, + FlutterRenderer.DisplayFeatureState.UNKNOWN)); // Execute the behavior under test. flutterRenderer.setViewportMetrics(metrics); diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java index 0b1ec96a0789f..815adfc11ebe1 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java @@ -9,14 +9,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.annotation.TargetApi; -import android.util.SparseArray; -import android.view.InputDevice; import android.view.KeyEvent; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.plugin.common.BinaryMessenger; @@ -25,7 +21,6 @@ import java.nio.ByteBuffer; import org.json.JSONException; import org.json.JSONObject; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,14 +28,10 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.Resetter; -import org.robolectric.shadow.api.Shadow; @Config(manifest = Config.NONE) @RunWith(AndroidJUnit4.class) -@TargetApi(API_LEVELS.API_24) +@TargetApi(API_LEVELS.API_35) public class KeyEventChannelTest { KeyEvent keyEvent; @@ -66,20 +57,8 @@ public void setUp() { keyEventChannel = new KeyEventChannel(fakeMessenger); } - @After - public void tearDown() { - ShadowInputDevice.reset(); - } - @Test - @Config(shadows = {ShadowInputDevice.class}) public void keyDownEventIsSentToFramework() throws JSONException { - final InputDevice device = mock(InputDevice.class); - when(device.isVirtual()).thenReturn(false); - when(device.getName()).thenReturn("keyboard"); - ShadowInputDevice.sDeviceIds = new int[] {0}; - ShadowInputDevice.addDevice(0, device); - KeyEventChannel.FlutterKeyEvent flutterKeyEvent = new KeyEventChannel.FlutterKeyEvent(keyEvent, null); keyEventChannel.sendFlutterKeyEvent( @@ -106,14 +85,7 @@ public void keyDownEventIsSentToFramework() throws JSONException { } @Test - @Config(shadows = {ShadowInputDevice.class}) public void keyUpEventIsSentToFramework() throws JSONException { - final InputDevice device = mock(InputDevice.class); - when(device.isVirtual()).thenReturn(false); - when(device.getName()).thenReturn("keyboard"); - ShadowInputDevice.sDeviceIds = new int[] {0}; - ShadowInputDevice.addDevice(0, device); - keyEvent = new FakeKeyEvent(KeyEvent.ACTION_UP, 65); KeyEventChannel.FlutterKeyEvent flutterKeyEvent = new KeyEventChannel.FlutterKeyEvent(keyEvent, null); @@ -139,48 +111,4 @@ public void keyUpEventIsSentToFramework() throws JSONException { sendReply(true, replyArgumentCaptor.getValue()); assertTrue(handled[0]); } - - @Implements(InputDevice.class) - public static class ShadowInputDevice extends org.robolectric.shadows.ShadowInputDevice { - public static int[] sDeviceIds; - private static SparseArray sDeviceMap = new SparseArray<>(); - - private int mDeviceId; - - @Implementation - protected static int[] getDeviceIds() { - return sDeviceIds; - } - - @Implementation - protected static InputDevice getDevice(int id) { - return sDeviceMap.get(id); - } - - public static void addDevice(int id, InputDevice device) { - sDeviceMap.append(id, device); - } - - @Resetter - public static void reset() { - sDeviceIds = null; - sDeviceMap.clear(); - } - - @Implementation - protected int getId() { - return mDeviceId; - } - - public static InputDevice makeInputDevicebyId(int id) { - final InputDevice inputDevice = Shadow.newInstanceOf(InputDevice.class); - final ShadowInputDevice shadowInputDevice = Shadow.extract(inputDevice); - shadowInputDevice.setId(id); - return inputDevice; - } - - public void setId(int id) { - mDeviceId = id; - } - } } diff --git a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java index 77f71f0aaf923..d10df7bf1aee2 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -1356,6 +1356,40 @@ public void showTextInput_textInputTypeNone() { assertEquals(testImm.isSoftInputVisible(), false); } + @Test + public void showTextInput_textInputTypeWebSearch() { + TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE)); + View testView = new View(ctx); + DartExecutor dartExecutor = mock(DartExecutor.class); + TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class)); + TextInputPlugin textInputPlugin = + new TextInputPlugin( + testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class)); + textInputPlugin.setTextInputClient( + 0, + new TextInputChannel.Configuration( + false, + false, + true, + true, + false, + TextInputChannel.TextCapitalization.NONE, + new TextInputChannel.InputType(TextInputChannel.TextInputType.WEB_SEARCH, false, false), + null, + null, + null, + null, + null)); + + EditorInfo editorInfo = new EditorInfo(); + InputConnection connection = + textInputPlugin.createInputConnection(testView, mock(KeyboardManager.class), editorInfo); + + assertEquals( + editorInfo.inputType, InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); + } + @Test public void inputConnection_textInputTypeMultilineAndSuggestionsDisabled() { // Regression test for https://github.com/flutter/flutter/issues/71679. diff --git a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java index f86ea7a3b8d79..011616724511c 100644 --- a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java +++ b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java @@ -2165,6 +2165,21 @@ public void SetSourceAndPackageNameForAccessibilityEvent() { verify(mockEvent).setSource(eq(mockRootView), eq(123)); } + @Test + public void itAddsClickActionToSliderNodeInfo() { + AccessibilityBridge accessibilityBridge = setUpBridge(); + + TestSemanticsNode testSemanticsNode = new TestSemanticsNode(); + testSemanticsNode.addFlag(AccessibilityBridge.Flag.IS_SLIDER); + TestSemanticsUpdate testSemanticsUpdate = testSemanticsNode.toUpdate(); + testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge); + AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(0); + + assertEquals(nodeInfo.isClickable(), true); + List actions = nodeInfo.getActionList(); + assertTrue(actions.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)); + } + AccessibilityBridge setUpBridge() { return setUpBridge(null, null, null, null, null, null); } diff --git a/shell/platform/android/test_runner/build.gradle b/shell/platform/android/test_runner/build.gradle index 458cc13d6b5fe..5e5243f14a2d0 100644 --- a/shell/platform/android/test_runner/build.gradle +++ b/shell/platform/android/test_runner/build.gradle @@ -4,7 +4,7 @@ buildscript { mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:8.5.0" + classpath "com.android.tools.build:gradle:8.7.2" } } @@ -71,7 +71,7 @@ android { testImplementation "androidx.test:core:1.4.0" testImplementation "com.google.android.play:core:1.8.0" testImplementation "com.ibm.icu:icu4j:69.1" - testImplementation "org.robolectric:robolectric:4.12.1" + testImplementation "org.robolectric:robolectric:4.14.1" testImplementation "junit:junit:4.13.2" testImplementation "androidx.test.ext:junit:1.1.4-alpha07" diff --git a/shell/platform/android/test_runner/src/main/resources/robolectric.properties b/shell/platform/android/test_runner/src/main/resources/robolectric.properties index ebf6f5b5a478d..7e47f6fb21ee6 100644 --- a/shell/platform/android/test_runner/src/main/resources/robolectric.properties +++ b/shell/platform/android/test_runner/src/main/resources/robolectric.properties @@ -1,2 +1,2 @@ -sdk=33 +sdk=35 shadows=io.flutter.CustomShadowContextImpl diff --git a/shell/platform/darwin/common/BUILD.gn b/shell/platform/darwin/common/BUILD.gn index 3d509700e054f..3881e6df3f227 100644 --- a/shell/platform/darwin/common/BUILD.gn +++ b/shell/platform/darwin/common/BUILD.gn @@ -40,9 +40,6 @@ source_set("common") { # See: Upstream clang change: https://reviews.llvm.org/D150397 # See: https://github.com/flutter/flutter/issues/133777 source_set("availability_version_check") { - cflags_objc = flutter_cflags_objc - cflags_objcc = flutter_cflags_objcc - sources = [ "availability_version_check.cc" ] deps = [ "//flutter/fml" ] @@ -78,8 +75,8 @@ config("framework_relative_headers") { # Framework code shared between iOS and macOS. source_set("framework_common") { - cflags_objc = flutter_cflags_objc_arc - cflags_objcc = flutter_cflags_objcc_arc + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc sources = [ "framework/Source/FlutterBinaryMessengerRelay.mm", @@ -114,6 +111,9 @@ test_fixtures("framework_common_fixtures") { # Unit tests for channels. executable("framework_common_unittests") { testonly = true + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + ldflags = [ "-ObjC" ] sources = [ "framework/Source/FlutterBinaryMessengerRelayTest.mm", @@ -122,10 +122,6 @@ executable("framework_common_unittests") { "framework/Source/flutter_standard_codec_unittest.mm", ] - cflags_objcc = flutter_cflags_objcc_arc - - ldflags = [ "-ObjC" ] - deps = [ ":framework_common", ":framework_common_fixtures", diff --git a/shell/platform/darwin/common/buffer_conversions.mm b/shell/platform/darwin/common/buffer_conversions.mm index 1aa07df2653a8..6a44c2ca56c6d 100644 --- a/shell/platform/darwin/common/buffer_conversions.mm +++ b/shell/platform/darwin/common/buffer_conversions.mm @@ -5,24 +5,23 @@ #import "flutter/shell/platform/darwin/common/buffer_conversions.h" #include "flutter/fml/macros.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" + +static_assert(__has_feature(objc_arc), "ARC must be enabled."); namespace flutter { namespace { class NSDataMapping : public fml::Mapping { public: - explicit NSDataMapping(NSData* data) : data_([data retain]) {} + explicit NSDataMapping(NSData* data) : data_(data) {} - size_t GetSize() const override { return [data_.get() length]; } + size_t GetSize() const override { return data_.length; } - const uint8_t* GetMapping() const override { - return static_cast([data_.get() bytes]); - } + const uint8_t* GetMapping() const override { return static_cast(data_.bytes); } bool IsDontNeedSafe() const override { return false; } private: - fml::scoped_nsobject data_; + NSData* data_; FML_DISALLOW_COPY_AND_ASSIGN(NSDataMapping); }; } // namespace diff --git a/shell/platform/darwin/common/command_line.mm b/shell/platform/darwin/common/command_line.mm index d9f42e634fde6..47c8ef2b8ede1 100644 --- a/shell/platform/darwin/common/command_line.mm +++ b/shell/platform/darwin/common/command_line.mm @@ -6,6 +6,8 @@ #import +static_assert(__has_feature(objc_arc), "ARC must be enabled."); + namespace flutter { fml::CommandLine CommandLineFromNSProcessInfo(NSProcessInfo* processInfoOrNil) { diff --git a/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.h b/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.h index 93dfc77989b88..e9b745cfdb145 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.h +++ b/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.h @@ -9,7 +9,7 @@ NS_ASSUME_NONNULL_BEGIN -extern const NSString* kDefaultAssetPath; +extern NSString* const kDefaultAssetPath; // Finds a bundle with the named `flutterFrameworkBundleID` within `searchURL`. // diff --git a/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.mm b/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.mm index c96b1a9af181e..369e3ce263827 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.mm +++ b/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.mm @@ -2,13 +2,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#import "flutter/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.h" + #include #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" FLUTTER_ASSERT_ARC -const NSString* kDefaultAssetPath = @"Frameworks/App.framework/flutter_assets"; +NSString* const kDefaultAssetPath = @"Frameworks/App.framework/flutter_assets"; + static NSString* GetFlutterAssetsPathFromBundle(NSBundle* bundle, NSString* relativeAssetsPath); NSBundle* FLTFrameworkBundleInternal(NSString* flutterFrameworkBundleID, NSURL* searchURL) { diff --git a/shell/platform/darwin/graphics/BUILD.gn b/shell/platform/darwin/graphics/BUILD.gn index 88ca95c51b593..54d8b17aa49dc 100644 --- a/shell/platform/darwin/graphics/BUILD.gn +++ b/shell/platform/darwin/graphics/BUILD.gn @@ -8,8 +8,8 @@ import("//flutter/common/config.gni") import("//flutter/impeller/tools/impeller.gni") source_set("graphics") { - cflags_objc = flutter_cflags_objc_arc - cflags_objcc = flutter_cflags_objcc_arc + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc sources = [ "FlutterDarwinContextMetalSkia.h", diff --git a/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.mm b/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.mm index af73983665ff1..3e41eedf91e2d 100644 --- a/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.mm +++ b/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.mm @@ -25,14 +25,8 @@ std::make_shared(impeller_framebuffer_blend_shaders_data, impeller_framebuffer_blend_shaders_length), }; - auto context = impeller::ContextMTL::Create(shader_mappings, is_gpu_disabled_sync_switch, - "Impeller Library"); - if (!context) { - FML_LOG(ERROR) << "Could not create Metal Impeller Context."; - return nullptr; - } - - return context; + return impeller::ContextMTL::Create(shader_mappings, is_gpu_disabled_sync_switch, + "Impeller Library"); } @implementation FlutterDarwinContextMetalImpeller @@ -41,11 +35,9 @@ - (instancetype)init:(const std::shared_ptr&)is_gpu_disab self = [super init]; if (self != nil) { _context = CreateImpellerContext(is_gpu_disabled_sync_switch); + FML_CHECK(_context) << "Could not create Metal Impeller Context."; id device = _context->GetMTLDevice(); - if (!device) { - FML_DLOG(ERROR) << "Could not acquire Metal device."; - return nil; - } + FML_CHECK(device) << "Could not acquire Metal device."; CVMetalTextureCacheRef textureCache; CVReturn cvReturn = CVMetalTextureCacheCreate(kCFAllocatorDefault, // allocator @@ -55,10 +47,7 @@ - (instancetype)init:(const std::shared_ptr&)is_gpu_disab &textureCache // [out] cache ); - if (cvReturn != kCVReturnSuccess) { - FML_DLOG(ERROR) << "Could not create Metal texture cache."; - return nil; - } + FML_CHECK(cvReturn == kCVReturnSuccess) << "Could not acquire Metal device."; _textureCache.Reset(textureCache); } return self; diff --git a/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.mm b/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.mm index fb2d561aff480..390328f1ed435 100644 --- a/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.mm +++ b/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.mm @@ -8,6 +8,7 @@ #include "flutter/common/graphics/persistent_cache.h" #include "flutter/fml/logging.h" +#include "flutter/fml/platform/darwin/cf_utils.h" #include "flutter/shell/common/context_options.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" @@ -16,7 +17,9 @@ FLUTTER_ASSERT_ARC -@implementation FlutterDarwinContextMetalSkia +@implementation FlutterDarwinContextMetalSkia { + fml::CFRef _textureCache; +} - (instancetype)initWithDefaultMTLDevice { id device = MTLCreateSystemDefaultDevice(); @@ -43,15 +46,19 @@ - (instancetype)initWithMTLDevice:(id)device [_commandQueue setLabel:@"Flutter Main Queue"]; - CVReturn cvReturn = CVMetalTextureCacheCreate(kCFAllocatorDefault, // allocator - nil, // cache attributes (nil default) - _device, // metal device - nil, // texture attributes (nil default) - &_textureCache // [out] cache - ); - if (cvReturn != kCVReturnSuccess) { - FML_DLOG(ERROR) << "Could not create Metal texture cache."; - return nil; + { + CVMetalTextureCacheRef cache = nullptr; + CVReturn cvReturn = CVMetalTextureCacheCreate(kCFAllocatorDefault, // allocator + nil, // cache attributes (nil default) + _device, // metal device + nil, // texture attributes (nil default) + &cache // [out] cache + ); + _textureCache.Reset(cache); + if (cvReturn != kCVReturnSuccess) { + FML_DLOG(ERROR) << "Could not create Metal texture cache."; + return nil; + } } // The devices are in the same "sharegroup" because they share the same device and command @@ -77,8 +84,6 @@ - (instancetype)initWithMTLDevice:(id)device } - (sk_sp)createGrContext { - const auto contextOptions = - flutter::MakeDefaultContextOptions(flutter::ContextType::kRender, GrBackendApi::kMetal); id device = _device; id commandQueue = _commandQueue; return [FlutterDarwinContextMetalSkia createGrContext:device commandQueue:commandQueue]; @@ -91,17 +96,11 @@ - (instancetype)initWithMTLDevice:(id)device GrMtlBackendContext backendContext = {}; // Skia expect arguments to `MakeMetal` transfer ownership of the reference in for release later // when the GrDirectContext is collected. - backendContext.fDevice.reset((__bridge_retained void*)device); - backendContext.fQueue.reset((__bridge_retained void*)commandQueue); + backendContext.fDevice.retain((__bridge void*)device); + backendContext.fQueue.retain((__bridge void*)commandQueue); return GrDirectContexts::MakeMetal(backendContext, contextOptions); } -- (void)dealloc { - if (_textureCache) { - CFRelease(_textureCache); - } -} - - (FlutterDarwinExternalTextureMetal*) createExternalTextureWithIdentifier:(int64_t)textureID texture:(NSObject*)texture { @@ -111,6 +110,10 @@ - (void)dealloc { enableImpeller:NO]; } +- (CVMetalTextureCacheRef)textureCache { + return _textureCache; +} + @end #endif // !SLIMPELLER diff --git a/shell/platform/darwin/graphics/FlutterDarwinExternalTextureMetal.mm b/shell/platform/darwin/graphics/FlutterDarwinExternalTextureMetal.mm index 5938458018a73..097a16076a0d6 100644 --- a/shell/platform/darwin/graphics/FlutterDarwinExternalTextureMetal.mm +++ b/shell/platform/darwin/graphics/FlutterDarwinExternalTextureMetal.mm @@ -3,7 +3,9 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/graphics/FlutterDarwinExternalTextureMetal.h" + #include "flutter/display_list/image/dl_image.h" +#include "flutter/fml/platform/darwin/cf_utils.h" #include "impeller/base/validation.h" #include "impeller/display_list/aiks_context.h" #include "impeller/display_list/dl_image_impeller.h" @@ -23,7 +25,7 @@ FLUTTER_ASSERT_ARC @implementation FlutterDarwinExternalTextureMetal { - CVMetalTextureCacheRef _textureCache; + fml::CFRef _textureCache; NSObject* _externalTexture; BOOL _textureFrameAvailable; sk_sp _externalImage; @@ -37,8 +39,7 @@ - (instancetype)initWithTextureCache:(nonnull CVMetalTextureCacheRef)textureCach texture:(NSObject*)texture enableImpeller:(BOOL)enableImpeller { if (self = [super init]) { - _textureCache = textureCache; - CFRetain(_textureCache); + _textureCache.Retain(textureCache); _textureID = textureID; _externalTexture = texture; _enableImpeller = enableImpeller; @@ -50,10 +51,7 @@ - (instancetype)initWithTextureCache:(nonnull CVMetalTextureCacheRef)textureCach - (void)dealloc { CVPixelBufferRelease(_lastPixelBuffer); if (_textureCache) { - CVMetalTextureCacheFlush(_textureCache, // cache - 0 // options (must be zero) - ); - CFRelease(_textureCache); + CVMetalTextureCacheFlush(_textureCache, /* options (must be zero) */ 0); } } @@ -107,9 +105,9 @@ - (void)onGrContextDestroyed { // buffer will be used to materialize the image in case the application fails to provide a new // one. _externalImage.reset(); - CVMetalTextureCacheFlush(_textureCache, // cache - 0 // options (must be zero) - ); + if (_textureCache) { + CVMetalTextureCacheFlush(_textureCache, /* options (must be zero) */ 0); + } } - (void)markNewFrameAvailable { @@ -279,14 +277,14 @@ @implementation FlutterDarwinExternalTextureSkImageWrapper return nullptr; #else // SLIMPELLER GrMtlTextureInfo ySkiaTextureInfo; - ySkiaTextureInfo.fTexture = sk_cfp{(__bridge_retained const void*)yTex}; + ySkiaTextureInfo.fTexture.retain((__bridge GrMTLHandle)yTex); GrBackendTexture skiaBackendTextures[2]; skiaBackendTextures[0] = GrBackendTextures::MakeMtl(width, height, skgpu::Mipmapped::kNo, ySkiaTextureInfo); GrMtlTextureInfo uvSkiaTextureInfo; - uvSkiaTextureInfo.fTexture = sk_cfp{(__bridge_retained const void*)uvTex}; + uvSkiaTextureInfo.fTexture.retain((__bridge GrMTLHandle)uvTex); skiaBackendTextures[1] = GrBackendTextures::MakeMtl(width, height, skgpu::Mipmapped::kNo, uvSkiaTextureInfo); @@ -310,7 +308,7 @@ GrYUVABackendTextures yuvaBackendTextures(yuvaInfo, skiaBackendTextures, #else // SLIMPELLER GrMtlTextureInfo skiaTextureInfo; - skiaTextureInfo.fTexture = sk_cfp{(__bridge_retained const void*)rgbaTex}; + skiaTextureInfo.fTexture.retain((__bridge GrMTLHandle)rgbaTex); GrBackendTexture skiaBackendTexture = GrBackendTextures::MakeMtl(width, height, skgpu::Mipmapped ::kNo, skiaTextureInfo); diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 5e197c315828e..6020365844afb 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -39,24 +39,19 @@ _flutter_framework_headers = [ _flutter_framework_headers_copy_dir = "$_flutter_framework_dir/Headers" -source_set("flutter_framework_source_arc") { +source_set("flutter_framework_source") { visibility = [ ":*" ] - cflags_objc = flutter_cflags_objc_arc - cflags_objcc = flutter_cflags_objcc_arc + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc defines = [ "FLUTTER_FRAMEWORK=1" ] if (darwin_extension_safe) { defines += [ "APPLICATION_EXTENSION_API_ONLY=1" ] } - allow_circular_includes_from = [ ":flutter_framework_source" ] - deps = [ - ":flutter_framework_source", - "//flutter/fml", - "//flutter/shell/platform/common:common_cpp_input", - "//flutter/shell/platform/darwin/common:framework_common", - "//flutter/third_party/icu", + public_configs = [ + ":ios_gpu_configuration_config", + "//flutter:config", ] - public_configs = [ "//flutter:config" ] sources = [ "framework/Source/FlutterAppDelegate.mm", @@ -84,8 +79,10 @@ source_set("flutter_framework_source_arc") { "framework/Source/FlutterOverlayView.mm", "framework/Source/FlutterPlatformPlugin.h", "framework/Source/FlutterPlatformPlugin.mm", + "framework/Source/FlutterPlatformViews.mm", + "framework/Source/FlutterPlatformViewsController.h", + "framework/Source/FlutterPlatformViewsController.mm", "framework/Source/FlutterPlatformViews_Internal.h", - "framework/Source/FlutterPlatformViews_Internal.mm", "framework/Source/FlutterPluginAppLifeCycleDelegate.mm", "framework/Source/FlutterRestorationPlugin.h", "framework/Source/FlutterRestorationPlugin.mm", @@ -105,9 +102,12 @@ source_set("flutter_framework_source_arc") { "framework/Source/FlutterUndoManagerPlugin.mm", "framework/Source/FlutterView.h", "framework/Source/FlutterView.mm", + "framework/Source/FlutterViewController.mm", + "framework/Source/FlutterViewController_Internal.h", "framework/Source/FlutterViewResponder.h", "framework/Source/KeyCodeMap.g.mm", "framework/Source/KeyCodeMap_Internal.h", + "framework/Source/SemanticsObject+UIFocusSystem.mm", "framework/Source/SemanticsObject.h", "framework/Source/SemanticsObject.mm", "framework/Source/TextInputSemanticsObject.h", @@ -122,8 +122,6 @@ source_set("flutter_framework_source_arc") { "framework/Source/overlay_layer_pool.mm", "framework/Source/platform_message_response_darwin.h", "framework/Source/platform_message_response_darwin.mm", - "framework/Source/platform_views_controller.h", - "framework/Source/platform_views_controller.mm", "framework/Source/profiler_metrics_ios.h", "framework/Source/profiler_metrics_ios.mm", "framework/Source/vsync_waiter_ios.h", @@ -154,81 +152,45 @@ source_set("flutter_framework_source_arc") { "ios_surface_software.mm", "platform_message_handler_ios.h", "platform_message_handler_ios.mm", + "platform_view_ios.h", + "platform_view_ios.mm", "rendering_api_selection.h", "rendering_api_selection.mm", ] + sources += _flutter_framework_headers frameworks = [ - "UIKit.framework", + "AudioToolbox.framework", + "CoreMedia.framework", + "CoreVideo.framework", "IOSurface.framework", + "QuartzCore.framework", + "WebKit.framework", + "UIKit.framework", ] + if (flutter_runtime_mode == "profile" || flutter_runtime_mode == "debug") { + # This is required by the profiler_metrics_ios.mm to get GPU statistics. + # Usage in release builds will cause rejection from the App Store. + frameworks += [ "IOKit.framework" ] + } - deps += [ + deps = [ ":ios_gpu_configuration", "//flutter/common:common", "//flutter/common/graphics", - "//flutter/lib/ui", - "//flutter/runtime", - "//flutter/shell/common", - "//flutter/shell/platform/darwin/common", - "//flutter/shell/platform/darwin/graphics", - "//flutter/shell/platform/embedder:embedder_as_internal_library", - "//flutter/shell/profiling:profiling", - ] -} - -source_set("flutter_framework_source") { - visibility = [ ":*" ] - cflags_objc = flutter_cflags_objc - cflags_objcc = flutter_cflags_objcc - - deps = [] - - sources = [ - # iOS embedder is migrating to ARC. - # New files are highly encouraged to be in ARC. - # To add new files in ARC, add them to the `flutter_framework_source_arc` target. - "framework/Source/FlutterViewController.mm", - "framework/Source/FlutterViewController_Internal.h", - "platform_view_ios.h", - "platform_view_ios.mm", - ] - - sources += _flutter_framework_headers - - defines = [ "FLUTTER_FRAMEWORK=1" ] - if (darwin_extension_safe) { - defines += [ "APPLICATION_EXTENSION_API_ONLY=1" ] - } - - deps += [ "//flutter/fml", + "//flutter/lib/ui", "//flutter/runtime", "//flutter/shell/common", + "//flutter/shell/platform/common:common_cpp_input", "//flutter/shell/platform/darwin/common", "//flutter/shell/platform/darwin/common:framework_common", + "//flutter/shell/platform/darwin/graphics", "//flutter/shell/platform/embedder:embedder_as_internal_library", "//flutter/shell/profiling:profiling", + "//flutter/third_party/icu", "//flutter/third_party/spring_animation", ] - - public_configs = [ - ":ios_gpu_configuration_config", - "//flutter:config", - ] - - frameworks = [ - "AudioToolbox.framework", - "CoreMedia.framework", - "CoreVideo.framework", - "QuartzCore.framework", - "UIKit.framework", - ] - if (flutter_runtime_mode == "profile" || flutter_runtime_mode == "debug") { - # This is required by the profiler_metrics_ios.mm to get GPU statistics. - # Usage in release builds will cause rejection from the App Store. - frameworks += [ "IOKit.framework" ] - } } platform_frameworks_path = @@ -237,18 +199,15 @@ platform_frameworks_path = shared_library("ios_test_flutter") { testonly = true visibility = [ "*" ] + + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc cflags = [ "-fvisibility=default", "-F$platform_frameworks_path", - "-fobjc-arc", "-mios-simulator-version-min=$ios_testing_deployment_target", ] - # XCode 15 beta has a bug where iOS 17 API usage is not guarded. - # This bug results engine build failure since the engine treats warnings as errors. - # The `-Wno-unguarded-availability-new` can be removed when the XCode bug is fixed. - # See details in https://github.com/flutter/flutter/issues/128958. - cflags_objcc = [ "-Wno-unguarded-availability-new" ] ldflags = [ "-F$platform_frameworks_path", "-Wl,-install_name,@rpath/Frameworks/libios_test_flutter.dylib", @@ -295,7 +254,6 @@ shared_library("ios_test_flutter") { deps = [ ":flutter_framework", ":flutter_framework_source", - ":flutter_framework_source_arc", ":ios_gpu_configuration", "//flutter/common:common", "//flutter/lib/ui:ui", @@ -332,10 +290,7 @@ shared_library("create_flutter_framework_dylib") { public = _flutter_framework_headers - deps = [ - ":flutter_framework_source", - ":flutter_framework_source_arc", - ] + deps = [ ":flutter_framework_source" ] public_configs = [ "//flutter:config" ] } diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h b/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h index 7e2ea40fb8d1f..e41565c3b1fc2 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h @@ -233,7 +233,7 @@ FLUTTER_DARWIN_EXPORT * `FlutterViewController` is initialized with or a new `FlutterEngine` implicitly created if * no engine was supplied during initialization. */ -@property(weak, nonatomic, readonly) FlutterEngine* engine; +@property(nonatomic, readonly) FlutterEngine* engine; /** * The `FlutterBinaryMessenger` associated with this FlutterViewController (used for communicating diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index 8232fe100167c..3f31c181f3d17 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -177,21 +177,6 @@ static BOOL DoesHardwareSupportWideGamut() { settings.enable_wide_gamut = enableWideGamut; #endif -#if FML_OS_IOS_SIMULATOR - if (!command_line.HasOption("enable-impeller")) { - // Next, look in the app bundle. - NSNumber* enableImpeller = [bundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]; - if (enableImpeller == nil) { - // If it isn't in the app bundle, look in the main bundle. - enableImpeller = [mainBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]; - } - // Change the default only if the option is present. - if (enableImpeller != nil) { - settings.enable_impeller = enableImpeller.boolValue; - } - } -#endif // FML_OS_IOS_SIMULATOR - settings.warn_on_impeller_opt_out = true; NSNumber* enableTraceSystrace = [mainBundle objectForInfoDictionaryKey:@"FLTTraceSystrace"]; @@ -218,6 +203,12 @@ static BOOL DoesHardwareSupportWideGamut() { settings.leak_vm = leakDartVM.boolValue; } + NSNumber* enableMergedPlatformUIThread = + [mainBundle objectForInfoDictionaryKey:@"FLTEnableMergedPlatformUIThread"]; + if (enableMergedPlatformUIThread != nil) { + settings.merged_platform_ui_thread = enableMergedPlatformUIThread.boolValue; + } + #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG // There are no ownership concerns here as all mappings are owned by the // embedder and not the engine. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm index 19c7d4fc66252..fbb2cbe3c21aa 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm @@ -225,16 +225,6 @@ - (void)testLookUpForAssetsFromPackageFromBundle { } } -- (void)testDisableImpellerSettingIsCorrectlyParsed { - id mockMainBundle = OCMPartialMock([NSBundle mainBundle]); - OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]).andReturn(@"NO"); - - auto settings = FLTDefaultSettingsForBundle(); - // Check settings.enable_impeller value is same as the value defined in Info.plist. - XCTAssertEqual(settings.enable_impeller, NO); - [mockMainBundle stopMocking]; -} - - (void)testRequestsWarningWhenImpellerOptOut { auto settings = FLTDefaultSettingsForBundle(); XCTAssertEqual(settings.warn_on_impeller_opt_out, YES); @@ -277,49 +267,6 @@ - (void)testEnableDartAssertsCommandLineArgument { [mockMainBundle stopMocking]; } -- (void)testDisableImpellerSettingIsCorrectlyOverriddenByCommandLine { - id mockMainBundle = OCMPartialMock([NSBundle mainBundle]); - OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]).andReturn(@"YES"); - id mockProcessInfo = OCMPartialMock([NSProcessInfo processInfo]); - NSArray* arguments = @[ @"process_name", @"--enable-impeller=false" ]; - OCMStub([mockProcessInfo arguments]).andReturn(arguments); - - auto settings = FLTDefaultSettingsForBundle(nil, mockProcessInfo); - // Check settings.enable_impeller value is same as the value on command line. - XCTAssertEqual(settings.enable_impeller, NO); - [mockMainBundle stopMocking]; -} - -- (void)testDisableImpellerAppBundleSettingIsCorrectlyParsed { - NSString* bundleId = [FlutterDartProject defaultBundleIdentifier]; - id mockAppBundle = OCMClassMock([NSBundle class]); - OCMStub([mockAppBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]).andReturn(@"NO"); - OCMStub([mockAppBundle bundleWithIdentifier:bundleId]).andReturn(mockAppBundle); - - auto settings = FLTDefaultSettingsForBundle(); - // Check settings.enable_impeller value is same as the value defined in Info.plist. - XCTAssertEqual(settings.enable_impeller, NO); - - [mockAppBundle stopMocking]; -} - -- (void)testEnableImpellerAppBundleSettingIsCorrectlyParsed { - NSString* bundleId = [FlutterDartProject defaultBundleIdentifier]; - id mockAppBundle = OCMClassMock([NSBundle class]); - OCMStub([mockAppBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]).andReturn(@"YES"); - OCMStub([mockAppBundle bundleWithIdentifier:bundleId]).andReturn(mockAppBundle); - - // Since FLTEnableImpeller is set to false in the main bundle, this is also - // testing that setting FLTEnableImpeller in the app bundle takes - // precedence over setting it in the root bundle. - - auto settings = FLTDefaultSettingsForBundle(); - // Check settings.enable_impeller value is same as the value defined in Info.plist. - XCTAssertEqual(settings.enable_impeller, YES); - - [mockAppBundle stopMocking]; -} - - (void)testEnableTraceSystraceSettingIsCorrectlyParsed { NSBundle* mainBundle = [NSBundle mainBundle]; NSNumber* enableTraceSystrace = [mainBundle objectForInfoDictionaryKey:@"FLTTraceSystrace"]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartVMServicePublisher.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartVMServicePublisher.mm index 597bc5b8056c6..690d8c1936a7f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartVMServicePublisher.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartVMServicePublisher.mm @@ -41,7 +41,6 @@ - (instancetype)initWithEnableVMServicePublication:(BOOL)enableVMServicePublicat #include "flutter/fml/logging.h" #include "flutter/fml/message_loop.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/runtime/dart_service_isolate.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index b5398514d2e9c..eff08e6cc53e5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -12,7 +12,6 @@ #include "flutter/common/constants.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/platform/darwin/platform_version.h" -#include "flutter/fml/platform/darwin/weak_nsobject.h" #include "flutter/fml/trace_event.h" #include "flutter/runtime/ptrace_check.h" #include "flutter/shell/common/engine.h" @@ -85,7 +84,6 @@ static void IOSPlatformThreadConfigSetter(const fml::Thread::ThreadConfig& confi #pragma mark - Internal constants -NSString* const kFlutterEngineWillDealloc = @"FlutterEngineWillDealloc"; NSString* const kFlutterKeyDataChannel = @"flutter/keydata"; static constexpr int kNumProfilerSamplesPerSec = 5; @@ -107,6 +105,8 @@ @interface FlutterEngine () _threadHost; std::unique_ptr _shell; - // TODO(cbracken): https://github.com/flutter/flutter/issues/155943 - // Migrate to @property(nonatomic, weak). - fml::WeakNSObject _viewController; - - std::shared_ptr _platformViewsController; flutter::IOSRenderingAPI _renderingApi; std::shared_ptr _profiler; @@ -217,7 +212,7 @@ - (instancetype)initWithName:(NSString*)labelPrefix _pluginPublications = [[NSMutableDictionary alloc] init]; _registrars = [[NSMutableDictionary alloc] init]; - [self recreatePlatformViewController]; + [self recreatePlatformViewsController]; _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self]; _textureRegistry = [[FlutterTextureRegistryRelay alloc] initWithParent:self]; _connections.reset(new flutter::ConnectionCollection()); @@ -268,9 +263,9 @@ - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center { object:nil]; } -- (void)recreatePlatformViewController { +- (void)recreatePlatformViewsController { _renderingApi = flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering); - _platformViewsController.reset(new flutter::PlatformViewsController()); + _platformViewsController = [[FlutterPlatformViewsController alloc] init]; } - (flutter::IOSRenderingAPI)platformViewsRenderingAPI { @@ -287,10 +282,6 @@ - (void)dealloc { } }]; - [[NSNotificationCenter defaultCenter] postNotificationName:kFlutterEngineWillDealloc - object:self - userInfo:nil]; - // nil out weak references. // TODO(cbracken): https://github.com/flutter/flutter/issues/156222 // Ensure that FlutterEngineRegistrar is using weak pointers, then eliminate this code. @@ -406,8 +397,7 @@ - (void)ensureSemanticsEnabled { - (void)setViewController:(FlutterViewController*)viewController { FML_DCHECK(self.iosPlatformView); - _viewController = viewController ? [viewController getWeakNSObject] - : fml::WeakNSObject(); + _viewController = viewController; self.iosPlatformView->SetOwnerViewController(_viewController); [self maybeSetupPlatformViewChannels]; [self updateDisplays]; @@ -454,7 +444,7 @@ - (void)notifyViewControllerDeallocated { } } [self.textInputPlugin resetViewResponder]; - _viewController.reset(); + _viewController = nil; } - (void)destroyContext { @@ -463,18 +453,7 @@ - (void)destroyContext { _shell.reset(); _profiler.reset(); _threadHost.reset(); - _platformViewsController.reset(); -} - -- (FlutterViewController*)viewController { - if (!_viewController) { - return nil; - } - return _viewController.get(); -} - -- (std::shared_ptr&)platformViewsController { - return _platformViewsController; + _platformViewsController = nil; } - (NSURL*)observatoryUrl { @@ -653,7 +632,7 @@ - (void)maybeSetupPlatformViewChannels { [self.platformViewsChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { if (weakSelf) { - weakSelf.platformViewsController->OnMethodCall(call, result); + [weakSelf.platformViewsController onMethodCall:call result:result]; } }]; @@ -719,7 +698,7 @@ + (NSString*)generateThreadLabel:(NSString*)labelPrefix { fml::MessageLoop::EnsureInitializedForCurrentThread(); uint32_t threadHostType = flutter::ThreadHost::Type::kRaster | flutter::ThreadHost::Type::kIo; - if (!settings.enable_impeller) { + if (!settings.enable_impeller || !settings.merged_platform_ui_thread) { threadHostType |= flutter::ThreadHost::Type::kUi; } @@ -795,11 +774,11 @@ - (BOOL)createShell:(NSString*)entrypoint if (!strongSelf) { return std::unique_ptr(); } - [strongSelf recreatePlatformViewController]; - strongSelf->_platformViewsController->SetTaskRunner( - shell.GetTaskRunners().GetPlatformTaskRunner()); + [strongSelf recreatePlatformViewsController]; + strongSelf.platformViewsController.taskRunner = + shell.GetTaskRunners().GetPlatformTaskRunner(); return std::make_unique( - shell, strongSelf->_renderingApi, strongSelf->_platformViewsController, + shell, strongSelf->_renderingApi, strongSelf.platformViewsController, shell.GetTaskRunners(), shell.GetConcurrentWorkerTaskRunner(), shell.GetIsGpuDisabledSyncSwitch()); }; @@ -808,7 +787,7 @@ - (BOOL)createShell:(NSString*)entrypoint [](flutter::Shell& shell) { return std::make_unique(shell); }; fml::RefPtr ui_runner; - if (settings.enable_impeller) { + if (settings.enable_impeller && settings.merged_platform_ui_thread) { ui_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); } else { ui_runner = _threadHost->ui_thread->GetTaskRunner(); @@ -1121,7 +1100,7 @@ - (void)flutterTextInputView:(FlutterTextInputView*)textInputView // Have to check in the next run loop, because iOS requests the previous first responder to // resign before requesting the next view to become first responder. dispatch_async(dispatch_get_main_queue(), ^(void) { - long platform_view_id = self.platformViewsController->FindFirstResponderPlatformViewId(); + long platform_view_id = [self.platformViewsController firstResponderPlatformViewId]; if (platform_view_id == -1) { return; } @@ -1418,11 +1397,10 @@ - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint // create call is synchronous. flutter::Shell::CreateCallback on_create_platform_view = [result, context](flutter::Shell& shell) { - [result recreatePlatformViewController]; - result->_platformViewsController->SetTaskRunner( - shell.GetTaskRunners().GetPlatformTaskRunner()); + [result recreatePlatformViewsController]; + result.platformViewsController.taskRunner = shell.GetTaskRunners().GetPlatformTaskRunner(); return std::make_unique( - shell, context, result->_platformViewsController, shell.GetTaskRunners()); + shell, context, result.platformViewsController, shell.GetTaskRunners()); }; flutter::Shell::CreateCallback on_create_rasterizer = @@ -1517,8 +1495,9 @@ - (void)registerViewFactory:(NSObject*)factory withId:(NSString*)factoryId gestureRecognizersBlockingPolicy: (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy { - [_flutterEngine platformViewsController]->RegisterViewFactory(factory, factoryId, - gestureRecognizersBlockingPolicy); + [_flutterEngine.platformViewsController registerViewFactory:factory + withId:factoryId + gestureRecognizersBlockingPolicy:gestureRecognizersBlockingPolicy]; } @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm index b274475f7ed14..3202c195d9db2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm @@ -12,7 +12,7 @@ @implementation FlutterEngineGroupOptions @interface FlutterEngineGroup () @property(nonatomic, copy) NSString* name; -@property(nonatomic, strong) NSMutableArray* engines; +@property(nonatomic, strong) NSPointerArray* engines; @property(nonatomic, copy) FlutterDartProject* project; @property(nonatomic, assign) NSUInteger enginesCreatedCount; @end @@ -23,7 +23,7 @@ - (instancetype)initWithName:(NSString*)name project:(nullable FlutterDartProjec self = [super init]; if (self) { _name = [name copy]; - _engines = [[NSMutableArray alloc] init]; + _engines = [NSPointerArray weakObjectsPointerArray]; _project = project; } return self; @@ -51,6 +51,12 @@ - (FlutterEngine*)makeEngineWithOptions:(nullable FlutterEngineGroupOptions*)opt NSArray* entrypointArgs = options.entrypointArgs; FlutterEngine* engine; + // NSPointerArray is clever and assumes that unless a mutation operation has occurred on it that + // has set one of its values to nil, nothing could have changed and it can skip compaction. + // That's reasonable behaviour on a regular NSPointerArray but not for a weakObjectPointerArray. + // As a workaround, we mutate it first. See: http://www.openradar.me/15396578 + [self.engines addPointer:nil]; + [self.engines compact]; if (self.engines.count <= 0) { engine = [self makeEngine]; [engine runWithEntrypoint:entrypoint @@ -58,20 +64,13 @@ - (FlutterEngine*)makeEngineWithOptions:(nullable FlutterEngineGroupOptions*)opt initialRoute:initialRoute entrypointArgs:entrypointArgs]; } else { - FlutterEngine* spawner = (FlutterEngine*)[self.engines[0] pointerValue]; + FlutterEngine* spawner = (__bridge FlutterEngine*)[self.engines pointerAtIndex:0]; engine = [spawner spawnWithEntrypoint:entrypoint libraryURI:libraryURI initialRoute:initialRoute entrypointArgs:entrypointArgs]; } - // TODO(cbracken): https://github.com/flutter/flutter/issues/155943 - [self.engines addObject:[NSValue valueWithPointer:(__bridge void*)engine]]; - - NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; - [center addObserver:self - selector:@selector(onEngineWillBeDealloced:) - name:kFlutterEngineWillDealloc - object:engine]; + [self.engines addPointer:(__bridge void*)engine]; return engine; } @@ -82,9 +81,4 @@ - (FlutterEngine*)makeEngine { return [[FlutterEngine alloc] initWithName:engineName project:self.project]; } -- (void)onEngineWillBeDealloced:(NSNotification*)notification { - // TODO(cbracken): https://github.com/flutter/flutter/issues/155943 - [self.engines removeObject:[NSValue valueWithPointer:(__bridge void*)notification.object]]; -} - @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm index 77929ea6b4c20..2d25d457c0281 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm @@ -37,11 +37,16 @@ - (void)testSpawn { } - (void)testDeleteLastEngine { + __weak FlutterEngine* weakSpawner; FlutterEngineGroup* group = [[FlutterEngineGroup alloc] initWithName:@"foo" project:nil]; @autoreleasepool { FlutterEngine* spawner = [group makeEngineWithEntrypoint:nil libraryURI:nil]; + weakSpawner = spawner; XCTAssertNotNil(spawner); + XCTAssertNotNil(weakSpawner); } + XCTAssertNil(weakSpawner); + FlutterEngine* spawnee = [group makeEngineWithEntrypoint:nil libraryURI:nil]; XCTAssertNotNil(spawnee); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index 2e044dd4b9830..d82993310d499 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -262,23 +262,6 @@ - (void)testSpawn { XCTAssertNotNil(spawn); } -- (void)testDeallocNotification { - XCTestExpectation* deallocNotification = [self expectationWithDescription:@"deallocNotification"]; - NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; - id observer; - @autoreleasepool { - FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; - observer = [center addObserverForName:kFlutterEngineWillDealloc - object:engine - queue:[NSOperationQueue mainQueue] - usingBlock:^(NSNotification* note) { - [deallocNotification fulfill]; - }]; - } - [self waitForExpectations:@[ deallocNotification ]]; - [center removeObserver:observer]; -} - - (void)testSetHandlerAfterRun { FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"]; @@ -287,12 +270,13 @@ - (void)testSetHandlerAfterRun { fml::AutoResetWaitableEvent latch; [engine run]; flutter::Shell& shell = engine.shell; - engine.shell.GetTaskRunners().GetUITaskRunner()->PostTask([&latch, &shell] { - flutter::Engine::Delegate& delegate = shell; - auto message = std::make_unique("foo", nullptr); - delegate.OnEngineHandlePlatformMessage(std::move(message)); - latch.Signal(); - }); + fml::TaskRunner::RunNowOrPostTask( + engine.shell.GetTaskRunners().GetUITaskRunner(), [&latch, &shell] { + flutter::Engine::Delegate& delegate = shell; + auto message = std::make_unique("foo", nullptr); + delegate.OnEngineHandlePlatformMessage(std::move(message)); + latch.Signal(); + }); latch.Wait(); [registrar.messenger setMessageHandlerOnChannel:@"foo" binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) { @@ -304,14 +288,11 @@ - (void)testSetHandlerAfterRun { - (void)testThreadPrioritySetCorrectly { XCTestExpectation* prioritiesSet = [self expectationWithDescription:@"prioritiesSet"]; - prioritiesSet.expectedFulfillmentCount = 3; + prioritiesSet.expectedFulfillmentCount = 2; IMP mockSetThreadPriority = imp_implementationWithBlock(^(NSThread* thread, double threadPriority) { - if ([thread.name hasSuffix:@".ui"]) { - XCTAssertEqual(threadPriority, 1.0); - [prioritiesSet fulfill]; - } else if ([thread.name hasSuffix:@".raster"]) { + if ([thread.name hasSuffix:@".raster"]) { XCTAssertEqual(threadPriority, 1.0); [prioritiesSet fulfill]; } else if ([thread.name hasSuffix:@".io"]) { @@ -446,10 +427,6 @@ - (void)testSpawnsShareGpuContext { std::shared_ptr engine_context = [engine iosPlatformView]->GetIosContext(); std::shared_ptr spawn_context = [spawn iosPlatformView]->GetIosContext(); XCTAssertEqual(engine_context, spawn_context); - // If this assert fails it means we may be using the software. For software rendering, this is - // expected to be nullptr. - XCTAssertTrue(engine_context->GetMainContext() != nullptr); - XCTAssertEqual(engine_context->GetMainContext(), spawn_context->GetMainContext()); } - (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall { @@ -462,7 +439,6 @@ - (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall { - (void)testCanMergePlatformAndUIThread { #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR auto settings = FLTDefaultSettingsForBundle(); - settings.enable_impeller = true; FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings]; FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project]; [engine run]; @@ -472,16 +448,16 @@ - (void)testCanMergePlatformAndUIThread { #endif // defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR } -- (void)testCanNotUnMergePlatformAndUIThread { +- (void)testCanUnMergePlatformAndUIThread { #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR auto settings = FLTDefaultSettingsForBundle(); - settings.enable_impeller = true; + settings.merged_platform_ui_thread = false; FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings]; FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project]; [engine run]; - XCTAssertEqual(engine.shell.GetTaskRunners().GetUITaskRunner(), - engine.shell.GetTaskRunners().GetPlatformTaskRunner()); + XCTAssertNotEqual(engine.shell.GetTaskRunners().GetUITaskRunner(), + engine.shell.GetTaskRunners().GetPlatformTaskRunner()); #endif // defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index 489dfd38c4d87..bb3beca9dbbd2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h @@ -29,8 +29,6 @@ NS_ASSUME_NONNULL_BEGIN -extern NSString* const kFlutterEngineWillDealloc; - @interface FlutterEngine () - (flutter::Shell&)shell; @@ -48,7 +46,6 @@ extern NSString* const kFlutterEngineWillDealloc; base64Encode:(bool)base64Encode; - (FlutterPlatformPlugin*)platformPlugin; -- (std::shared_ptr&)platformViewsController; - (FlutterTextInputPlugin*)textInputPlugin; - (FlutterRestorationPlugin*)restorationPlugin; - (void)launchEngine:(nullable NSString*)entrypoint @@ -83,6 +80,7 @@ extern NSString* const kFlutterEngineWillDealloc; userData:(nullable void*)userData; @property(nonatomic, readonly) FlutterDartProject* project; + @end NS_ASSUME_NONNULL_END diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h index 238f6528aaf26..104ab9aef5767 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h @@ -11,8 +11,6 @@ #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" #include "flutter/shell/platform/embedder/embedder.h" -extern NSString* const kFlutterEngineWillDealloc; - @class FlutterBinaryMessengerRelay; namespace flutter { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.h b/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.h index 21b63b4a048ac..7aab78db946c8 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.h @@ -9,16 +9,20 @@ /// Drop-in replacement (as far as Flutter is concerned) for CAMetalLayer /// that can present with transaction from a background thread. +/// +/// Properties and method declarations must exactly match those in the +/// CAMetalLayer interface declaration. @interface FlutterMetalLayer : CALayer @property(nullable, retain) id device; -@property(nullable, readonly) id preferredDevice; +@property(nullable, readonly) + id preferredDevice API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0)) + API_UNAVAILABLE(watchos); @property MTLPixelFormat pixelFormat; @property BOOL framebufferOnly; @property CGSize drawableSize; @property BOOL presentsWithTransaction; @property(nullable) CGColorSpaceRef colorspace; -@property BOOL wantsExtendedDynamicRangeContent; - (nullable id)nextDrawable; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm b/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm index b26df26ca9a8c..f31eda904b92d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm @@ -4,6 +4,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.h" +#include #include #include #include @@ -56,11 +57,7 @@ - (void)returnTexture:(FlutterTexture*)texture; @end -@interface FlutterTexture : NSObject { - id _texture; - IOSurface* _surface; - CFTimeInterval _presentedTime; -} +@interface FlutterTexture : NSObject @property(readonly, nonatomic) id texture; @property(readonly, nonatomic) IOSurface* surface; @@ -71,11 +68,6 @@ @interface FlutterTexture : NSObject { @implementation FlutterTexture -@synthesize texture = _texture; -@synthesize surface = _surface; -@synthesize presentedTime = _presentedTime; -@synthesize waitingForCompletion; - - (instancetype)initWithTexture:(id)texture surface:(IOSurface*)surface { if (self = [super init]) { _texture = texture; @@ -186,13 +178,6 @@ - (void)onDisplayLink:(CADisplayLink*)link { @implementation FlutterMetalLayer -@synthesize preferredDevice = _preferredDevice; -@synthesize device = _device; -@synthesize pixelFormat = _pixelFormat; -@synthesize framebufferOnly = _framebufferOnly; -@synthesize colorspace = _colorspace; -@synthesize wantsExtendedDynamicRangeContent = _wantsExtendedDynamicRangeContent; - - (instancetype)init { if (self = [super init]) { _preferredDevice = MTLCreateSystemDefaultDevice(); @@ -316,9 +301,9 @@ - (IOSurface*)createIOSurface { if (self.colorspace != nil) { CFStringRef name = CGColorSpaceGetName(self.colorspace); - IOSurfaceSetValue(res, CFSTR("IOSurfaceColorSpace"), name); + IOSurfaceSetValue(res, kIOSurfaceColorSpace, name); } else { - IOSurfaceSetValue(res, CFSTR("IOSurfaceColorSpace"), kCGColorSpaceSRGB); + IOSurfaceSetValue(res, kIOSurfaceColorSpace, kCGColorSpaceSRGB); } return (__bridge_transfer IOSurface*)res; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm index 76d81013dc90f..8000df2830f20 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm @@ -42,7 +42,7 @@ - (instancetype)initWithContentsScale:(CGFloat)contentsScale pixelFormat:(MTLPixelFormat)pixelFormat { self = [self init]; - if ([self.layer isKindOfClass:NSClassFromString(@"CAMetalLayer")]) { + if ([self.layer isKindOfClass:[CAMetalLayer class]]) { self.layer.allowsGroupOpacity = NO; self.layer.contentsScale = contentsScale; self.layer.rasterizationScale = contentsScale; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h index 71f79c2ba42e5..45fd69cddfb68 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h @@ -5,7 +5,6 @@ #ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMPLUGIN_H_ #define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMPLUGIN_H_ -#include "flutter/fml/platform/darwin/weak_nsobject.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h" diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index c8a0dc6fe4219..6a662381fdf03 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -23,7 +23,7 @@ const UInt32 kKeyPressClickSoundId = 1306; #if not APPLICATION_EXTENSION_API_ONLY -const NSString* searchURLPrefix = @"x-web-search://?"; +NSString* const kSearchURLPrefix = @"x-web-search://?"; #endif } // namespace @@ -226,7 +226,7 @@ - (void)searchWeb:(NSString*)searchTerm { NSString* escapedText = [searchTerm stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]; - NSString* searchURL = [NSString stringWithFormat:@"%@%@", searchURLPrefix, escapedText]; + NSString* searchURL = [NSString stringWithFormat:@"%@%@", kSearchURLPrefix, escapedText]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:searchURL] options:@{} diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm index b6259e120cced..779a2158c5f9f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm @@ -5,7 +5,6 @@ #import #import -#import "flutter/fml/platform/darwin/weak_nsobject.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm similarity index 88% rename from shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm rename to shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 5e76654bed179..a036668fce3ce 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -4,6 +4,8 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" +#import + #include "flutter/display_list/effects/dl_image_filter.h" #include "flutter/fml/platform/darwin/cf_utils.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" @@ -510,45 +512,6 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { } @end -// This recognizer delays touch events from being dispatched to the responder chain until it failed -// recognizing a gesture. -// -// We only fail this recognizer when asked to do so by the Flutter framework (which does so by -// invoking an acceptGesture method on the platform_views channel). And this is how we allow the -// Flutter framework to delay or prevent the embedded view from getting a touch sequence. -@interface FlutterDelayingGestureRecognizer : UIGestureRecognizer - -// Indicates that if the `FlutterDelayingGestureRecognizer`'s state should be set to -// `UIGestureRecognizerStateEnded` during next `touchesEnded` call. -@property(nonatomic) BOOL shouldEndInNextTouchesEnded; - -// Indicates that the `FlutterDelayingGestureRecognizer`'s `touchesEnded` has been invoked without -// setting the state to `UIGestureRecognizerStateEnded`. -@property(nonatomic) BOOL touchedEndedWithoutBlocking; - -@property(nonatomic, readonly) UIGestureRecognizer* forwardingRecognizer; - -- (instancetype)initWithTarget:(id)target - action:(SEL)action - forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer; -@end - -// While the FlutterDelayingGestureRecognizer is preventing touches from hitting the responder chain -// the touch events are not arriving to the FlutterView (and thus not arriving to the Flutter -// framework). We use this gesture recognizer to dispatch the events directly to the FlutterView -// while during this phase. -// -// If the Flutter framework decides to dispatch events to the embedded view, we fail the -// FlutterDelayingGestureRecognizer which sends the events up the responder chain. But since the -// events are handled by the embedded view they are not delivered to the Flutter framework in this -// phase as well. So during this phase as well the ForwardingGestureRecognizer dispatched the events -// directly to the FlutterView. -@interface ForwardingGestureRecognizer : UIGestureRecognizer -- (instancetype)initWithTarget:(id)target - platformViewsController: - (fml::WeakPtr)platformViewsController; -@end - @interface FlutterTouchInterceptingView () @property(nonatomic, weak, readonly) UIView* embeddedView; @property(nonatomic, readonly) FlutterDelayingGestureRecognizer* delayingRecognizer; @@ -557,8 +520,7 @@ @interface FlutterTouchInterceptingView () @implementation FlutterTouchInterceptingView - (instancetype)initWithEmbeddedView:(UIView*)embeddedView - platformViewsController: - (fml::WeakPtr)platformViewsController + platformViewsController:(FlutterPlatformViewsController*)platformViewsController gestureRecognizersBlockingPolicy: (FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy { self = [super initWithFrame:embeddedView.frame]; @@ -586,6 +548,20 @@ - (instancetype)initWithEmbeddedView:(UIView*)embeddedView return self; } +- (void)forceResetForwardingGestureRecognizerState { + // When iPad pencil is involved in a finger touch gesture, the gesture is not reset to "possible" + // state and is stuck on "failed" state, which causes subsequent touches to be blocked. As a + // workaround, we force reset the state by recreating the forwarding gesture recognizer. See: + // https://github.com/flutter/flutter/issues/136244 + ForwardingGestureRecognizer* oldForwardingRecognizer = + (ForwardingGestureRecognizer*)self.delayingRecognizer.forwardingRecognizer; + ForwardingGestureRecognizer* newForwardingRecognizer = + [oldForwardingRecognizer recreateRecognizerWithTarget:self]; + self.delayingRecognizer.forwardingRecognizer = newForwardingRecognizer; + [self removeGestureRecognizer:oldForwardingRecognizer]; + [self addGestureRecognizer:newForwardingRecognizer]; +} + - (void)releaseGesture { self.delayingRecognizer.state = UIGestureRecognizerStateFailed; } @@ -595,6 +571,22 @@ - (void)blockGesture { case FlutterPlatformViewGestureRecognizersBlockingPolicyEager: // We block all other gesture recognizers immediately in this policy. self.delayingRecognizer.state = UIGestureRecognizerStateEnded; + + // On iOS 18.2, WKWebView's internal recognizer likely caches the old state of its blocking + // recognizers (i.e. delaying recognizer), resulting in non-tappable links. See + // https://github.com/flutter/flutter/issues/158961. Removing and adding back the delaying + // recognizer solves the problem, possibly because UIKit notifies all the recognizers related + // to (blocking or blocked by) this recognizer. It is not possible to inject this workaround + // from the web view plugin level. Right now we only observe this issue for + // FlutterPlatformViewGestureRecognizersBlockingPolicyEager, but we should try it if a similar + // issue arises for the other policy. + if (@available(iOS 18.2, *)) { + if ([self.embeddedView isKindOfClass:[WKWebView class]]) { + [self removeGestureRecognizer:self.delayingRecognizer]; + [self addGestureRecognizer:self.delayingRecognizer]; + } + } + break; case FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded: if (self.delayingRecognizer.touchedEndedWithoutBlocking) { @@ -692,47 +684,51 @@ @implementation ForwardingGestureRecognizer { // outlives the FlutterViewController. And ForwardingGestureRecognizer is owned by a subview of // FlutterView, so the ForwardingGestureRecognizer never out lives FlutterViewController. // Therefore, `_platformViewsController` should never be nullptr. - fml::WeakPtr _platformViewsController; + __weak FlutterPlatformViewsController* _platformViewsController; // Counting the pointers that has started in one touch sequence. NSInteger _currentTouchPointersCount; // We can't dispatch events to the framework without this back pointer. // This gesture recognizer retains the `FlutterViewController` until the // end of a gesture sequence, that is all the touches in touchesBegan are concluded // with |touchesCancelled| or |touchesEnded|. - fml::scoped_nsobject> _flutterViewController; + UIViewController* _flutterViewController; } - (instancetype)initWithTarget:(id)target - platformViewsController: - (fml::WeakPtr)platformViewsController { + platformViewsController:(FlutterPlatformViewsController*)platformViewsController { self = [super initWithTarget:target action:nil]; if (self) { self.delegate = self; - FML_DCHECK(platformViewsController.get() != nullptr); - _platformViewsController = std::move(platformViewsController); + FML_DCHECK(platformViewsController); + _platformViewsController = platformViewsController; _currentTouchPointersCount = 0; } return self; } +- (ForwardingGestureRecognizer*)recreateRecognizerWithTarget:(id)target { + return [[ForwardingGestureRecognizer alloc] initWithTarget:target + platformViewsController:_platformViewsController]; +} + - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { FML_DCHECK(_currentTouchPointersCount >= 0); if (_currentTouchPointersCount == 0) { // At the start of each gesture sequence, we reset the `_flutterViewController`, // so that all the touch events in the same sequence are forwarded to the same // `_flutterViewController`. - _flutterViewController.reset(_platformViewsController->GetFlutterViewController()); + _flutterViewController = _platformViewsController.flutterViewController; } - [_flutterViewController.get() touchesBegan:touches withEvent:event]; + [_flutterViewController touchesBegan:touches withEvent:event]; _currentTouchPointersCount += touches.count; } - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { - [_flutterViewController.get() touchesMoved:touches withEvent:event]; + [_flutterViewController touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { - [_flutterViewController.get() touchesEnded:touches withEvent:event]; + [_flutterViewController touchesEnded:touches withEvent:event]; _currentTouchPointersCount -= touches.count; // Touches in one touch sequence are sent to the touchesEnded method separately if different // fingers stop touching the screen at different time. So one touchesEnded method triggering does @@ -740,7 +736,8 @@ - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { // UIGestureRecognizerStateFailed when all the touches in the current touch sequence is ended. if (_currentTouchPointersCount == 0) { self.state = UIGestureRecognizerStateFailed; - _flutterViewController.reset(nil); + _flutterViewController = nil; + [self forceResetStateIfNeeded]; } } @@ -750,14 +747,28 @@ - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { // Flutter needs all the cancelled touches to be "cancelled" change types in order to correctly // handle gesture sequence. // We always override the change type to "cancelled". - [_flutterViewController.get() forceTouchesCancelled:touches]; + [_flutterViewController forceTouchesCancelled:touches]; _currentTouchPointersCount -= touches.count; if (_currentTouchPointersCount == 0) { self.state = UIGestureRecognizerStateFailed; - _flutterViewController.reset(nil); + _flutterViewController = nil; + [self forceResetStateIfNeeded]; } } +- (void)forceResetStateIfNeeded { + __weak ForwardingGestureRecognizer* weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + ForwardingGestureRecognizer* strongSelf = weakSelf; + if (!strongSelf) { + return; + } + if (strongSelf.state != UIGestureRecognizerStatePossible) { + [(FlutterTouchInterceptingView*)strongSelf.view forceResetForwardingGestureRecognizerState]; + } + }); +} + - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer*)otherGestureRecognizer { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h new file mode 100644 index 0000000000000..5b55ce6d72f28 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h @@ -0,0 +1,150 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWSCONTROLLER_H_ +#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWSCONTROLLER_H_ + +#include +#include +#include +#include + +#include "flutter/flow/surface.h" +#include "flutter/fml/task_runner.h" +#include "flutter/fml/trace_event.h" +#include "impeller/base/thread_safety.h" +#include "third_party/skia/include/core/SkRect.h" + +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h" +#import "flutter/shell/platform/darwin/ios/ios_context.h" + +NS_ASSUME_NONNULL_BEGIN + +@class FlutterTouchInterceptingView; +@class FlutterClippingMaskViewPool; + +@interface FlutterPlatformViewsController : NSObject + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/// The task runner used to post rendering tasks to the platform thread. +@property(nonatomic, assign) const fml::RefPtr& taskRunner; + +/// The flutter view. +@property(nonatomic, weak) UIView* _Nullable flutterView; + +/// @brief The flutter view controller. +@property(nonatomic, weak) UIViewController* _Nullable flutterViewController; + +/// @brief set the factory used to construct embedded UI Views. +- (void)registerViewFactory:(NSObject*)factory + withId:(NSString*)factoryId + gestureRecognizersBlockingPolicy: + (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizerBlockingPolicy; + +/// @brief Mark the beginning of a frame and record the size of the onscreen. +- (void)beginFrameWithSize:(SkISize)frameSize; + +/// @brief Cancel the current frame, indicating that no platform views are composited. +/// +/// Additionally, reverts the composition order to its original state at the beginning of the +/// frame. +- (void)cancelFrame; + +/// @brief Record a platform view in the layer tree to be rendered, along with the positioning and +/// mutator parameters. +/// +/// Called from the raster thread. +- (void)prerollCompositeEmbeddedView:(int64_t)viewId + withParams:(std::unique_ptr)params; + +/// @brief Returns the`FlutterTouchInterceptingView` with the provided view_id. +/// +/// Returns nil if there is no platform view with the provided id. Called +/// from the platform thread. +- (FlutterTouchInterceptingView*)flutterTouchInterceptingViewForId:(int64_t)viewId; + +/// @brief Determine if thread merging is required after prerolling platform views. +/// +/// Called from the raster thread. +- (flutter::PostPrerollResult)postPrerollActionWithThreadMerger: + (const fml::RefPtr&)rasterThreadMerger + impellerEnabled:(BOOL)impellerEnabled; + +/// @brief Mark the end of a compositor frame. +/// +/// May determine changes are required to the thread merging state. +/// Called from the raster thread. +- (void)endFrameWithResubmit:(BOOL)shouldResubmitFrame + threadMerger:(const fml::RefPtr&)rasterThreadMerger + impellerEnabled:(BOOL)impellerEnabled; + +/// @brief Returns the Canvas for the overlay slice for the given platform view. +/// +/// Called from the raster thread. +- (flutter::DlCanvas*)compositeEmbeddedViewWithId:(int64_t)viewId; + +/// @brief Discards all platform views instances and auxiliary resources. +/// +/// Called from the raster thread. +- (void)reset; + +/// @brief Encode rendering for the Flutter overlay views and queue up perform platform view +/// mutations. +/// +/// Called from the raster thread. +- (BOOL)submitFrame:(std::unique_ptr)frame + withIosContext:(const std::shared_ptr&)iosContext + grContext:(GrDirectContext* _Nullable)grContext; + +/// @brief Handler for platform view message channels. +- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; + +/// @brief Returns the platform view id if the platform view (or any of its descendant view) is +/// the first responder. +/// +/// Returns -1 if no such platform view is found. +- (long)firstResponderPlatformViewId; + +/// @brief Pushes backdrop filter mutation to the mutator stack of each visited platform view. +- (void)pushFilterToVisitedPlatformViews:(const std::shared_ptr&)filter + withRect:(const SkRect&)filterRect; + +/// @brief Pushes the view id of a visted platform view to the list of visied platform views. +- (void)pushVisitedPlatformViewId:(int64_t)viewId; + +@end + +@interface FlutterPlatformViewsController (Testing) + +- (size_t)embeddedViewCount; + +// Returns the `FlutterPlatformView`'s `view` object associated with the view_id. +// +// If the `PlatformViewsController` does not contain any `FlutterPlatformView` object or +// a `FlutterPlatformView` object associated with the view_id cannot be found, the method +// returns nil. +- (UIView* _Nullable)platformViewForId:(int64_t)viewId; + +// Composite the PlatformView with `viewId`. +// +// Every frame, during the paint traversal of the layer tree, this method is called for all +// the PlatformViews in `_viewsToRecomposite`. +// +// Note that `_viewsToRecomposite` does not represent all the views in the view hierarchy, +// if a PlatformView does not change its composition parameter from last frame, it is not +// included in the `views_to_recomposite_`. +- (void)compositeView:(int64_t)viewId withParams:(const flutter::EmbeddedViewParams&)params; + +- (const flutter::EmbeddedViewParams&)compositionParamsForView:(int64_t)viewId; + +@end + +NS_ASSUME_NONNULL_END + +#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWSCONTROLLER_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm new file mode 100644 index 0000000000000..7a452ba74443f --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm @@ -0,0 +1,1075 @@ +// 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 "shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h" + +#include "flutter/display_list/effects/image_filters/dl_blur_image_filter.h" +#include "flutter/flow/surface_frame.h" +#include "flutter/flow/view_slicer.h" +#include "flutter/fml/make_copyable.h" +#include "flutter/fml/synchronization/count_down_latch.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" +#include "flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h" +#import "flutter/shell/platform/darwin/ios/ios_surface.h" + +// The number of frames the rasterizer task runner will continue +// to run on the platform thread after no platform view is rendered. +// +// Note: this is an arbitrary number. +static constexpr int kDefaultMergedLeaseDuration = 10; + +static constexpr NSUInteger kFlutterClippingMaskViewPoolCapacity = 5; + +struct LayerData { + SkRect rect; + int64_t view_id; + int64_t overlay_id; + std::shared_ptr layer; +}; +using LayersMap = std::unordered_map; + +/// Each of the following structs stores part of the platform view hierarchy according to its +/// ID. +/// +/// This data must only be accessed on the platform thread. +struct PlatformViewData { + NSObject* view; + FlutterTouchInterceptingView* touch_interceptor; + UIView* root_view; +}; + +// Converts a SkMatrix to CATransform3D. +// +// Certain fields are ignored in CATransform3D since SkMatrix is 3x3 and CATransform3D is 4x4. +static CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix) { + // Skia only supports 2D transform so we don't map z. + CATransform3D transform = CATransform3DIdentity; + transform.m11 = matrix.getScaleX(); + transform.m21 = matrix.getSkewX(); + transform.m41 = matrix.getTranslateX(); + transform.m14 = matrix.getPerspX(); + + transform.m12 = matrix.getSkewY(); + transform.m22 = matrix.getScaleY(); + transform.m42 = matrix.getTranslateY(); + transform.m24 = matrix.getPerspY(); + return transform; +} + +// Reset the anchor of `layer` to match the transform operation from flow. +// +// The position of the `layer` should be unchanged after resetting the anchor. +static void ResetAnchor(CALayer* layer) { + // Flow uses (0, 0) to apply transform matrix so we need to match that in Quartz. + layer.anchorPoint = CGPointZero; + layer.position = CGPointZero; +} + +static CGRect GetCGRectFromSkRect(const SkRect& clipSkRect) { + return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft, + clipSkRect.fBottom - clipSkRect.fTop); +} + +// Determines if the `clip_rect` from a clipRect mutator contains the +// `platformview_boundingrect`. +// +// `clip_rect` is in its own coordinate space. The rect needs to be transformed by +// `transform_matrix` to be in the coordinate space where the PlatformView is displayed. +// +// `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate +// space where the PlatformView is displayed. +static bool ClipRectContainsPlatformViewBoundingRect(const SkRect& clip_rect, + const SkRect& platformview_boundingrect, + const SkMatrix& transform_matrix) { + SkRect transformed_rect = transform_matrix.mapRect(clip_rect); + return transformed_rect.contains(platformview_boundingrect); +} + +// Determines if the `clipRRect` from a clipRRect mutator contains the +// `platformview_boundingrect`. +// +// `clip_rrect` is in its own coordinate space. The rrect needs to be transformed by +// `transform_matrix` to be in the coordinate space where the PlatformView is displayed. +// +// `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate +// space where the PlatformView is displayed. +static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, + const SkRect& platformview_boundingrect, + const SkMatrix& transform_matrix) { + SkVector upper_left = clip_rrect.radii(SkRRect::Corner::kUpperLeft_Corner); + SkVector upper_right = clip_rrect.radii(SkRRect::Corner::kUpperRight_Corner); + SkVector lower_right = clip_rrect.radii(SkRRect::Corner::kLowerRight_Corner); + SkVector lower_left = clip_rrect.radii(SkRRect::Corner::kLowerLeft_Corner); + SkScalar transformed_upper_left_x = transform_matrix.mapRadius(upper_left.x()); + SkScalar transformed_upper_left_y = transform_matrix.mapRadius(upper_left.y()); + SkScalar transformed_upper_right_x = transform_matrix.mapRadius(upper_right.x()); + SkScalar transformed_upper_right_y = transform_matrix.mapRadius(upper_right.y()); + SkScalar transformed_lower_right_x = transform_matrix.mapRadius(lower_right.x()); + SkScalar transformed_lower_right_y = transform_matrix.mapRadius(lower_right.y()); + SkScalar transformed_lower_left_x = transform_matrix.mapRadius(lower_left.x()); + SkScalar transformed_lower_left_y = transform_matrix.mapRadius(lower_left.y()); + SkRect transformed_clip_rect = transform_matrix.mapRect(clip_rrect.rect()); + SkRRect transformed_rrect; + SkVector corners[] = {{transformed_upper_left_x, transformed_upper_left_y}, + {transformed_upper_right_x, transformed_upper_right_y}, + {transformed_lower_right_x, transformed_lower_right_y}, + {transformed_lower_left_x, transformed_lower_left_y}}; + transformed_rrect.setRectRadii(transformed_clip_rect, corners); + return transformed_rrect.contains(platformview_boundingrect); +} + +@interface FlutterPlatformViewsController () + +// The pool of reusable view layers. The pool allows to recycle layer in each frame. +@property(nonatomic, readonly) flutter::OverlayLayerPool* layerPool; + +// The platform view's |EmbedderViewSlice| keyed off the view id, which contains any subsequent +// operation until the next platform view or the end of the last leaf node in the layer tree. +// +// The Slices are deleted by the PlatformViewsController.reset(). +@property(nonatomic, readonly) + std::unordered_map>& slices; + +@property(nonatomic, readonly) FlutterClippingMaskViewPool* maskViewPool; + +@property(nonatomic, readonly) + std::unordered_map*>& factories; + +// The FlutterPlatformViewGestureRecognizersBlockingPolicy for each type of platform view. +@property(nonatomic, readonly) + std::unordered_map& + gestureRecognizersBlockingPolicies; + +/// The size of the current onscreen surface in physical pixels. +@property(nonatomic, assign) SkISize frameSize; + +/// The task runner for posting tasks to the platform thread. +@property(nonatomic, readonly) const fml::RefPtr& platformTaskRunner; + +/// This data must only be accessed on the platform thread. +@property(nonatomic, readonly) std::unordered_map& platformViews; + +/// The composition parameters for each platform view. +/// +/// This state is only modified on the raster thread. +@property(nonatomic, readonly) + std::unordered_map& currentCompositionParams; + +/// Method channel `OnDispose` calls adds the views to be disposed to this set to be disposed on +/// the next frame. +/// +/// This state is modified on both the platform and raster thread. +@property(nonatomic, readonly) std::unordered_set& viewsToDispose; + +/// view IDs in composition order. +/// +/// This state is only modified on the raster thread. +@property(nonatomic, readonly) std::vector& compositionOrder; + +/// platform view IDs visited during layer tree composition. +/// +/// This state is only modified on the raster thread. +@property(nonatomic, readonly) std::vector& visitedPlatformViews; + +/// Only composite platform views in this set. +/// +/// This state is only modified on the raster thread. +@property(nonatomic, readonly) std::unordered_set& viewsToRecomposite; + +/// @brief The composition order from the previous thread. +/// +/// Only accessed from the platform thread. +@property(nonatomic, readonly) std::vector& previousCompositionOrder; + +/// Whether the previous frame had any platform views in active composition order. +/// +/// This state is tracked so that the first frame after removing the last platform view +/// runs through the platform view rendering code path, giving us a chance to remove the +/// platform view from the UIView hierarchy. +/// +/// Only accessed from the raster thread. +@property(nonatomic, assign) BOOL hadPlatformViews; + +/// Whether blurred backdrop filters can be applied. +/// +/// Defaults to YES, but becomes NO if blurred backdrop filters cannot be applied. +@property(nonatomic, assign) BOOL canApplyBlurBackdrop; + +/// Populate any missing overlay layers. +/// +/// This requires posting a task to the platform thread and blocking on its completion. +- (void)createMissingOverlays:(size_t)requiredOverlayLayers + withIosContext:(const std::shared_ptr&)iosContext + grContext:(GrDirectContext*)grContext; + +/// Update the buffers and mutate the platform views in CATransaction on the platform thread. +- (void)performSubmit:(const LayersMap&)platformViewLayers + currentCompositionParams: + (std::unordered_map&)currentCompositionParams + viewsToRecomposite:(const std::unordered_set&)viewsToRecomposite + compositionOrder:(const std::vector&)compositionOrder + unusedLayers: + (const std::vector>&)unusedLayers + surfaceFrames: + (const std::vector>&)surfaceFrames; + +- (void)onCreate:(FlutterMethodCall*)call result:(FlutterResult)result; +- (void)onDispose:(FlutterMethodCall*)call result:(FlutterResult)result; +- (void)onAcceptGesture:(FlutterMethodCall*)call result:(FlutterResult)result; +- (void)onRejectGesture:(FlutterMethodCall*)call result:(FlutterResult)result; + +- (void)clipViewSetMaskView:(UIView*)clipView; + +// Applies the mutators in the mutatorsStack to the UIView chain that was constructed by +// `ReconstructClipViewsChain` +// +// Clips are applied to the `embeddedView`'s super view(|ChildClippingView|) using a +// |FlutterClippingMaskView|. Transforms are applied to `embeddedView` +// +// The `boundingRect` is the final bounding rect of the PlatformView +// (EmbeddedViewParams::finalBoundingRect). If a clip mutator's rect contains the final bounding +// rect of the PlatformView, the clip mutator is not applied for performance optimization. +// +// This method is only called when the `embeddedView` needs to be re-composited at the current +// frame. See: `compositeView:withParams:` for details. +- (void)applyMutators:(const flutter::MutatorsStack&)mutatorsStack + embeddedView:(UIView*)embeddedView + boundingRect:(const SkRect&)boundingRect; + +// Appends the overlay views and platform view and sets their z index based on the composition +// order. +- (void)bringLayersIntoView:(const LayersMap&)layerMap + withCompositionOrder:(const std::vector&)compositionOrder; + +- (std::shared_ptr)nextLayerInPool; + +/// Runs on the platform thread. +- (void)createLayerWithIosContext:(const std::shared_ptr&)iosContext + grContext:(GrDirectContext*)grContext + pixelFormat:(MTLPixelFormat)pixelFormat; + +/// Removes overlay views and platform views that aren't needed in the current frame. +/// Must run on the platform thread. +- (void)removeUnusedLayers:(const std::vector>&)unusedLayers + withCompositionOrder:(const std::vector&)compositionOrder; + +/// Computes and returns all views to be disposed on the platform thread, removes them from +/// self.platformViews, self.viewsToRecomposite, and self.currentCompositionParams. Any views that +/// still require compositing are not returned, but instead added to `viewsToDelayDispose` for +/// disposal on the next call. +- (std::vector)computeViewsToDispose; + +/// Resets the state of the frame. +- (void)resetFrameState; +@end + +@implementation FlutterPlatformViewsController { + // TODO(cbracken): Replace with Obj-C types and use @property declarations to automatically + // synthesize the ivars. + // + // These ivars are required because we're transitioning the previous C++ implementation to Obj-C. + // We require ivars to declare the concrete types and then wrap with @property declarations that + // return a reference to the ivar, allowing for use like `self.layerPool` and + // `self.slices[viewId] = x`. + std::unique_ptr _layerPool; + std::unordered_map> _slices; + std::unordered_map*> _factories; + std::unordered_map + _gestureRecognizersBlockingPolicies; + fml::RefPtr _platformTaskRunner; + std::unordered_map _platformViews; + std::unordered_map _currentCompositionParams; + std::unordered_set _viewsToDispose; + std::vector _compositionOrder; + std::vector _visitedPlatformViews; + std::unordered_set _viewsToRecomposite; + std::vector _previousCompositionOrder; +} + +- (id)init { + if (self = [super init]) { + _layerPool = std::make_unique(); + _maskViewPool = + [[FlutterClippingMaskViewPool alloc] initWithCapacity:kFlutterClippingMaskViewPoolCapacity]; + _hadPlatformViews = NO; + _canApplyBlurBackdrop = YES; + } + return self; +} + +- (const fml::RefPtr&)taskRunner { + return _platformTaskRunner; +} + +- (void)setTaskRunner:(const fml::RefPtr&)platformTaskRunner { + _platformTaskRunner = platformTaskRunner; +} + +- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { + if ([[call method] isEqualToString:@"create"]) { + [self onCreate:call result:result]; + } else if ([[call method] isEqualToString:@"dispose"]) { + [self onDispose:call result:result]; + } else if ([[call method] isEqualToString:@"acceptGesture"]) { + [self onAcceptGesture:call result:result]; + } else if ([[call method] isEqualToString:@"rejectGesture"]) { + [self onRejectGesture:call result:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)onCreate:(FlutterMethodCall*)call result:(FlutterResult)result { + NSDictionary* args = [call arguments]; + + int64_t viewId = [args[@"id"] longLongValue]; + NSString* viewTypeString = args[@"viewType"]; + std::string viewType(viewTypeString.UTF8String); + + if (self.platformViews.count(viewId) != 0) { + result([FlutterError errorWithCode:@"recreating_view" + message:@"trying to create an already created view" + details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); + return; + } + + NSObject* factory = self.factories[viewType]; + if (factory == nil) { + result([FlutterError + errorWithCode:@"unregistered_view_type" + message:[NSString stringWithFormat:@"A UIKitView widget is trying to create a " + @"PlatformView with an unregistered type: < %@ >", + viewTypeString] + details:@"If you are the author of the PlatformView, make sure `registerViewFactory` " + @"is invoked.\n" + @"See: " + @"https://docs.flutter.dev/development/platform-integration/" + @"platform-views#on-the-platform-side-1 for more details.\n" + @"If you are not the author of the PlatformView, make sure to call " + @"`GeneratedPluginRegistrant.register`."]); + return; + } + + id params = nil; + if ([factory respondsToSelector:@selector(createArgsCodec)]) { + NSObject* codec = [factory createArgsCodec]; + if (codec != nil && args[@"params"] != nil) { + FlutterStandardTypedData* paramsData = args[@"params"]; + params = [codec decode:paramsData.data]; + } + } + + NSObject* embeddedView = [factory createWithFrame:CGRectZero + viewIdentifier:viewId + arguments:params]; + UIView* platformView = [embeddedView view]; + // Set a unique view identifier, so the platform view can be identified in unit tests. + platformView.accessibilityIdentifier = [NSString stringWithFormat:@"platform_view[%lld]", viewId]; + + FlutterTouchInterceptingView* touchInterceptor = [[FlutterTouchInterceptingView alloc] + initWithEmbeddedView:platformView + platformViewsController:self + gestureRecognizersBlockingPolicy:self.gestureRecognizersBlockingPolicies[viewType]]; + + ChildClippingView* clippingView = [[ChildClippingView alloc] initWithFrame:CGRectZero]; + [clippingView addSubview:touchInterceptor]; + + self.platformViews.emplace(viewId, PlatformViewData{ + .view = embeddedView, // + .touch_interceptor = touchInterceptor, // + .root_view = clippingView // + }); + + result(nil); +} + +- (void)onDispose:(FlutterMethodCall*)call result:(FlutterResult)result { + NSNumber* arg = [call arguments]; + int64_t viewId = [arg longLongValue]; + + if (self.platformViews.count(viewId) == 0) { + result([FlutterError errorWithCode:@"unknown_view" + message:@"trying to dispose an unknown" + details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); + return; + } + // We wait for next submitFrame to dispose views. + self.viewsToDispose.insert(viewId); + result(nil); +} + +- (void)onAcceptGesture:(FlutterMethodCall*)call result:(FlutterResult)result { + NSDictionary* args = [call arguments]; + int64_t viewId = [args[@"id"] longLongValue]; + + if (self.platformViews.count(viewId) == 0) { + result([FlutterError errorWithCode:@"unknown_view" + message:@"trying to set gesture state for an unknown view" + details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); + return; + } + + FlutterTouchInterceptingView* view = self.platformViews[viewId].touch_interceptor; + [view releaseGesture]; + + result(nil); +} + +- (void)onRejectGesture:(FlutterMethodCall*)call result:(FlutterResult)result { + NSDictionary* args = [call arguments]; + int64_t viewId = [args[@"id"] longLongValue]; + + if (self.platformViews.count(viewId) == 0) { + result([FlutterError errorWithCode:@"unknown_view" + message:@"trying to set gesture state for an unknown view" + details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); + return; + } + + FlutterTouchInterceptingView* view = self.platformViews[viewId].touch_interceptor; + [view blockGesture]; + + result(nil); +} + +- (void)registerViewFactory:(NSObject*)factory + withId:(NSString*)factoryId + gestureRecognizersBlockingPolicy: + (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizerBlockingPolicy { + std::string idString([factoryId UTF8String]); + FML_CHECK(self.factories.count(idString) == 0); + self.factories[idString] = factory; + self.gestureRecognizersBlockingPolicies[idString] = gestureRecognizerBlockingPolicy; +} + +- (void)beginFrameWithSize:(SkISize)frameSize { + [self resetFrameState]; + self.frameSize = frameSize; +} + +- (void)cancelFrame { + [self resetFrameState]; +} + +- (flutter::PostPrerollResult)postPrerollActionWithThreadMerger: + (const fml::RefPtr&)rasterThreadMerger + impellerEnabled:(BOOL)impellerEnabled { + // TODO(jonahwilliams): remove this once Software backend is removed for iOS Sim. +#ifdef FML_OS_IOS_SIMULATOR + const bool mergeThreads = true; +#else + const bool mergeThreads = !impellerEnabled; +#endif // FML_OS_IOS_SIMULATOR + + if (mergeThreads) { + if (self.compositionOrder.empty()) { + return flutter::PostPrerollResult::kSuccess; + } + if (!rasterThreadMerger->IsMerged()) { + // The raster thread merger may be disabled if the rasterizer is being + // created or teared down. + // + // In such cases, the current frame is dropped, and a new frame is attempted + // with the same layer tree. + // + // Eventually, the frame is submitted once this method returns `kSuccess`. + // At that point, the raster tasks are handled on the platform thread. + [self cancelFrame]; + return flutter::PostPrerollResult::kSkipAndRetryFrame; + } + // If the post preroll action is successful, we will display platform views in the current + // frame. In order to sync the rendering of the platform views (quartz) with skia's rendering, + // We need to begin an explicit CATransaction. This transaction needs to be submitted + // after the current frame is submitted. + rasterThreadMerger->ExtendLeaseTo(kDefaultMergedLeaseDuration); + } + return flutter::PostPrerollResult::kSuccess; +} + +- (void)endFrameWithResubmit:(BOOL)shouldResubmitFrame + threadMerger:(const fml::RefPtr&)rasterThreadMerger + impellerEnabled:(BOOL)impellerEnabled { +#if FML_OS_IOS_SIMULATOR + BOOL runCheck = YES; +#else + BOOL runCheck = !impellerEnabled; +#endif // FML_OS_IOS_SIMULATOR + if (runCheck && shouldResubmitFrame) { + rasterThreadMerger->MergeWithLease(kDefaultMergedLeaseDuration); + } +} + +- (void)pushFilterToVisitedPlatformViews:(const std::shared_ptr&)filter + withRect:(const SkRect&)filterRect { + for (int64_t id : self.visitedPlatformViews) { + flutter::EmbeddedViewParams params = self.currentCompositionParams[id]; + params.PushImageFilter(filter, filterRect); + self.currentCompositionParams[id] = params; + } +} + +- (void)prerollCompositeEmbeddedView:(int64_t)viewId + withParams:(std::unique_ptr)params { + SkRect viewBounds = SkRect::Make(self.frameSize); + std::unique_ptr view; + view = std::make_unique(viewBounds); + self.slices.insert_or_assign(viewId, std::move(view)); + + self.compositionOrder.push_back(viewId); + + if (self.currentCompositionParams.count(viewId) == 1 && + self.currentCompositionParams[viewId] == *params.get()) { + // Do nothing if the params didn't change. + return; + } + self.currentCompositionParams[viewId] = flutter::EmbeddedViewParams(*params.get()); + self.viewsToRecomposite.insert(viewId); +} + +- (size_t)embeddedViewCount { + return self.compositionOrder.size(); +} + +- (UIView*)platformViewForId:(int64_t)viewId { + return [self flutterTouchInterceptingViewForId:viewId].embeddedView; +} + +- (FlutterTouchInterceptingView*)flutterTouchInterceptingViewForId:(int64_t)viewId { + if (self.platformViews.empty()) { + return nil; + } + return self.platformViews[viewId].touch_interceptor; +} + +- (long)firstResponderPlatformViewId { + for (auto const& [id, platformViewData] : self.platformViews) { + UIView* rootView = platformViewData.root_view; + if (rootView.flt_hasFirstResponderInViewHierarchySubtree) { + return id; + } + } + return -1; +} + +- (void)clipViewSetMaskView:(UIView*)clipView { + FML_DCHECK([[NSThread currentThread] isMainThread]); + if (clipView.maskView) { + return; + } + CGRect frame = + CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y, + CGRectGetWidth(self.flutterView.bounds), CGRectGetHeight(self.flutterView.bounds)); + clipView.maskView = [self.maskViewPool getMaskViewWithFrame:frame]; +} + +- (void)applyMutators:(const flutter::MutatorsStack&)mutatorsStack + embeddedView:(UIView*)embeddedView + boundingRect:(const SkRect&)boundingRect { + if (self.flutterView == nil) { + return; + } + + ResetAnchor(embeddedView.layer); + ChildClippingView* clipView = (ChildClippingView*)embeddedView.superview; + + SkMatrix transformMatrix; + NSMutableArray* blurFilters = [[NSMutableArray alloc] init]; + FML_DCHECK(!clipView.maskView || + [clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]); + if (clipView.maskView) { + [self.maskViewPool insertViewToPoolIfNeeded:(FlutterClippingMaskView*)(clipView.maskView)]; + clipView.maskView = nil; + } + CGFloat screenScale = [UIScreen mainScreen].scale; + auto iter = mutatorsStack.Begin(); + while (iter != mutatorsStack.End()) { + switch ((*iter)->GetType()) { + case flutter::kTransform: { + transformMatrix.preConcat((*iter)->GetMatrix()); + break; + } + case flutter::kClipRect: { + if (ClipRectContainsPlatformViewBoundingRect((*iter)->GetRect(), boundingRect, + transformMatrix)) { + break; + } + [self clipViewSetMaskView:clipView]; + [(FlutterClippingMaskView*)clipView.maskView clipRect:(*iter)->GetRect() + matrix:transformMatrix]; + break; + } + case flutter::kClipRRect: { + if (ClipRRectContainsPlatformViewBoundingRect((*iter)->GetRRect(), boundingRect, + transformMatrix)) { + break; + } + [self clipViewSetMaskView:clipView]; + [(FlutterClippingMaskView*)clipView.maskView clipRRect:(*iter)->GetRRect() + matrix:transformMatrix]; + break; + } + case flutter::kClipPath: { + // TODO(cyanglaz): Find a way to pre-determine if path contains the PlatformView boudning + // rect. See `ClipRRectContainsPlatformViewBoundingRect`. + // https://github.com/flutter/flutter/issues/118650 + [self clipViewSetMaskView:clipView]; + [(FlutterClippingMaskView*)clipView.maskView clipPath:(*iter)->GetPath() + matrix:transformMatrix]; + break; + } + case flutter::kOpacity: + embeddedView.alpha = (*iter)->GetAlphaFloat() * embeddedView.alpha; + break; + case flutter::kBackdropFilter: { + // Only support DlBlurImageFilter for BackdropFilter. + if (!self.canApplyBlurBackdrop || !(*iter)->GetFilterMutation().GetFilter().asBlur()) { + break; + } + CGRect filterRect = GetCGRectFromSkRect((*iter)->GetFilterMutation().GetFilterRect()); + // `filterRect` is in global coordinates. We need to convert to local space. + filterRect = CGRectApplyAffineTransform( + filterRect, CGAffineTransformMakeScale(1 / screenScale, 1 / screenScale)); + // `filterRect` reprents the rect that should be filtered inside the `_flutterView`. + // The `PlatformViewFilter` needs the frame inside the `clipView` that needs to be + // filtered. + if (CGRectIsNull(CGRectIntersection(filterRect, clipView.frame))) { + break; + } + CGRect intersection = CGRectIntersection(filterRect, clipView.frame); + CGRect frameInClipView = [self.flutterView convertRect:intersection toView:clipView]; + // sigma_x is arbitrarily chosen as the radius value because Quartz sets + // sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode + // is not supported in Quartz's gaussianBlur CAFilter, so it is not used + // to blur the PlatformView. + CGFloat blurRadius = (*iter)->GetFilterMutation().GetFilter().asBlur()->sigma_x(); + UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] + initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; + PlatformViewFilter* filter = [[PlatformViewFilter alloc] initWithFrame:frameInClipView + blurRadius:blurRadius + visualEffectView:visualEffectView]; + if (!filter) { + self.canApplyBlurBackdrop = NO; + } else { + [blurFilters addObject:filter]; + } + break; + } + } + ++iter; + } + + if (self.canApplyBlurBackdrop) { + [clipView applyBlurBackdropFilters:blurFilters]; + } + + // The UIKit frame is set based on the logical resolution (points) instead of physical. + // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html). + // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals + // 500 points in UIKit for devices that has screenScale of 2. We need to scale the transformMatrix + // down to the logical resoltion before applying it to the layer of PlatformView. + transformMatrix.postScale(1 / screenScale, 1 / screenScale); + + // Reverse the offset of the clipView. + // The clipView's frame includes the final translate of the final transform matrix. + // Thus, this translate needs to be reversed so the platform view can layout at the correct + // offset. + // + // Note that the transforms are not applied to the clipping paths because clipping paths happen on + // the mask view, whose origin is always (0,0) to the _flutterView. + transformMatrix.postTranslate(-clipView.frame.origin.x, -clipView.frame.origin.y); + + embeddedView.layer.transform = GetCATransform3DFromSkMatrix(transformMatrix); +} + +- (void)compositeView:(int64_t)viewId withParams:(const flutter::EmbeddedViewParams&)params { + // TODO(https://github.com/flutter/flutter/issues/109700) + CGRect frame = CGRectMake(0, 0, params.sizePoints().width(), params.sizePoints().height()); + FlutterTouchInterceptingView* touchInterceptor = self.platformViews[viewId].touch_interceptor; + touchInterceptor.layer.transform = CATransform3DIdentity; + touchInterceptor.frame = frame; + touchInterceptor.alpha = 1; + + const flutter::MutatorsStack& mutatorStack = params.mutatorsStack(); + UIView* clippingView = self.platformViews[viewId].root_view; + // The frame of the clipping view should be the final bounding rect. + // Because the translate matrix in the Mutator Stack also includes the offset, + // when we apply the transforms matrix in |applyMutators:embeddedView:boundingRect|, we need + // to remember to do a reverse translate. + const SkRect& rect = params.finalBoundingRect(); + CGFloat screenScale = [UIScreen mainScreen].scale; + clippingView.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale, + rect.width() / screenScale, rect.height() / screenScale); + [self applyMutators:mutatorStack embeddedView:touchInterceptor boundingRect:rect]; +} + +- (flutter::DlCanvas*)compositeEmbeddedViewWithId:(int64_t)viewId { + FML_DCHECK(self.slices.find(viewId) != self.slices.end()); + return self.slices[viewId]->canvas(); +} + +- (void)reset { + // Reset will only be called from the raster thread or a merged raster/platform thread. + // _platformViews must only be modified on the platform thread, and any operations that + // read or modify platform views should occur there. + fml::TaskRunner::RunNowOrPostTask(self.platformTaskRunner, [self]() { + for (int64_t viewId : self.compositionOrder) { + [self.platformViews[viewId].root_view removeFromSuperview]; + } + self.platformViews.clear(); + }); + + self.compositionOrder.clear(); + self.slices.clear(); + self.currentCompositionParams.clear(); + self.viewsToRecomposite.clear(); + self.layerPool->RecycleLayers(); + self.visitedPlatformViews.clear(); +} + +- (BOOL)submitFrame:(std::unique_ptr)background_frame + withIosContext:(const std::shared_ptr&)iosContext + grContext:(GrDirectContext*)grContext { + TRACE_EVENT0("flutter", "PlatformViewsController::SubmitFrame"); + + // No platform views to render; we're done. + if (self.flutterView == nil || (self.compositionOrder.empty() && !self.hadPlatformViews)) { + self.hadPlatformViews = NO; + return background_frame->Submit(); + } + self.hadPlatformViews = !self.compositionOrder.empty(); + + bool didEncode = true; + LayersMap platformViewLayers; + std::vector> surfaceFrames; + surfaceFrames.reserve(self.compositionOrder.size()); + std::unordered_map viewRects; + + for (int64_t viewId : self.compositionOrder) { + viewRects[viewId] = self.currentCompositionParams[viewId].finalBoundingRect(); + } + + std::unordered_map overlayLayers = + SliceViews(background_frame->Canvas(), self.compositionOrder, self.slices, viewRects); + + size_t requiredOverlayLayers = 0; + for (int64_t viewId : self.compositionOrder) { + std::unordered_map::const_iterator overlay = overlayLayers.find(viewId); + if (overlay == overlayLayers.end()) { + continue; + } + requiredOverlayLayers++; + } + + // If there are not sufficient overlay layers, we must construct them on the platform + // thread, at least until we've refactored iOS surface creation to use IOSurfaces + // instead of CALayers. + [self createMissingOverlays:requiredOverlayLayers withIosContext:iosContext grContext:grContext]; + + int64_t overlayId = 0; + for (int64_t viewId : self.compositionOrder) { + std::unordered_map::const_iterator overlay = overlayLayers.find(viewId); + if (overlay == overlayLayers.end()) { + continue; + } + std::shared_ptr layer = self.nextLayerInPool; + if (!layer) { + continue; + } + + std::unique_ptr frame = layer->surface->AcquireFrame(self.frameSize); + // If frame is null, AcquireFrame already printed out an error message. + if (!frame) { + continue; + } + flutter::DlCanvas* overlayCanvas = frame->Canvas(); + int restoreCount = overlayCanvas->GetSaveCount(); + overlayCanvas->Save(); + overlayCanvas->ClipRect(overlay->second); + overlayCanvas->Clear(flutter::DlColor::kTransparent()); + self.slices[viewId]->render_into(overlayCanvas); + overlayCanvas->RestoreToCount(restoreCount); + + // This flutter view is never the last in a frame, since we always submit the + // underlay view last. + frame->set_submit_info({.frame_boundary = false, .present_with_transaction = true}); + layer->did_submit_last_frame = frame->Encode(); + + didEncode &= layer->did_submit_last_frame; + platformViewLayers[viewId] = LayerData{ + .rect = overlay->second, // + .view_id = viewId, // + .overlay_id = overlayId, // + .layer = layer // + }; + surfaceFrames.push_back(std::move(frame)); + overlayId++; + } + + auto previousSubmitInfo = background_frame->submit_info(); + background_frame->set_submit_info({ + .frame_damage = previousSubmitInfo.frame_damage, + .buffer_damage = previousSubmitInfo.buffer_damage, + .present_with_transaction = true, + }); + background_frame->Encode(); + surfaceFrames.push_back(std::move(background_frame)); + + // Mark all layers as available, so they can be used in the next frame. + std::vector> unusedLayers = + self.layerPool->RemoveUnusedLayers(); + self.layerPool->RecycleLayers(); + + auto task = [self, // + platformViewLayers = std::move(platformViewLayers), // + currentCompositionParams = self.currentCompositionParams, // + viewsToRecomposite = self.viewsToRecomposite, // + compositionOrder = self.compositionOrder, // + unusedLayers = std::move(unusedLayers), // + surfaceFrames = std::move(surfaceFrames) // + ]() mutable { + [self performSubmit:platformViewLayers + currentCompositionParams:currentCompositionParams + viewsToRecomposite:viewsToRecomposite + compositionOrder:compositionOrder + unusedLayers:unusedLayers + surfaceFrames:surfaceFrames]; + }; + + fml::TaskRunner::RunNowOrPostTask(self.platformTaskRunner, fml::MakeCopyable(std::move(task))); + + return didEncode; +} + +- (void)createMissingOverlays:(size_t)requiredOverlayLayers + withIosContext:(const std::shared_ptr&)iosContext + grContext:(GrDirectContext*)grContext { + TRACE_EVENT0("flutter", "PlatformViewsController::CreateMissingLayers"); + + if (requiredOverlayLayers <= self.layerPool->size()) { + return; + } + auto missingLayerCount = requiredOverlayLayers - self.layerPool->size(); + + // If the raster thread isn't merged, create layers on the platform thread and block until + // complete. + auto latch = std::make_shared(1u); + fml::TaskRunner::RunNowOrPostTask( + self.platformTaskRunner, [self, missingLayerCount, iosContext, grContext, latch]() { + for (auto i = 0u; i < missingLayerCount; i++) { + [self createLayerWithIosContext:iosContext + grContext:grContext + pixelFormat:((FlutterView*)self.flutterView).pixelFormat]; + } + latch->CountDown(); + }); + if (![[NSThread currentThread] isMainThread]) { + latch->Wait(); + } +} + +- (void)performSubmit:(const LayersMap&)platformViewLayers + currentCompositionParams: + (std::unordered_map&)currentCompositionParams + viewsToRecomposite:(const std::unordered_set&)viewsToRecomposite + compositionOrder:(const std::vector&)compositionOrder + unusedLayers: + (const std::vector>&)unusedLayers + surfaceFrames: + (const std::vector>&)surfaceFrames { + TRACE_EVENT0("flutter", "PlatformViewsController::PerformSubmit"); + FML_DCHECK([[NSThread currentThread] isMainThread]); + + [CATransaction begin]; + + // Configure Flutter overlay views. + for (const auto& [viewId, layerData] : platformViewLayers) { + layerData.layer->UpdateViewState(self.flutterView, // + layerData.rect, // + layerData.view_id, // + layerData.overlay_id // + ); + } + + // Dispose unused Flutter Views. + for (auto& view : [self computeViewsToDispose]) { + [view removeFromSuperview]; + } + + // Composite Platform Views. + for (int64_t viewId : viewsToRecomposite) { + [self compositeView:viewId withParams:currentCompositionParams[viewId]]; + } + + // Present callbacks. + for (const auto& frame : surfaceFrames) { + frame->Submit(); + } + + // If a layer was allocated in the previous frame, but it's not used in the current frame, + // then it can be removed from the scene. + [self removeUnusedLayers:unusedLayers withCompositionOrder:compositionOrder]; + + // Organize the layers by their z indexes. + [self bringLayersIntoView:platformViewLayers withCompositionOrder:compositionOrder]; + + [CATransaction commit]; +} + +- (void)bringLayersIntoView:(const LayersMap&)layerMap + withCompositionOrder:(const std::vector&)compositionOrder { + FML_DCHECK(self.flutterView); + UIView* flutterView = self.flutterView; + + self.previousCompositionOrder.clear(); + NSMutableArray* desiredPlatformSubviews = [NSMutableArray array]; + for (int64_t platformViewId : compositionOrder) { + self.previousCompositionOrder.push_back(platformViewId); + UIView* platformViewRoot = self.platformViews[platformViewId].root_view; + if (platformViewRoot != nil) { + [desiredPlatformSubviews addObject:platformViewRoot]; + } + + auto maybeLayerData = layerMap.find(platformViewId); + if (maybeLayerData != layerMap.end()) { + auto view = maybeLayerData->second.layer->overlay_view_wrapper; + if (view != nil) { + [desiredPlatformSubviews addObject:view]; + } + } + } + + NSSet* desiredPlatformSubviewsSet = [NSSet setWithArray:desiredPlatformSubviews]; + NSArray* existingPlatformSubviews = [flutterView.subviews + filteredArrayUsingPredicate:[NSPredicate + predicateWithBlock:^BOOL(id object, NSDictionary* bindings) { + return [desiredPlatformSubviewsSet containsObject:object]; + }]]; + + // Manipulate view hierarchy only if needed, to address a performance issue where + // this method is called even when view hierarchy stays the same. + // See: https://github.com/flutter/flutter/issues/121833 + // TODO(hellohuanlin): investigate if it is possible to skip unnecessary bringLayersIntoView. + if (![desiredPlatformSubviews isEqualToArray:existingPlatformSubviews]) { + for (UIView* subview in desiredPlatformSubviews) { + // `addSubview` will automatically reorder subview if it is already added. + [flutterView addSubview:subview]; + } + } +} + +- (std::shared_ptr)nextLayerInPool { + return self.layerPool->GetNextLayer(); +} + +- (void)createLayerWithIosContext:(const std::shared_ptr&)iosContext + grContext:(GrDirectContext*)grContext + pixelFormat:(MTLPixelFormat)pixelFormat { + self.layerPool->CreateLayer(grContext, iosContext, pixelFormat); +} + +- (void)removeUnusedLayers:(const std::vector>&)unusedLayers + withCompositionOrder:(const std::vector&)compositionOrder { + for (const std::shared_ptr& layer : unusedLayers) { + [layer->overlay_view_wrapper removeFromSuperview]; + } + + std::unordered_set compositionOrderSet; + for (int64_t viewId : compositionOrder) { + compositionOrderSet.insert(viewId); + } + // Remove unused platform views. + for (int64_t viewId : self.previousCompositionOrder) { + if (compositionOrderSet.find(viewId) == compositionOrderSet.end()) { + UIView* platformViewRoot = self.platformViews[viewId].root_view; + [platformViewRoot removeFromSuperview]; + } + } +} + +- (std::vector)computeViewsToDispose { + std::vector views; + if (self.viewsToDispose.empty()) { + return views; + } + + std::unordered_set viewsToComposite(self.compositionOrder.begin(), + self.compositionOrder.end()); + std::unordered_set viewsToDelayDispose; + for (int64_t viewId : self.viewsToDispose) { + if (viewsToComposite.count(viewId)) { + viewsToDelayDispose.insert(viewId); + continue; + } + UIView* rootView = self.platformViews[viewId].root_view; + views.push_back(rootView); + self.currentCompositionParams.erase(viewId); + self.viewsToRecomposite.erase(viewId); + self.platformViews.erase(viewId); + } + self.viewsToDispose = std::move(viewsToDelayDispose); + return views; +} + +- (void)resetFrameState { + self.slices.clear(); + self.compositionOrder.clear(); + self.visitedPlatformViews.clear(); +} + +- (void)pushVisitedPlatformViewId:(int64_t)viewId { + self.visitedPlatformViews.push_back(viewId); +} + +- (const flutter::EmbeddedViewParams&)compositionParamsForView:(int64_t)viewId { + return self.currentCompositionParams.find(viewId)->second; +} + +#pragma mark - Properties + +- (flutter::OverlayLayerPool*)layerPool { + return _layerPool.get(); +} + +- (std::unordered_map>&)slices { + return _slices; +} + +- (std::unordered_map*>&)factories { + return _factories; +} +- (std::unordered_map&) + gestureRecognizersBlockingPolicies { + return _gestureRecognizersBlockingPolicies; +} + +- (std::unordered_map&)platformViews { + return _platformViews; +} + +- (std::unordered_map&)currentCompositionParams { + return _currentCompositionParams; +} + +- (std::unordered_set&)viewsToDispose { + return _viewsToDispose; +} + +- (std::vector&)compositionOrder { + return _compositionOrder; +} + +- (std::vector&)visitedPlatformViews { + return _visitedPlatformViews; +} + +- (std::unordered_set&)viewsToRecomposite { + return _viewsToRecomposite; +} + +- (std::vector&)previousCompositionOrder { + return _previousCompositionOrder; +} + +@end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 904a3eace2564..465aa8d722527 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -2,25 +2,31 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" + #import #import +#import #import -#include "fml/synchronization/count_down_latch.h" -#include "shell/platform/darwin/ios/framework/Source/platform_views_controller.h" -#import "flutter/fml/thread.h" +#include + +#include "flutter/display_list/effects/dl_image_filters.h" +#include "flutter/fml/synchronization/count_down_latch.h" +#include "flutter/fml/thread.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" -#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h" -#import "flutter/shell/platform/darwin/ios/platform_view_ios.h" +#include "flutter/shell/platform/darwin/ios/ios_context_noop.h" +#include "flutter/shell/platform/darwin/ios/platform_view_ios.h" FLUTTER_ASSERT_ARC @class FlutterPlatformViewsTestMockPlatformView; -__weak static FlutterPlatformViewsTestMockPlatformView* gMockPlatformView = nil; +__weak static UIView* gMockPlatformView = nil; const float kFloatCompareEpsilon = 0.001; @interface FlutterPlatformViewsTestMockPlatformView : UIView @@ -83,6 +89,45 @@ @implementation FlutterPlatformViewsTestMockFlutterPlatformFactory @end +@interface FlutterPlatformViewsTestMockWebView : NSObject +@property(nonatomic, strong) UIView* view; +@property(nonatomic, assign) BOOL viewCreated; +@end + +@implementation FlutterPlatformViewsTestMockWebView +- (instancetype)init { + if (self = [super init]) { + _view = [[WKWebView alloc] init]; + gMockPlatformView = _view; + _viewCreated = NO; + } + return self; +} + +- (UIView*)view { + [self checkViewCreatedOnce]; + return _view; +} + +- (void)checkViewCreatedOnce { + if (self.viewCreated) { + abort(); + } + self.viewCreated = YES; +} +@end + +@interface FlutterPlatformViewsTestMockWebViewFactory : NSObject +@end + +@implementation FlutterPlatformViewsTestMockWebViewFactory +- (NSObject*)createWithFrame:(CGRect)frame + viewIdentifier:(int64_t)viewId + arguments:(id _Nullable)args { + return [[FlutterPlatformViewsTestMockWebView alloc] init]; +} +@end + @interface FlutterPlatformViewsTestNilFlutterPlatformFactory : NSObject @end @@ -162,8 +207,9 @@ - (void)testFlutterViewOnlyCreateOnceInOneFrame { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -176,18 +222,21 @@ - (void)testFlutterViewOnlyCreateOnceInOneFrame { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -203,11 +252,12 @@ - (void)testFlutterViewOnlyCreateOnceInOneFrame { auto embeddedViewParams = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; XCTAssertNotNil(gMockPlatformView); - flutterPlatformViewsController->Reset(); + [flutterPlatformViewsController reset]; } - (void)testCanCreatePlatformViewWithoutFlutterView { @@ -218,8 +268,9 @@ - (void)testCanCreatePlatformViewWithoutFlutterView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -232,16 +283,19 @@ - (void)testCanCreatePlatformViewWithoutFlutterView { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); } @@ -311,8 +365,9 @@ - (void)testApplyBackdropFilter { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -325,21 +380,24 @@ - (void)testApplyBackdropFilter { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -347,15 +405,17 @@ - (void)testApplyBackdropFilter { SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); stack.PushTransform(screenScaleMatrix); // Push a backdrop filter - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp); stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; @@ -388,8 +448,9 @@ - (void)testApplyBackdropFilterWithCorrectFrame { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -402,21 +463,24 @@ - (void)testApplyBackdropFilterWithCorrectFrame { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -424,15 +488,17 @@ - (void)testApplyBackdropFilterWithCorrectFrame { SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); stack.PushTransform(screenScaleMatrix); // Push a backdrop filter - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp); stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 8, screenScale * 8)); auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(5, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; @@ -465,8 +531,9 @@ - (void)testApplyMultipleBackdropFilters { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -479,21 +546,24 @@ - (void)testApplyMultipleBackdropFilters { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -502,16 +572,18 @@ - (void)testApplyMultipleBackdropFilters { stack.PushTransform(screenScaleMatrix); // Push backdrop filters for (int i = 0; i < 50; i++) { - auto filter = std::make_shared(i, 2, flutter::DlTileMode::kClamp); + auto filter = flutter::DlBlurImageFilter::Make(i, 2, flutter::DlTileMode::kClamp); stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); } auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(20, 20), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; @@ -543,8 +615,9 @@ - (void)testAddBackdropFilters { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -557,21 +630,24 @@ - (void)testAddBackdropFilters { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -579,15 +655,17 @@ - (void)testAddBackdropFilters { SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); stack.PushTransform(screenScaleMatrix); // Push a backdrop filter - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp); stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; @@ -624,9 +702,11 @@ - (void)testAddBackdropFilters { embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; [flutterView setNeedsLayout]; [flutterView layoutIfNeeded]; @@ -664,8 +744,9 @@ - (void)testRemoveBackdropFilters { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -678,21 +759,24 @@ - (void)testRemoveBackdropFilters { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -700,7 +784,7 @@ - (void)testRemoveBackdropFilters { SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); stack.PushTransform(screenScaleMatrix); // Push backdrop filters - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp); for (int i = 0; i < 5; i++) { stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); } @@ -708,9 +792,11 @@ - (void)testRemoveBackdropFilters { auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; @@ -745,9 +831,11 @@ - (void)testRemoveBackdropFilters { embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; [flutterView setNeedsLayout]; [flutterView layoutIfNeeded]; @@ -786,9 +874,11 @@ - (void)testRemoveBackdropFilters { embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; [flutterView setNeedsLayout]; [flutterView layoutIfNeeded]; @@ -810,8 +900,9 @@ - (void)testEditBackdropFilters { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -824,21 +915,24 @@ - (void)testEditBackdropFilters { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -846,7 +940,7 @@ - (void)testEditBackdropFilters { SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); stack.PushTransform(screenScaleMatrix); // Push backdrop filters - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp); for (int i = 0; i < 5; i++) { stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); } @@ -854,9 +948,11 @@ - (void)testEditBackdropFilters { auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; @@ -886,8 +982,7 @@ - (void)testEditBackdropFilters { // Push backdrop filters for (int i = 0; i < 5; i++) { if (i == 3) { - auto filter2 = - std::make_shared(2, 5, flutter::DlTileMode::kClamp); + auto filter2 = flutter::DlBlurImageFilter::Make(2, 5, flutter::DlTileMode::kClamp); stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); @@ -900,9 +995,11 @@ - (void)testEditBackdropFilters { embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; [flutterView setNeedsLayout]; [flutterView layoutIfNeeded]; @@ -943,8 +1040,7 @@ - (void)testEditBackdropFilters { // Push backdrop filters for (int i = 0; i < 5; i++) { if (i == 0) { - auto filter2 = - std::make_shared(2, 5, flutter::DlTileMode::kClamp); + auto filter2 = flutter::DlBlurImageFilter::Make(2, 5, flutter::DlTileMode::kClamp); stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); continue; @@ -956,9 +1052,11 @@ - (void)testEditBackdropFilters { embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; [flutterView setNeedsLayout]; [flutterView layoutIfNeeded]; @@ -997,8 +1095,7 @@ - (void)testEditBackdropFilters { // Push backdrop filters for (int i = 0; i < 5; i++) { if (i == 4) { - auto filter2 = - std::make_shared(2, 5, flutter::DlTileMode::kClamp); + auto filter2 = flutter::DlBlurImageFilter::Make(2, 5, flutter::DlTileMode::kClamp); stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); continue; @@ -1010,9 +1107,11 @@ - (void)testEditBackdropFilters { embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; [flutterView setNeedsLayout]; [flutterView layoutIfNeeded]; @@ -1052,7 +1151,7 @@ - (void)testEditBackdropFilters { } // Push backdrop filters for (int i = 0; i < 5; i++) { - auto filter2 = std::make_shared(i, 2, flutter::DlTileMode::kClamp); + auto filter2 = flutter::DlBlurImageFilter::Make(i, 2, flutter::DlTileMode::kClamp); stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); } @@ -1060,9 +1159,11 @@ - (void)testEditBackdropFilters { embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; [flutterView setNeedsLayout]; [flutterView layoutIfNeeded]; @@ -1100,8 +1201,9 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -1114,21 +1216,24 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -1136,15 +1241,17 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale); stack.PushTransform(screenScaleMatrix); // Push a dilate backdrop filter - auto dilateFilter = std::make_shared(5, 2); + auto dilateFilter = flutter::DlDilateImageFilter::Make(5, 2); stack.PushBackdropFilter(dilateFilter, SkRect::MakeEmpty()); auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; @@ -1168,7 +1275,7 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { // Layer tree always pushes a screen scale factor to the stack stack2.PushTransform(screenScaleMatrix); // Push backdrop filters and dilate filter - auto blurFilter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + auto blurFilter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp); for (int i = 0; i < 5; i++) { if (i == 2) { @@ -1184,9 +1291,11 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; [flutterView setNeedsLayout]; [flutterView layoutIfNeeded]; @@ -1225,9 +1334,11 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; [flutterView setNeedsLayout]; [flutterView layoutIfNeeded]; @@ -1266,9 +1377,11 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; [flutterView setNeedsLayout]; [flutterView layoutIfNeeded]; @@ -1301,9 +1414,11 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; [flutterView setNeedsLayout]; [flutterView layoutIfNeeded]; @@ -1419,8 +1534,9 @@ - (void)testCompositePlatformView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -1433,21 +1549,24 @@ - (void)testCompositePlatformView { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -1463,9 +1582,11 @@ - (void)testCompositePlatformView { auto embeddedViewParams = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds toView:flutterView]; @@ -1480,8 +1601,9 @@ - (void)testBackdropFilterCorrectlyPushedAndReset { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -1494,21 +1616,24 @@ - (void)testBackdropFilterCorrectlyPushedAndReset { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -1519,14 +1644,17 @@ - (void)testBackdropFilterCorrectlyPushedAndReset { auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->BeginFrame(SkISize::Make(0, 0)); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->PushVisitedPlatformView(2); - auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - flutterPlatformViewsController->PushFilterToVisitedPlatformViews( - filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(0, 0)]; + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController pushVisitedPlatformViewId:2]; + auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp); + [flutterPlatformViewsController + pushFilterToVisitedPlatformViews:filter + withRect:SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; @@ -1553,10 +1681,12 @@ - (void)testBackdropFilterCorrectlyPushedAndReset { // New frame, with no filter pushed. auto embeddedViewParams2 = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->BeginFrame(SkISize::Make(0, 0)); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams2)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(0, 0)]; + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams2)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); @@ -1581,8 +1711,9 @@ - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -1595,21 +1726,24 @@ - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -1626,9 +1760,11 @@ - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { auto embeddedViewParams = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds toView:flutterView]; @@ -1657,8 +1793,9 @@ - (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -1671,21 +1808,24 @@ - (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params. flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack. @@ -1707,9 +1847,11 @@ - (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView { auto embeddedViewParams = std::make_unique( SkMatrix::Concat(screenScaleMatrix, translateMatrix), SkSize::Make(5, 5), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; gMockPlatformView.backgroundColor = UIColor.redColor; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); @@ -1729,8 +1871,9 @@ - (void)testClipRRectOnlyHasCornersInterceptWithPlatformViewShouldAddMaskView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -1743,21 +1886,24 @@ - (void)testClipRRectOnlyHasCornersInterceptWithPlatformViewShouldAddMaskView { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack. @@ -1777,9 +1923,11 @@ - (void)testClipRRectOnlyHasCornersInterceptWithPlatformViewShouldAddMaskView { auto embeddedViewParams = std::make_unique( SkMatrix::Concat(screenScaleMatrix, translateMatrix), SkSize::Make(5, 5), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; gMockPlatformView.backgroundColor = UIColor.redColor; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); @@ -1800,8 +1948,9 @@ - (void)testClipRect { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -1814,21 +1963,24 @@ - (void)testClipRect { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -1842,9 +1994,11 @@ - (void)testClipRect { auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; gMockPlatformView.backgroundColor = UIColor.redColor; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); @@ -1876,8 +2030,9 @@ - (void)testClipRect_multipleClips { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -1890,21 +2045,24 @@ - (void)testClipRect_multipleClips { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -1921,9 +2079,11 @@ - (void)testClipRect_multipleClips { auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; gMockPlatformView.backgroundColor = UIColor.redColor; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); @@ -1972,8 +2132,9 @@ - (void)testClipRRect { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -1986,21 +2147,24 @@ - (void)testClipRRect { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -2014,9 +2178,11 @@ - (void)testClipRRect { auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; gMockPlatformView.backgroundColor = UIColor.redColor; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); @@ -2075,8 +2241,9 @@ - (void)testClipRRect_multipleClips { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -2089,21 +2256,24 @@ - (void)testClipRRect_multipleClips { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -2120,9 +2290,11 @@ - (void)testClipRRect_multipleClips { auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; gMockPlatformView.backgroundColor = UIColor.redColor; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); @@ -2195,8 +2367,9 @@ - (void)testClipPath { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -2209,21 +2382,24 @@ - (void)testClipPath { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -2238,9 +2414,11 @@ - (void)testClipPath { auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; gMockPlatformView.backgroundColor = UIColor.redColor; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); @@ -2299,8 +2477,9 @@ - (void)testClipPath_multipleClips { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -2313,21 +2492,24 @@ - (void)testClipPath_multipleClips { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -2345,9 +2527,11 @@ - (void)testClipPath_multipleClips { auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; gMockPlatformView.backgroundColor = UIColor.redColor; XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); @@ -2420,8 +2604,9 @@ - (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -2434,16 +2619,19 @@ - (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); @@ -2458,7 +2646,7 @@ - (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents { // Find ForwardGestureRecognizer UIGestureRecognizer* forwardGectureRecognizer = nil; for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { - if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { + if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) { forwardGectureRecognizer = gestureRecognizer; break; } @@ -2467,16 +2655,16 @@ - (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents { // Before setting flutter view controller, events are not dispatched. NSSet* touches1 = [[NSSet alloc] init]; id event1 = OCMClassMock([UIEvent class]); - id flutterViewContoller = OCMClassMock([FlutterViewController class]); + id flutterViewController = OCMClassMock([FlutterViewController class]); [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; - OCMReject([flutterViewContoller touchesBegan:touches1 withEvent:event1]); + OCMReject([flutterViewController touchesBegan:touches1 withEvent:event1]); // Set flutter view controller allows events to be dispatched. NSSet* touches2 = [[NSSet alloc] init]; id event2 = OCMClassMock([UIEvent class]); - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); + flutterPlatformViewsController.flutterViewController = flutterViewController; [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2]; - OCMVerify([flutterViewContoller touchesBegan:touches2 withEvent:event2]); + OCMVerify([flutterViewController touchesBegan:touches2 withEvent:event2]); } - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGesturesToBeHandled { @@ -2487,8 +2675,9 @@ - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGestu /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -2501,16 +2690,19 @@ - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGestu FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); @@ -2525,81 +2717,81 @@ - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGestu // Find ForwardGestureRecognizer UIGestureRecognizer* forwardGectureRecognizer = nil; for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { - if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { + if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) { forwardGectureRecognizer = gestureRecognizer; break; } } - id flutterViewContoller = OCMClassMock([FlutterViewController class]); + id flutterViewController = OCMClassMock([FlutterViewController class]); { // ***** Sequence 1, finishing touch event with touchEnded ***** // - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); + flutterPlatformViewsController.flutterViewController = flutterViewController; NSSet* touches1 = [[NSSet alloc] init]; id event1 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; - OCMVerify([flutterViewContoller touchesBegan:touches1 withEvent:event1]); + OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]); - flutterPlatformViewsController->SetFlutterViewController(nil); + flutterPlatformViewsController.flutterViewController = nil; // Allow the touch events to finish NSSet* touches2 = [[NSSet alloc] init]; id event2 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2]; - OCMVerify([flutterViewContoller touchesMoved:touches2 withEvent:event2]); + OCMVerify([flutterViewController touchesMoved:touches2 withEvent:event2]); NSSet* touches3 = [[NSSet alloc] init]; id event3 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesEnded:touches3 withEvent:event3]; - OCMVerify([flutterViewContoller touchesEnded:touches3 withEvent:event3]); + OCMVerify([flutterViewController touchesEnded:touches3 withEvent:event3]); // Now the 2nd touch sequence should not be allowed. NSSet* touches4 = [[NSSet alloc] init]; id event4 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4]; - OCMReject([flutterViewContoller touchesBegan:touches4 withEvent:event4]); + OCMReject([flutterViewController touchesBegan:touches4 withEvent:event4]); NSSet* touches5 = [[NSSet alloc] init]; id event5 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5]; - OCMReject([flutterViewContoller touchesEnded:touches5 withEvent:event5]); + OCMReject([flutterViewController touchesEnded:touches5 withEvent:event5]); } { // ***** Sequence 2, finishing touch event with touchCancelled ***** // - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); + flutterPlatformViewsController.flutterViewController = flutterViewController; NSSet* touches1 = [[NSSet alloc] init]; id event1 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; - OCMVerify([flutterViewContoller touchesBegan:touches1 withEvent:event1]); + OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]); - flutterPlatformViewsController->SetFlutterViewController(nil); + flutterPlatformViewsController.flutterViewController = nil; // Allow the touch events to finish NSSet* touches2 = [[NSSet alloc] init]; id event2 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2]; - OCMVerify([flutterViewContoller touchesMoved:touches2 withEvent:event2]); + OCMVerify([flutterViewController touchesMoved:touches2 withEvent:event2]); NSSet* touches3 = [[NSSet alloc] init]; id event3 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesCancelled:touches3 withEvent:event3]; - OCMVerify([flutterViewContoller forceTouchesCancelled:touches3]); + OCMVerify([flutterViewController forceTouchesCancelled:touches3]); // Now the 2nd touch sequence should not be allowed. NSSet* touches4 = [[NSSet alloc] init]; id event4 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4]; - OCMReject([flutterViewContoller touchesBegan:touches4 withEvent:event4]); + OCMReject([flutterViewController touchesBegan:touches4 withEvent:event4]); NSSet* touches5 = [[NSSet alloc] init]; id event5 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5]; - OCMReject([flutterViewContoller touchesEnded:touches5 withEvent:event5]); + OCMReject([flutterViewController touchesEnded:touches5 withEvent:event5]); } - flutterPlatformViewsController->Reset(); + [flutterPlatformViewsController reset]; } - (void) @@ -2611,8 +2803,9 @@ - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGestu /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -2625,16 +2818,19 @@ - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGestu FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); @@ -2649,72 +2845,71 @@ - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGestu // Find ForwardGestureRecognizer UIGestureRecognizer* forwardGectureRecognizer = nil; for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { - if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { + if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) { forwardGectureRecognizer = gestureRecognizer; break; } } - id flutterViewContoller = OCMClassMock([FlutterViewController class]); - - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); + id flutterViewController = OCMClassMock([FlutterViewController class]); + flutterPlatformViewsController.flutterViewController = flutterViewController; // The touches in this sequence requires 1 touch object, we always create the NSSet with one item. NSSet* touches1 = [NSSet setWithObject:@1]; id event1 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; - OCMVerify([flutterViewContoller touchesBegan:touches1 withEvent:event1]); + OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]); - FlutterViewController* flutterViewContoller2 = OCMClassMock([FlutterViewController class]); - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller2); + FlutterViewController* flutterViewController2 = OCMClassMock([FlutterViewController class]); + flutterPlatformViewsController.flutterViewController = flutterViewController2; // Touch events should still send to the old FlutterViewController if FlutterViewController // is updated in between. NSSet* touches2 = [NSSet setWithObject:@1]; id event2 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2]; - OCMVerify([flutterViewContoller touchesBegan:touches2 withEvent:event2]); - OCMReject([flutterViewContoller2 touchesBegan:touches2 withEvent:event2]); + OCMVerify([flutterViewController touchesBegan:touches2 withEvent:event2]); + OCMReject([flutterViewController2 touchesBegan:touches2 withEvent:event2]); NSSet* touches3 = [NSSet setWithObject:@1]; id event3 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesMoved:touches3 withEvent:event3]; - OCMVerify([flutterViewContoller touchesMoved:touches3 withEvent:event3]); - OCMReject([flutterViewContoller2 touchesMoved:touches3 withEvent:event3]); + OCMVerify([flutterViewController touchesMoved:touches3 withEvent:event3]); + OCMReject([flutterViewController2 touchesMoved:touches3 withEvent:event3]); NSSet* touches4 = [NSSet setWithObject:@1]; id event4 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesEnded:touches4 withEvent:event4]; - OCMVerify([flutterViewContoller touchesEnded:touches4 withEvent:event4]); - OCMReject([flutterViewContoller2 touchesEnded:touches4 withEvent:event4]); + OCMVerify([flutterViewController touchesEnded:touches4 withEvent:event4]); + OCMReject([flutterViewController2 touchesEnded:touches4 withEvent:event4]); NSSet* touches5 = [NSSet setWithObject:@1]; id event5 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5]; - OCMVerify([flutterViewContoller touchesEnded:touches5 withEvent:event5]); - OCMReject([flutterViewContoller2 touchesEnded:touches5 withEvent:event5]); + OCMVerify([flutterViewController touchesEnded:touches5 withEvent:event5]); + OCMReject([flutterViewController2 touchesEnded:touches5 withEvent:event5]); // Now the 2nd touch sequence should go to the new FlutterViewController NSSet* touches6 = [NSSet setWithObject:@1]; id event6 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesBegan:touches6 withEvent:event6]; - OCMVerify([flutterViewContoller2 touchesBegan:touches6 withEvent:event6]); - OCMReject([flutterViewContoller touchesBegan:touches6 withEvent:event6]); + OCMVerify([flutterViewController2 touchesBegan:touches6 withEvent:event6]); + OCMReject([flutterViewController touchesBegan:touches6 withEvent:event6]); // Allow the touch events to finish NSSet* touches7 = [NSSet setWithObject:@1]; id event7 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesMoved:touches7 withEvent:event7]; - OCMVerify([flutterViewContoller2 touchesMoved:touches7 withEvent:event7]); - OCMReject([flutterViewContoller touchesMoved:touches7 withEvent:event7]); + OCMVerify([flutterViewController2 touchesMoved:touches7 withEvent:event7]); + OCMReject([flutterViewController touchesMoved:touches7 withEvent:event7]); NSSet* touches8 = [NSSet setWithObject:@1]; id event8 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesEnded:touches8 withEvent:event8]; - OCMVerify([flutterViewContoller2 touchesEnded:touches8 withEvent:event8]); - OCMReject([flutterViewContoller touchesEnded:touches8 withEvent:event8]); + OCMVerify([flutterViewController2 touchesEnded:touches8 withEvent:event8]); + OCMReject([flutterViewController touchesEnded:touches8 withEvent:event8]); - flutterPlatformViewsController->Reset(); + [flutterPlatformViewsController reset]; } - (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled { @@ -2725,8 +2920,9 @@ - (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -2739,16 +2935,19 @@ - (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); @@ -2763,23 +2962,256 @@ - (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled { // Find ForwardGestureRecognizer UIGestureRecognizer* forwardGectureRecognizer = nil; for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { - if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) { + if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) { forwardGectureRecognizer = gestureRecognizer; break; } } - id flutterViewContoller = OCMClassMock([FlutterViewController class]); - - flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller); + id flutterViewController = OCMClassMock([FlutterViewController class]); + flutterPlatformViewsController.flutterViewController = flutterViewController; NSSet* touches1 = [NSSet setWithObject:@1]; id event1 = OCMClassMock([UIEvent class]); [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; [forwardGectureRecognizer touchesCancelled:touches1 withEvent:event1]; - OCMVerify([flutterViewContoller forceTouchesCancelled:touches1]); + OCMVerify([flutterViewController forceTouchesCancelled:touches1]); - flutterPlatformViewsController->Reset(); + [flutterPlatformViewsController reset]; +} + +- (void)testFlutterPlatformViewTouchesEndedOrTouchesCancelledEventDoesNotFailTheGestureRecognizer { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; + FlutterResult result = ^(id result) { + }; + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; + + XCTAssertNotNil(gMockPlatformView); + + // Find touch inteceptor view + UIView* touchInteceptorView = gMockPlatformView; + while (touchInteceptorView != nil && + ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) { + touchInteceptorView = touchInteceptorView.superview; + } + XCTAssertNotNil(touchInteceptorView); + + // Find ForwardGestureRecognizer + __block UIGestureRecognizer* forwardGestureRecognizer = nil; + for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { + if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) { + forwardGestureRecognizer = gestureRecognizer; + break; + } + } + id flutterViewController = OCMClassMock([FlutterViewController class]); + flutterPlatformViewsController.flutterViewController = flutterViewController; + + NSSet* touches1 = [NSSet setWithObject:@1]; + id event1 = OCMClassMock([UIEvent class]); + XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible, + @"Forwarding gesture recognizer must start with possible state."); + [forwardGestureRecognizer touchesBegan:touches1 withEvent:event1]; + [forwardGestureRecognizer touchesEnded:touches1 withEvent:event1]; + XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStateFailed, + @"Forwarding gesture recognizer must end with failed state."); + + XCTestExpectation* touchEndedExpectation = + [self expectationWithDescription:@"Wait for gesture recognizer's state change."]; + dispatch_async(dispatch_get_main_queue(), ^{ + // Re-query forward gesture recognizer since it's recreated. + for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { + if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) { + forwardGestureRecognizer = gestureRecognizer; + break; + } + } + XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible, + @"Forwarding gesture recognizer must be reset to possible state."); + [touchEndedExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:30 handler:nil]; + + XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible, + @"Forwarding gesture recognizer must start with possible state."); + [forwardGestureRecognizer touchesBegan:touches1 withEvent:event1]; + [forwardGestureRecognizer touchesCancelled:touches1 withEvent:event1]; + XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStateFailed, + @"Forwarding gesture recognizer must end with failed state."); + XCTestExpectation* touchCancelledExpectation = + [self expectationWithDescription:@"Wait for gesture recognizer's state change."]; + dispatch_async(dispatch_get_main_queue(), ^{ + // Re-query forward gesture recognizer since it's recreated. + for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) { + if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) { + forwardGestureRecognizer = gestureRecognizer; + break; + } + } + XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible, + @"Forwarding gesture recognizer must be reset to possible state."); + [touchCancelledExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:30 handler:nil]; + + [flutterPlatformViewsController reset]; +} + +- (void) + testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldRemoveAndAddBackDelayingRecognizerForWebView { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockWebViewFactory* factory = + [[FlutterPlatformViewsTestMockWebViewFactory alloc] init]; + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockWebView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; + FlutterResult result = ^(id result) { + }; + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall + methodCallWithMethodName:@"create" + arguments:@{@"id" : @2, @"viewType" : @"MockWebView"}] + result:result]; + + XCTAssertNotNil(gMockPlatformView); + + // Find touch inteceptor view + UIView* touchInteceptorView = gMockPlatformView; + while (touchInteceptorView != nil && + ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) { + touchInteceptorView = touchInteceptorView.superview; + } + XCTAssertNotNil(touchInteceptorView); + + XCTAssert(touchInteceptorView.gestureRecognizers.count == 2); + UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0]; + UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1]; + + XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]); + XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]); + + [(FlutterTouchInterceptingView*)touchInteceptorView blockGesture]; + + if (@available(iOS 18.2, *)) { + // Since we remove and add back delayingRecognizer, it would be reordered to the last. + XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], forwardingRecognizer); + XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], delayingRecognizer); + } else { + XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer); + XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer); + } +} + +- (void) + testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNonWebView { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/mock_delegate.settings_.enable_impeller + ? flutter::IOSRenderingAPI::kMetal + : flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; + FlutterResult result = ^(id result) { + }; + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; + + XCTAssertNotNil(gMockPlatformView); + + // Find touch inteceptor view + UIView* touchInteceptorView = gMockPlatformView; + while (touchInteceptorView != nil && + ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) { + touchInteceptorView = touchInteceptorView.superview; + } + XCTAssertNotNil(touchInteceptorView); + + XCTAssert(touchInteceptorView.gestureRecognizers.count == 2); + UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0]; + UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1]; + + XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]); + XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]); + + [(FlutterTouchInterceptingView*)touchInteceptorView blockGesture]; + + XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer); + XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer); } - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashing { @@ -2790,8 +3222,9 @@ - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashin /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -2804,16 +3237,19 @@ - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashin FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); @@ -2824,9 +3260,11 @@ - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashin auto embeddedViewParams_1 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams_1)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams_1)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; flutter::SurfaceFrame::FramebufferInfo framebuffer_info; auto mock_surface = std::make_unique( @@ -2834,22 +3272,28 @@ - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashin [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return false; }, [](const flutter::SurfaceFrame& surface_frame) { return true; }, /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertFalse( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + XCTAssertFalse([flutterPlatformViewsController + submitFrame:std::move(mock_surface) + withIosContext:std::make_shared() + grContext:nil]); auto embeddedViewParams_2 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams_2)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams_2)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; auto mock_surface_submit_true = std::make_unique( nullptr, framebuffer_info, [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, [](const flutter::SurfaceFrame& surface_frame) { return true; }, /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertTrue(flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, - std::move(mock_surface_submit_true))); + XCTAssertTrue([flutterPlatformViewsController + submitFrame:std::move(mock_surface_submit_true) + withIosContext:std::make_shared() + grContext:nil]); } - (void) @@ -2861,8 +3305,9 @@ - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashin /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -2874,34 +3319,38 @@ - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashin /*is_gpu_disabled_jsync_switch=*/std::make_shared()); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; // autorelease pool to trigger an autorelease for all the root_views_ and touch_interceptors_. @autoreleasepool { - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; flutter::MutatorsStack stack; SkMatrix finalMatrix; auto embeddedViewParams = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; - // Not calling |flutterPlatformViewsController::SubmitFrame| so that the platform views are not - // added to flutter_view_. + // Not calling |[flutterPlatformViewsController submitFrame:withIosContext:grContext:]| so that + // the platform views are not added to flutter_view_. XCTAssertNotNil(gMockPlatformView); - flutterPlatformViewsController->Reset(); + [flutterPlatformViewsController reset]; } XCTAssertNil(gMockPlatformView); } @@ -2914,8 +3363,9 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -2927,45 +3377,52 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { /*is_gpu_disabled_jsync_switch=*/std::make_shared()); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @0, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; - // First frame, |EmbeddedViewCount| is not empty after composite. - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + // First frame, |embeddedViewCount| is not empty after composite. + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)]; flutter::MutatorsStack stack; SkMatrix finalMatrix; auto embeddedViewParams1 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1)); - flutterPlatformViewsController->CompositeWithParams( - 0, flutterPlatformViewsController->GetCompositionParams(0)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:0 + withParams:std::move(embeddedViewParams1)]; + [flutterPlatformViewsController + compositeView:0 + withParams:[flutterPlatformViewsController compositionParamsForView:0]]; - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL); + XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL); - // Second frame, |EmbeddedViewCount| should be empty at the start - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 0UL); + // Second frame, |embeddedViewCount| should be empty at the start + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)]; + XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 0UL); auto embeddedViewParams2 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams2)); - flutterPlatformViewsController->CompositeWithParams( - 0, flutterPlatformViewsController->GetCompositionParams(0)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:0 + withParams:std::move(embeddedViewParams2)]; + [flutterPlatformViewsController + compositeView:0 + withParams:[flutterPlatformViewsController compositionParamsForView:0]]; - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL); + XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL); } - (void) @@ -2977,8 +3434,9 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -2990,40 +3448,47 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { /*is_gpu_disabled_jsync_switch=*/std::make_shared()); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @0, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; UIView* view1 = gMockPlatformView; // This overwrites `gMockPlatformView` to another view. - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @1, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; UIView* view2 = gMockPlatformView; - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)]; flutter::MutatorsStack stack; SkMatrix finalMatrix; auto embeddedViewParams1 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:0 + withParams:std::move(embeddedViewParams1)]; auto embeddedViewParams2 = std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:1 + withParams:std::move(embeddedViewParams2)]; // SKSurface is required if the root FlutterView is present. const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); @@ -3034,9 +3499,10 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, [](const flutter::SurfaceFrame& surface_frame) { return true; }, /*frame_size=*/SkISize::Make(800, 600)); - - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + XCTAssertTrue([flutterPlatformViewsController + submitFrame:std::move(mock_surface) + withIosContext:std::make_shared() + grContext:nil]); // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view. UIView* clippingView1 = view1.superview.superview; @@ -3046,15 +3512,17 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { @"The first clipping view should be added before the second clipping view."); // Need to recreate these params since they are `std::move`ed. - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)]; // Process the second frame in the opposite order. embeddedViewParams2 = std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:1 + withParams:std::move(embeddedViewParams2)]; embeddedViewParams1 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:0 + withParams:std::move(embeddedViewParams1)]; mock_sk_surface = SkSurfaces::Raster(image_info); mock_surface = std::make_unique( @@ -3062,8 +3530,10 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, [](const flutter::SurfaceFrame& surface_frame) { return true; }, /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + XCTAssertTrue([flutterPlatformViewsController + submitFrame:std::move(mock_surface) + withIosContext:std::make_shared() + grContext:nil]); XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] > [flutterView.subviews indexOfObject:clippingView2], @@ -3079,8 +3549,9 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -3092,40 +3563,47 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { /*is_gpu_disabled_jsync_switch=*/std::make_shared()); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @0, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; UIView* view1 = gMockPlatformView; // This overwrites `gMockPlatformView` to another view. - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @1, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; UIView* view2 = gMockPlatformView; - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)]; flutter::MutatorsStack stack; SkMatrix finalMatrix; auto embeddedViewParams1 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:0 + withParams:std::move(embeddedViewParams1)]; auto embeddedViewParams2 = std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:1 + withParams:std::move(embeddedViewParams2)]; // SKSurface is required if the root FlutterView is present. const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); @@ -3136,9 +3614,10 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, [](const flutter::SurfaceFrame& surface_frame) { return true; }, /*frame_size=*/SkISize::Make(800, 600)); - - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + XCTAssertTrue([flutterPlatformViewsController + submitFrame:std::move(mock_surface) + withIosContext:std::make_shared() + grContext:nil]); // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view. UIView* clippingView1 = view1.superview.superview; @@ -3148,15 +3627,17 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { @"The first clipping view should be added before the second clipping view."); // Need to recreate these params since they are `std::move`ed. - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)]; // Process the second frame in the same order. embeddedViewParams1 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:0 + withParams:std::move(embeddedViewParams1)]; embeddedViewParams2 = std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:1 + withParams:std::move(embeddedViewParams2)]; mock_sk_surface = SkSurfaces::Raster(image_info); mock_surface = std::make_unique( @@ -3164,8 +3645,10 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, [](const flutter::SurfaceFrame& surface_frame) { return true; }, /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + XCTAssertTrue([flutterPlatformViewsController + submitFrame:std::move(mock_surface) + withIosContext:std::make_shared() + grContext:nil]); XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] < [flutterView.subviews indexOfObject:clippingView2], @@ -3266,8 +3749,9 @@ - (void)testClipMaskViewIsReused { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -3280,20 +3764,23 @@ - (void)testClipMaskViewIsReused { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @1, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack1; // Layer tree always pushes a screen scale factor to the stack @@ -3307,39 +3794,47 @@ - (void)testClipMaskViewIsReused { auto embeddedViewParams1 = std::make_unique( screenScaleMatrix, SkSize::Make(10, 10), stack1); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); - flutterPlatformViewsController->CompositeWithParams( - 1, flutterPlatformViewsController->GetCompositionParams(1)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:1 + withParams:std::move(embeddedViewParams1)]; + [flutterPlatformViewsController + compositeView:1 + withParams:[flutterPlatformViewsController compositionParamsForView:1]]; UIView* childClippingView1 = gMockPlatformView.superview.superview; UIView* maskView1 = childClippingView1.maskView; XCTAssertNotNil(maskView1); // Composite a new frame. - flutterPlatformViewsController->BeginFrame(SkISize::Make(100, 100)); + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(100, 100)]; flutter::MutatorsStack stack2; auto embeddedViewParams2 = std::make_unique( screenScaleMatrix, SkSize::Make(10, 10), stack2); auto embeddedViewParams3 = std::make_unique( screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams3)); - flutterPlatformViewsController->CompositeWithParams( - 1, flutterPlatformViewsController->GetCompositionParams(1)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:1 + withParams:std::move(embeddedViewParams3)]; + [flutterPlatformViewsController + compositeView:1 + withParams:[flutterPlatformViewsController compositionParamsForView:1]]; childClippingView1 = gMockPlatformView.superview.superview; // This overrides gMockPlatformView to point to the newly created platform view. - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; auto embeddedViewParams4 = std::make_unique( screenScaleMatrix, SkSize::Make(10, 10), stack1); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams4)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams4)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; UIView* childClippingView2 = gMockPlatformView.superview.superview; @@ -3357,8 +3852,9 @@ - (void)testDifferentClipMaskViewIsUsedForEachView { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -3371,30 +3867,35 @@ - (void)testDifferentClipMaskViewIsUsedForEachView { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @1, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; UIView* view1 = gMockPlatformView; // This overwrites `gMockPlatformView` to another view. - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; UIView* view2 = gMockPlatformView; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack1; // Layer tree always pushes a screen scale factor to the stack @@ -3413,15 +3914,19 @@ - (void)testDifferentClipMaskViewIsUsedForEachView { auto embeddedViewParams2 = std::make_unique( screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); - flutterPlatformViewsController->CompositeWithParams( - 1, flutterPlatformViewsController->GetCompositionParams(1)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:1 + withParams:std::move(embeddedViewParams1)]; + [flutterPlatformViewsController + compositeView:1 + withParams:[flutterPlatformViewsController compositionParamsForView:1]]; UIView* childClippingView1 = view1.superview.superview; - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams2)); - flutterPlatformViewsController->CompositeWithParams( - 2, flutterPlatformViewsController->GetCompositionParams(2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams2)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; UIView* childClippingView2 = view2.superview.superview; UIView* maskView1 = childClippingView1.maskView; @@ -3437,8 +3942,9 @@ - (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -3451,21 +3957,24 @@ - (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @1, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; XCTAssertNotNil(gMockPlatformView); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack1; // Layer tree always pushes a screen scale factor to the stack @@ -3484,9 +3993,11 @@ - (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer { auto embeddedViewParams2 = std::make_unique( screenScaleMatrix, SkSize::Make(10, 10), stack2); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); - flutterPlatformViewsController->CompositeWithParams( - 1, flutterPlatformViewsController->GetCompositionParams(1)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:1 + withParams:std::move(embeddedViewParams1)]; + [flutterPlatformViewsController + compositeView:1 + withParams:[flutterPlatformViewsController compositionParamsForView:1]]; UIView* childClippingView = gMockPlatformView.superview.superview; @@ -3530,8 +4041,9 @@ - (void)testDisposingViewInCompositionOrderDoNotCrash { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -3543,50 +4055,58 @@ - (void)testDisposingViewInCompositionOrderDoNotCrash { /*is_gpu_disabled_jsync_switch=*/std::make_shared()); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], - result); - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @0, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @1, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; { // **** First frame, view id 0, 1 in the composition_order_, disposing view 0 is called. **** // // No view should be disposed, or removed from the composition order. - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)]; flutter::MutatorsStack stack; SkMatrix finalMatrix; auto embeddedViewParams0 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams0)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:0 + withParams:std::move(embeddedViewParams0)]; auto embeddedViewParams1 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:1 + withParams:std::move(embeddedViewParams1)]; - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 2UL); + XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 2UL); XCTestExpectation* expectation = [self expectationWithDescription:@"dispose call ended."]; FlutterResult disposeResult = ^(id result) { [expectation fulfill]; }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall methodCallWithMethodName:@"dispose" arguments:@0], disposeResult); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"dispose" arguments:@0] + result:disposeResult]; [self waitForExpectationsWithTimeout:30 handler:nil]; const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); @@ -3597,24 +4117,27 @@ - (void)testDisposingViewInCompositionOrderDoNotCrash { [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, [](const flutter::SurfaceFrame& surface_frame) { return true; }, /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + XCTAssertTrue([flutterPlatformViewsController + submitFrame:std::move(mock_surface) + withIosContext:std::make_shared() + grContext:nil]); // Disposing won't remove embedded views until the view is removed from the composition_order_ - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 2UL); - XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(0)); - XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(1)); + XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 2UL); + XCTAssertNotNil([flutterPlatformViewsController platformViewForId:0]); + XCTAssertNotNil([flutterPlatformViewsController platformViewForId:1]); } { // **** Second frame, view id 1 in the composition_order_, no disposing view is called, **** // // View 0 is removed from the composition order in this frame, hence also disposed. - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)]; flutter::MutatorsStack stack; SkMatrix finalMatrix; auto embeddedViewParams1 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:1 + withParams:std::move(embeddedViewParams1)]; const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); sk_sp mock_sk_surface = SkSurfaces::Raster(image_info); @@ -3624,13 +4147,15 @@ - (void)testDisposingViewInCompositionOrderDoNotCrash { [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, [](const flutter::SurfaceFrame& surface_frame) { return true; }, /*frame_size=*/SkISize::Make(800, 600)); - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + XCTAssertTrue([flutterPlatformViewsController + submitFrame:std::move(mock_surface) + withIosContext:std::make_shared() + grContext:nil]); // Disposing won't remove embedded views until the view is removed from the composition_order_ - XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL); - XCTAssertNil(flutterPlatformViewsController->GetPlatformViewByID(0)); - XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(1)); + XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL); + XCTAssertNil([flutterPlatformViewsController platformViewForId:0]); + XCTAssertNotNil([flutterPlatformViewsController platformViewForId:1]); } } - (void)testOnlyPlatformViewsAreRemovedWhenReset { @@ -3641,8 +4166,9 @@ - (void)testOnlyPlatformViewsAreRemovedWhenReset { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -3655,18 +4181,21 @@ - (void)testOnlyPlatformViewsAreRemovedWhenReset { FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; // Layer tree always pushes a screen scale factor to the stack @@ -3682,7 +4211,8 @@ - (void)testOnlyPlatformViewsAreRemovedWhenReset { auto embeddedViewParams = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; // SKSurface is required if the root FlutterView is present. const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); @@ -3693,13 +4223,14 @@ - (void)testOnlyPlatformViewsAreRemovedWhenReset { [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, [](const flutter::SurfaceFrame& surface_frame) { return true; }, /*frame_size=*/SkISize::Make(800, 600)); - - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface)); + [flutterPlatformViewsController submitFrame:std::move(mock_surface) + withIosContext:std::make_shared() + grContext:nil]; UIView* someView = [[UIView alloc] init]; [flutterView addSubview:someView]; - flutterPlatformViewsController->Reset(); + [flutterPlatformViewsController reset]; XCTAssertEqual(flutterView.subviews.count, 1u); XCTAssertEqual(flutterView.subviews.firstObject, someView); } @@ -3712,8 +4243,9 @@ - (void)testNilPlatformViewDoesntCrash { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -3726,18 +4258,21 @@ - (void)testNilPlatformViewDoesntCrash { FlutterPlatformViewsTestNilFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestNilFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; // Create embedded view params flutter::MutatorsStack stack; @@ -3754,7 +4289,8 @@ - (void)testNilPlatformViewDoesntCrash { auto embeddedViewParams = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; // SKSurface is required if the root FlutterView is present. const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); @@ -3765,8 +4301,9 @@ - (void)testNilPlatformViewDoesntCrash { [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; }, [](const flutter::SurfaceFrame& surface_frame) { return true; }, /*frame_size=*/SkISize::Make(800, 600)); - - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface)); + [flutterPlatformViewsController submitFrame:std::move(mock_surface) + withIosContext:std::make_shared() + grContext:nil]; XCTAssertEqual(flutterView.subviews.count, 1u); } @@ -3812,8 +4349,9 @@ - (void)testFlutterPlatformViewControllerSubmitFramePreservingFrameDamage { /*raster=*/GetDefaultTaskRunner(), /*ui=*/GetDefaultTaskRunner(), /*io=*/GetDefaultTaskRunner()); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -3825,38 +4363,45 @@ - (void)testFlutterPlatformViewControllerSubmitFramePreservingFrameDamage { /*is_gpu_disabled_jsync_switch=*/std::make_shared()); UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - flutterPlatformViewsController->SetFlutterView(flutterView); + flutterPlatformViewsController.flutterView = flutterView; FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @0, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; // This overwrites `gMockPlatformView` to another view. - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}], - result); - - flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @1, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; + + [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)]; flutter::MutatorsStack stack; SkMatrix finalMatrix; auto embeddedViewParams1 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:0 + withParams:std::move(embeddedViewParams1)]; auto embeddedViewParams2 = std::make_unique(finalMatrix, SkSize::Make(500, 500), stack); - flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2)); + [flutterPlatformViewsController prerollCompositeEmbeddedView:1 + withParams:std::move(embeddedViewParams2)]; // SKSurface is required if the root FlutterView is present. const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000); @@ -3876,8 +4421,9 @@ - (void)testFlutterPlatformViewControllerSubmitFramePreservingFrameDamage { .buffer_damage = SkIRect::MakeWH(400, 600), }); - XCTAssertTrue( - flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface))); + [flutterPlatformViewsController submitFrame:std::move(mock_surface) + withIosContext:std::make_shared() + grContext:nil]; XCTAssertTrue(submit_info.has_value()); XCTAssertEqual(*submit_info->frame_damage, SkIRect::MakeWH(800, 600)); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index b42c267074d4f..b23d532cf4e00 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -6,23 +6,20 @@ #define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWS_INTERNAL_H_ #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" -#include "fml/task_runner.h" -#include "impeller/base/thread_safety.h" -#include "third_party/skia/include/core/SkRect.h" #include #include "flutter/flow/surface.h" #include "flutter/fml/memory/weak_ptr.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/fml/task_runner.h" #include "flutter/fml/trace_event.h" +#include "flutter/impeller/base/thread_safety.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/platform_views_controller.h" -#import "flutter/shell/platform/darwin/ios/ios_context.h" - -@class FlutterTouchInterceptingView; +#include "flutter/shell/platform/darwin/ios/ios_context.h" +#include "third_party/skia/include/core/SkRect.h" // A UIView that acts as a clipping mask for the |ChildClippingView|. // @@ -137,8 +134,7 @@ // 2. Dispatching all events that are hittested to the embedded view to the FlutterView. @interface FlutterTouchInterceptingView : UIView - (instancetype)initWithEmbeddedView:(UIView*)embeddedView - platformViewsController: - (fml::WeakPtr)platformViewsController + platformViewsController:(FlutterPlatformViewsController*)platformViewsController gestureRecognizersBlockingPolicy: (FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy; @@ -160,4 +156,43 @@ @property(nonatomic, readonly) BOOL flt_hasFirstResponderInViewHierarchySubtree; @end +// This recognizer delays touch events from being dispatched to the responder chain until it failed +// recognizing a gesture. +// +// We only fail this recognizer when asked to do so by the Flutter framework (which does so by +// invoking an acceptGesture method on the platform_views channel). And this is how we allow the +// Flutter framework to delay or prevent the embedded view from getting a touch sequence. +@interface FlutterDelayingGestureRecognizer : UIGestureRecognizer + +// Indicates that if the `FlutterDelayingGestureRecognizer`'s state should be set to +// `UIGestureRecognizerStateEnded` during next `touchesEnded` call. +@property(nonatomic) BOOL shouldEndInNextTouchesEnded; + +// Indicates that the `FlutterDelayingGestureRecognizer`'s `touchesEnded` has been invoked without +// setting the state to `UIGestureRecognizerStateEnded`. +@property(nonatomic) BOOL touchedEndedWithoutBlocking; + +@property(nonatomic) UIGestureRecognizer* forwardingRecognizer; + +- (instancetype)initWithTarget:(id)target + action:(SEL)action + forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer; +@end + +// While the FlutterDelayingGestureRecognizer is preventing touches from hitting the responder chain +// the touch events are not arriving to the FlutterView (and thus not arriving to the Flutter +// framework). We use this gesture recognizer to dispatch the events directly to the FlutterView +// while during this phase. +// +// If the Flutter framework decides to dispatch events to the embedded view, we fail the +// FlutterDelayingGestureRecognizer which sends the events up the responder chain. But since the +// events are handled by the embedded view they are not delivered to the Flutter framework in this +// phase as well. So during this phase as well the ForwardingGestureRecognizer dispatched the events +// directly to the FlutterView. +@interface ForwardingGestureRecognizer : UIGestureRecognizer +- (instancetype)initWithTarget:(id)target + platformViewsController:(FlutterPlatformViewsController*)platformViewsController; +- (ForwardingGestureRecognizer*)recreateRecognizerWithTarget:(id)target; +@end + #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWS_INTERNAL_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.h b/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.h index 28b9b5636296b..e5f2f6343b843 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.h @@ -18,10 +18,30 @@ NS_ASSUME_NONNULL_BEGIN * sends all of selector calls from accessibility services to the * owner SemanticsObject. */ -@interface FlutterSemanticsScrollView : UIScrollView +@interface FlutterSemanticsScrollView : UIScrollView @property(nonatomic, weak, nullable) SemanticsObject* semanticsObject; +/// Whether this scroll view's content offset is actively being updated by UIKit +/// or other the system services. +/// +/// This flag is set by the `FlutterSemanticsScrollView` itself, typically in +/// one of the `UIScrollViewDelegate` methods. +/// +/// When this flag is true, the `SemanticsObject` implementation ignores all +/// content offset updates coming from the Flutter framework, to prevent +/// potential feedback loops (especially when the framework is only echoing +/// the new content offset back to this scroll view). +/// +/// For example, to scroll a scrollable container with iOS full keyboard access, +/// the iOS focus system uses a display link to scroll the container to the +/// desired offset animatedly. If the user changes the scroll offset during the +/// animation, the display link will be invalidated and the scrolling animation +/// will be interrupted. For simplicity, content offset updates coming from the +/// framework will be ignored in the relatively short animation duration (~1s), +/// allowing the scrolling animation to finish. +@property(nonatomic, readonly) BOOL isDoingSystemScrolling; + - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; - (instancetype)initWithCoder:(NSCoder*)coder NS_UNAVAILABLE; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.mm b/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.mm index 9b74e2ddabc79..c6952c72b71ec 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.mm @@ -15,6 +15,8 @@ - (instancetype)initWithSemanticsObject:(SemanticsObject*)semanticsObject { self = [super initWithFrame:CGRectZero]; if (self) { _semanticsObject = semanticsObject; + _isDoingSystemScrolling = NO; + self.delegate = self; } return self; } @@ -105,4 +107,14 @@ - (NSInteger)accessibilityElementCount { return self.semanticsObject.children.count; } +- (void)scrollViewWillEndDragging:(UIScrollView*)scrollView + withVelocity:(CGPoint)velocity + targetContentOffset:(inout CGPoint*)targetContentOffset { + _isDoingSystemScrolling = YES; +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView { + _isDoingSystemScrolling = NO; +} + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 61419ed9cd307..9358431cf5a6f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -140,6 +140,9 @@ static UIKeyboardType ToUIKeyboardType(NSDictionary* type) { if ([inputType isEqualToString:@"TextInputType.visiblePassword"]) { return UIKeyboardTypeASCIICapable; } + if ([inputType isEqualToString:@"TextInputType.webSearch"]) { + return UIKeyboardTypeWebSearch; + } return UIKeyboardTypeDefault; } @@ -659,6 +662,7 @@ - (UITextRange*)lineEnclosingPosition:(UITextPosition*)position @implementation FlutterTextSelectionRect +// Synthesize properties declared readonly in UITextSelectionRect. @synthesize rect = _rect; @synthesize writingDirection = _writingDirection; @synthesize containsStart = _containsStart; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index cd75d64dc903f..be00f6e1e63d4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -237,7 +237,7 @@ - (void)testNoDanglingEnginePointer { @autoreleasepool { FlutterEngine* flutterEngine = OCMClassMock([FlutterEngine class]); weakFlutterEngine = flutterEngine; - NSAssert(weakFlutterEngine, @"flutter engine must not be nil"); + XCTAssertNotNil(weakFlutterEngine, @"flutter engine must not be nil"); FlutterTextInputPlugin* flutterTextInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:(id)flutterEngine]; weakFlutterTextInputPlugin = flutterTextInputPlugin; @@ -254,8 +254,8 @@ - (void)testNoDanglingEnginePointer { currentView = flutterTextInputPlugin.activeView; } - NSAssert(!weakFlutterEngine, @"flutter engine must be nil"); - NSAssert(currentView, @"current view must not be nil"); + XCTAssertNil(weakFlutterEngine, @"flutter engine must be nil"); + XCTAssertNotNil(currentView, @"current view must not be nil"); XCTAssertNil(weakFlutterTextInputPlugin); // Verify that the view can no longer access the deallocated engine/text input plugin @@ -307,6 +307,20 @@ - (void)testKeyboardType { XCTAssertEqual(inputView.keyboardType, UIKeyboardTypeURL); } +- (void)testKeyboardTypeWebSearch { + NSDictionary* config = self.mutableTemplateCopy; + [config setValue:@{@"name" : @"TextInputType.webSearch"} forKey:@"inputType"]; + [self setClientId:123 configuration:config]; + + // Find all the FlutterTextInputViews we created. + NSArray* inputFields = self.installedInputViews; + + FlutterTextInputView* inputView = inputFields[0]; + + // Verify keyboardType is set to the value specified in config. + XCTAssertEqual(inputView.keyboardType, UIKeyboardTypeWebSearch); +} + - (void)testVisiblePasswordUseAlphanumeric { NSDictionary* config = self.mutableTemplateCopy; [config setValue:@{@"name" : @"TextInputType.visiblePassword"} forKey:@"inputType"]; @@ -834,8 +848,10 @@ - (void)testReplaceTestLocalAdjustSelectionAndMarkedTextRange { - (void)testFlutterTextInputViewOnlyRespondsToInsertionPointColorBelowIOS17 { FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin]; - BOOL respondsToInsertionPointColor = - [inputView respondsToSelector:@selector(insertionPointColor)]; + // [UITextInputTraits insertionPointColor] is non-public API, so @selector(insertionPointColor) + // would generate a compile-time warning. + SEL insertionPointColor = NSSelectorFromString(@"insertionPointColor"); + BOOL respondsToInsertionPointColor = [inputView respondsToSelector:insertionPointColor]; if (@available(iOS 17, *)) { XCTAssertFalse(respondsToInsertionPointColor); } else { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm index d5d445ca62cee..261b421b17722 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm @@ -8,37 +8,24 @@ #import #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" FLUTTER_ASSERT_ARC -/// OCMock does not allow mocking both class and protocol. Use this to mock the methods used on -/// `UIView*` in the plugin. -@interface TextInputViewTest : NSObject - -@property(nonatomic, weak) id inputDelegate; -@property(nonatomic, readonly) UITextInputAssistantItem* inputAssistantItem; - -@end - -@implementation TextInputViewTest -@end - @interface FakeFlutterUndoManagerDelegate : NSObject @property(readonly) NSUInteger undoCount; @property(readonly) NSUInteger redoCount; @property(nonatomic, nullable) NSUndoManager* undoManager; +@property(nonatomic, nullable) UIView* activeTextInputView; - (instancetype)initWithUndoManager:(NSUndoManager*)undoManager - activeTextInputView:(TextInputViewTest*)activeTextInputView; + activeTextInputView:(UIView*)activeTextInputView; @end @implementation FakeFlutterUndoManagerDelegate -@synthesize undoManager = _undoManager; -@synthesize activeTextInputView = _activeTextInputView; - - (instancetype)initWithUndoManager:(NSUndoManager*)undoManager activeTextInputView:(UIView*)activeTextInputView { self = [super init]; @@ -62,7 +49,7 @@ - (void)handleUndoWithDirection:(FlutterUndoRedoDirection)direction { @interface FlutterUndoManagerPluginTest : XCTestCase @property(nonatomic) FakeFlutterUndoManagerDelegate* undoManagerDelegate; @property(nonatomic) FlutterUndoManagerPlugin* undoManagerPlugin; -@property(nonatomic) TextInputViewTest* activeTextInputView; +@property(nonatomic) UIView* activeTextInputView; @property(nonatomic) NSUndoManager* undoManager; @end @@ -72,7 +59,7 @@ - (void)setUp { [super setUp]; self.undoManager = OCMClassMock([NSUndoManager class]); - self.activeTextInputView = OCMClassMock([TextInputViewTest class]); + self.activeTextInputView = OCMClassMock([FlutterTextInputView class]); self.undoManagerDelegate = [[FakeFlutterUndoManagerDelegate alloc] initWithUndoManager:self.undoManager @@ -170,7 +157,7 @@ - (void)testDeallocRemovesAllUndoManagerActions { // Use a real undo manager. NSUndoManager* undoManager = [[NSUndoManager alloc] init]; @autoreleasepool { - id activeTextInputView = OCMClassMock([TextInputViewTest class]); + id activeTextInputView = OCMClassMock([FlutterTextInputView class]); FakeFlutterUndoManagerDelegate* undoManagerDelegate = [[FakeFlutterUndoManagerDelegate alloc] initWithUndoManager:undoManager diff --git a/shell/platform/darwin/ios/framework/Source/FlutterView.h b/shell/platform/darwin/ios/framework/Source/FlutterView.h index 53dd50e45d6a8..80619e44b777c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterView.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterView.h @@ -14,12 +14,11 @@ @protocol FlutterViewEngineDelegate @property(nonatomic, readonly) BOOL isUsingImpeller; +@property(nonatomic, readonly) FlutterPlatformViewsController* platformViewsController; - (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type asBase64Encoded:(BOOL)base64Encode; -- (std::shared_ptr&)platformViewsController; - /** * A callback that is called when iOS queries accessibility information of the Flutter view. * diff --git a/shell/platform/darwin/ios/framework/Source/FlutterView.mm b/shell/platform/darwin/ios/framework/Source/FlutterView.mm index 7e8e1108b13b5..cab7a67e15a48 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterView.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterView.mm @@ -5,6 +5,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" #include "flutter/fml/platform/darwin/cf_utils.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h" FLUTTER_ASSERT_ARC @@ -39,7 +40,7 @@ - (UIScreen*)screen { } - (MTLPixelFormat)pixelFormat { - if ([self.layer isKindOfClass:NSClassFromString(@"CAMetalLayer")]) { + if ([self.layer isKindOfClass:[CAMetalLayer class]]) { // It is a known Apple bug that CAMetalLayer incorrectly reports its supported // SDKs. It is, in fact, available since iOS 8. #pragma clang diagnostic push @@ -92,7 +93,7 @@ static void PrintWideGamutWarningOnce() { } - (void)layoutSubviews { - if ([self.layer isKindOfClass:NSClassFromString(@"CAMetalLayer")]) { + if ([self.layer isKindOfClass:[CAMetalLayer class]]) { // It is a known Apple bug that CAMetalLayer incorrectly reports its supported // SDKs. It is, in fact, available since iOS 8. #pragma clang diagnostic push @@ -106,9 +107,8 @@ - (void)layoutSubviews { layer.framebufferOnly = flutter::Settings::kSurfaceDataAccessible ? NO : YES; BOOL isWideGamutSupported = self.isWideGamutSupported; if (_isWideGamutEnabled && isWideGamutSupported) { - CGColorSpaceRef srgb = CGColorSpaceCreateWithName(kCGColorSpaceExtendedSRGB); + fml::CFRef srgb(CGColorSpaceCreateWithName(kCGColorSpaceExtendedSRGB)); layer.colorspace = srgb; - CFRelease(srgb); layer.pixelFormat = MTLPixelFormatBGRA10_XR; } else if (_isWideGamutEnabled && !isWideGamutSupported) { PrintWideGamutWarningOnce(); @@ -226,4 +226,30 @@ - (BOOL)isAccessibilityElement { return NO; } +// Enables keyboard-based navigation when the user turns on +// full keyboard access (FKA), using existing accessibility information. +// +// iOS does not provide any API for monitoring or querying whether FKA is on, +// but it does call isAccessibilityElement if FKA is on, +// so the isAccessibilityElement implementation above will be called +// when the view appears and the accessibility information will most likely +// be available by the time the user starts to interact with the app using FKA. +// +// See SemanticsObject+UIFocusSystem.mm for more details. +- (NSArray>*)focusItemsInRect:(CGRect)rect { + NSObject* rootAccessibilityElement = + [self.accessibilityElements count] > 0 ? self.accessibilityElements[0] : nil; + return [rootAccessibilityElement isKindOfClass:[SemanticsObjectContainer class]] + ? @[ [rootAccessibilityElement accessibilityElementAtIndex:0] ] + : nil; +} + +- (NSArray>*)preferredFocusEnvironments { + // Occasionally we add subviews to FlutterView (text fields for example). + // These views shouldn't be directly visible to the iOS focus engine, instead + // the focus engine should only interact with the designated focus items + // (SemanticsObjects). + return nil; +} + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index ebf48bdd1076a..2fd71c0874b16 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -13,7 +13,6 @@ #include "flutter/fml/memory/weak_ptr.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/platform/darwin/platform_version.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/runtime/ptrace_check.h" #include "flutter/shell/common/thread_host.h" #import "flutter/shell/platform/darwin/common/framework/Source/FlutterBinaryMessengerRelay.h" @@ -34,6 +33,8 @@ #import "flutter/shell/platform/embedder/embedder.h" #import "flutter/third_party/spring_animation/spring_animation.h" +FLUTTER_ASSERT_ARC + static constexpr int kMicrosecondsPerSecond = 1000 * 1000; static constexpr CGFloat kScrollViewContentSize = 2.0; @@ -64,10 +65,32 @@ @interface FlutterViewController () * ongoingTouches; +// This scroll view is a workaround to accommodate iOS 13 and higher. There isn't a way to get +// touches on the status bar to trigger scrolling to the top of a scroll view. We place a +// UIScrollView with height zero and a content offset so we can get those events. See also: +// https://github.com/flutter/flutter/issues/35050 +@property(nonatomic, strong) UIScrollView* scrollView; +@property(nonatomic, strong) UIView* keyboardAnimationView; +@property(nonatomic, strong) SpringAnimation* keyboardSpringAnimation; + /** * Whether we should ignore viewport metrics updates during rotation transition. */ @@ -78,82 +101,72 @@ @interface FlutterViewController () > _weakFactory; - fml::scoped_nsobject _engine; - - // We keep a separate reference to this and create it ahead of time because we want to be able to - // set up a shell along with its platform view before the view has to appear. - fml::scoped_nsobject _flutterView; - fml::scoped_nsobject _splashScreenView; - fml::ScopedBlock _flutterViewRenderedCallback; - UIInterfaceOrientationMask _orientationPreferences; - UIStatusBarStyle _statusBarStyle; flutter::ViewportMetrics _viewportMetrics; - BOOL _initialized; - BOOL _viewOpaque; - BOOL _engineNeedsLaunch; - fml::scoped_nsobject> _ongoingTouches; - // This scroll view is a workaround to accommodate iOS 13 and higher. There isn't a way to get - // touches on the status bar to trigger scrolling to the top of a scroll view. We place a - // UIScrollView with height zero and a content offset so we can get those events. See also: - // https://github.com/flutter/flutter/issues/35050 - fml::scoped_nsobject _scrollView; - fml::scoped_nsobject _keyboardAnimationView; - fml::scoped_nsobject _keyboardSpringAnimation; MouseState _mouseState; - // Timestamp after which a scroll inertia cancel event should be inferred. - NSTimeInterval _scrollInertiaEventStartline; - // When an iOS app is running in emulation on an Apple Silicon Mac, trackpad input goes through - // a translation layer, and events are not received with precise deltas. Due to this, we can't - // rely on checking for a stationary trackpad event. Fortunately, AppKit will send an event of - // type UIEventTypeScroll following a scroll when inertia should stop. This field is needed to - // estimate if such an event represents the natural end of scrolling inertia or a user-initiated - // cancellation. - NSTimeInterval _scrollInertiaEventAppKitDeadline; } +// Synthesize properties with an overridden getter/setter. +@synthesize viewOpaque = _viewOpaque; @synthesize displayingFlutterUI = _displayingFlutterUI; -@synthesize prefersStatusBarHidden = _flutterPrefersStatusBarHidden; + +// TODO(dkwingsmt): https://github.com/flutter/flutter/issues/138168 +// No backing ivar is currently required; when multiple views are supported, we'll need to +// synthesize the ivar and store the view identifier. @dynamic viewIdentifier; #pragma mark - Manage and override all designated initializers @@ -173,14 +186,15 @@ - (instancetype)initWithEngine:(FlutterEngine*)engine "FlutterViewController at a time. Set FlutterEngine.viewController " "to nil before attaching it to another FlutterViewController."; } - _engine.reset([engine retain]); + _engine = engine; _engineNeedsLaunch = NO; - _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine - opaque:self.isViewOpaque - enableWideGamut:engine.project.isWideGamutEnabled]); - _weakFactory = std::make_unique>(self); - _ongoingTouches.reset([[NSMutableSet alloc] init]); + _flutterView = [[FlutterView alloc] initWithDelegate:_engine + opaque:self.isViewOpaque + enableWideGamut:engine.project.isWideGamutEnabled]; + _ongoingTouches = [[NSMutableSet alloc] init]; + // TODO(cbracken): https://github.com/flutter/flutter/issues/157140 + // Eliminate method calls in initializers and dealloc. [self performCommonViewControllerInitialization]; [engine setViewController:self]; } @@ -193,6 +207,8 @@ - (instancetype)initWithProject:(FlutterDartProject*)project bundle:(NSBundle*)nibBundle { self = [super initWithNibName:nibName bundle:nibBundle]; if (self) { + // TODO(cbracken): https://github.com/flutter/flutter/issues/157140 + // Eliminate method calls in initializers and dealloc. [self sharedSetupWithProject:project initialRoute:nil]; } @@ -205,6 +221,8 @@ - (instancetype)initWithProject:(FlutterDartProject*)project bundle:(NSBundle*)nibBundle { self = [super initWithNibName:nibName bundle:nibBundle]; if (self) { + // TODO(cbracken): https://github.com/flutter/flutter/issues/157140 + // Eliminate method calls in initializers and dealloc. [self sharedSetupWithProject:project initialRoute:initialRoute]; } @@ -222,7 +240,7 @@ - (instancetype)initWithCoder:(NSCoder*)aDecoder { - (void)awakeFromNib { [super awakeFromNib]; - if (!_engine) { + if (!self.engine) { [self sharedSetupWithProject:nil initialRoute:nil]; } } @@ -236,28 +254,28 @@ - (void)sharedSetupWithProject:(nullable FlutterDartProject*)project // Need the project to get settings for the view. Initializing it here means // the Engine class won't initialize it later. if (!project) { - project = [[[FlutterDartProject alloc] init] autorelease]; + project = [[FlutterDartProject alloc] init]; } FlutterView.forceSoftwareRendering = project.settings.enable_software_rendering; - _weakFactory = std::make_unique>(self); - auto engine = fml::scoped_nsobject{[[FlutterEngine alloc] - initWithName:@"io.flutter" - project:project - allowHeadlessExecution:self.engineAllowHeadlessExecution - restorationEnabled:[self restorationIdentifier] != nil]}; - + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"io.flutter" + project:project + allowHeadlessExecution:self.engineAllowHeadlessExecution + restorationEnabled:self.restorationIdentifier != nil]; if (!engine) { return; } _viewOpaque = YES; _engine = engine; - _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine - opaque:self.isViewOpaque - enableWideGamut:project.isWideGamutEnabled]); - [_engine.get() createShell:nil libraryURI:nil initialRoute:initialRoute]; + _flutterView = [[FlutterView alloc] initWithDelegate:_engine + opaque:_viewOpaque + enableWideGamut:project.isWideGamutEnabled]; + [_engine createShell:nil libraryURI:nil initialRoute:initialRoute]; _engineNeedsLaunch = YES; - _ongoingTouches.reset([[NSMutableSet alloc] init]); + _ongoingTouches = [[NSMutableSet alloc] init]; + + // TODO(cbracken): https://github.com/flutter/flutter/issues/157140 + // Eliminate method calls in initializers and dealloc. [self loadDefaultSplashScreenView]; [self performCommonViewControllerInitialization]; } @@ -268,9 +286,9 @@ - (BOOL)isViewOpaque { - (void)setViewOpaque:(BOOL)value { _viewOpaque = value; - if (_flutterView.get().layer.opaque != value) { - _flutterView.get().layer.opaque = value; - [_flutterView.get().layer setNeedsLayout]; + if (self.flutterView.layer.opaque != value) { + self.flutterView.layer.opaque = value; + [self.flutterView.layer setNeedsLayout]; } } @@ -282,21 +300,14 @@ - (void)performCommonViewControllerInitialization { } _initialized = YES; - _orientationPreferences = UIInterfaceOrientationMaskAll; _statusBarStyle = UIStatusBarStyleDefault; + // TODO(cbracken): https://github.com/flutter/flutter/issues/157140 + // Eliminate method calls in initializers and dealloc. [self setUpNotificationCenterObservers]; } -- (FlutterEngine*)engine { - return _engine.get(); -} - -- (fml::WeakNSObject)getWeakNSObject { - return _weakFactory->GetWeakNSObject(); -} - - (void)setUpNotificationCenterObservers { NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; [center addObserver:self @@ -447,15 +458,15 @@ - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center { } - (void)setInitialRoute:(NSString*)route { - [[_engine.get() navigationChannel] invokeMethod:@"setInitialRoute" arguments:route]; + [self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route]; } - (void)popRoute { - [[_engine.get() navigationChannel] invokeMethod:@"popRoute" arguments:nil]; + [self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil]; } - (void)pushRoute:(NSString*)route { - [[_engine.get() navigationChannel] invokeMethod:@"pushRoute" arguments:route]; + [self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route]; } #pragma mark - Loading the view @@ -465,7 +476,7 @@ - (void)pushRoute:(NSString*)route { return existing_view; } - auto placeholder = [[[UIView alloc] init] autorelease]; + auto placeholder = [[UIView alloc] init]; placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; if (@available(iOS 13.0, *)) { @@ -479,7 +490,7 @@ - (void)pushRoute:(NSString*)route { // Otherwise, a spurious warning will be shown in cases where an engine cannot be initialized for // other reasons. if (flutter::GetTracingResult() == flutter::TracingResult::kDisabled) { - auto messageLabel = [[[UILabel alloc] init] autorelease]; + auto messageLabel = [[UILabel alloc] init]; messageLabel.numberOfLines = 0u; messageLabel.textAlignment = NSTextAlignmentCenter; messageLabel.autoresizingMask = @@ -495,11 +506,13 @@ - (void)pushRoute:(NSString*)route { } - (void)loadView { - self.view = GetViewOrPlaceholder(_flutterView.get()); + self.view = GetViewOrPlaceholder(self.flutterView); self.view.multipleTouchEnabled = YES; self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self installSplashScreenViewIfNecessary]; + + // Create and set up the scroll view. UIScrollView* scrollView = [[UIScrollView alloc] init]; scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth; // The color shouldn't matter since it is offscreen. @@ -509,8 +522,9 @@ - (void)loadView { scrollView.contentSize = CGSizeMake(kScrollViewContentSize, kScrollViewContentSize); // This is an arbitrary offset that is not CGPointZero. scrollView.contentOffset = CGPointMake(kScrollViewContentSize, kScrollViewContentSize); + [self.view addSubview:scrollView]; - _scrollView.reset(scrollView); + self.scrollView = scrollView; } - (flutter::PointerData)generatePointerDataForFake { @@ -539,14 +553,14 @@ static void SendFakeTouchEvent(UIScreen* screen, } - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView { - if (!_engine) { + if (!self.engine) { return NO; } CGPoint statusBarPoint = CGPointZero; - UIScreen* screen = [self flutterScreenIfViewLoaded]; + UIScreen* screen = self.flutterScreenIfViewLoaded; if (screen) { - SendFakeTouchEvent(screen, _engine.get(), statusBarPoint, flutter::PointerData::Change::kDown); - SendFakeTouchEvent(screen, _engine.get(), statusBarPoint, flutter::PointerData::Change::kUp); + SendFakeTouchEvent(screen, self.engine, statusBarPoint, flutter::PointerData::Change::kDown); + SendFakeTouchEvent(screen, self.engine, statusBarPoint, flutter::PointerData::Change::kUp); } return NO; } @@ -556,9 +570,9 @@ - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView { - (void)installSplashScreenViewIfNecessary { // Show the launch screen view again on top of the FlutterView if available. // This launch screen view will be removed once the first Flutter frame is rendered. - if (_splashScreenView && (self.isBeingPresented || self.isMovingToParentViewController)) { - [_splashScreenView.get() removeFromSuperview]; - _splashScreenView.reset(); + if (self.splashScreenView && (self.isBeingPresented || self.isMovingToParentViewController)) { + [self.splashScreenView removeFromSuperview]; + self.splashScreenView = nil; return; } @@ -590,60 +604,58 @@ - (void)setDisplayingFlutterUI:(BOOL)displayingFlutterUI { - (void)callViewRenderedCallback { self.displayingFlutterUI = YES; - if (_flutterViewRenderedCallback != nil) { - _flutterViewRenderedCallback.get()(); - _flutterViewRenderedCallback.reset(); + if (self.flutterViewRenderedCallback) { + self.flutterViewRenderedCallback(); + self.flutterViewRenderedCallback = nil; } } -- (void)removeSplashScreenView:(dispatch_block_t _Nullable)onComplete { - NSAssert(_splashScreenView, @"The splash screen view must not be null"); - UIView* splashScreen = [_splashScreenView.get() retain]; - _splashScreenView.reset(); +- (void)removeSplashScreenWithCompletion:(dispatch_block_t _Nullable)onComplete { + NSAssert(self.splashScreenView, @"The splash screen view must not be nil"); + UIView* splashScreen = self.splashScreenView; + // setSplashScreenView calls this method. Assign directly to ivar to avoid an infinite loop. + _splashScreenView = nil; [UIView animateWithDuration:0.2 animations:^{ splashScreen.alpha = 0; } completion:^(BOOL finished) { [splashScreen removeFromSuperview]; - [splashScreen release]; if (onComplete) { onComplete(); } }]; } +- (void)onFirstFrameRendered { + if (self.splashScreenView) { + __weak FlutterViewController* weakSelf = self; + [self removeSplashScreenWithCompletion:^{ + [weakSelf callViewRenderedCallback]; + }]; + } else { + [self callViewRenderedCallback]; + } +} + - (void)installFirstFrameCallback { - if (!_engine) { + if (!self.engine) { return; } - fml::WeakPtr weakPlatformView = [_engine.get() platformView]; + fml::WeakPtr weakPlatformView = self.engine.platformView; if (!weakPlatformView) { return; } // Start on the platform thread. - weakPlatformView->SetNextFrameCallback([weakSelf = [self getWeakNSObject], - platformTaskRunner = [_engine.get() platformTaskRunner], - rasterTaskRunner = [_engine.get() rasterTaskRunner]]() { + __weak FlutterViewController* weakSelf = self; + weakPlatformView->SetNextFrameCallback([weakSelf, + platformTaskRunner = self.engine.platformTaskRunner, + rasterTaskRunner = self.engine.rasterTaskRunner]() { FML_DCHECK(rasterTaskRunner->RunsTasksOnCurrentThread()); // Get callback on raster thread and jump back to platform thread. - platformTaskRunner->PostTask([weakSelf]() { - if (weakSelf) { - fml::scoped_nsobject flutterViewController( - [(FlutterViewController*)weakSelf.get() retain]); - if (flutterViewController) { - if (flutterViewController.get()->_splashScreenView) { - [flutterViewController removeSplashScreenView:^{ - [flutterViewController callViewRenderedCallback]; - }]; - } else { - [flutterViewController callViewRenderedCallback]; - } - } - } - }); + platformTaskRunner->PostTask([weakSelf]() { [weakSelf onFirstFrameRendered]; }); }); } @@ -655,21 +667,6 @@ - (int64_t)viewIdentifier { return flutter::kFlutterImplicitViewId; } -- (UIView*)splashScreenView { - if (!_splashScreenView) { - return nil; - } - return _splashScreenView.get(); -} - -- (UIView*)keyboardAnimationView { - return _keyboardAnimationView.get(); -} - -- (SpringAnimation*)keyboardSpringAnimation { - return _keyboardSpringAnimation.get(); -} - - (BOOL)loadDefaultSplashScreenView { NSString* launchscreenName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"]; @@ -716,27 +713,31 @@ - (UIView*)splashScreenFromXib:(NSString*)name { } - (void)setSplashScreenView:(UIView*)view { + if (view == _splashScreenView) { + return; + } + + // Special case: user wants to remove the splash screen view. if (!view) { - // Special case: user wants to remove the splash screen view. if (_splashScreenView) { - [self removeSplashScreenView:nil]; + [self removeSplashScreenWithCompletion:nil]; } return; } - _splashScreenView.reset([view retain]); - _splashScreenView.get().autoresizingMask = + _splashScreenView = view; + _splashScreenView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; } - (void)setFlutterViewDidRenderCallback:(void (^)(void))callback { - _flutterViewRenderedCallback.reset(callback, fml::scoped_policy::OwnershipPolicy::kRetain); + _flutterViewRenderedCallback = callback; } #pragma mark - Surface creation and teardown updates - (void)surfaceUpdated:(BOOL)appeared { - if (!_engine) { + if (!self.engine) { return; } @@ -744,14 +745,14 @@ - (void)surfaceUpdated:(BOOL)appeared { // thread. if (appeared) { [self installFirstFrameCallback]; - [_engine.get() platformViewsController]->SetFlutterView(_flutterView.get()); - [_engine.get() platformViewsController]->SetFlutterViewController(self); - [_engine.get() iosPlatformView]->NotifyCreated(); + self.platformViewsController.flutterView = self.flutterView; + self.platformViewsController.flutterViewController = self; + [self.engine iosPlatformView]->NotifyCreated(); } else { self.displayingFlutterUI = NO; - [_engine.get() iosPlatformView]->NotifyDestroyed(); - [_engine.get() platformViewsController]->SetFlutterView(nullptr); - [_engine.get() platformViewsController]->SetFlutterViewController(nullptr); + [self.engine iosPlatformView]->NotifyDestroyed(); + self.platformViewsController.flutterView = nil; + self.platformViewsController.flutterViewController = nil; } } @@ -760,12 +761,12 @@ - (void)surfaceUpdated:(BOOL)appeared { - (void)viewDidLoad { TRACE_EVENT0("flutter", "viewDidLoad"); - if (_engine && _engineNeedsLaunch) { - [_engine.get() launchEngine:nil libraryURI:nil entrypointArgs:nil]; - [_engine.get() setViewController:self]; - _engineNeedsLaunch = NO; - } else if ([_engine.get() viewController] == self) { - [_engine.get() attachView]; + if (self.engine && self.engineNeedsLaunch) { + [self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil]; + [self.engine setViewController:self]; + self.engineNeedsLaunch = NO; + } else if (self.engine.viewController == self) { + [self.engine attachView]; } // Register internal plugins. @@ -778,7 +779,7 @@ - (void)viewDidLoad { _hoverGestureRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)]; _hoverGestureRecognizer.delegate = self; - [_flutterView.get() addGestureRecognizer:_hoverGestureRecognizer]; + [self.flutterView addGestureRecognizer:_hoverGestureRecognizer]; _discreteScrollingPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)]; @@ -789,47 +790,45 @@ - (void)viewDidLoad { // than touch events, so they will still be received. _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[]; _discreteScrollingPanGestureRecognizer.delegate = self; - [_flutterView.get() addGestureRecognizer:_discreteScrollingPanGestureRecognizer]; + [self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer]; _continuousScrollingPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(continuousScrollEvent:)]; _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous; _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[]; _continuousScrollingPanGestureRecognizer.delegate = self; - [_flutterView.get() addGestureRecognizer:_continuousScrollingPanGestureRecognizer]; + [self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer]; _pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)]; _pinchGestureRecognizer.allowedTouchTypes = @[]; _pinchGestureRecognizer.delegate = self; - [_flutterView.get() addGestureRecognizer:_pinchGestureRecognizer]; + [self.flutterView addGestureRecognizer:_pinchGestureRecognizer]; _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init]; _rotationGestureRecognizer.allowedTouchTypes = @[]; _rotationGestureRecognizer.delegate = self; - [_flutterView.get() addGestureRecognizer:_rotationGestureRecognizer]; + [self.flutterView addGestureRecognizer:_rotationGestureRecognizer]; } [super viewDidLoad]; } - (void)addInternalPlugins { - self.keyboardManager = [[[FlutterKeyboardManager alloc] init] autorelease]; - fml::WeakNSObject weakSelf = [self getWeakNSObject]; + self.keyboardManager = [[FlutterKeyboardManager alloc] init]; + __weak FlutterViewController* weakSelf = self; FlutterSendKeyEvent sendEvent = ^(const FlutterKeyEvent& event, FlutterKeyEventCallback callback, void* userData) { - if (weakSelf) { - [weakSelf.get()->_engine.get() sendKeyEvent:event callback:callback userData:userData]; - } + [weakSelf.engine sendKeyEvent:event callback:callback userData:userData]; }; - [self.keyboardManager addPrimaryResponder:[[[FlutterEmbedderKeyResponder alloc] - initWithSendEvent:sendEvent] autorelease]]; - FlutterChannelKeyResponder* responder = [[[FlutterChannelKeyResponder alloc] - initWithChannel:self.engine.keyEventChannel] autorelease]; + [self.keyboardManager + addPrimaryResponder:[[FlutterEmbedderKeyResponder alloc] initWithSendEvent:sendEvent]]; + FlutterChannelKeyResponder* responder = + [[FlutterChannelKeyResponder alloc] initWithChannel:self.engine.keyEventChannel]; [self.keyboardManager addPrimaryResponder:responder]; FlutterTextInputPlugin* textInputPlugin = self.engine.textInputPlugin; if (textInputPlugin != nil) { [self.keyboardManager addSecondaryResponder:textInputPlugin]; } - if ([_engine.get() viewController] == self) { + if (self.engine.viewController == self) { [textInputPlugin setUpIndirectScribbleInteraction:self]; } } @@ -840,7 +839,7 @@ - (void)removeInternalPlugins { - (void)viewWillAppear:(BOOL)animated { TRACE_EVENT0("flutter", "viewWillAppear"); - if ([_engine.get() viewController] == self) { + if (self.engine.viewController == self) { // Send platform settings to Flutter, e.g., platform brightness. [self onUserSettingsChanged:nil]; @@ -849,8 +848,8 @@ - (void)viewWillAppear:(BOOL)animated { if (_viewportMetrics.physical_width) { [self surfaceUpdated:YES]; } - [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"]; - [[_engine.get() restorationPlugin] markRestorationComplete]; + [self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"]; + [self.engine.restorationPlugin markRestorationComplete]; } [super viewWillAppear:animated]; @@ -858,7 +857,7 @@ - (void)viewWillAppear:(BOOL)animated { - (void)viewDidAppear:(BOOL)animated { TRACE_EVENT0("flutter", "viewDidAppear"); - if ([_engine.get() viewController] == self) { + if (self.engine.viewController == self) { [self onUserSettingsChanged:nil]; [self onAccessibilityStatusChanged:nil]; BOOL stateIsActive = YES; @@ -871,7 +870,7 @@ - (void)viewDidAppear:(BOOL)animated { stateIsActive = UIApplication.sharedApplication.applicationState == UIApplicationStateActive; #endif if (stateIsActive) { - [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.resumed"]; + [self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"]; } } [super viewDidAppear:animated]; @@ -879,21 +878,21 @@ - (void)viewDidAppear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated { TRACE_EVENT0("flutter", "viewWillDisappear"); - if ([_engine.get() viewController] == self) { - [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"]; + if (self.engine.viewController == self) { + [self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"]; } [super viewWillDisappear:animated]; } - (void)viewDidDisappear:(BOOL)animated { TRACE_EVENT0("flutter", "viewDidDisappear"); - if ([_engine.get() viewController] == self) { + if (self.engine.viewController == self) { [self invalidateKeyboardAnimationVSyncClient]; [self ensureViewportMetricsIsCorrect]; [self surfaceUpdated:NO]; - [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.paused"]; + [self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"]; [self flushOngoingTouches]; - [_engine.get() notifyLowMemory]; + [self.engine notifyLowMemory]; } [super viewDidDisappear:animated]; @@ -917,24 +916,30 @@ - (void)viewWillTransitionToSize:(CGSize)size return; } + __weak FlutterViewController* weakSelf = self; _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, static_cast(transitionDuration / 2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + FlutterViewController* strongSelf = weakSelf; + if (!strongSelf) { + return; + } + // `viewWillTransitionToSize` is only called after the previous rotation is // complete. So there won't be race condition for this flag. - _shouldIgnoreViewportMetricsUpdatesDuringRotation = NO; - [self updateViewportMetricsIfNeeded]; + strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO; + [strongSelf updateViewportMetricsIfNeeded]; }); } - (void)flushOngoingTouches { - if (_engine && _ongoingTouches.get().count > 0) { - auto packet = std::make_unique(_ongoingTouches.get().count); + if (self.engine && self.ongoingTouches.count > 0) { + auto packet = std::make_unique(self.ongoingTouches.count); size_t pointer_index = 0; // If the view controller is going away, we want to flush cancel all the ongoing // touches to the framework so nothing gets orphaned. - for (NSNumber* device in _ongoingTouches.get()) { + for (NSNumber* device in self.ongoingTouches) { // Create fake PointerData to balance out each previously started one for the framework. flutter::PointerData pointer_data = [self generatePointerDataForFake]; @@ -954,8 +959,8 @@ - (void)flushOngoingTouches { packet->SetPointerData(pointer_index++, pointer_data); } - [_ongoingTouches removeAllObjects]; - [_engine.get() dispatchPointerDataPacket:std::move(packet)]; + [self.ongoingTouches removeAllObjects]; + [self.engine dispatchPointerDataPacket:std::move(packet)]; } } @@ -967,27 +972,22 @@ - (void)deregisterNotifications { } - (void)dealloc { - // It will be destroyed and invalidate its weak pointers - // before any other members are destroyed. - _weakFactory.reset(); - + // TODO(cbracken): https://github.com/flutter/flutter/issues/157140 + // Eliminate method calls in initializers and dealloc. [self removeInternalPlugins]; [self deregisterNotifications]; [self invalidateKeyboardAnimationVSyncClient]; [self invalidateTouchRateCorrectionVSyncClient]; - _scrollView.get().delegate = nil; + + // TODO(cbracken): https://github.com/flutter/flutter/issues/156222 + // Ensure all delegates are weak and remove this. + _scrollView.delegate = nil; _hoverGestureRecognizer.delegate = nil; - [_hoverGestureRecognizer release]; _discreteScrollingPanGestureRecognizer.delegate = nil; - [_discreteScrollingPanGestureRecognizer release]; _continuousScrollingPanGestureRecognizer.delegate = nil; - [_continuousScrollingPanGestureRecognizer release]; _pinchGestureRecognizer.delegate = nil; - [_pinchGestureRecognizer release]; _rotationGestureRecognizer.delegate = nil; - [_rotationGestureRecognizer release]; - [super dealloc]; } #pragma mark - Application lifecycle notifications @@ -1081,7 +1081,7 @@ - (void)goToApplicationLifecycle:(nonnull NSString*)state { // Accessing self.view will create the view. Instead use viewIfLoaded // to check whether the view is attached to window. if (self.viewIfLoaded.window) { - [[_engine.get() lifecycleChannel] sendMessage:state]; + [self.engine.lifecycleChannel sendMessage:state]; } } @@ -1133,7 +1133,7 @@ - (void)goToApplicationLifecycle:(nonnull NSString*)state { - (void)dispatchTouches:(NSSet*)touches pointerDataChangeOverride:(flutter::PointerData::Change*)overridden_change event:(UIEvent*)event { - if (!_engine) { + if (!self.engine) { return; } @@ -1167,7 +1167,7 @@ - (void)dispatchTouches:(NSSet*)touches // Activate or pause the correction of delivery frame rate of touch events. [self triggerTouchRateCorrectionIfNeeded:touches]; - const CGFloat scale = [self flutterScreenIfViewLoaded].scale; + const CGFloat scale = self.flutterScreenIfViewLoaded.scale; auto packet = std::make_unique(touches.count + touches_to_remove_count); @@ -1207,11 +1207,11 @@ - (void)dispatchTouches:(NSSet*)touches // if the view controller goes away. switch (pointer_data.change) { case flutter::PointerData::Change::kDown: - [_ongoingTouches addObject:deviceKey]; + [self.ongoingTouches addObject:deviceKey]; break; case flutter::PointerData::Change::kCancel: case flutter::PointerData::Change::kUp: - [_ongoingTouches removeObject:deviceKey]; + [self.ongoingTouches removeObject:deviceKey]; break; case flutter::PointerData::Change::kHover: case flutter::PointerData::Change::kMove: @@ -1290,7 +1290,7 @@ - (void)dispatchTouches:(NSSet*)touches } } - [_engine.get() dispatchPointerDataPacket:std::move(packet)]; + [self.engine dispatchPointerDataPacket:std::move(packet)]; } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { @@ -1331,7 +1331,7 @@ - (void)createTouchRateCorrectionVSyncClientIfNeeded { return; } - flutter::Shell& shell = [_engine.get() shell]; + flutter::Shell& shell = self.engine.shell; auto callback = [](std::unique_ptr recorder) { // Do nothing in this block. Just trigger system to callback touch events with correct rate. }; @@ -1358,7 +1358,7 @@ - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches { } } - if (isUserInteracting && [_engine.get() viewController] == self) { + if (isUserInteracting && self.engine.viewController == self) { [_touchRateCorrectionVSyncClient await]; } else { [_touchRateCorrectionVSyncClient pause]; @@ -1367,7 +1367,6 @@ - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches { - (void)invalidateTouchRateCorrectionVSyncClient { [_touchRateCorrectionVSyncClient invalidate]; - [_touchRateCorrectionVSyncClient release]; _touchRateCorrectionVSyncClient = nil; } @@ -1377,18 +1376,18 @@ - (void)updateViewportMetricsIfNeeded { if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) { return; } - if ([_engine.get() viewController] == self) { - [_engine.get() updateViewportMetrics:_viewportMetrics]; + if (self.engine.viewController == self) { + [self.engine updateViewportMetrics:_viewportMetrics]; } } - (void)viewDidLayoutSubviews { CGRect viewBounds = self.view.bounds; - CGFloat scale = [self flutterScreenIfViewLoaded].scale; + CGFloat scale = self.flutterScreenIfViewLoaded.scale; // Purposefully place this not visible. - _scrollView.get().frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0); - _scrollView.get().contentOffset = CGPointMake(kScrollViewContentSize, kScrollViewContentSize); + self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0); + self.scrollView.contentOffset = CGPointMake(kScrollViewContentSize, kScrollViewContentSize); // First time since creation that the dimensions of its view is known. bool firstViewBoundsUpdate = !_viewportMetrics.physical_width; @@ -1413,10 +1412,10 @@ - (void)viewDidLayoutSubviews { // This must run after updateViewportMetrics so that the surface creation tasks are queued after // the viewport metrics update tasks. - if (firstViewBoundsUpdate && applicationOrSceneIsActive && _engine) { + if (firstViewBoundsUpdate && applicationOrSceneIsActive && self.engine) { [self surfaceUpdated:YES]; - flutter::Shell& shell = [_engine.get() shell]; + flutter::Shell& shell = self.engine.shell; fml::TimeDelta waitTime = #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG fml::TimeDelta::FromMilliseconds(200); @@ -1439,7 +1438,7 @@ - (void)viewSafeAreaInsetsDidChange { // Set _viewportMetrics physical size. - (void)setViewportMetricsSize { - UIScreen* screen = [self flutterScreenIfViewLoaded]; + UIScreen* screen = self.flutterScreenIfViewLoaded; if (!screen) { return; } @@ -1453,7 +1452,7 @@ - (void)setViewportMetricsSize { // // Viewport paddings represent the iOS safe area insets. - (void)setViewportMetricsPaddings { - UIScreen* screen = [self flutterScreenIfViewLoaded]; + UIScreen* screen = self.flutterScreenIfViewLoaded; if (!screen) { return; } @@ -1525,8 +1524,8 @@ - (void)handleKeyboardNotification:(NSNotification*)notification { if (!keyboardAnimationIsCompounding) { [self startKeyBoardAnimation:duration]; - } else if ([self keyboardSpringAnimation]) { - [self keyboardSpringAnimation].toValue = self.targetViewInsetBottom; + } else if (self.keyboardSpringAnimation) { + self.keyboardSpringAnimation.toValue = self.targetViewInsetBottom; } } @@ -1587,11 +1586,7 @@ - (BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification { if (isLocal && ![isLocal boolValue]) { return YES; } - // Engine’s viewController is not current viewController. - if ([_engine.get() viewController] != self) { - return YES; - } - return NO; + return self.engine.viewController != self; } - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification { @@ -1619,7 +1614,7 @@ - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification return FlutterKeyboardModeHidden; } - CGRect screenRect = [self flutterScreenIfViewLoaded].bounds; + CGRect screenRect = self.flutterScreenIfViewLoaded.bounds; CGRect adjustedKeyboardFrame = keyboardFrame; adjustedKeyboardFrame.origin.y += [self calculateMultitaskingAdjustment:screenRect keyboardFrame:keyboardFrame]; @@ -1659,7 +1654,7 @@ - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGR } CGRect viewRectRelativeToScreen = [self.viewIfLoaded convertRect:self.viewIfLoaded.frame - toCoordinateSpace:[self flutterScreenIfViewLoaded].coordinateSpace]; + toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace]; CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen); CGFloat offset = screenHeight - viewBottom; if (offset > 0) { @@ -1675,14 +1670,14 @@ - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger) // Calculate how much of the keyboard intersects with the view. CGRect viewRectRelativeToScreen = [self.viewIfLoaded convertRect:self.viewIfLoaded.frame - toCoordinateSpace:[self flutterScreenIfViewLoaded].coordinateSpace]; + toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace]; CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen); CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection); // The keyboard is treated as an inset since we want to effectively reduce the window size by // the keyboard height. The Dart side will compute a value accounting for the keyboard-consuming // bottom padding. - CGFloat scale = [self flutterScreenIfViewLoaded].scale; + CGFloat scale = self.flutterScreenIfViewLoaded.scale; return portionOfKeyboardInView * scale; } return 0; @@ -1696,21 +1691,21 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { // When this method is called for the first time, // initialize the keyboardAnimationView to get animation interpolation during animation. - if ([self keyboardAnimationView] == nil) { + if (!self.keyboardAnimationView) { UIView* keyboardAnimationView = [[UIView alloc] init]; - [keyboardAnimationView setHidden:YES]; - _keyboardAnimationView.reset(keyboardAnimationView); + keyboardAnimationView.hidden = YES; + self.keyboardAnimationView = keyboardAnimationView; } - if ([self keyboardAnimationView].superview == nil) { - [self.view addSubview:[self keyboardAnimationView]]; + if (!self.keyboardAnimationView.superview) { + [self.view addSubview:self.keyboardAnimationView]; } // Remove running animation when start another animation. - [[self keyboardAnimationView].layer removeAllAnimations]; + [self.keyboardAnimationView.layer removeAllAnimations]; // Set animation begin value and DisplayLink tracking values. - [self keyboardAnimationView].frame = + self.keyboardAnimationView.frame = CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0); self.keyboardAnimationStartTime = fml::TimePoint().Now(); self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom; @@ -1718,74 +1713,40 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { // Invalidate old vsync client if old animation is not completed. [self invalidateKeyboardAnimationVSyncClient]; - fml::WeakNSObject weakSelf = [self getWeakNSObject]; - FlutterKeyboardAnimationCallback keyboardAnimationCallback = ^( - fml::TimePoint keyboardAnimationTargetTime) { - if (!weakSelf) { - return; - } - fml::scoped_nsobject flutterViewController( - [(FlutterViewController*)weakSelf.get() retain]); - if (!flutterViewController) { - return; - } - - // If the view controller's view is not loaded, bail out. - if (!flutterViewController.get().isViewLoaded) { - return; - } - // If the view for tracking keyboard animation is nil, means it is not - // created, bail out. - if ([flutterViewController keyboardAnimationView] == nil) { - return; - } - // If keyboardAnimationVSyncClient is nil, means the animation ends. - // And should bail out. - if (flutterViewController.get().keyboardAnimationVSyncClient == nil) { - return; - } - - if ([flutterViewController keyboardAnimationView].superview == nil) { - // Ensure the keyboardAnimationView is in view hierarchy when animation running. - [flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]]; - } - - if ([flutterViewController keyboardSpringAnimation] == nil) { - if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) { - flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = - flutterViewController.get() - .keyboardAnimationView.layer.presentationLayer.frame.origin.y; - [flutterViewController updateViewportMetricsIfNeeded]; - } - } else { - fml::TimeDelta timeElapsed = - keyboardAnimationTargetTime - flutterViewController.get().keyboardAnimationStartTime; - flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = - [[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()]; - [flutterViewController updateViewportMetricsIfNeeded]; - } - }; - [self setUpKeyboardAnimationVsyncClient:keyboardAnimationCallback]; + __weak FlutterViewController* weakSelf = self; + [self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) { + [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime]; + }]; VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient; [UIView animateWithDuration:duration animations:^{ + FlutterViewController* strongSelf = weakSelf; + if (!strongSelf) { + return; + } + // Set end value. - [self keyboardAnimationView].frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0); + strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0); // Setup keyboard animation interpolation. CAAnimation* keyboardAnimation = - [[self keyboardAnimationView].layer animationForKey:@"position"]; - [self setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation]; + [strongSelf.keyboardAnimationView.layer animationForKey:@"position"]; + [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation]; } completion:^(BOOL finished) { if (_keyboardAnimationVSyncClient == currentVsyncClient) { + FlutterViewController* strongSelf = weakSelf; + if (!strongSelf) { + return; + } + // Indicates the vsync client captured by this block is the original one, which also // indicates the animation has not been interrupted from its beginning. Moreover, // indicates the animation is over and there is no more to execute. - [self invalidateKeyboardAnimationVSyncClient]; - [self removeKeyboardAnimationView]; - [self ensureViewportMetricsIsCorrect]; + [strongSelf invalidateKeyboardAnimationVSyncClient]; + [strongSelf removeKeyboardAnimationView]; + [strongSelf ensureViewportMetricsIsCorrect]; } }]; } @@ -1793,19 +1754,54 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation { // If keyboard animation is null or not a spring animation, fallback to DisplayLink tracking. if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation class]]) { - _keyboardSpringAnimation.reset(); + _keyboardSpringAnimation = nil; return; } // Setup keyboard spring animation details for spring curve animation calculation. CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation; - _keyboardSpringAnimation.reset([[SpringAnimation alloc] - initWithStiffness:keyboardCASpringAnimation.stiffness - damping:keyboardCASpringAnimation.damping - mass:keyboardCASpringAnimation.mass - initialVelocity:keyboardCASpringAnimation.initialVelocity - fromValue:self.originalViewInsetBottom - toValue:self.targetViewInsetBottom]); + _keyboardSpringAnimation = + [[SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness + damping:keyboardCASpringAnimation.damping + mass:keyboardCASpringAnimation.mass + initialVelocity:keyboardCASpringAnimation.initialVelocity + fromValue:self.originalViewInsetBottom + toValue:self.targetViewInsetBottom]; +} + +- (void)handleKeyboardAnimationCallbackWithTargetTime:(fml::TimePoint)targetTime { + // If the view controller's view is not loaded, bail out. + if (!self.isViewLoaded) { + return; + } + // If the view for tracking keyboard animation is nil, means it is not + // created, bail out. + if (!self.keyboardAnimationView) { + return; + } + // If keyboardAnimationVSyncClient is nil, means the animation ends. + // And should bail out. + if (!self.keyboardAnimationVSyncClient) { + return; + } + + if (!self.keyboardAnimationView.superview) { + // Ensure the keyboardAnimationView is in view hierarchy when animation running. + [self.view addSubview:self.keyboardAnimationView]; + } + + if (!self.keyboardSpringAnimation) { + if (self.keyboardAnimationView.layer.presentationLayer) { + self->_viewportMetrics.physical_view_inset_bottom = + self.keyboardAnimationView.layer.presentationLayer.frame.origin.y; + [self updateViewportMetricsIfNeeded]; + } + } else { + fml::TimeDelta timeElapsed = targetTime - self.keyboardAnimationStartTime; + self->_viewportMetrics.physical_view_inset_bottom = + [self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()]; + [self updateViewportMetricsIfNeeded]; + } } - (void)setUpKeyboardAnimationVsyncClient: @@ -1817,17 +1813,16 @@ - (void)setUpKeyboardAnimationVsyncClient: @"_keyboardAnimationVSyncClient must be nil when setting up."); // Make sure the new viewport metrics get sent after the begin frame event has processed. - fml::scoped_nsprotocol animationCallback( - [keyboardAnimationCallback copy]); + FlutterKeyboardAnimationCallback animationCallback = [keyboardAnimationCallback copy]; auto uiCallback = [animationCallback](std::unique_ptr recorder) { fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime(); - fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval; + fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval; dispatch_async(dispatch_get_main_queue(), ^(void) { - animationCallback.get()(keyboardAnimationTargetTime); + animationCallback(targetTime); }); }; - _keyboardAnimationVSyncClient = [[VSyncClient alloc] initWithTaskRunner:[_engine uiTaskRunner] + _keyboardAnimationVSyncClient = [[VSyncClient alloc] initWithTaskRunner:[self.engine uiTaskRunner] callback:uiCallback]; _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO; [_keyboardAnimationVSyncClient await]; @@ -1835,13 +1830,12 @@ - (void)setUpKeyboardAnimationVsyncClient: - (void)invalidateKeyboardAnimationVSyncClient { [_keyboardAnimationVSyncClient invalidate]; - [_keyboardAnimationVSyncClient release]; _keyboardAnimationVSyncClient = nil; } - (void)removeKeyboardAnimationView { - if ([self keyboardAnimationView].superview != nil) { - [[self keyboardAnimationView] removeFromSuperview]; + if (self.keyboardAnimationView.superview != nil) { + [self.keyboardAnimationView removeFromSuperview]; } } @@ -1864,7 +1858,8 @@ - (void)handlePressEvent:(FlutterUIPressProxy*)press } - (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(void (^)(BOOL success))completion { - [_engine.get() + __weak FlutterViewController* weakSelf = self; + [self.engine waitForFirstFrame:3.0 callback:^(BOOL didTimeout) { if (didTimeout) { @@ -1872,7 +1867,7 @@ - (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(void (^)(BOOL suc completion(NO); } else { // invove the method and get the result - [[_engine.get() navigationChannel] + [weakSelf.engine.navigationChannel invokeMethod:@"pushRouteInformation" arguments:@{ @"location" : url.absoluteString ?: [NSNull null], @@ -1899,20 +1894,39 @@ - (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(void (^)(BOOL suc // the wild, however, so I suspect that the API is built for a tvOS remote or // something, and perhaps only one ever appears in the set on iOS from a // keyboard. +// +// We define separate superPresses* overrides to avoid implicitly capturing self in the blocks +// passed to the presses* methods below. + +- (void)superPressesBegan:(NSSet*)presses withEvent:(UIPressesEvent*)event { + [super pressesBegan:presses withEvent:event]; +} + +- (void)superPressesChanged:(NSSet*)presses withEvent:(UIPressesEvent*)event { + [super pressesChanged:presses withEvent:event]; +} + +- (void)superPressesEnded:(NSSet*)presses withEvent:(UIPressesEvent*)event { + [super pressesEnded:presses withEvent:event]; +} + +- (void)superPressesCancelled:(NSSet*)presses withEvent:(UIPressesEvent*)event { + [super pressesCancelled:presses withEvent:event]; +} // If you substantially change these presses overrides, consider also changing // the similar ones in FlutterTextInputPlugin. They need to be overridden in // both places to capture keys both inside and outside of a text field, but have -// slightly different implmentations. +// slightly different implementations. - (void)pressesBegan:(NSSet*)presses withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) { if (@available(iOS 13.4, *)) { + __weak FlutterViewController* weakSelf = self; for (UIPress* press in presses) { - [self handlePressEvent:[[[FlutterUIPressProxy alloc] initWithPress:press - withEvent:event] autorelease] + [self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] nextAction:^() { - [super pressesBegan:[NSSet setWithObject:press] withEvent:event]; + [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event]; }]; } } else { @@ -1923,11 +1937,11 @@ - (void)pressesBegan:(NSSet*)presses - (void)pressesChanged:(NSSet*)presses withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) { if (@available(iOS 13.4, *)) { + __weak FlutterViewController* weakSelf = self; for (UIPress* press in presses) { - [self handlePressEvent:[[[FlutterUIPressProxy alloc] initWithPress:press - withEvent:event] autorelease] + [self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] nextAction:^() { - [super pressesChanged:[NSSet setWithObject:press] withEvent:event]; + [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event]; }]; } } else { @@ -1938,11 +1952,11 @@ - (void)pressesChanged:(NSSet*)presses - (void)pressesEnded:(NSSet*)presses withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) { if (@available(iOS 13.4, *)) { + __weak FlutterViewController* weakSelf = self; for (UIPress* press in presses) { - [self handlePressEvent:[[[FlutterUIPressProxy alloc] initWithPress:press - withEvent:event] autorelease] + [self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] nextAction:^() { - [super pressesEnded:[NSSet setWithObject:press] withEvent:event]; + [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event]; }]; } } else { @@ -1953,11 +1967,11 @@ - (void)pressesEnded:(NSSet*)presses - (void)pressesCancelled:(NSSet*)presses withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) { if (@available(iOS 13.4, *)) { + __weak FlutterViewController* weakSelf = self; for (UIPress* press in presses) { - [self handlePressEvent:[[[FlutterUIPressProxy alloc] initWithPress:press - withEvent:event] autorelease] + [self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] nextAction:^() { - [super pressesCancelled:[NSSet setWithObject:press] withEvent:event]; + [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event]; }]; } } else { @@ -1969,15 +1983,14 @@ - (void)pressesCancelled:(NSSet*)presses - (void)onOrientationPreferencesUpdated:(NSNotification*)notification { // Notifications may not be on the iOS UI thread + __weak FlutterViewController* weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ NSDictionary* info = notification.userInfo; - NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)]; - if (update == nil) { return; } - [self performOrientationUpdate:update.unsignedIntegerValue]; + [weakSelf performOrientationUpdate:update.unsignedIntegerValue]; }); } @@ -1985,8 +1998,8 @@ - (void)requestGeometryUpdateForWindowScenes:(NSSet*)windowScenes API_AVAILABLE(ios(16.0)) { for (UIScene* windowScene in windowScenes) { FML_DCHECK([windowScene isKindOfClass:[UIWindowScene class]]); - UIWindowSceneGeometryPreferencesIOS* preference = [[[UIWindowSceneGeometryPreferencesIOS alloc] - initWithInterfaceOrientations:_orientationPreferences] autorelease]; + UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc] + initWithInterfaceOrientations:self.orientationPreferences]; [(UIWindowScene*)windowScene requestGeometryUpdateWithPreferences:preference errorHandler:^(NSError* error) { @@ -1998,8 +2011,8 @@ - (void)requestGeometryUpdateForWindowScenes:(NSSet*)windowScenes } - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences { - if (new_preferences != _orientationPreferences) { - _orientationPreferences = new_preferences; + if (new_preferences != self.orientationPreferences) { + self.orientationPreferences = new_preferences; if (@available(iOS 16.0, *)) { NSSet* scenes = @@ -2018,7 +2031,7 @@ - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences { } else { UIInterfaceOrientationMask currentInterfaceOrientation = 0; if (@available(iOS 13.0, *)) { - UIWindowScene* windowScene = [self flutterWindowSceneIfViewLoaded]; + UIWindowScene* windowScene = self.flutterWindowSceneIfViewLoaded; if (!windowScene) { FML_LOG(WARNING) << "Accessing the interface orientation when the window scene is unavailable."; @@ -2034,22 +2047,22 @@ - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences { currentInterfaceOrientation = 1 << [[UIApplication sharedApplication] statusBarOrientation]; #endif } - if (!(_orientationPreferences & currentInterfaceOrientation)) { + if (!(self.orientationPreferences & currentInterfaceOrientation)) { [UIViewController attemptRotationToDeviceOrientation]; // Force orientation switch if the current orientation is not allowed - if (_orientationPreferences & UIInterfaceOrientationMaskPortrait) { + if (self.orientationPreferences & UIInterfaceOrientationMaskPortrait) { // This is no official API but more like a workaround / hack (using // key-value coding on a read-only property). This might break in // the future, but currently it´s the only way to force an orientation change [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait) forKey:@"orientation"]; - } else if (_orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) { + } else if (self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) { [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown) forKey:@"orientation"]; - } else if (_orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) { + } else if (self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) { [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft) forKey:@"orientation"]; - } else if (_orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) { + } else if (self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) { [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight) forKey:@"orientation"]; } @@ -2082,17 +2095,17 @@ - (BOOL)shouldAutorotate { } - (NSUInteger)supportedInterfaceOrientations { - return _orientationPreferences; + return self.orientationPreferences; } #pragma mark - Accessibility - (void)onAccessibilityStatusChanged:(NSNotification*)notification { - if (!_engine) { + if (!self.engine) { return; } - auto platformView = [_engine.get() platformView]; - int32_t flags = [self accessibilityFlags]; + fml::WeakPtr platformView = self.engine.platformView; + int32_t flags = self.accessibilityFlags; #if TARGET_OS_SIMULATOR // There doesn't appear to be any way to determine whether the accessibility // inspector is enabled on the simulator. We conservatively always turn on the @@ -2132,7 +2145,7 @@ - (int32_t)accessibilityFlags { } - (BOOL)accessibilityPerformEscape { - FlutterMethodChannel* navigationChannel = [_engine.get() navigationChannel]; + FlutterMethodChannel* navigationChannel = self.engine.navigationChannel; if (navigationChannel) { [self popRoute]; return YES; @@ -2156,13 +2169,13 @@ - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { } - (void)onUserSettingsChanged:(NSNotification*)notification { - [[_engine.get() settingsChannel] sendMessage:@{ - @"textScaleFactor" : @([self textScaleFactor]), - @"alwaysUse24HourFormat" : @([FlutterHourFormat isAlwaysUse24HourFormat]), - @"platformBrightness" : [self brightnessMode], - @"platformContrast" : [self contrastMode], - @"nativeSpellCheckServiceDefined" : @true, - @"supportsShowingSystemContextMenu" : @([self supportsShowingSystemContextMenu]) + [self.engine.settingsChannel sendMessage:@{ + @"textScaleFactor" : @(self.textScaleFactor), + @"alwaysUse24HourFormat" : @(FlutterHourFormat.isAlwaysUse24HourFormat), + @"platformBrightness" : self.brightnessMode, + @"platformContrast" : self.contrastMode, + @"nativeSpellCheckServiceDefined" : @YES, + @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu) }]; } @@ -2269,65 +2282,68 @@ - (NSString*)contrastMode { #pragma mark - Status bar style - (UIStatusBarStyle)preferredStatusBarStyle { - return _statusBarStyle; + return self.statusBarStyle; } - (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification { // Notifications may not be on the iOS UI thread + __weak FlutterViewController* weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ - NSDictionary* info = notification.userInfo; + FlutterViewController* strongSelf = weakSelf; + if (!strongSelf) { + return; + } + NSDictionary* info = notification.userInfo; NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)]; - if (update == nil) { return; } - NSInteger style = update.integerValue; - - if (style != _statusBarStyle) { - _statusBarStyle = static_cast(style); - [self setNeedsStatusBarAppearanceUpdate]; + UIStatusBarStyle style = static_cast(update.integerValue); + if (style != strongSelf.statusBarStyle) { + strongSelf.statusBarStyle = style; + [strongSelf setNeedsStatusBarAppearanceUpdate]; } }); } - (void)setPrefersStatusBarHidden:(BOOL)hidden { - if (hidden != _flutterPrefersStatusBarHidden) { - _flutterPrefersStatusBarHidden = hidden; + if (hidden != self.flutterPrefersStatusBarHidden) { + self.flutterPrefersStatusBarHidden = hidden; [self setNeedsStatusBarAppearanceUpdate]; } } - (BOOL)prefersStatusBarHidden { - return _flutterPrefersStatusBarHidden; + return self.flutterPrefersStatusBarHidden; } #pragma mark - Platform views -- (std::shared_ptr&)platformViewsController { - return [_engine.get() platformViewsController]; +- (FlutterPlatformViewsController*)platformViewsController { + return self.engine.platformViewsController; } - (NSObject*)binaryMessenger { - return _engine.get().binaryMessenger; + return self.engine.binaryMessenger; } #pragma mark - FlutterBinaryMessenger - (void)sendOnChannel:(NSString*)channel message:(NSData*)message { - [_engine.get().binaryMessenger sendOnChannel:channel message:message]; + [self.engine.binaryMessenger sendOnChannel:channel message:message]; } - (void)sendOnChannel:(NSString*)channel message:(NSData*)message binaryReply:(FlutterBinaryReply)callback { NSAssert(channel, @"The channel must not be null"); - [_engine.get().binaryMessenger sendOnChannel:channel message:message binaryReply:callback]; + [self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback]; } - (NSObject*)makeBackgroundTaskQueue { - return [_engine.get().binaryMessenger makeBackgroundTaskQueue]; + return [self.engine.binaryMessenger makeBackgroundTaskQueue]; } - (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channel @@ -2341,27 +2357,27 @@ - (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channe binaryMessageHandler:(FlutterBinaryMessageHandler _Nullable)handler taskQueue:(NSObject* _Nullable)taskQueue { NSAssert(channel, @"The channel must not be null"); - return [_engine.get().binaryMessenger setMessageHandlerOnChannel:channel - binaryMessageHandler:handler - taskQueue:taskQueue]; + return [self.engine.binaryMessenger setMessageHandlerOnChannel:channel + binaryMessageHandler:handler + taskQueue:taskQueue]; } - (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection { - [_engine.get().binaryMessenger cleanUpConnection:connection]; + [self.engine.binaryMessenger cleanUpConnection:connection]; } #pragma mark - FlutterTextureRegistry - (int64_t)registerTexture:(NSObject*)texture { - return [_engine.get().textureRegistry registerTexture:texture]; + return [self.engine.textureRegistry registerTexture:texture]; } - (void)unregisterTexture:(int64_t)textureId { - [_engine.get().textureRegistry unregisterTexture:textureId]; + [self.engine.textureRegistry unregisterTexture:textureId]; } - (void)textureFrameAvailable:(int64_t)textureId { - [_engine.get().textureRegistry textureFrameAvailable:textureId]; + [self.engine.textureRegistry textureFrameAvailable:textureId]; } - (NSString*)lookupKeyForAsset:(NSString*)asset { @@ -2373,7 +2389,7 @@ - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package { } - (id)pluginRegistry { - return _engine; + return self.engine; } + (BOOL)isUIAccessibilityIsVoiceOverRunning { @@ -2383,25 +2399,26 @@ + (BOOL)isUIAccessibilityIsVoiceOverRunning { #pragma mark - FlutterPluginRegistry - (NSObject*)registrarForPlugin:(NSString*)pluginKey { - return [_engine.get() registrarForPlugin:pluginKey]; + return [self.engine registrarForPlugin:pluginKey]; } - (BOOL)hasPlugin:(NSString*)pluginKey { - return [_engine.get() hasPlugin:pluginKey]; + return [self.engine hasPlugin:pluginKey]; } - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey { - return [_engine.get() valuePublishedByPlugin:pluginKey]; + return [self.engine valuePublishedByPlugin:pluginKey]; } - (void)presentViewController:(UIViewController*)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion { self.isPresentingViewControllerAnimating = YES; + __weak FlutterViewController* weakSelf = self; [super presentViewController:viewControllerToPresent animated:flag completion:^{ - self.isPresentingViewControllerAnimating = NO; + weakSelf.isPresentingViewControllerAnimating = NO; if (completion) { completion(); } @@ -2415,7 +2432,7 @@ - (BOOL)isPresentingViewController { - (flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer API_AVAILABLE(ios(13.4)) { CGPoint location = [gestureRecognizer locationInView:self.view]; - CGFloat scale = [self flutterScreenIfViewLoaded].scale; + CGFloat scale = self.flutterScreenIfViewLoaded.scale; _mouseState.location = {location.x * scale, location.y * scale}; flutter::PointerData pointer_data; pointer_data.Clear(); @@ -2442,13 +2459,13 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel; pointer_data.view_id = self.viewIdentifier; - if (event.timestamp < _scrollInertiaEventAppKitDeadline) { + if (event.timestamp < self.scrollInertiaEventAppKitDeadline) { // Only send the event if it occured before the expected natural end of gesture momentum. // If received after the deadline, it's not likely the event is from a user-initiated cancel. auto packet = std::make_unique(1); packet->SetPointerData(/*i=*/0, pointer_data); - [_engine.get() dispatchPointerDataPacket:std::move(packet)]; - _scrollInertiaEventAppKitDeadline = 0; + [self.engine dispatchPointerDataPacket:std::move(packet)]; + self.scrollInertiaEventAppKitDeadline = 0; } } // This method is also called for UITouches, should return YES to process all touches. @@ -2490,7 +2507,7 @@ - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4) isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac; } if (!isRunningOnMac && CGPointEqualToPoint(oldLocation, _mouseState.location) && - time > _scrollInertiaEventStartline) { + time > self.scrollInertiaEventStartline) { // iPadOS reports trackpad movements events with high (sub-pixel) precision. When an event // is received with the same position as the previous one, it can only be from a finger // making or breaking contact with the trackpad surface. @@ -2502,18 +2519,18 @@ - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4) inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel; inertia_cancel.view_id = self.viewIdentifier; packet->SetPointerData(/*i=*/1, inertia_cancel); - [_engine.get() dispatchPointerDataPacket:std::move(packet)]; - _scrollInertiaEventStartline = DBL_MAX; + [self.engine dispatchPointerDataPacket:std::move(packet)]; + self.scrollInertiaEventStartline = DBL_MAX; } else { auto packet = std::make_unique(1); packet->SetPointerData(/*i=*/0, pointer_data); - [_engine.get() dispatchPointerDataPacket:std::move(packet)]; + [self.engine dispatchPointerDataPacket:std::move(packet)]; } } - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) { CGPoint translation = [recognizer translationInView:self.view]; - const CGFloat scale = [self flutterScreenIfViewLoaded].scale; + const CGFloat scale = self.flutterScreenIfViewLoaded.scale; translation.x *= scale; translation.y *= scale; @@ -2538,12 +2555,12 @@ - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(io auto packet = std::make_unique(1); packet->SetPointerData(/*i=*/0, pointer_data); - [_engine.get() dispatchPointerDataPacket:std::move(packet)]; + [self.engine dispatchPointerDataPacket:std::move(packet)]; } - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) { CGPoint translation = [recognizer translationInView:self.view]; - const CGFloat scale = [self flutterScreenIfViewLoaded].scale; + const CGFloat scale = self.flutterScreenIfViewLoaded.scale; flutter::PointerData pointer_data = [self updateMousePointerDataFrom:recognizer]; pointer_data.device = reinterpret_cast(recognizer); @@ -2563,7 +2580,7 @@ - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE( break; case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: - _scrollInertiaEventStartline = + self.scrollInertiaEventStartline = [[NSProcessInfo processInfo] systemUptime] + 0.1; // Time to lift fingers off trackpad (experimentally determined) // When running an iOS app on an Apple Silicon Mac, AppKit will send an event @@ -2574,7 +2591,7 @@ - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE( // The following (curve-fitted) calculation provides a cutoff point after which any // UIEventTypeScroll event will likely be from the system instead of the user. // See https://github.com/flutter/engine/pull/34929. - _scrollInertiaEventAppKitDeadline = + self.scrollInertiaEventAppKitDeadline = [[NSProcessInfo processInfo] systemUptime] + (0.1821 * log(fmax([recognizer velocityInView:self.view].x, [recognizer velocityInView:self.view].y))) - @@ -2583,14 +2600,14 @@ - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE( break; default: // continuousScrollEvent: should only ever be triggered with the above phases - NSAssert(false, @"Trackpad pan event occured with unexpected phase 0x%lx", + NSAssert(NO, @"Trackpad pan event occured with unexpected phase 0x%lx", (long)recognizer.state); break; } auto packet = std::make_unique(1); packet->SetPointerData(/*i=*/0, pointer_data); - [_engine.get() dispatchPointerDataPacket:std::move(packet)]; + [self.engine dispatchPointerDataPacket:std::move(packet)]; } - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) { @@ -2613,20 +2630,20 @@ - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4) break; default: // pinchEvent: should only ever be triggered with the above phases - NSAssert(false, @"Trackpad pinch event occured with unexpected phase 0x%lx", + NSAssert(NO, @"Trackpad pinch event occured with unexpected phase 0x%lx", (long)recognizer.state); break; } auto packet = std::make_unique(1); packet->SetPointerData(/*i=*/0, pointer_data); - [_engine.get() dispatchPointerDataPacket:std::move(packet)]; + [self.engine dispatchPointerDataPacket:std::move(packet)]; } #pragma mark - State Restoration - (void)encodeRestorableStateWithCoder:(NSCoder*)coder { - NSData* restorationData = [[_engine.get() restorationPlugin] restorationData]; + NSData* restorationData = [self.engine.restorationPlugin restorationData]; [coder encodeBytes:(const unsigned char*)restorationData.bytes length:restorationData.length forKey:kFlutterRestorationStateAppData]; @@ -2638,11 +2655,11 @@ - (void)decodeRestorableStateWithCoder:(NSCoder*)coder { const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData returnedLength:&restorationDataLength]; NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength]; - [[_engine.get() restorationPlugin] setRestorationData:restorationData]; + [self.engine.restorationPlugin setRestorationData:restorationData]; } - (FlutterRestorationPlugin*)restorationPlugin { - return [_engine.get() restorationPlugin]; + return self.engine.restorationPlugin; } @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 0dede34e2e94a..97d6a5554812c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -38,21 +38,26 @@ - (void)sendKeyEvent:(const FlutterKeyEvent&)event /// Sometimes we have to use a custom mock to avoid retain cycles in OCMock. /// Used for testing low memory notification. @interface FlutterEnginePartialMock : FlutterEngine + @property(nonatomic, strong) FlutterBasicMessageChannel* lifecycleChannel; @property(nonatomic, strong) FlutterBasicMessageChannel* keyEventChannel; @property(nonatomic, weak) FlutterViewController* viewController; @property(nonatomic, strong) FlutterTextInputPlugin* textInputPlugin; @property(nonatomic, assign) BOOL didCallNotifyLowMemory; + - (FlutterTextInputPlugin*)textInputPlugin; + - (void)sendKeyEvent:(const FlutterKeyEvent&)event callback:(nullable FlutterKeyEventCallback)callback userData:(nullable void*)userData; @end @implementation FlutterEnginePartialMock -@synthesize viewController; + +// Synthesize properties declared readonly in FlutterEngine. @synthesize lifecycleChannel; @synthesize keyEventChannel; +@synthesize viewController; @synthesize textInputPlugin; - (void)notifyLowMemory { @@ -2093,7 +2098,7 @@ - (void)testLifeCycleNotificationCancelledInvalidResumed { - (void)testSetupKeyboardAnimationVsyncClientWillCreateNewVsyncClientForFlutterViewController { id bundleMock = OCMPartialMock([NSBundle mainBundle]); - OCMStub([bundleMock objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"]) + OCMStub([bundleMock objectForInfoDictionaryKey:kCADisableMinimumFrameDurationOnPhoneKey]) .andReturn(@YES); id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]]; double maxFrameRate = 120; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h index c41533ff9c041..3645d2c376f65 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h @@ -5,12 +5,12 @@ #ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERVIEWCONTROLLER_INTERNAL_H_ #define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERVIEWCONTROLLER_INTERNAL_H_ -#include "flutter/fml/platform/darwin/weak_nsobject.h" -#include "flutter/fml/time/time_point.h" - #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" + +#include "flutter/fml/time/time_point.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeySecondaryResponder.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterUIPressProxy.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h" @@ -46,7 +46,7 @@ typedef void (^FlutterKeyboardAnimationCallback)(fml::TimePoint); @property(class, nonatomic, readonly) BOOL accessibilityIsOnOffSwitchLabelsEnabled; @property(nonatomic, readonly) BOOL isPresentingViewController; @property(nonatomic, readonly) BOOL isVoiceOverRunning; -@property(nonatomic, retain) FlutterKeyboardManager* keyboardManager; +@property(nonatomic, strong) FlutterKeyboardManager* keyboardManager; /** * @brief Whether the status bar is preferred hidden. @@ -57,8 +57,8 @@ typedef void (^FlutterKeyboardAnimationCallback)(fml::TimePoint); */ @property(nonatomic, assign, readwrite) BOOL prefersStatusBarHidden; -- (fml::WeakNSObject)getWeakNSObject; -- (std::shared_ptr&)platformViewsController; +@property(nonatomic, readonly) FlutterPlatformViewsController* platformViewsController; + - (FlutterRestorationPlugin*)restorationPlugin; // Accepts keypress events, and then calls |nextAction| if the event was not diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm index e7b26cbe70eeb..357c65e4c8e0f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm @@ -13,13 +13,12 @@ @interface FakeDelegate : NSObject @property(nonatomic, assign) BOOL isUsingImpeller; @end -@implementation FakeDelegate { - std::shared_ptr _platformViewsController; -} +@implementation FakeDelegate + +@synthesize platformViewsController = _platformViewsController; - (instancetype)init { _callbackCalled = NO; - _platformViewsController = std::shared_ptr(nullptr); return self; } @@ -28,10 +27,6 @@ - (instancetype)init { return {}; } -- (std::shared_ptr&)platformViewsController { - return _platformViewsController; -} - - (void)flutterViewAccessibilityDidCall { _callbackCalled = YES; } @@ -62,7 +57,7 @@ - (void)testIgnoreWideColorWithoutImpeller { delegate.isUsingImpeller = NO; FlutterView* view = [[FlutterView alloc] initWithDelegate:delegate opaque:NO enableWideGamut:YES]; [view layoutSubviews]; - XCTAssertTrue([view.layer isKindOfClass:NSClassFromString(@"CAMetalLayer")]); + XCTAssertTrue([view.layer isKindOfClass:[CAMetalLayer class]]); CAMetalLayer* layer = (CAMetalLayer*)view.layer; XCTAssertEqual(layer.pixelFormat, MTLPixelFormatBGRA8Unorm); } diff --git a/shell/platform/darwin/ios/framework/Source/IOKit.h b/shell/platform/darwin/ios/framework/Source/IOKit.h index 53e8fe1b35eb4..4b1e8446af4b6 100644 --- a/shell/platform/darwin/ios/framework/Source/IOKit.h +++ b/shell/platform/darwin/ios/framework/Source/IOKit.h @@ -25,7 +25,7 @@ extern "C" { #define IOKIT #include -static const char* kIOServicePlane = "IOService"; +constexpr const char* kIOServicePlane = "IOService"; typedef io_object_t io_registry_entry_t; typedef io_object_t io_service_t; diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject+UIFocusSystem.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject+UIFocusSystem.mm new file mode 100644 index 0000000000000..c3262de538dab --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject+UIFocusSystem.mm @@ -0,0 +1,241 @@ +// 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 "SemanticsObject.h" +#include "flutter/lib/ui/semantics/semantics_node.h" +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.h" + +FLUTTER_ASSERT_ARC + +// The SemanticsObject class conforms to UIFocusItem and UIFocusItemContainer +// protocols, so the SemanticsObject tree can also be used to represent +// interactive UI components on screen that can receive UIFocusSystem focus. +// +// Typically, physical key events received by the FlutterViewController is +// first delivered to the framework, but that stopped working for navigation keys +// since iOS 15 when full keyboard access (FKA) is on, because those events are +// consumed by the UIFocusSystem and never dispatched to the UIResponders in the +// application (see +// https://developer.apple.com/documentation/uikit/uikeycommand/3780513-wantspriorityoversystembehavior +// ). FKA relies on the iOS focus engine, to enable FKA on iOS 15+, we use +// SemanticsObject to provide the iOS focus engine with the required hierarchical +// information and geometric context. +// +// The focus engine focus is different from accessibility focus, or even the +// currentFocus of the Flutter FocusManager in the framework. On iOS 15+, FKA +// key events are dispatched to the current iOS focus engine focus (and +// translated to calls such as -[NSObject accessibilityActivate]), while most +// other key events are dispatched to the framework. +@interface SemanticsObject (UIFocusSystem) +/// The `UIFocusItem` that represents this SemanticsObject. +/// +/// For regular `SemanticsObject`s, this method returns `self`, +/// for `FlutterScrollableSemanticsObject`s, this method returns its scroll view. +- (id)focusItem; +@end + +@implementation SemanticsObject (UIFocusSystem) + +- (id)focusItem { + return self; +} + +#pragma mark - UIFocusEnvironment Conformance + +- (void)setNeedsFocusUpdate { +} + +- (void)updateFocusIfNeeded { +} + +- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext*)context { + return YES; +} + +- (void)didUpdateFocusInContext:(UIFocusUpdateContext*)context + withAnimationCoordinator:(UIFocusAnimationCoordinator*)coordinator { +} + +- (id)parentFocusEnvironment { + // The root SemanticsObject node's parent is the FlutterView. + return self.parent.focusItem ?: self.bridge->view(); +} + +- (NSArray>*)preferredFocusEnvironments { + return nil; +} + +- (id)focusItemContainer { + return self; +} + +#pragma mark - UIFocusItem Conformance + +- (BOOL)canBecomeFocused { + if ((self.node.flags & static_cast(flutter::SemanticsFlags::kIsHidden)) != 0) { + return NO; + } + // Currently only supports SemanticsObjects that handle + // -[NSObject accessibilityActivate]. + return self.node.HasAction(flutter::SemanticsAction::kTap); +} + +// The frame is described in the `coordinateSpace` of the +// `parentFocusEnvironment` (all `parentFocusEnvironment`s are `UIFocusItem`s). +// +// See also the `coordinateSpace` implementation. +// TODO(LongCatIsLooong): use CoreGraphics types. +- (CGRect)frame { + SkPoint quad[4] = {SkPoint::Make(self.node.rect.left(), self.node.rect.top()), + SkPoint::Make(self.node.rect.left(), self.node.rect.bottom()), + SkPoint::Make(self.node.rect.right(), self.node.rect.top()), + SkPoint::Make(self.node.rect.right(), self.node.rect.bottom())}; + + SkM44 transform = self.node.transform; + FlutterSemanticsScrollView* scrollView; + for (SemanticsObject* ancestor = self.parent; ancestor; ancestor = ancestor.parent) { + if ([ancestor isKindOfClass:[FlutterScrollableSemanticsObject class]]) { + scrollView = ((FlutterScrollableSemanticsObject*)ancestor).scrollView; + break; + } + transform = ancestor.node.transform * transform; + } + + for (auto& vertex : quad) { + SkV4 vector = transform.map(vertex.x(), vertex.y(), 0, 1); + vertex = SkPoint::Make(vector.x / vector.w, vector.y / vector.w); + } + + SkRect rect; + rect.setBounds(quad, 4); + // If this UIFocusItemContainer's coordinateSpace is a UIScrollView, offset + // the rect by `contentOffset` because the contentOffset translation is + // incorporated into the paint transform at different node depth in UIKit + // and Flutter. In Flutter, the translation is added to the cells + // while in UIKit the viewport's bounds is manipulated (IOW, each cell's frame + // in the UIScrollView coordinateSpace does not change when the UIScrollView + // scrolls). + CGRect unscaledRect = + CGRectMake(rect.x() + scrollView.bounds.origin.x, rect.y() + scrollView.bounds.origin.y, + rect.width(), rect.height()); + if (scrollView) { + return unscaledRect; + } + // `rect` could be in physical pixels since the root RenderObject ("RenderView") + // applies a transform that turns logical pixels to physical pixels. Undo the + // transform by dividing the coordinates by the screen's scale factor, if this + // UIFocusItem's reported `coordinateSpace` is the root view (which means this + // UIFocusItem is not inside of a scroll view). + // + // Screen can be nil if the FlutterView is covered by another native view. + CGFloat scale = (self.bridge->view().window.screen ?: UIScreen.mainScreen).scale; + return CGRectMake(unscaledRect.origin.x / scale, unscaledRect.origin.y / scale, + unscaledRect.size.width / scale, unscaledRect.size.height / scale); +} + +#pragma mark - UIFocusItemContainer Conformance + +- (NSArray>*)focusItemsInRect:(CGRect)rect { + // It seems the iOS focus system relies heavily on focusItemsInRect + // (instead of preferredFocusEnvironments) for directional navigation. + // + // The order of the items seems to be important, menus and dialogs become + // unreachable via FKA if the returned children are organized + // in hit-test order. + // + // This method is only supposed to return items within the given + // rect but returning everything in the subtree seems to work fine. + NSMutableArray>* reversedItems = + [[NSMutableArray alloc] initWithCapacity:self.childrenInHitTestOrder.count]; + for (NSUInteger i = 0; i < self.childrenInHitTestOrder.count; ++i) { + SemanticsObject* child = self.childrenInHitTestOrder[self.childrenInHitTestOrder.count - 1 - i]; + [reversedItems addObject:child.focusItem]; + } + return reversedItems; +} + +- (id)coordinateSpace { + // A regular SemanticsObject uses the same coordinate space as its parent. + return self.parent.coordinateSpace ?: self.bridge->view(); +} + +@end + +/// Scrollable containers interact with the iOS focus engine using the +/// `UIFocusItemScrollableContainer` protocol. The said protocol (and other focus-related protocols) +/// does not provide means to inform the focus system of layout changes. In order for the focus +/// highlight to update properly as the scroll view scrolls, this implementation incorporates a +/// UIScrollView into the focus hierarchy to workaround the highlight update problem. +/// +/// As a result, in the current implementation only scrollable containers and the root node +/// establish their own `coordinateSpace`s. All other `UIFocusItemContainter`s use the same +/// `coordinateSpace` as the containing UIScrollView, or the root `FlutterView`, whichever is +/// closer. +/// +/// See also the `frame` method implementation. +#pragma mark - Scrolling + +@interface FlutterScrollableSemanticsObject (CoordinateSpace) +@end + +@implementation FlutterScrollableSemanticsObject (CoordinateSpace) +- (id)coordinateSpace { + // A scrollable SemanticsObject uses the same coordinate space as the scroll view. + // This may not work very well in nested scroll views. + return self.scrollView; +} + +- (id)focusItem { + return self.scrollView; +} + +@end + +@interface FlutterSemanticsScrollView (UIFocusItemScrollableContainer) < + UIFocusItemScrollableContainer> +@end + +@implementation FlutterSemanticsScrollView (UIFocusItemScrollableContainer) + +#pragma mark - FlutterSemanticsScrollView UIFocusItemScrollableContainer Conformance + +- (CGSize)visibleSize { + return self.frame.size; +} + +- (void)setContentOffset:(CGPoint)contentOffset { + [super setContentOffset:contentOffset]; + // Do no send flutter::SemanticsAction::kScrollToOffset if it's triggered + // by a framework update. + if (![self.semanticsObject isAccessibilityBridgeAlive] || !self.isDoingSystemScrolling) { + return; + } + + double offset[2] = {contentOffset.x, contentOffset.y}; + FlutterStandardTypedData* offsetData = [FlutterStandardTypedData + typedDataWithFloat64:[NSData dataWithBytes:&offset length:sizeof(offset)]]; + NSData* encoded = [[FlutterStandardMessageCodec sharedInstance] encode:offsetData]; + self.semanticsObject.bridge->DispatchSemanticsAction( + self.semanticsObject.uid, flutter::SemanticsAction::kScrollToOffset, + fml::MallocMapping::Copy(encoded.bytes, encoded.length)); +} + +- (BOOL)canBecomeFocused { + return NO; +} + +- (id)parentFocusEnvironment { + return self.semanticsObject.parentFocusEnvironment; +} + +- (NSArray>*)preferredFocusEnvironments { + return nil; +} + +- (NSArray>*)focusItemsInRect:(CGRect)rect { + return [self.semanticsObject focusItemsInRect:rect]; +} +@end diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h index d3441ec0058b1..2c9e0516112ea 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h @@ -10,6 +10,7 @@ #include "flutter/fml/macros.h" #include "flutter/fml/memory/weak_ptr.h" #include "flutter/lib/ui/semantics/semantics_node.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.h" #import "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h" constexpr int32_t kRootNodeId = 0; @@ -186,7 +187,7 @@ constexpr float kScrollExtentMaxForInf = 1000; /// The semantics object for scrollable. This class creates an UIScrollView to interact with the /// iOS. @interface FlutterScrollableSemanticsObject : SemanticsObject - +@property(nonatomic, readonly) FlutterSemanticsScrollView* scrollView; @end /** diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm index 2fe38516f4f5f..39882196e8e99 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm @@ -154,6 +154,8 @@ - (instancetype)initWithBridge:(fml::WeakPtr)br _scrollView = [[FlutterSemanticsScrollView alloc] initWithSemanticsObject:self]; [_scrollView setShowsHorizontalScrollIndicator:NO]; [_scrollView setShowsVerticalScrollIndicator:NO]; + [_scrollView setContentInset:UIEdgeInsetsZero]; + [_scrollView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever]; [self.bridge->view() addSubview:_scrollView]; } return self; @@ -174,7 +176,10 @@ - (void)accessibilityBridgeDidFinishUpdate { // contentOffset is 0.0, only the scroll down action is available. self.scrollView.frame = self.accessibilityFrame; self.scrollView.contentSize = [self contentSizeInternal]; - [self.scrollView setContentOffset:[self contentOffsetInternal] animated:NO]; + // See the documentation on `isDoingSystemScrolling`. + if (!self.scrollView.isDoingSystemScrolling) { + [self.scrollView setContentOffset:self.contentOffsetInternal animated:NO]; + } } - (id)nativeAccessibility { @@ -692,6 +697,13 @@ - (BOOL)accessibilityActivate { return NO; } if (!self.node.HasAction(flutter::SemanticsAction::kTap)) { + // Prevent sliders to receive a regular tap which will change the value. + // + // This is needed because it causes slider to select to middle if it + // does not have a semantics tap. + if (self.node.HasFlag(flutter::SemanticsFlags::kIsSlider)) { + return YES; + } return NO; } self.bridge->DispatchSemanticsAction(self.uid, flutter::SemanticsAction::kTap); diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm index e95078a419cd6..02fb11f98336c 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm @@ -7,6 +7,7 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h" #import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h" #import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h" @@ -16,6 +17,13 @@ const float kFloatCompareEpsilon = 0.001; +@interface SemanticsObject (UIFocusSystem) +@end + +@interface FlutterScrollableSemanticsObject (UIFocusItemScrollableContainer) < + UIFocusItemScrollableContainer> +@end + @interface TextInputSemanticsObject (Test) - (UIView*)textInputSurrogate; @end @@ -662,7 +670,7 @@ - (void)testFlutterScrollableSemanticsObjectReturnsParentContainerIfNoChildren { XCTAssertEqual(container.semanticsObject, parentObject); } -- (void)testFlutterScrollableSemanticsObjectHidesScrollBar { +- (void)testFlutterScrollableSemanticsObjectNoScrollBarOrContentInsets { fml::WeakPtrFactory factory( new flutter::testing::MockAccessibilityBridge()); fml::WeakPtr bridge = factory.GetWeakPtr(); @@ -682,6 +690,9 @@ - (void)testFlutterScrollableSemanticsObjectHidesScrollBar { XCTAssertFalse(scrollView.showsHorizontalScrollIndicator); XCTAssertFalse(scrollView.showsVerticalScrollIndicator); + XCTAssertEqual(scrollView.contentInsetAdjustmentBehavior, + UIScrollViewContentInsetAdjustmentNever); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(scrollView.contentInset, UIEdgeInsetsZero)); } - (void)testSemanticsObjectBuildsAttributedString { @@ -1152,4 +1163,122 @@ - (void)testTextInputSemanticsObject_editActions { [self waitForExpectationsWithTimeout:1 handler:nil]; } +- (void)testSliderSemanticsObject { + fml::WeakPtrFactory factory( + new flutter::testing::MockAccessibilityBridge()); + fml::WeakPtr bridge = factory.GetWeakPtr(); + + flutter::SemanticsNode node; + node.flags = static_cast(flutter::SemanticsFlags::kIsSlider); + SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0]; + [object setSemanticsNode:&node]; + [object accessibilityBridgeDidFinishUpdate]; + XCTAssertEqual([object accessibilityActivate], YES); +} + +- (void)testUIFocusItemConformance { + fml::WeakPtrFactory factory( + new flutter::testing::MockAccessibilityBridge()); + fml::WeakPtr bridge = factory.GetWeakPtr(); + SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0]; + SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1]; + parent.children = @[ child ]; + + // parentFocusEnvironment + XCTAssertTrue([parent.parentFocusEnvironment isKindOfClass:[UIView class]]); + XCTAssertEqual(child.parentFocusEnvironment, child.parent); + + // canBecomeFocused + flutter::SemanticsNode childNode; + childNode.flags = static_cast(flutter::SemanticsFlags::kIsHidden); + childNode.actions = static_cast(flutter::SemanticsAction::kTap); + [child setSemanticsNode:&childNode]; + XCTAssertFalse(child.canBecomeFocused); + childNode.flags = 0; + [child setSemanticsNode:&childNode]; + XCTAssertTrue(child.canBecomeFocused); + childNode.actions = 0; + [child setSemanticsNode:&childNode]; + XCTAssertFalse(child.canBecomeFocused); + + CGFloat scale = ((bridge->view().window.screen ?: UIScreen.mainScreen)).scale; + + childNode.rect = SkRect::MakeXYWH(0, 0, 100 * scale, 100 * scale); + [child setSemanticsNode:&childNode]; + flutter::SemanticsNode parentNode; + parentNode.rect = SkRect::MakeXYWH(0, 0, 200, 200); + [parent setSemanticsNode:&parentNode]; + + XCTAssertTrue(CGRectEqualToRect(child.frame, CGRectMake(0, 0, 100, 100))); +} + +- (void)testUIFocusItemContainerConformance { + fml::WeakPtrFactory factory( + new flutter::testing::MockAccessibilityBridge()); + fml::WeakPtr bridge = factory.GetWeakPtr(); + SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0]; + SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1]; + SemanticsObject* child2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2]; + parent.childrenInHitTestOrder = @[ child1, child2 ]; + + // focusItemsInRect + NSArray>* itemsInRect = [parent focusItemsInRect:CGRectMake(0, 0, 100, 100)]; + XCTAssertEqual(itemsInRect.count, (unsigned long)2); + XCTAssertTrue([itemsInRect containsObject:child1]); + XCTAssertTrue([itemsInRect containsObject:child2]); +} + +- (void)testUIFocusItemScrollableContainerConformance { + fml::WeakPtrFactory factory( + new flutter::testing::MockAccessibilityBridge()); + fml::WeakPtr bridge = factory.GetWeakPtr(); + FlutterScrollableSemanticsObject* scrollable = + [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5]; + + // setContentOffset + CGPoint p = CGPointMake(123.0, 456.0); + [scrollable.scrollView scrollViewWillEndDragging:scrollable.scrollView + withVelocity:CGPointZero + targetContentOffset:&p]; + scrollable.scrollView.contentOffset = p; + [scrollable.scrollView scrollViewDidEndDecelerating:scrollable.scrollView]; + XCTAssertEqual(bridge->observations.size(), (size_t)1); + XCTAssertEqual(bridge->observations[0].id, 5); + XCTAssertEqual(bridge->observations[0].action, flutter::SemanticsAction::kScrollToOffset); + + std::vector args = bridge->observations[0].args; + XCTAssertEqual(args.size(), 3 * sizeof(CGFloat)); + + NSData* encoded = [NSData dataWithBytes:args.data() length:args.size()]; + FlutterStandardTypedData* decoded = [[FlutterStandardMessageCodec sharedInstance] decode:encoded]; + CGPoint point = CGPointZero; + memcpy(&point, decoded.data.bytes, decoded.data.length); + XCTAssertTrue(CGPointEqualToPoint(point, p)); +} + +- (void)testUIFocusItemScrollableContainerNoFeedbackLoops { + fml::WeakPtrFactory factory( + new flutter::testing::MockAccessibilityBridge()); + fml::WeakPtr bridge = factory.GetWeakPtr(); + FlutterScrollableSemanticsObject* scrollable = + [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5]; + + // setContentOffset + const CGPoint p = CGPointMake(0.0, 456.0); + scrollable.scrollView.contentOffset = p; + bridge->observations.clear(); + + const SkScalar scrollPosition = p.y + 0.0000000000000001; + flutter::SemanticsNode node; + node.flags = static_cast(flutter::SemanticsFlags::kHasImplicitScrolling); + node.actions = flutter::kVerticalScrollSemanticsActions; + node.rect = SkRect::MakeXYWH(0, 0, 100, 200); + node.scrollExtentMax = 10000; + node.scrollPosition = scrollPosition; + node.transform = {1.0, 0, 0, 0, 0, 1.0, 0, 0, 0, 0, 1.0, 0, 0, scrollPosition, 0, 1.0}; + [scrollable setSemanticsNode:&node]; + [scrollable accessibilityBridgeDidFinishUpdate]; + + XCTAssertEqual(bridge->observations.size(), (size_t)0); +} @end diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h index 4ccd57b1bbf6b..a740349ab1376 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h @@ -15,10 +15,18 @@ namespace testing { class SemanticsActionObservation { public: SemanticsActionObservation(int32_t observed_id, SemanticsAction observed_action) - : id(observed_id), action(observed_action) {} + : id(observed_id), action(observed_action), args({}) {} + + SemanticsActionObservation(int32_t observed_id, + SemanticsAction observed_action, + fml::MallocMapping& args) + : id(observed_id), + action(observed_action), + args(args.GetMapping(), args.GetMapping() + args.GetSize()) {} int32_t id; SemanticsAction action; + std::vector args; }; class MockAccessibilityBridge : public AccessibilityBridgeIos { @@ -38,14 +46,12 @@ class MockAccessibilityBridge : public AccessibilityBridgeIos { void DispatchSemanticsAction(int32_t id, SemanticsAction action, fml::MallocMapping args) override { - SemanticsActionObservation observation(id, action); + SemanticsActionObservation observation(id, action, args); observations.push_back(observation); } void AccessibilityObjectDidBecomeFocused(int32_t id) override {} void AccessibilityObjectDidLoseFocus(int32_t id) override {} - std::shared_ptr GetPlatformViewsController() const override { - return nil; - } + FlutterPlatformViewsController* GetPlatformViewsController() const override { return nil; } std::vector observations; bool isVoiceOverRunningValue; @@ -69,14 +75,12 @@ class MockAccessibilityBridgeNoWindow : public AccessibilityBridgeIos { void DispatchSemanticsAction(int32_t id, SemanticsAction action, fml::MallocMapping args) override { - SemanticsActionObservation observation(id, action); + SemanticsActionObservation observation(id, action, args); observations.push_back(observation); } void AccessibilityObjectDidBecomeFocused(int32_t id) override {} void AccessibilityObjectDidLoseFocus(int32_t id) override {} - std::shared_ptr GetPlatformViewsController() const override { - return nil; - } + FlutterPlatformViewsController* GetPlatformViewsController() const override { return nil; } std::vector observations; bool isVoiceOverRunningValue; diff --git a/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.mm index 3695947b3ccdc..f258367ae7f84 100644 --- a/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.mm +++ b/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.mm @@ -22,6 +22,7 @@ @interface FlutterInactiveTextInput : UIView @implementation FlutterInactiveTextInput +// Synthesize properties declared in UITextInput protocol. @synthesize beginningOfDocument = _beginningOfDocument; @synthesize endOfDocument = _endOfDocument; @synthesize inputDelegate = _inputDelegate; diff --git a/shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm b/shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm index cbe58384f6fdf..4a547bf28596e 100644 --- a/shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm +++ b/shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm @@ -53,7 +53,7 @@ - (void)testSetCorrectVariableRefreshRates { auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest"); auto callback = [](std::unique_ptr recorder) {}; id bundleMock = OCMPartialMock([NSBundle mainBundle]); - OCMStub([bundleMock objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"]) + OCMStub([bundleMock objectForInfoDictionaryKey:kCADisableMinimumFrameDurationOnPhoneKey]) .andReturn(@YES); id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]]; double maxFrameRate = 120; @@ -75,7 +75,7 @@ - (void)testDoNotSetVariableRefreshRatesIfCADisableMinimumFrameDurationOnPhoneIs auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest"); auto callback = [](std::unique_ptr recorder) {}; id bundleMock = OCMPartialMock([NSBundle mainBundle]); - OCMStub([bundleMock objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"]) + OCMStub([bundleMock objectForInfoDictionaryKey:kCADisableMinimumFrameDurationOnPhoneKey]) .andReturn(@NO); id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]]; double maxFrameRate = 120; diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h index 787da4d60d059..e808b88d20f8a 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h @@ -14,7 +14,6 @@ #include "flutter/fml/macros.h" #include "flutter/fml/memory/weak_ptr.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/lib/ui/semantics/custom_accessibility_action.h" #include "flutter/lib/ui/semantics/semantics_node.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" @@ -51,7 +50,7 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { AccessibilityBridge(FlutterViewController* view_controller, PlatformViewIOS* platform_view, - std::shared_ptr platform_views_controller, + __weak FlutterPlatformViewsController* platform_views_controller, std::unique_ptr ios_delegate = nullptr); ~AccessibilityBridge(); @@ -73,7 +72,7 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { fml::WeakPtr GetWeakPtr(); - std::shared_ptr GetPlatformViewsController() const override { + FlutterPlatformViewsController* GetPlatformViewsController() const override { return platform_views_controller_; }; @@ -92,16 +91,14 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { FlutterViewController* view_controller_; PlatformViewIOS* platform_view_; - const std::shared_ptr platform_views_controller_; + __weak FlutterPlatformViewsController* platform_views_controller_; // If the this id is kSemanticObjectIdInvalid, it means either nothing has // been focused or the focus is currently outside of the flutter application // (i.e. the status bar or keyboard) int32_t last_focused_semantics_object_id_; - // TODO(cbracken): https://github.com/flutter/flutter/issues/137801 - // Eliminate use of fml::scoped_* wrappers here. - fml::scoped_nsobject> objects_; - fml::scoped_nsprotocol accessibility_channel_; + NSMutableDictionary* objects_; + FlutterBasicMessageChannel* accessibility_channel_; int32_t previous_route_id_ = 0; std::unordered_map actions_; std::vector previous_routes_; diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm index b3a69b1ab5d8d..daf6389834c81 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm @@ -42,39 +42,39 @@ void PostAccessibilityNotification(UIAccessibilityNotifications notification, AccessibilityBridge::AccessibilityBridge( FlutterViewController* view_controller, PlatformViewIOS* platform_view, - std::shared_ptr platform_views_controller, + __weak FlutterPlatformViewsController* platform_views_controller, std::unique_ptr ios_delegate) : view_controller_(view_controller), platform_view_(platform_view), - platform_views_controller_(std::move(platform_views_controller)), + platform_views_controller_(platform_views_controller), last_focused_semantics_object_id_(kSemanticObjectIdInvalid), objects_([[NSMutableDictionary alloc] init]), previous_routes_({}), ios_delegate_(ios_delegate ? std::move(ios_delegate) : std::make_unique()), weak_factory_(this) { - accessibility_channel_.reset([[FlutterBasicMessageChannel alloc] + accessibility_channel_ = [[FlutterBasicMessageChannel alloc] initWithName:@"flutter/accessibility" - binaryMessenger:platform_view->GetOwnerViewController().get().engine.binaryMessenger - codec:[FlutterStandardMessageCodec sharedInstance]]); - [accessibility_channel_.get() setMessageHandler:^(id message, FlutterReply reply) { + binaryMessenger:platform_view->GetOwnerViewController().engine.binaryMessenger + codec:[FlutterStandardMessageCodec sharedInstance]]; + [accessibility_channel_ setMessageHandler:^(id message, FlutterReply reply) { HandleEvent((NSDictionary*)message); }]; } AccessibilityBridge::~AccessibilityBridge() { - [accessibility_channel_.get() setMessageHandler:nil]; + [accessibility_channel_ setMessageHandler:nil]; clearState(); view_controller_.viewIfLoaded.accessibilityElements = nil; } UIView* AccessibilityBridge::textInputView() { - return [[platform_view_->GetOwnerViewController().get().engine textInputPlugin] textInputView]; + return [[platform_view_->GetOwnerViewController().engine textInputPlugin] textInputView]; } void AccessibilityBridge::AccessibilityObjectDidBecomeFocused(int32_t id) { last_focused_semantics_object_id_ = id; - [accessibility_channel_.get() sendMessage:@{@"type" : @"didGainFocus", @"nodeId" : @(id)}]; + [accessibility_channel_ sendMessage:@{@"type" : @"didGainFocus", @"nodeId" : @(id)}]; } void AccessibilityBridge::AccessibilityObjectDidLoseFocus(int32_t id) { @@ -152,7 +152,7 @@ void PostAccessibilityNotification(UIAccessibilityNotifications notification, } } - SemanticsObject* root = objects_.get()[@(kRootNodeId)]; + SemanticsObject* root = objects_[@(kRootNodeId)]; bool routeChanged = false; SemanticsObject* lastAdded = nil; @@ -196,13 +196,13 @@ void PostAccessibilityNotification(UIAccessibilityNotifications notification, view_controller_.viewIfLoaded.accessibilityElements = nil; } - NSMutableArray* doomed_uids = [NSMutableArray arrayWithArray:[objects_ allKeys]]; + NSMutableArray* doomed_uids = [NSMutableArray arrayWithArray:objects_.allKeys]; if (root) { VisitObjectsRecursivelyAndRemove(root, doomed_uids); } [objects_ removeObjectsForKeys:doomed_uids]; - for (SemanticsObject* object in [objects_ allValues]) { + for (SemanticsObject* object in objects_.allValues) { [object accessibilityBridgeDidFinishUpdate]; } @@ -217,8 +217,7 @@ void PostAccessibilityNotification(UIAccessibilityNotifications notification, if (layoutChanged) { SemanticsObject* next = FindNextFocusableIfNecessary(); - SemanticsObject* lastFocused = - [objects_.get() objectForKey:@(last_focused_semantics_object_id_)]; + SemanticsObject* lastFocused = [objects_ objectForKey:@(last_focused_semantics_object_id_)]; // Only specify the focus item if the new focus is different, avoiding double focuses on the // same item. See: https://github.com/flutter/flutter/issues/104176. If there is a route // change, we always refocus. @@ -272,11 +271,13 @@ static void ReplaceSemanticsObject(SemanticsObject* oldObject, } else if (node.HasFlag(flutter::SemanticsFlags::kHasImplicitScrolling)) { return [[FlutterScrollableSemanticsObject alloc] initWithBridge:weak_ptr uid:node.id]; } else if (node.IsPlatformViewNode()) { - return [[FlutterPlatformViewSemanticsContainer alloc] - initWithBridge:weak_ptr - uid:node.id - platformView:weak_ptr->GetPlatformViewsController()->GetFlutterTouchInterceptingViewByID( - node.platformViewId)]; + FlutterPlatformViewsController* platformViewsController = + weak_ptr->GetPlatformViewsController(); + FlutterTouchInterceptingView* touchInterceptingView = + [platformViewsController flutterTouchInterceptingViewForId:node.platformViewId]; + return [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:weak_ptr + uid:node.id + platformView:touchInterceptingView]; } else { return [[FlutterSemanticsObject alloc] initWithBridge:weak_ptr uid:node.id]; } @@ -290,10 +291,10 @@ static bool DidFlagChange(const flutter::SemanticsNode& oldNode, SemanticsObject* AccessibilityBridge::GetOrCreateObject(int32_t uid, flutter::SemanticsNodeUpdates& updates) { - SemanticsObject* object = objects_.get()[@(uid)]; + SemanticsObject* object = objects_[@(uid)]; if (!object) { object = CreateObject(updates[uid], GetWeakPtr()); - objects_.get()[@(uid)] = object; + objects_[@(uid)] = object; } else { // Existing node case auto nodeEntry = updates.find(object.node.id); @@ -309,7 +310,7 @@ static bool DidFlagChange(const flutter::SemanticsNode& oldNode, // SemanticsObject implementation. Instead, we replace it with a new // instance. SemanticsObject* newSemanticsObject = CreateObject(node, GetWeakPtr()); - ReplaceSemanticsObject(object, newSemanticsObject, objects_.get()); + ReplaceSemanticsObject(object, newSemanticsObject, objects_); object = newSemanticsObject; } } @@ -332,11 +333,11 @@ static bool DidFlagChange(const flutter::SemanticsNode& oldNode, } // Tries to refocus the previous focused semantics object to avoid random jumps. - return FindFirstFocusable([objects_.get() objectForKey:@(last_focused_semantics_object_id_)]); + return FindFirstFocusable(objects_[@(last_focused_semantics_object_id_)]); } SemanticsObject* AccessibilityBridge::FindFirstFocusable(SemanticsObject* parent) { - SemanticsObject* currentObject = parent ?: objects_.get()[@(kRootNodeId)]; + SemanticsObject* currentObject = parent ?: objects_[@(kRootNodeId)]; if (!currentObject) { return nil; } @@ -360,7 +361,7 @@ static bool DidFlagChange(const flutter::SemanticsNode& oldNode, ios_delegate_->PostAccessibilityNotification(UIAccessibilityAnnouncementNotification, message); } if ([type isEqualToString:@"focus"]) { - SemanticsObject* node = objects_.get()[annotatedEvent[@"nodeId"]]; + SemanticsObject* node = objects_[annotatedEvent[@"nodeId"]]; ios_delegate_->PostAccessibilityNotification(UIAccessibilityLayoutChangedNotification, node); } } diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h index 0da3646a2e802..2168ca050d072 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h @@ -12,9 +12,9 @@ #include "flutter/lib/ui/semantics/semantics_node.h" @class UIView; +@class FlutterPlatformViewsController; namespace flutter { -class PlatformViewsController; /// Interface that represents an accessibility bridge for iOS. class AccessibilityBridgeIos { @@ -39,7 +39,7 @@ class AccessibilityBridgeIos { * The input id is the uid of the newly focused SemanticObject. */ virtual void AccessibilityObjectDidLoseFocus(int32_t id) = 0; - virtual std::shared_ptr GetPlatformViewsController() const = 0; + virtual FlutterPlatformViewsController* GetPlatformViewsController() const = 0; }; } // namespace flutter diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index f89c02febf616..343ed99eae983 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -279,8 +279,9 @@ - (void)testSemanticsDeallocated { /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(thread_task_runner); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = thread_task_runner; auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -294,19 +295,22 @@ - (void)testSemanticsDeallocated { id mockFlutterViewController = OCMClassMock([FlutterViewController class]); OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView); std::string label = "some label"; - flutterPlatformViewsController->SetFlutterView(mockFlutterView); + flutterPlatformViewsController.flutterView = mockFlutterView; MockFlutterPlatformFactory* factory = [[MockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; auto bridge = std::make_unique( /*view_controller=*/mockFlutterViewController, @@ -322,7 +326,7 @@ - (void)testSemanticsDeallocated { flutter::CustomAccessibilityActionUpdates actions; bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions); XCTAssertNotNil(gMockPlatformView); - flutterPlatformViewsController->Reset(); + [flutterPlatformViewsController reset]; } XCTAssertNil(gMockPlatformView); } @@ -340,8 +344,9 @@ - (void)testSemanticsDeallocatedWithoutLoadingView { /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(thread_task_runner); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = thread_task_runner; auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -353,16 +358,19 @@ - (void)testSemanticsDeallocatedWithoutLoadingView { /*is_gpu_disabled_sync_switch=*/std::make_shared()); MockFlutterPlatformFactory* factory = [[MockFlutterPlatformFactory alloc] init]; - flutterPlatformViewsController->RegisterViewFactory( - factory, @"MockFlutterPlatformView", - FlutterPlatformViewGestureRecognizersBlockingPolicyEager); + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; FlutterResult result = ^(id result) { }; - flutterPlatformViewsController->OnMethodCall( - [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], - result); + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; auto bridge = std::make_unique( /*view_controller=*/flutterViewController, @@ -370,7 +378,7 @@ - (void)testSemanticsDeallocatedWithoutLoadingView { /*platform_views_controller=*/flutterPlatformViewsController); XCTAssertNotNil(gMockPlatformView); - flutterPlatformViewsController->Reset(); + [flutterPlatformViewsController reset]; platform_view->NotifyDestroyed(); } XCTAssertNil(gMockPlatformView); @@ -387,8 +395,9 @@ - (void)testReplacedSemanticsDoesNotCleanupChildren { /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(thread_task_runner); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = thread_task_runner; auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -484,8 +493,9 @@ - (void)testScrollableSemanticsDeallocated { /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(thread_task_runner); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = thread_task_runner; auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -559,8 +569,8 @@ - (void)testBridgeReplacesSemanticsNode { /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(thread_task_runner); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/mock_delegate.settings_.enable_impeller @@ -1402,9 +1412,7 @@ - (void)testAccessibilityObjectDidBecomeFocused { /*is_gpu_disabled_sync_switch=*/std::make_shared()); fml::AutoResetWaitableEvent latch; thread_task_runner->PostTask([&] { - auto weakFactory = - std::make_unique>(flutterViewController); - platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject()); + platform_view->SetOwnerViewController(flutterViewController); auto bridge = std::make_unique(/*view=*/nil, /*platform_view=*/platform_view.get(), @@ -2091,9 +2099,7 @@ - (void)testAccessibilityMessageAfterDeletion { /*is_gpu_disabled_sync_switch=*/std::make_shared()); fml::AutoResetWaitableEvent latch; thread_task_runner->PostTask([&] { - auto weakFactory = - std::make_unique>(flutterViewController); - platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject()); + platform_view->SetOwnerViewController(flutterViewController); auto bridge = std::make_unique(/*view=*/nil, /*platform_view=*/platform_view.get(), @@ -2179,14 +2185,13 @@ - (void)testPlatformViewDestructorDoesNotCallSemanticsAPIs { /*is_gpu_disabled_sync_switch=*/std::make_shared()); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); - auto flutterPlatformViewsController = std::make_shared(); - flutterPlatformViewsController->SetTaskRunner(thread_task_runner); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = thread_task_runner; OCMStub([mockFlutterViewController platformViewsController]) - .andReturn(flutterPlatformViewsController.get()); - auto weakFactory = std::make_unique>( - mockFlutterViewController); - platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject()); + .andReturn(flutterPlatformViewsController); + platform_view->SetOwnerViewController(mockFlutterViewController); platform_view->SetSemanticsEnabled(true); XCTAssertNotEqual(test_delegate.set_semantics_enabled_calls, 0); diff --git a/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h b/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h index bd04aa81a8bf5..530dc782c68f5 100644 --- a/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h +++ b/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h @@ -11,7 +11,6 @@ #import #include "flow/surface.h" -#include "fml/platform/darwin/scoped_nsobject.h" #import "flutter/shell/platform/darwin/ios/ios_context.h" @@ -21,15 +20,15 @@ class IOSSurface; /// @brief State holder for a Flutter overlay layer. struct OverlayLayer { - OverlayLayer(const fml::scoped_nsobject& overlay_view, - const fml::scoped_nsobject& overlay_view_wrapper, + OverlayLayer(UIView* overlay_view, + UIView* overlay_view_wrapper, std::unique_ptr ios_surface, std::unique_ptr surface); ~OverlayLayer() = default; - fml::scoped_nsobject overlay_view; - fml::scoped_nsobject overlay_view_wrapper; + UIView* overlay_view; + UIView* overlay_view_wrapper; std::unique_ptr ios_surface; std::unique_ptr surface; diff --git a/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm b/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm index 4d63a595fcc4f..ee409b778d9fa 100644 --- a/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm +++ b/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.mm @@ -9,8 +9,8 @@ namespace flutter { -OverlayLayer::OverlayLayer(const fml::scoped_nsobject& overlay_view, - const fml::scoped_nsobject& overlay_view_wrapper, +OverlayLayer::OverlayLayer(UIView* overlay_view, + UIView* overlay_view_wrapper, std::unique_ptr ios_surface, std::unique_ptr surface) : overlay_view(overlay_view), @@ -22,7 +22,6 @@ SkRect rect, int64_t view_id, int64_t overlay_id) { - UIView* overlay_view_wrapper = this->overlay_view_wrapper.get(); auto screenScale = [UIScreen mainScreen].scale; // Set the size of the overlay view wrapper. // This wrapper view masks the overlay view. @@ -32,7 +31,6 @@ overlay_view_wrapper.accessibilityIdentifier = [NSString stringWithFormat:@"platform_view[%lld].overlay[%lld]", view_id, overlay_id]; - UIView* overlay_view = this->overlay_view.get(); // Set the size of the overlay view. // This size is equal to the device screen size. overlay_view.frame = [flutter_view convertRect:flutter_view.bounds toView:overlay_view_wrapper]; @@ -59,32 +57,32 @@ MTLPixelFormat pixel_format) { FML_DCHECK([[NSThread currentThread] isMainThread]); std::shared_ptr layer; - fml::scoped_nsobject overlay_view; - fml::scoped_nsobject overlay_view_wrapper; + UIView* overlay_view; + UIView* overlay_view_wrapper; bool impeller_enabled = !!ios_context->GetImpellerContext(); if (!gr_context && !impeller_enabled) { - overlay_view.reset([[FlutterOverlayView alloc] init]); - overlay_view_wrapper.reset([[FlutterOverlayView alloc] init]); + overlay_view = [[FlutterOverlayView alloc] init]; + overlay_view_wrapper = [[FlutterOverlayView alloc] init]; - auto ca_layer = fml::scoped_nsobject{[overlay_view.get() layer]}; + CALayer* ca_layer = overlay_view.layer; std::unique_ptr ios_surface = IOSSurface::Create(ios_context, ca_layer); std::unique_ptr surface = ios_surface->CreateGPUSurface(); - layer = std::make_shared(std::move(overlay_view), std::move(overlay_view_wrapper), + layer = std::make_shared(overlay_view, overlay_view_wrapper, std::move(ios_surface), std::move(surface)); } else { CGFloat screenScale = [UIScreen mainScreen].scale; - overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale - pixelFormat:pixel_format]); - overlay_view_wrapper.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale - pixelFormat:pixel_format]); + overlay_view = [[FlutterOverlayView alloc] initWithContentsScale:screenScale + pixelFormat:pixel_format]; + overlay_view_wrapper = [[FlutterOverlayView alloc] initWithContentsScale:screenScale + pixelFormat:pixel_format]; - auto ca_layer = fml::scoped_nsobject{[overlay_view.get() layer]}; + CALayer* ca_layer = overlay_view.layer; std::unique_ptr ios_surface = IOSSurface::Create(ios_context, ca_layer); std::unique_ptr surface = ios_surface->CreateGPUSurface(gr_context); - layer = std::make_shared(std::move(overlay_view), std::move(overlay_view_wrapper), + layer = std::make_shared(overlay_view, overlay_view_wrapper, std::move(ios_surface), std::move(surface)); layer->gr_context = gr_context; } @@ -102,8 +100,8 @@ // | | wrapper | | == mask => | overlay_view | // | +--------------+ | +--------------+ // +------------------------+ - layer->overlay_view_wrapper.get().clipsToBounds = YES; - [layer->overlay_view_wrapper.get() addSubview:layer->overlay_view]; + layer->overlay_view_wrapper.clipsToBounds = YES; + [layer->overlay_view_wrapper addSubview:layer->overlay_view]; layers_.push_back(layer); } diff --git a/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h b/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h index 8db0d7dfd729f..9a83e500218e2 100644 --- a/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h +++ b/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h @@ -9,7 +9,6 @@ #include "flutter/fml/macros.h" #include "flutter/fml/make_copyable.h" -#include "flutter/fml/platform/darwin/scoped_block.h" #include "flutter/fml/task_runner.h" #include "flutter/lib/ui/window/platform_message_response.h" #import "flutter/shell/platform/darwin/common/buffer_conversions.h" @@ -30,7 +29,7 @@ class PlatformMessageResponseDarwin : public flutter::PlatformMessageResponse { ~PlatformMessageResponseDarwin() override; - fml::ScopedBlock callback_; + PlatformMessageResponseCallback callback_; fml::RefPtr platform_task_runner_; FML_FRIEND_MAKE_REF_COUNTED(PlatformMessageResponseDarwin); diff --git a/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm b/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm index 00c155af049ca..e8a719422b04e 100644 --- a/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm +++ b/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm @@ -13,22 +13,20 @@ PlatformMessageResponseDarwin::PlatformMessageResponseDarwin( PlatformMessageResponseCallback callback, fml::RefPtr platform_task_runner) - : callback_(callback, fml::scoped_policy::OwnershipPolicy::kRetain), - platform_task_runner_(std::move(platform_task_runner)) {} + : callback_(callback), platform_task_runner_(std::move(platform_task_runner)) {} PlatformMessageResponseDarwin::~PlatformMessageResponseDarwin() = default; void PlatformMessageResponseDarwin::Complete(std::unique_ptr data) { fml::RefPtr self(this); platform_task_runner_->PostTask(fml::MakeCopyable([self, data = std::move(data)]() mutable { - self->callback_.get()(CopyMappingPtrToNSData(std::move(data))); + self->callback_(CopyMappingPtrToNSData(std::move(data))); })); } void PlatformMessageResponseDarwin::CompleteEmpty() { fml::RefPtr self(this); - platform_task_runner_->PostTask( - fml::MakeCopyable([self]() mutable { self->callback_.get()(nil); })); + platform_task_runner_->PostTask(fml::MakeCopyable([self]() mutable { self->callback_(nil); })); } } // namespace flutter diff --git a/shell/platform/darwin/ios/framework/Source/platform_views_controller.h b/shell/platform/darwin/ios/framework/Source/platform_views_controller.h deleted file mode 100644 index 1079b6d767c3a..0000000000000 --- a/shell/platform/darwin/ios/framework/Source/platform_views_controller.h +++ /dev/null @@ -1,326 +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. - -#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PLATFORM_VIEWS_CONTROLLER_H_ -#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PLATFORM_VIEWS_CONTROLLER_H_ - -#include - -#include "flutter/flow/surface.h" -#include "flutter/fml/memory/weak_ptr.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" -#include "flutter/fml/task_runner.h" -#include "flutter/fml/trace_event.h" -#include "impeller/base/thread_safety.h" -#include "third_party/skia/include/core/SkRect.h" - -#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" -#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" -#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/overlay_layer_pool.h" -#import "flutter/shell/platform/darwin/ios/ios_context.h" - -@class FlutterTouchInterceptingView; -@class FlutterClippingMaskViewPool; - -namespace flutter { - -/// @brief Composites Flutter UI and overlay layers alongside embedded UIViews. -class PlatformViewsController { - public: - PlatformViewsController(); - - ~PlatformViewsController() = default; - - /// @brief Retrieve a weak pointer to this controller. - fml::WeakPtr GetWeakPtr(); - - /// @brief Set the platform task runner used to post rendering tasks. - void SetTaskRunner(const fml::RefPtr& platform_task_runner); - - /// @brief Set the flutter view. - void SetFlutterView(UIView* flutter_view) __attribute__((cf_audited_transfer)); - - /// @brief Set the flutter view controller. - void SetFlutterViewController(UIViewController* flutter_view_controller) - __attribute__((cf_audited_transfer)); - - /// @brief Retrieve the view controller. - UIViewController* GetFlutterViewController() - __attribute__((cf_audited_transfer)); - - /// @brief set the factory used to construct embedded UI Views. - void RegisterViewFactory( - NSObject* factory, - NSString* factoryId, - FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) - __attribute__((cf_audited_transfer)); - - /// @brief Mark the beginning of a frame and record the size of the onscreen. - void BeginFrame(SkISize frame_size); - - /// @brief Cancel the current frame, indicating that no platform views are composited. - /// - /// Additionally, reverts the composition order to its original state at the beginning of the - /// frame. - void CancelFrame(); - - /// @brief Record a platform view in the layer tree to be rendered, along with the positioning and - /// mutator parameters. - /// - /// Called from the raster thread. - void PrerollCompositeEmbeddedView(int64_t view_id, - std::unique_ptr params); - - /// @brief Returns the`FlutterTouchInterceptingView` with the provided view_id. - /// - /// Returns nil if there is no platform view with the provided id. Called - /// from the platform thread. - FlutterTouchInterceptingView* GetFlutterTouchInterceptingViewByID(int64_t view_id); - - /// @brief Determine if thread merging is required after prerolling platform views. - /// - /// Called from the raster thread. - PostPrerollResult PostPrerollAction( - const fml::RefPtr& raster_thread_merger, - bool impeller_enabled); - - /// @brief Mark the end of a compositor frame. - /// - /// May determine changes are required to the thread merging state. - /// Called from the raster thread. - void EndFrame(bool should_resubmit_frame, - const fml::RefPtr& raster_thread_merger, - bool impeller_enabled); - - /// @brief Returns the Canvas for the overlay slice for the given platform view. - /// - /// Called from the raster thread. - DlCanvas* CompositeEmbeddedView(int64_t view_id); - - /// @brief Discards all platform views instances and auxiliary resources. - /// - /// Called from the raster thread. - void Reset(); - - /// @brief Encode rendering for the Flutter overlay views and queue up perform platform view - /// mutations. - /// - /// Called from the raster thread. - bool SubmitFrame(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - std::unique_ptr frame); - - /// @brief Handler for platform view message channels. - void OnMethodCall(FlutterMethodCall* call, FlutterResult result) - __attribute__((cf_audited_transfer)); - - /// @brief Returns the platform view id if the platform view (or any of its descendant view) is - /// the first responder. - /// - /// Returns -1 if no such platform view is found. - long FindFirstResponderPlatformViewId(); - - /// @brief Pushes backdrop filter mutation to the mutator stack of each visited platform view. - void PushFilterToVisitedPlatformViews(const std::shared_ptr& filter, - const SkRect& filter_rect); - - /// @brief Pushes the view id of a visted platform view to the list of visied platform views. - void PushVisitedPlatformView(int64_t view_id) { visited_platform_views_.push_back(view_id); } - - // visible for testing. - size_t EmbeddedViewCount() const; - - // visible for testing. - size_t LayerPoolSize() const; - - // visible for testing. - // Returns the `FlutterPlatformView`'s `view` object associated with the view_id. - // - // If the `PlatformViewsController` does not contain any `FlutterPlatformView` object or - // a `FlutterPlatformView` object associated with the view_id cannot be found, the method - // returns nil. - UIView* GetPlatformViewByID(int64_t view_id); - - // Visible for testing. - void CompositeWithParams(int64_t view_id, const EmbeddedViewParams& params); - - // Visible for testing. - const EmbeddedViewParams& GetCompositionParams(int64_t view_id) const { - return current_composition_params_.find(view_id)->second; - } - - private: - PlatformViewsController(const PlatformViewsController&) = delete; - PlatformViewsController& operator=(const PlatformViewsController&) = delete; - - struct LayerData { - SkRect rect; - int64_t view_id; - int64_t overlay_id; - std::shared_ptr layer; - }; - - using LayersMap = std::unordered_map; - - // Update the buffers and mutate the platform views in CATransaction. - // - // Runs on the platform thread. - void PerformSubmit(const LayersMap& platform_view_layers, - std::unordered_map& current_composition_params, - const std::unordered_set& views_to_recomposite, - const std::vector& composition_order, - const std::vector>& unused_layers, - const std::vector>& surface_frames); - - /// @brief Populate any missing overlay layers. - /// - /// This requires posting a task to the platform thread and blocking on its completion. - void CreateMissingOverlays(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - size_t required_overlay_layers); - - void OnCreate(FlutterMethodCall* call, FlutterResult result) __attribute__((cf_audited_transfer)); - - void OnDispose(FlutterMethodCall* call, FlutterResult result) - __attribute__((cf_audited_transfer)); - - void OnAcceptGesture(FlutterMethodCall* call, FlutterResult result) - __attribute__((cf_audited_transfer)); - - void OnRejectGesture(FlutterMethodCall* call, FlutterResult result) - __attribute__((cf_audited_transfer)); - - /// @brief Return all views to be disposed on the platform thread. - std::vector GetViewsToDispose(); - - void ClipViewSetMaskView(UIView* clipView) __attribute__((cf_audited_transfer)); - - // Applies the mutators in the mutators_stack to the UIView chain that was constructed by - // `ReconstructClipViewsChain` - // - // Clips are applied to the `embedded_view`'s super view(|ChildClippingView|) using a - // |FlutterClippingMaskView|. Transforms are applied to `embedded_view` - // - // The `bounding_rect` is the final bounding rect of the PlatformView - // (EmbeddedViewParams::finalBoundingRect). If a clip mutator's rect contains the final bounding - // rect of the PlatformView, the clip mutator is not applied for performance optimization. - void ApplyMutators(const MutatorsStack& mutators_stack, - UIView* embedded_view, - const SkRect& bounding_rect) __attribute__((cf_audited_transfer)); - - std::shared_ptr GetExistingLayer(); - - // Runs on the platform thread. - void CreateLayer(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - MTLPixelFormat pixel_format); - - // Removes overlay views and platform views that aren't needed in the current frame. - // Must run on the platform thread. - void RemoveUnusedLayers(const std::vector>& unused_layers, - const std::vector& composition_order); - - // Appends the overlay views and platform view and sets their z index based on the composition - // order. - void BringLayersIntoView(const LayersMap& layer_map, - const std::vector& composition_order); - - // Resets the state of the frame. - void ResetFrameState(); - - // The pool of reusable view layers. The pool allows to recycle layer in each frame. - std::unique_ptr layer_pool_; - - // The platform view's |EmbedderViewSlice| keyed off the view id, which contains any subsequent - // operation until the next platform view or the end of the last leaf node in the layer tree. - // - // The Slices are deleted by the PlatformViewsController.reset(). - std::unordered_map> slices_; - - fml::scoped_nsobject channel_; - fml::scoped_nsobject flutter_view_; - fml::scoped_nsobject> flutter_view_controller_; - fml::scoped_nsobject mask_view_pool_; - std::unordered_map>> - factories_; - - // The FlutterPlatformViewGestureRecognizersBlockingPolicy for each type of platform view. - std::unordered_map - gesture_recognizers_blocking_policies_; - - /// The size of the current onscreen surface in physical pixels. - SkISize frame_size_; - - /// The task runner for posting tasks to the platform thread. - fml::RefPtr platform_task_runner_; - - /// Each of the following structs stores part of the platform view hierarchy according to its - /// ID. - /// - /// This data must only be accessed on the platform thread. - struct PlatformViewData { - fml::scoped_nsobject> view; - fml::scoped_nsobject touch_interceptor; - fml::scoped_nsobject root_view; - }; - - /// This data must only be accessed on the platform thread. - std::unordered_map platform_views_; - - /// The composition parameters for each platform view. - /// - /// This state is only modified on the raster thread. - std::unordered_map current_composition_params_; - - /// Method channel `OnDispose` calls adds the views to be disposed to this set to be disposed on - /// the next frame. - /// - /// This state is modified on both the platform and raster thread. - std::unordered_set views_to_dispose_; - - /// view IDs in composition order. - /// - /// This state is only modified on the raster thread. - std::vector composition_order_; - - /// platform view IDs visited during layer tree composition. - /// - /// This state is only modified on the raster thread. - std::vector visited_platform_views_; - - /// Only composite platform views in this set. - /// - /// This state is only modified on the raster thread. - std::unordered_set views_to_recomposite_; - -#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG - /// A set to keep track of embedded views that do not have (0, 0) origin. - /// An insertion triggers a warning message about non-zero origin logged on the debug console. - /// See https://github.com/flutter/flutter/issues/109700 for details. - std::unordered_set non_zero_origin_views_; -#endif - - /// @brief The composition order from the previous thread. - /// - /// Only accessed from the platform thread. - std::vector previous_composition_order_; - - /// Whether the previous frame had any platform views in active composition order. - /// - /// This state is tracked so that the first frame after removing the last platform view - /// runs through the platform view rendering code path, giving us a chance to remove the - /// platform view from the UIView hierarchy. - /// - /// Only accessed from the raster thread. - bool had_platform_views_ = false; - - // WeakPtrFactory must be the last member. - std::unique_ptr> weak_factory_; -}; - -} // namespace flutter - -#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PLATFORM_VIEWS_CONTROLLER_H_ diff --git a/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm b/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm deleted file mode 100644 index 7f8a73294953c..0000000000000 --- a/shell/platform/darwin/ios/framework/Source/platform_views_controller.mm +++ /dev/null @@ -1,893 +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. - -#import "shell/platform/darwin/ios/framework/Source/platform_views_controller.h" - -#include "flow/surface_frame.h" -#include "flutter/flow/view_slicer.h" -#include "flutter/fml/make_copyable.h" -#include "fml/synchronization/count_down_latch.h" - -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" -#import "flutter/shell/platform/darwin/ios/ios_surface.h" - -namespace { - -// The number of frames the rasterizer task runner will continue -// to run on the platform thread after no platform view is rendered. -// -// Note: this is an arbitrary number. -static const int kDefaultMergedLeaseDuration = 10; - -static constexpr NSUInteger kFlutterClippingMaskViewPoolCapacity = 5; - -// Converts a SkMatrix to CATransform3D. -// -// Certain fields are ignored in CATransform3D since SkMatrix is 3x3 and CATransform3D is 4x4. -CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix) { - // Skia only supports 2D transform so we don't map z. - CATransform3D transform = CATransform3DIdentity; - transform.m11 = matrix.getScaleX(); - transform.m21 = matrix.getSkewX(); - transform.m41 = matrix.getTranslateX(); - transform.m14 = matrix.getPerspX(); - - transform.m12 = matrix.getSkewY(); - transform.m22 = matrix.getScaleY(); - transform.m42 = matrix.getTranslateY(); - transform.m24 = matrix.getPerspY(); - return transform; -} - -// Reset the anchor of `layer` to match the transform operation from flow. -// -// The position of the `layer` should be unchanged after resetting the anchor. -void ResetAnchor(CALayer* layer) { - // Flow uses (0, 0) to apply transform matrix so we need to match that in Quartz. - layer.anchorPoint = CGPointZero; - layer.position = CGPointZero; -} - -CGRect GetCGRectFromSkRect(const SkRect& clipSkRect) { - return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft, - clipSkRect.fBottom - clipSkRect.fTop); -} - -// Determines if the `clip_rect` from a clipRect mutator contains the -// `platformview_boundingrect`. -// -// `clip_rect` is in its own coordinate space. The rect needs to be transformed by -// `transform_matrix` to be in the coordinate space where the PlatformView is displayed. -// -// `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate -// space where the PlatformView is displayed. -bool ClipRectContainsPlatformViewBoundingRect(const SkRect& clip_rect, - const SkRect& platformview_boundingrect, - const SkMatrix& transform_matrix) { - SkRect transformed_rect = transform_matrix.mapRect(clip_rect); - return transformed_rect.contains(platformview_boundingrect); -} - -// Determines if the `clipRRect` from a clipRRect mutator contains the -// `platformview_boundingrect`. -// -// `clip_rrect` is in its own coordinate space. The rrect needs to be transformed by -// `transform_matrix` to be in the coordinate space where the PlatformView is displayed. -// -// `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate -// space where the PlatformView is displayed. -bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, - const SkRect& platformview_boundingrect, - const SkMatrix& transform_matrix) { - SkVector upper_left = clip_rrect.radii(SkRRect::Corner::kUpperLeft_Corner); - SkVector upper_right = clip_rrect.radii(SkRRect::Corner::kUpperRight_Corner); - SkVector lower_right = clip_rrect.radii(SkRRect::Corner::kLowerRight_Corner); - SkVector lower_left = clip_rrect.radii(SkRRect::Corner::kLowerLeft_Corner); - SkScalar transformed_upper_left_x = transform_matrix.mapRadius(upper_left.x()); - SkScalar transformed_upper_left_y = transform_matrix.mapRadius(upper_left.y()); - SkScalar transformed_upper_right_x = transform_matrix.mapRadius(upper_right.x()); - SkScalar transformed_upper_right_y = transform_matrix.mapRadius(upper_right.y()); - SkScalar transformed_lower_right_x = transform_matrix.mapRadius(lower_right.x()); - SkScalar transformed_lower_right_y = transform_matrix.mapRadius(lower_right.y()); - SkScalar transformed_lower_left_x = transform_matrix.mapRadius(lower_left.x()); - SkScalar transformed_lower_left_y = transform_matrix.mapRadius(lower_left.y()); - SkRect transformed_clip_rect = transform_matrix.mapRect(clip_rrect.rect()); - SkRRect transformed_rrect; - SkVector corners[] = {{transformed_upper_left_x, transformed_upper_left_y}, - {transformed_upper_right_x, transformed_upper_right_y}, - {transformed_lower_right_x, transformed_lower_right_y}, - {transformed_lower_left_x, transformed_lower_left_y}}; - transformed_rrect.setRectRadii(transformed_clip_rect, corners); - return transformed_rrect.contains(platformview_boundingrect); -} - -} // namespace - -namespace flutter { - -// Becomes NO if Apple's API changes and blurred backdrop filters cannot be applied. -BOOL canApplyBlurBackdrop = YES; - -PlatformViewsController::PlatformViewsController() - : layer_pool_(std::make_unique()), - weak_factory_(std::make_unique>(this)) { - mask_view_pool_.reset( - [[FlutterClippingMaskViewPool alloc] initWithCapacity:kFlutterClippingMaskViewPoolCapacity]); -}; - -void PlatformViewsController::SetTaskRunner( - const fml::RefPtr& platform_task_runner) { - platform_task_runner_ = platform_task_runner; -} - -fml::WeakPtr PlatformViewsController::GetWeakPtr() { - return weak_factory_->GetWeakPtr(); -} - -void PlatformViewsController::SetFlutterView(UIView* flutter_view) { - flutter_view_.reset(flutter_view); -} - -void PlatformViewsController::SetFlutterViewController( - UIViewController* flutter_view_controller) { - flutter_view_controller_.reset(flutter_view_controller); -} - -UIViewController* PlatformViewsController::GetFlutterViewController() { - return flutter_view_controller_.get(); -} - -void PlatformViewsController::OnMethodCall(FlutterMethodCall* call, FlutterResult result) { - if ([[call method] isEqualToString:@"create"]) { - OnCreate(call, result); - } else if ([[call method] isEqualToString:@"dispose"]) { - OnDispose(call, result); - } else if ([[call method] isEqualToString:@"acceptGesture"]) { - OnAcceptGesture(call, result); - } else if ([[call method] isEqualToString:@"rejectGesture"]) { - OnRejectGesture(call, result); - } else { - result(FlutterMethodNotImplemented); - } -} - -void PlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterResult result) { - NSDictionary* args = [call arguments]; - - int64_t viewId = [args[@"id"] longLongValue]; - NSString* viewTypeString = args[@"viewType"]; - std::string viewType(viewTypeString.UTF8String); - - if (platform_views_.count(viewId) != 0) { - result([FlutterError errorWithCode:@"recreating_view" - message:@"trying to create an already created view" - details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); - return; - } - - NSObject* factory = factories_[viewType].get(); - if (factory == nil) { - result([FlutterError - errorWithCode:@"unregistered_view_type" - message:[NSString stringWithFormat:@"A UIKitView widget is trying to create a " - @"PlatformView with an unregistered type: < %@ >", - viewTypeString] - details:@"If you are the author of the PlatformView, make sure `registerViewFactory` " - @"is invoked.\n" - @"See: " - @"https://docs.flutter.dev/development/platform-integration/" - @"platform-views#on-the-platform-side-1 for more details.\n" - @"If you are not the author of the PlatformView, make sure to call " - @"`GeneratedPluginRegistrant.register`."]); - return; - } - - id params = nil; - if ([factory respondsToSelector:@selector(createArgsCodec)]) { - NSObject* codec = [factory createArgsCodec]; - if (codec != nil && args[@"params"] != nil) { - FlutterStandardTypedData* paramsData = args[@"params"]; - params = [codec decode:paramsData.data]; - } - } - - NSObject* embedded_view = [factory createWithFrame:CGRectZero - viewIdentifier:viewId - arguments:params]; - UIView* platform_view = [embedded_view view]; - // Set a unique view identifier, so the platform view can be identified in unit tests. - platform_view.accessibilityIdentifier = - [NSString stringWithFormat:@"platform_view[%lld]", viewId]; - - FlutterTouchInterceptingView* touch_interceptor = [[FlutterTouchInterceptingView alloc] - initWithEmbeddedView:platform_view - platformViewsController:GetWeakPtr() - gestureRecognizersBlockingPolicy:gesture_recognizers_blocking_policies_[viewType]]; - - ChildClippingView* clipping_view = [[ChildClippingView alloc] initWithFrame:CGRectZero]; - [clipping_view addSubview:touch_interceptor]; - - platform_views_.emplace( - viewId, PlatformViewData{ - .view = fml::scoped_nsobject>(embedded_view), // - .touch_interceptor = - fml::scoped_nsobject(touch_interceptor), // - .root_view = fml::scoped_nsobject(clipping_view) // - }); - - result(nil); -} - -void PlatformViewsController::OnDispose(FlutterMethodCall* call, FlutterResult result) { - NSNumber* arg = [call arguments]; - int64_t viewId = [arg longLongValue]; - - if (platform_views_.count(viewId) == 0) { - result([FlutterError errorWithCode:@"unknown_view" - message:@"trying to dispose an unknown" - details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); - return; - } - // We wait for next submitFrame to dispose views. - views_to_dispose_.insert(viewId); - result(nil); -} - -void PlatformViewsController::OnAcceptGesture(FlutterMethodCall* call, FlutterResult result) { - NSDictionary* args = [call arguments]; - int64_t viewId = [args[@"id"] longLongValue]; - - if (platform_views_.count(viewId) == 0) { - result([FlutterError errorWithCode:@"unknown_view" - message:@"trying to set gesture state for an unknown view" - details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); - return; - } - - FlutterTouchInterceptingView* view = platform_views_[viewId].touch_interceptor.get(); - [view releaseGesture]; - - result(nil); -} - -void PlatformViewsController::OnRejectGesture(FlutterMethodCall* call, FlutterResult result) { - NSDictionary* args = [call arguments]; - int64_t viewId = [args[@"id"] longLongValue]; - - if (platform_views_.count(viewId) == 0) { - result([FlutterError errorWithCode:@"unknown_view" - message:@"trying to set gesture state for an unknown view" - details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); - return; - } - - FlutterTouchInterceptingView* view = platform_views_[viewId].touch_interceptor.get(); - [view blockGesture]; - - result(nil); -} - -void PlatformViewsController::RegisterViewFactory( - NSObject* factory, - NSString* factoryId, - FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) { - std::string idString([factoryId UTF8String]); - FML_CHECK(factories_.count(idString) == 0); - factories_[idString] = fml::scoped_nsobject>(factory); - gesture_recognizers_blocking_policies_[idString] = gestureRecognizerBlockingPolicy; -} - -void PlatformViewsController::BeginFrame(SkISize frame_size) { - ResetFrameState(); - frame_size_ = frame_size; -} - -void PlatformViewsController::CancelFrame() { - ResetFrameState(); -} - -PostPrerollResult PlatformViewsController::PostPrerollAction( - const fml::RefPtr& raster_thread_merger, - bool impeller_enabled) { - // TODO(jonahwilliams): remove this once Software backend is removed for iOS Sim. -#ifdef FML_OS_IOS_SIMULATOR - const bool merge_threads = true; -#else - const bool merge_threads = !impeller_enabled; -#endif // FML_OS_IOS_SIMULATOR - - if (merge_threads) { - if (composition_order_.empty()) { - return PostPrerollResult::kSuccess; - } - if (!raster_thread_merger->IsMerged()) { - // The raster thread merger may be disabled if the rasterizer is being - // created or teared down. - // - // In such cases, the current frame is dropped, and a new frame is attempted - // with the same layer tree. - // - // Eventually, the frame is submitted once this method returns `kSuccess`. - // At that point, the raster tasks are handled on the platform thread. - CancelFrame(); - return PostPrerollResult::kSkipAndRetryFrame; - } - // If the post preroll action is successful, we will display platform views in the current - // frame. In order to sync the rendering of the platform views (quartz) with skia's rendering, - // We need to begin an explicit CATransaction. This transaction needs to be submitted - // after the current frame is submitted. - raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration); - } - return PostPrerollResult::kSuccess; -} - -void PlatformViewsController::EndFrame( - bool should_resubmit_frame, - const fml::RefPtr& raster_thread_merger, - bool impeller_enabled) { -#if FML_OS_IOS_SIMULATOR - bool run_check = true; -#else - bool run_check = !impeller_enabled; -#endif // FML_OS_IOS_SIMULATOR - if (run_check && should_resubmit_frame) { - raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration); - } -} - -void PlatformViewsController::PushFilterToVisitedPlatformViews( - const std::shared_ptr& filter, - const SkRect& filter_rect) { - for (int64_t id : visited_platform_views_) { - EmbeddedViewParams params = current_composition_params_[id]; - params.PushImageFilter(filter, filter_rect); - current_composition_params_[id] = params; - } -} - -void PlatformViewsController::PrerollCompositeEmbeddedView( - int64_t view_id, - std::unique_ptr params) { - SkRect view_bounds = SkRect::Make(frame_size_); - std::unique_ptr view; - view = std::make_unique(view_bounds); - slices_.insert_or_assign(view_id, std::move(view)); - - composition_order_.push_back(view_id); - - if (current_composition_params_.count(view_id) == 1 && - current_composition_params_[view_id] == *params.get()) { - // Do nothing if the params didn't change. - return; - } - current_composition_params_[view_id] = EmbeddedViewParams(*params.get()); - views_to_recomposite_.insert(view_id); -} - -size_t PlatformViewsController::EmbeddedViewCount() const { - return composition_order_.size(); -} - -size_t PlatformViewsController::LayerPoolSize() const { - return layer_pool_->size(); -} - -UIView* PlatformViewsController::GetPlatformViewByID(int64_t view_id) { - return [GetFlutterTouchInterceptingViewByID(view_id) embeddedView]; -} - -FlutterTouchInterceptingView* PlatformViewsController::GetFlutterTouchInterceptingViewByID( - int64_t view_id) { - if (platform_views_.empty()) { - return nil; - } - return platform_views_[view_id].touch_interceptor.get(); -} - -long PlatformViewsController::FindFirstResponderPlatformViewId() { - for (auto const& [id, platform_view_data] : platform_views_) { - UIView* root_view = (UIView*)platform_view_data.root_view.get(); - if (root_view.flt_hasFirstResponderInViewHierarchySubtree) { - return id; - } - } - return -1; -} - -void PlatformViewsController::ClipViewSetMaskView(UIView* clipView) { - FML_DCHECK([[NSThread currentThread] isMainThread]); - if (clipView.maskView) { - return; - } - UIView* flutterView = flutter_view_.get(); - CGRect frame = - CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y, - CGRectGetWidth(flutterView.bounds), CGRectGetHeight(flutterView.bounds)); - clipView.maskView = [mask_view_pool_.get() getMaskViewWithFrame:frame]; -} - -// This method is only called when the `embedded_view` needs to be re-composited at the current -// frame. See: `CompositeWithParams` for details. -void PlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack, - UIView* embedded_view, - const SkRect& bounding_rect) { - if (flutter_view_ == nullptr) { - return; - } - - ResetAnchor(embedded_view.layer); - ChildClippingView* clipView = (ChildClippingView*)embedded_view.superview; - - SkMatrix transformMatrix; - NSMutableArray* blurFilters = [[NSMutableArray alloc] init]; - FML_DCHECK(!clipView.maskView || - [clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]); - if (clipView.maskView) { - [mask_view_pool_.get() insertViewToPoolIfNeeded:(FlutterClippingMaskView*)(clipView.maskView)]; - clipView.maskView = nil; - } - CGFloat screenScale = [UIScreen mainScreen].scale; - auto iter = mutators_stack.Begin(); - while (iter != mutators_stack.End()) { - switch ((*iter)->GetType()) { - case kTransform: { - transformMatrix.preConcat((*iter)->GetMatrix()); - break; - } - case kClipRect: { - if (ClipRectContainsPlatformViewBoundingRect((*iter)->GetRect(), bounding_rect, - transformMatrix)) { - break; - } - ClipViewSetMaskView(clipView); - [(FlutterClippingMaskView*)clipView.maskView clipRect:(*iter)->GetRect() - matrix:transformMatrix]; - break; - } - case kClipRRect: { - if (ClipRRectContainsPlatformViewBoundingRect((*iter)->GetRRect(), bounding_rect, - transformMatrix)) { - break; - } - ClipViewSetMaskView(clipView); - [(FlutterClippingMaskView*)clipView.maskView clipRRect:(*iter)->GetRRect() - matrix:transformMatrix]; - break; - } - case kClipPath: { - // TODO(cyanglaz): Find a way to pre-determine if path contains the PlatformView boudning - // rect. See `ClipRRectContainsPlatformViewBoundingRect`. - // https://github.com/flutter/flutter/issues/118650 - ClipViewSetMaskView(clipView); - [(FlutterClippingMaskView*)clipView.maskView clipPath:(*iter)->GetPath() - matrix:transformMatrix]; - break; - } - case kOpacity: - embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha; - break; - case kBackdropFilter: { - // Only support DlBlurImageFilter for BackdropFilter. - if (!canApplyBlurBackdrop || !(*iter)->GetFilterMutation().GetFilter().asBlur()) { - break; - } - CGRect filterRect = GetCGRectFromSkRect((*iter)->GetFilterMutation().GetFilterRect()); - // `filterRect` is in global coordinates. We need to convert to local space. - filterRect = CGRectApplyAffineTransform( - filterRect, CGAffineTransformMakeScale(1 / screenScale, 1 / screenScale)); - // `filterRect` reprents the rect that should be filtered inside the `flutter_view_`. - // The `PlatformViewFilter` needs the frame inside the `clipView` that needs to be - // filtered. - if (CGRectIsNull(CGRectIntersection(filterRect, clipView.frame))) { - break; - } - CGRect intersection = CGRectIntersection(filterRect, clipView.frame); - CGRect frameInClipView = [flutter_view_.get() convertRect:intersection toView:clipView]; - // sigma_x is arbitrarily chosen as the radius value because Quartz sets - // sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode - // is not supported in Quartz's gaussianBlur CAFilter, so it is not used - // to blur the PlatformView. - CGFloat blurRadius = (*iter)->GetFilterMutation().GetFilter().asBlur()->sigma_x(); - UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] - initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - PlatformViewFilter* filter = [[PlatformViewFilter alloc] initWithFrame:frameInClipView - blurRadius:blurRadius - visualEffectView:visualEffectView]; - if (!filter) { - canApplyBlurBackdrop = NO; - } else { - [blurFilters addObject:filter]; - } - break; - } - } - ++iter; - } - - if (canApplyBlurBackdrop) { - [clipView applyBlurBackdropFilters:blurFilters]; - } - - // The UIKit frame is set based on the logical resolution (points) instead of physical. - // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html). - // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals - // 500 points in UIKit for devices that has screenScale of 2. We need to scale the transformMatrix - // down to the logical resoltion before applying it to the layer of PlatformView. - transformMatrix.postScale(1 / screenScale, 1 / screenScale); - - // Reverse the offset of the clipView. - // The clipView's frame includes the final translate of the final transform matrix. - // Thus, this translate needs to be reversed so the platform view can layout at the correct - // offset. - // - // Note that the transforms are not applied to the clipping paths because clipping paths happen on - // the mask view, whose origin is always (0,0) to the flutter_view. - transformMatrix.postTranslate(-clipView.frame.origin.x, -clipView.frame.origin.y); - - embedded_view.layer.transform = GetCATransform3DFromSkMatrix(transformMatrix); -} - -// Composite the PlatformView with `view_id`. -// -// Every frame, during the paint traversal of the layer tree, this method is called for all -// the PlatformViews in `views_to_recomposite_`. -// -// Note that `views_to_recomposite_` does not represent all the views in the view hierarchy, -// if a PlatformView does not change its composition parameter from last frame, it is not -// included in the `views_to_recomposite_`. -void PlatformViewsController::CompositeWithParams(int64_t view_id, - const EmbeddedViewParams& params) { - CGRect frame = CGRectMake(0, 0, params.sizePoints().width(), params.sizePoints().height()); - FlutterTouchInterceptingView* touchInterceptor = platform_views_[view_id].touch_interceptor.get(); -#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG - FML_DCHECK(CGPointEqualToPoint([touchInterceptor embeddedView].frame.origin, CGPointZero)); - if (non_zero_origin_views_.find(view_id) == non_zero_origin_views_.end() && - !CGPointEqualToPoint([touchInterceptor embeddedView].frame.origin, CGPointZero)) { - non_zero_origin_views_.insert(view_id); - NSLog( - @"A Embedded PlatformView's origin is not CGPointZero.\n" - " View id: %@\n" - " View info: \n %@ \n" - "A non-zero origin might cause undefined behavior.\n" - "See https://github.com/flutter/flutter/issues/109700 for more details.\n" - "If you are the author of the PlatformView, please update the implementation of the " - "PlatformView to have a (0, 0) origin.\n" - "If you have a valid case of using a non-zero origin, " - "please leave a comment at https://github.com/flutter/flutter/issues/109700 with details.", - @(view_id), [touchInterceptor embeddedView]); - } -#endif - touchInterceptor.layer.transform = CATransform3DIdentity; - touchInterceptor.frame = frame; - touchInterceptor.alpha = 1; - - const MutatorsStack& mutatorStack = params.mutatorsStack(); - UIView* clippingView = platform_views_[view_id].root_view.get(); - // The frame of the clipping view should be the final bounding rect. - // Because the translate matrix in the Mutator Stack also includes the offset, - // when we apply the transforms matrix in |ApplyMutators|, we need - // to remember to do a reverse translate. - const SkRect& rect = params.finalBoundingRect(); - CGFloat screenScale = [UIScreen mainScreen].scale; - clippingView.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale, - rect.width() / screenScale, rect.height() / screenScale); - ApplyMutators(mutatorStack, touchInterceptor, rect); -} - -DlCanvas* PlatformViewsController::CompositeEmbeddedView(int64_t view_id) { - return slices_[view_id]->canvas(); -} - -void PlatformViewsController::Reset() { - // Reset will only be called from the raster thread or a merged raster/platform thread. - // platform_views_ must only be modified on the platform thread, and any operations that - // read or modify platform views should occur there. - fml::TaskRunner::RunNowOrPostTask( - platform_task_runner_, [&, composition_order = composition_order_]() { - for (int64_t view_id : composition_order_) { - [platform_views_[view_id].root_view.get() removeFromSuperview]; - } - platform_views_.clear(); - }); - - composition_order_.clear(); - slices_.clear(); - current_composition_params_.clear(); - views_to_recomposite_.clear(); - layer_pool_->RecycleLayers(); - visited_platform_views_.clear(); -} - -bool PlatformViewsController::SubmitFrame(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - std::unique_ptr background_frame) { - TRACE_EVENT0("flutter", "PlatformViewsController::SubmitFrame"); - - // No platform views to render; we're done. - if (flutter_view_ == nullptr || (composition_order_.empty() && !had_platform_views_)) { - had_platform_views_ = false; - return background_frame->Submit(); - } - had_platform_views_ = !composition_order_.empty(); - - bool did_encode = true; - LayersMap platform_view_layers; - std::vector> surface_frames; - surface_frames.reserve(composition_order_.size()); - std::unordered_map view_rects; - - for (int64_t view_id : composition_order_) { - view_rects[view_id] = current_composition_params_[view_id].finalBoundingRect(); - } - - std::unordered_map overlay_layers = - SliceViews(background_frame->Canvas(), composition_order_, slices_, view_rects); - - size_t required_overlay_layers = 0; - for (int64_t view_id : composition_order_) { - std::unordered_map::const_iterator overlay = overlay_layers.find(view_id); - if (overlay == overlay_layers.end()) { - continue; - } - required_overlay_layers++; - } - - // If there are not sufficient overlay layers, we must construct them on the platform - // thread, at least until we've refactored iOS surface creation to use IOSurfaces - // instead of CALayers. - CreateMissingOverlays(gr_context, ios_context, required_overlay_layers); - - int64_t overlay_id = 0; - for (int64_t view_id : composition_order_) { - std::unordered_map::const_iterator overlay = overlay_layers.find(view_id); - if (overlay == overlay_layers.end()) { - continue; - } - std::shared_ptr layer = GetExistingLayer(); - if (!layer) { - continue; - } - - std::unique_ptr frame = layer->surface->AcquireFrame(frame_size_); - // If frame is null, AcquireFrame already printed out an error message. - if (!frame) { - continue; - } - DlCanvas* overlay_canvas = frame->Canvas(); - int restore_count = overlay_canvas->GetSaveCount(); - overlay_canvas->Save(); - overlay_canvas->ClipRect(overlay->second); - overlay_canvas->Clear(DlColor::kTransparent()); - slices_[view_id]->render_into(overlay_canvas); - overlay_canvas->RestoreToCount(restore_count); - - // This flutter view is never the last in a frame, since we always submit the - // underlay view last. - frame->set_submit_info({.frame_boundary = false, .present_with_transaction = true}); - layer->did_submit_last_frame = frame->Encode(); - - did_encode &= layer->did_submit_last_frame; - platform_view_layers[view_id] = LayerData{ - .rect = overlay->second, // - .view_id = view_id, // - .overlay_id = overlay_id, // - .layer = layer // - }; - surface_frames.push_back(std::move(frame)); - overlay_id++; - } - - auto previous_submit_info = background_frame->submit_info(); - background_frame->set_submit_info({ - .frame_damage = previous_submit_info.frame_damage, - .buffer_damage = previous_submit_info.buffer_damage, - .present_with_transaction = true, - }); - background_frame->Encode(); - surface_frames.push_back(std::move(background_frame)); - - // Mark all layers as available, so they can be used in the next frame. - std::vector> unused_layers = layer_pool_->RemoveUnusedLayers(); - layer_pool_->RecycleLayers(); - - auto task = [&, // - platform_view_layers = std::move(platform_view_layers), // - current_composition_params = current_composition_params_, // - views_to_recomposite = views_to_recomposite_, // - composition_order = composition_order_, // - unused_layers = std::move(unused_layers), // - surface_frames = std::move(surface_frames) // - ]() mutable { - PerformSubmit(platform_view_layers, // - current_composition_params, // - views_to_recomposite, // - composition_order, // - unused_layers, // - surface_frames // - ); - }; - - fml::TaskRunner::RunNowOrPostTask(platform_task_runner_, fml::MakeCopyable(std::move(task))); - - return did_encode; -} - -void PlatformViewsController::CreateMissingOverlays(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - size_t required_overlay_layers) { - TRACE_EVENT0("flutter", "PlatformViewsController::CreateMissingLayers"); - - if (required_overlay_layers <= layer_pool_->size()) { - return; - } - auto missing_layer_count = required_overlay_layers - layer_pool_->size(); - - // If the raster thread isn't merged, create layers on the platform thread and block until - // complete. - auto latch = std::make_shared(1u); - fml::TaskRunner::RunNowOrPostTask(platform_task_runner_, [&]() { - for (auto i = 0u; i < missing_layer_count; i++) { - CreateLayer(gr_context, // - ios_context, // - ((FlutterView*)flutter_view_.get()).pixelFormat // - ); - } - latch->CountDown(); - }); - if (![[NSThread currentThread] isMainThread]) { - latch->Wait(); - } -} - -/// Update the buffers and mutate the platform views in CATransaction on the platform thread. -void PlatformViewsController::PerformSubmit( - const LayersMap& platform_view_layers, - std::unordered_map& current_composition_params, - const std::unordered_set& views_to_recomposite, - const std::vector& composition_order, - const std::vector>& unused_layers, - const std::vector>& surface_frames) { - TRACE_EVENT0("flutter", "PlatformViewsController::PerformSubmit"); - FML_DCHECK([[NSThread currentThread] isMainThread]); - - [CATransaction begin]; - - // Configure Flutter overlay views. - for (const auto& [view_id, layer_data] : platform_view_layers) { - layer_data.layer->UpdateViewState(flutter_view_, // - layer_data.rect, // - layer_data.view_id, // - layer_data.overlay_id // - ); - } - - // Dispose unused Flutter Views. - for (auto& view : GetViewsToDispose()) { - [view removeFromSuperview]; - } - - // Composite Platform Views. - for (int64_t view_id : views_to_recomposite) { - CompositeWithParams(view_id, current_composition_params[view_id]); - } - - // Present callbacks. - for (const auto& frame : surface_frames) { - frame->Submit(); - } - - // If a layer was allocated in the previous frame, but it's not used in the current frame, - // then it can be removed from the scene. - RemoveUnusedLayers(unused_layers, composition_order); - - // Organize the layers by their z indexes. - BringLayersIntoView(platform_view_layers, composition_order); - - [CATransaction commit]; -} - -void PlatformViewsController::BringLayersIntoView(const LayersMap& layer_map, - const std::vector& composition_order) { - FML_DCHECK(flutter_view_); - UIView* flutter_view = flutter_view_.get(); - - previous_composition_order_.clear(); - NSMutableArray* desired_platform_subviews = [NSMutableArray array]; - for (int64_t platform_view_id : composition_order) { - UIView* platform_view_root = platform_views_[platform_view_id].root_view.get(); - if (platform_view_root != nil) { - [desired_platform_subviews addObject:platform_view_root]; - } - - auto maybe_layer_data = layer_map.find(platform_view_id); - if (maybe_layer_data != layer_map.end()) { - auto view = maybe_layer_data->second.layer->overlay_view_wrapper; - if (view != nil) { - [desired_platform_subviews addObject:view]; - previous_composition_order_.push_back(platform_view_id); - } - } - } - - NSSet* desired_platform_subviews_set = [NSSet setWithArray:desired_platform_subviews]; - NSArray* existing_platform_subviews = [flutter_view.subviews - filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, - NSDictionary* bindings) { - return [desired_platform_subviews_set containsObject:object]; - }]]; - - // Manipulate view hierarchy only if needed, to address a performance issue where - // `BringLayersIntoView` is called even when view hierarchy stays the same. - // See: https://github.com/flutter/flutter/issues/121833 - // TODO(hellohuanlin): investigate if it is possible to skip unnecessary BringLayersIntoView. - if (![desired_platform_subviews isEqualToArray:existing_platform_subviews]) { - for (UIView* subview in desired_platform_subviews) { - // `addSubview` will automatically reorder subview if it is already added. - [flutter_view addSubview:subview]; - } - } -} - -std::shared_ptr PlatformViewsController::GetExistingLayer() { - return layer_pool_->GetNextLayer(); -} - -void PlatformViewsController::CreateLayer(GrDirectContext* gr_context, - const std::shared_ptr& ios_context, - MTLPixelFormat pixel_format) { - layer_pool_->CreateLayer(gr_context, ios_context, pixel_format); -} - -void PlatformViewsController::RemoveUnusedLayers( - const std::vector>& unused_layers, - const std::vector& composition_order) { - for (const std::shared_ptr& layer : unused_layers) { - [layer->overlay_view_wrapper removeFromSuperview]; - } - - std::unordered_set composition_order_set; - for (int64_t view_id : composition_order) { - composition_order_set.insert(view_id); - } - // Remove unused platform views. - for (int64_t view_id : previous_composition_order_) { - if (composition_order_set.find(view_id) == composition_order_set.end()) { - UIView* platform_view_root = platform_views_[view_id].root_view.get(); - [platform_view_root removeFromSuperview]; - } - } -} - -std::vector PlatformViewsController::GetViewsToDispose() { - std::vector views; - if (views_to_dispose_.empty()) { - return views; - } - - std::unordered_set views_to_composite(composition_order_.begin(), - composition_order_.end()); - std::unordered_set views_to_delay_dispose; - for (int64_t viewId : views_to_dispose_) { - if (views_to_composite.count(viewId)) { - views_to_delay_dispose.insert(viewId); - continue; - } - UIView* root_view = platform_views_[viewId].root_view.get(); - views.push_back(root_view); - current_composition_params_.erase(viewId); - views_to_recomposite_.erase(viewId); - platform_views_.erase(viewId); - } - views_to_dispose_ = std::move(views_to_delay_dispose); - return views; -} - -void PlatformViewsController::ResetFrameState() { - slices_.clear(); - composition_order_.clear(); - visited_platform_views_.clear(); -} - -} // namespace flutter diff --git a/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm b/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm index ad97326d1116b..0a4ba99a0dd1d 100644 --- a/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm @@ -6,6 +6,7 @@ #import +#import "flutter/fml/platform/darwin/cf_utils.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" #import "flutter/shell/platform/darwin/ios/framework/Source/IOKit.h" @@ -34,75 +35,42 @@ } // namespace -namespace flutter { -namespace { - #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \ FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE -template -T ClearValue() { - return nullptr; -} +namespace fml { +/// fml::CFRef retain and release implementations for io_object_t and related types. template <> -io_object_t ClearValue() { - return 0; -} +struct CFRefTraits { + static constexpr io_object_t kNullValue = 0; + static void Retain(io_object_t instance) { IOObjectRetain(instance); } + static void Release(io_object_t instance) { IOObjectRelease(instance); } +}; -template -/// Generic RAII wrapper like unique_ptr but gives access to its handle. -class Scoped { - public: - typedef void (*Deleter)(T); - explicit Scoped(Deleter deleter) : object_(ClearValue()), deleter_(deleter) {} - Scoped(T object, Deleter deleter) : object_(object), deleter_(deleter) {} - ~Scoped() { - if (object_) { - deleter_(object_); - } - } - T* handle() { - if (object_) { - deleter_(object_); - object_ = ClearValue(); - } - return &object_; - } - T get() { return object_; } - void reset(T new_value) { - if (object_) { - deleter_(object_); - } - object_ = new_value; - } +} // namespace fml - private: - FML_DISALLOW_COPY_ASSIGN_AND_MOVE(Scoped); - T object_; - Deleter deleter_; -}; +#endif -void DeleteCF(CFMutableDictionaryRef value) { - CFRelease(value); -} +namespace flutter { +namespace { -void DeleteIO(io_object_t value) { - IOObjectRelease(value); -} +#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \ + FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE std::optional FindGpuUsageInfo(io_iterator_t iterator) { - for (Scoped regEntry(IOIteratorNext(iterator), DeleteIO); regEntry.get(); - regEntry.reset(IOIteratorNext(iterator))) { - Scoped serviceDictionary(DeleteCF); - if (IORegistryEntryCreateCFProperties(regEntry.get(), serviceDictionary.handle(), + for (fml::CFRef reg_entry(IOIteratorNext(iterator)); reg_entry.Get(); + reg_entry.Reset(IOIteratorNext(iterator))) { + CFMutableDictionaryRef cf_service_dictionary; + if (IORegistryEntryCreateCFProperties(reg_entry.Get(), &cf_service_dictionary, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) { continue; } - - NSDictionary* dictionary = - ((__bridge NSDictionary*)serviceDictionary.get())[@"PerformanceStatistics"]; - NSNumber* utilization = dictionary[@"Device Utilization %"]; + // Transfer ownership to ARC-managed pointer. + NSDictionary* service_dictionary = (__bridge_transfer NSDictionary*)cf_service_dictionary; + cf_service_dictionary = nullptr; + NSDictionary* performanceStatistics = service_dictionary[@"PerformanceStatistics"]; + NSNumber* utilization = performanceStatistics[@"Device Utilization %"]; if (utilization) { return (GpuUsageInfo){.percent_usage = [utilization doubleValue]}; } @@ -111,24 +79,27 @@ void DeleteIO(io_object_t value) { } [[maybe_unused]] std::optional FindSimulatorGpuUsageInfo() { - Scoped iterator(DeleteIO); + io_iterator_t io_iterator; if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("IntelAccelerator"), - iterator.handle()) == kIOReturnSuccess) { - return FindGpuUsageInfo(iterator.get()); + &io_iterator) == kIOReturnSuccess) { + fml::CFRef iterator(io_iterator); + return FindGpuUsageInfo(iterator.Get()); } return std::nullopt; } [[maybe_unused]] std::optional FindDeviceGpuUsageInfo() { - Scoped iterator(DeleteIO); + io_iterator_t io_iterator; if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("sgx"), - iterator.handle()) == kIOReturnSuccess) { - for (Scoped regEntry(IOIteratorNext(iterator.get()), DeleteIO); - regEntry.get(); regEntry.reset(IOIteratorNext(iterator.get()))) { - Scoped innerIterator(DeleteIO); - if (IORegistryEntryGetChildIterator(regEntry.get(), kIOServicePlane, - innerIterator.handle()) == kIOReturnSuccess) { - std::optional result = FindGpuUsageInfo(innerIterator.get()); + &io_iterator) == kIOReturnSuccess) { + fml::CFRef iterator(io_iterator); + for (fml::CFRef reg_entry(IOIteratorNext(iterator.Get())); reg_entry.Get(); + reg_entry.Reset(IOIteratorNext(iterator.Get()))) { + io_iterator_t io_inner_iterator; + if (IORegistryEntryGetChildIterator(reg_entry.Get(), kIOServicePlane, &io_inner_iterator) == + kIOReturnSuccess) { + fml::CFRef inner_iterator(io_inner_iterator); + std::optional result = FindGpuUsageInfo(inner_iterator.Get()); if (result.has_value()) { return result; } diff --git a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h index c4395f47a6982..b9f575b87f3db 100644 --- a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h +++ b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h @@ -11,13 +11,24 @@ #include "flutter/shell/common/variable_refresh_rate_reporter.h" #include "flutter/shell/common/vsync_waiter.h" +//------------------------------------------------------------------------------ +/// @brief Info.plist key enabling the full range of ProMotion refresh rates for CADisplayLink +/// callbacks and CAAnimation animations in the app. +/// +/// @see +/// https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro#3885321 +/// +extern NSString* const kCADisableMinimumFrameDurationOnPhoneKey; + @interface DisplayLinkManager : NSObject -// Whether the max refresh rate on iPhone Pro-motion devices are enabled. -// This reflects the value of `CADisableMinimumFrameDurationOnPhone` in the -// info.plist file. -// -// Note on iPads that support Pro-motion, the max refresh rate is always enabled. +//------------------------------------------------------------------------------ +/// @brief Whether the max refresh rate on iPhone ProMotion devices are enabled. This reflects +/// the value of `CADisableMinimumFrameDurationOnPhone` in the info.plist file. On iPads +/// that support ProMotion, the max refresh rate is always enabled. +/// +/// @return YES if the max refresh rate on ProMotion devices is enabled. +/// @property(class, nonatomic, readonly) BOOL maxRefreshRateEnabledOnIPhone; //------------------------------------------------------------------------------ diff --git a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm index 3c27604d2acf3..8552295f3a70c 100644 --- a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm @@ -18,6 +18,8 @@ FLUTTER_ASSERT_ARC +NSString* const kCADisableMinimumFrameDurationOnPhoneKey = @"CADisableMinimumFrameDurationOnPhone"; + @interface VSyncClient () @property(nonatomic, assign, readonly) double refreshRate; @end @@ -170,7 +172,7 @@ - (void)onDisplayLink:(CADisplayLink*)link { } + (BOOL)maxRefreshRateEnabledOnIPhone { - return [[NSBundle.mainBundle objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"] + return [[NSBundle.mainBundle objectForInfoDictionaryKey:kCADisableMinimumFrameDurationOnPhoneKey] boolValue]; } diff --git a/shell/platform/darwin/ios/ios_context.h b/shell/platform/darwin/ios/ios_context.h index b68c89efc6cae..a1ab361629707 100644 --- a/shell/platform/darwin/ios/ios_context.h +++ b/shell/platform/darwin/ios/ios_context.h @@ -11,7 +11,6 @@ #include "flutter/common/graphics/texture.h" #include "flutter/fml/concurrent_message_loop.h" #include "flutter/fml/macros.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/fml/synchronization/sync_switch.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterTexture.h" #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" @@ -121,9 +120,8 @@ class IOSContext { /// @return The texture proxy if the rendering backend supports embedder /// provided external textures. /// - virtual std::unique_ptr CreateExternalTexture( - int64_t texture_id, - fml::scoped_nsobject> texture) = 0; + virtual std::unique_ptr CreateExternalTexture(int64_t texture_id, + NSObject* texture) = 0; //---------------------------------------------------------------------------- /// @brief Accessor for the Skia context associated with IOSSurfaces and diff --git a/shell/platform/darwin/ios/ios_context.mm b/shell/platform/darwin/ios/ios_context.mm index 762847e8af780..4bd87fe7fa5e1 100644 --- a/shell/platform/darwin/ios/ios_context.mm +++ b/shell/platform/darwin/ios/ios_context.mm @@ -56,6 +56,7 @@ } IOSRenderingBackend IOSContext::GetBackend() const { + // Overridden by Impeller subclasses. return IOSRenderingBackend::kSkia; } diff --git a/shell/platform/darwin/ios/ios_context_metal_impeller.h b/shell/platform/darwin/ios/ios_context_metal_impeller.h index 4838bfecafe5c..c3edc7c8f9f62 100644 --- a/shell/platform/darwin/ios/ios_context_metal_impeller.h +++ b/shell/platform/darwin/ios/ios_context_metal_impeller.h @@ -34,7 +34,7 @@ class IOSContextMetalImpeller final : public IOSContext { sk_sp GetResourceContext() const; private: - fml::scoped_nsobject darwin_context_metal_impeller_; + FlutterDarwinContextMetalImpeller* darwin_context_metal_impeller_; std::shared_ptr aiks_context_; // |IOSContext| @@ -44,9 +44,8 @@ class IOSContextMetalImpeller final : public IOSContext { std::unique_ptr MakeCurrent() override; // |IOSContext| - std::unique_ptr CreateExternalTexture( - int64_t texture_id, - fml::scoped_nsobject> texture) override; + std::unique_ptr CreateExternalTexture(int64_t texture_id, + NSObject* texture) override; // |IOSContext| std::shared_ptr GetImpellerContext() const override; diff --git a/shell/platform/darwin/ios/ios_context_metal_impeller.mm b/shell/platform/darwin/ios/ios_context_metal_impeller.mm index bd0760ff41219..f6f89a34c1201 100644 --- a/shell/platform/darwin/ios/ios_context_metal_impeller.mm +++ b/shell/platform/darwin/ios/ios_context_metal_impeller.mm @@ -15,11 +15,11 @@ IOSContextMetalImpeller::IOSContextMetalImpeller( const std::shared_ptr& is_gpu_disabled_sync_switch) - : darwin_context_metal_impeller_(fml::scoped_nsobject{ - [[FlutterDarwinContextMetalImpeller alloc] init:is_gpu_disabled_sync_switch]}) { - if (darwin_context_metal_impeller_.get().context) { + : darwin_context_metal_impeller_( + [[FlutterDarwinContextMetalImpeller alloc] init:is_gpu_disabled_sync_switch]) { + if (darwin_context_metal_impeller_.context) { aiks_context_ = std::make_shared( - darwin_context_metal_impeller_.get().context, impeller::TypographerContextSkia::Make()); + darwin_context_metal_impeller_.context, impeller::TypographerContextSkia::Make()); } } @@ -44,7 +44,7 @@ // |IOSContext| std::shared_ptr IOSContextMetalImpeller::GetImpellerContext() const { - return darwin_context_metal_impeller_.get().context; + return darwin_context_metal_impeller_.context; } // |IOSContext| @@ -61,11 +61,10 @@ // |IOSContext| std::unique_ptr IOSContextMetalImpeller::CreateExternalTexture( int64_t texture_id, - fml::scoped_nsobject> texture) { - return std::make_unique( - fml::scoped_nsobject{[darwin_context_metal_impeller_ - createExternalTextureWithIdentifier:texture_id - texture:texture]}); + NSObject* texture) { + return std::make_unique([darwin_context_metal_impeller_ + createExternalTextureWithIdentifier:texture_id + texture:texture]); } } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_context_metal_skia.h b/shell/platform/darwin/ios/ios_context_metal_skia.h index a9b6ad47beb30..c02302e734c41 100644 --- a/shell/platform/darwin/ios/ios_context_metal_skia.h +++ b/shell/platform/darwin/ios/ios_context_metal_skia.h @@ -11,7 +11,6 @@ #include "flutter/fml/macros.h" #include "flutter/fml/platform/darwin/cf_utils.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #import "flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.h" #import "flutter/shell/platform/darwin/ios/ios_context.h" #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" @@ -24,7 +23,7 @@ class IOSContextMetalSkia final : public IOSContext { ~IOSContextMetalSkia(); - fml::scoped_nsobject GetDarwinContext() const; + FlutterDarwinContextMetalSkia* GetDarwinContext() const; // |IOSContext| IOSRenderingBackend GetBackend() const override; @@ -35,7 +34,7 @@ class IOSContextMetalSkia final : public IOSContext { sk_sp GetResourceContext() const; private: - fml::scoped_nsobject darwin_context_metal_; + FlutterDarwinContextMetalSkia* darwin_context_metal_; // |IOSContext| sk_sp CreateResourceContext() override; @@ -44,9 +43,8 @@ class IOSContextMetalSkia final : public IOSContext { std::unique_ptr MakeCurrent() override; // |IOSContext| - std::unique_ptr CreateExternalTexture( - int64_t texture_id, - fml::scoped_nsobject> texture) override; + std::unique_ptr CreateExternalTexture(int64_t texture_id, + NSObject* texture) override; FML_DISALLOW_COPY_AND_ASSIGN(IOSContextMetalSkia); }; diff --git a/shell/platform/darwin/ios/ios_context_metal_skia.mm b/shell/platform/darwin/ios/ios_context_metal_skia.mm index ebf1a70b6c345..6b83cf0d8a078 100644 --- a/shell/platform/darwin/ios/ios_context_metal_skia.mm +++ b/shell/platform/darwin/ios/ios_context_metal_skia.mm @@ -17,13 +17,12 @@ namespace flutter { IOSContextMetalSkia::IOSContextMetalSkia() { - darwin_context_metal_ = fml::scoped_nsobject{ - [[FlutterDarwinContextMetalSkia alloc] initWithDefaultMTLDevice]}; + darwin_context_metal_ = [[FlutterDarwinContextMetalSkia alloc] initWithDefaultMTLDevice]; } IOSContextMetalSkia::~IOSContextMetalSkia() = default; -fml::scoped_nsobject IOSContextMetalSkia::GetDarwinContext() const { +FlutterDarwinContextMetalSkia* IOSContextMetalSkia::GetDarwinContext() const { return darwin_context_metal_; } @@ -32,16 +31,16 @@ } sk_sp IOSContextMetalSkia::GetMainContext() const { - return darwin_context_metal_.get().mainContext; + return darwin_context_metal_.mainContext; } sk_sp IOSContextMetalSkia::GetResourceContext() const { - return darwin_context_metal_.get().resourceContext; + return darwin_context_metal_.resourceContext; } // |IOSContext| sk_sp IOSContextMetalSkia::CreateResourceContext() { - return darwin_context_metal_.get().resourceContext; + return darwin_context_metal_.resourceContext; } // |IOSContext| @@ -53,10 +52,9 @@ // |IOSContext| std::unique_ptr IOSContextMetalSkia::CreateExternalTexture( int64_t texture_id, - fml::scoped_nsobject> texture) { + NSObject* texture) { return std::make_unique( - fml::scoped_nsobject{ - [darwin_context_metal_ createExternalTextureWithIdentifier:texture_id texture:texture]}); + [darwin_context_metal_ createExternalTextureWithIdentifier:texture_id texture:texture]); } } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_context_noop.h b/shell/platform/darwin/ios/ios_context_noop.h index b4c8ec9fe486c..b331b9013cc3d 100644 --- a/shell/platform/darwin/ios/ios_context_noop.h +++ b/shell/platform/darwin/ios/ios_context_noop.h @@ -27,9 +27,8 @@ class IOSContextNoop final : public IOSContext { std::unique_ptr MakeCurrent() override; // |IOSContext| - std::unique_ptr CreateExternalTexture( - int64_t texture_id, - fml::scoped_nsobject> texture) override; + std::unique_ptr CreateExternalTexture(int64_t texture_id, + NSObject* texture) override; IOSRenderingBackend GetBackend() const override; diff --git a/shell/platform/darwin/ios/ios_context_noop.mm b/shell/platform/darwin/ios/ios_context_noop.mm index 1a083eb470036..254b0d4536f49 100644 --- a/shell/platform/darwin/ios/ios_context_noop.mm +++ b/shell/platform/darwin/ios/ios_context_noop.mm @@ -3,8 +3,8 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/ios/ios_context_noop.h" +#include "flutter/shell/platform/darwin/ios/rendering_api_selection.h" #include "ios_context.h" -#include "shell/platform/darwin/ios/rendering_api_selection.h" FLUTTER_ASSERT_ARC @@ -37,9 +37,8 @@ } // |IOSContext| -std::unique_ptr IOSContextNoop::CreateExternalTexture( - int64_t texture_id, - fml::scoped_nsobject> texture) { +std::unique_ptr IOSContextNoop::CreateExternalTexture(int64_t texture_id, + NSObject* texture) { // Don't use FML for logging as it will contain engine specific details. This is a user facing // message. NSLog(@"Flutter: Attempted to composite external texture sources using the noop backend. " diff --git a/shell/platform/darwin/ios/ios_context_software.h b/shell/platform/darwin/ios/ios_context_software.h index 3731bd3ed6ed5..5ed73930fe9b7 100644 --- a/shell/platform/darwin/ios/ios_context_software.h +++ b/shell/platform/darwin/ios/ios_context_software.h @@ -27,9 +27,8 @@ class IOSContextSoftware final : public IOSContext { std::unique_ptr MakeCurrent() override; // |IOSContext| - std::unique_ptr CreateExternalTexture( - int64_t texture_id, - fml::scoped_nsobject> texture) override; + std::unique_ptr CreateExternalTexture(int64_t texture_id, + NSObject* texture) override; private: FML_DISALLOW_COPY_AND_ASSIGN(IOSContextSoftware); diff --git a/shell/platform/darwin/ios/ios_context_software.mm b/shell/platform/darwin/ios/ios_context_software.mm index 15a7e2e94c0ef..545c56a7e567e 100644 --- a/shell/platform/darwin/ios/ios_context_software.mm +++ b/shell/platform/darwin/ios/ios_context_software.mm @@ -33,7 +33,7 @@ // |IOSContext| std::unique_ptr IOSContextSoftware::CreateExternalTexture( int64_t texture_id, - fml::scoped_nsobject> texture) { + NSObject* texture) { // Don't use FML for logging as it will contain engine specific details. This is a user facing // message. NSLog(@"Flutter: Attempted to composite external texture sources using the software backend. " diff --git a/shell/platform/darwin/ios/ios_external_texture_metal.h b/shell/platform/darwin/ios/ios_external_texture_metal.h index 5a6dfd2973c6b..c635bb1909019 100644 --- a/shell/platform/darwin/ios/ios_external_texture_metal.h +++ b/shell/platform/darwin/ios/ios_external_texture_metal.h @@ -7,7 +7,6 @@ #include "flutter/common/graphics/texture.h" #include "flutter/fml/macros.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #import "flutter/shell/platform/darwin/graphics/FlutterDarwinExternalTextureMetal.h" namespace flutter { @@ -15,15 +14,13 @@ namespace flutter { class IOSExternalTextureMetal final : public Texture { public: explicit IOSExternalTextureMetal( - const fml::scoped_nsobject& - darwin_external_texture_metal); + FlutterDarwinExternalTextureMetal* darwin_external_texture_metal); // |Texture| ~IOSExternalTextureMetal(); private: - fml::scoped_nsobject - darwin_external_texture_metal_; + FlutterDarwinExternalTextureMetal* darwin_external_texture_metal_; // |Texture| void Paint(PaintContext& context, diff --git a/shell/platform/darwin/ios/ios_external_texture_metal.mm b/shell/platform/darwin/ios/ios_external_texture_metal.mm index 4e77bd35b2e66..89856393c1b2a 100644 --- a/shell/platform/darwin/ios/ios_external_texture_metal.mm +++ b/shell/platform/darwin/ios/ios_external_texture_metal.mm @@ -10,7 +10,7 @@ namespace flutter { IOSExternalTextureMetal::IOSExternalTextureMetal( - const fml::scoped_nsobject& darwin_external_texture_metal) + FlutterDarwinExternalTextureMetal* darwin_external_texture_metal) : Texture([darwin_external_texture_metal textureID]), darwin_external_texture_metal_(darwin_external_texture_metal) {} diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.h b/shell/platform/darwin/ios/ios_external_view_embedder.h index a1ce91b637930..d22fc3fb47743 100644 --- a/shell/platform/darwin/ios/ios_external_view_embedder.h +++ b/shell/platform/darwin/ios/ios_external_view_embedder.h @@ -13,14 +13,14 @@ namespace flutter { class IOSExternalViewEmbedder : public ExternalViewEmbedder { public: IOSExternalViewEmbedder( - const std::shared_ptr& platform_views_controller, + __weak FlutterPlatformViewsController* platform_views_controller, const std::shared_ptr& context); // |ExternalViewEmbedder| virtual ~IOSExternalViewEmbedder() override; private: - const std::shared_ptr& platform_views_controller_; + __weak FlutterPlatformViewsController* platform_views_controller_; std::shared_ptr ios_context_; // |ExternalViewEmbedder| @@ -68,7 +68,7 @@ class IOSExternalViewEmbedder : public ExternalViewEmbedder { // |ExternalViewEmbedder| void PushFilterToVisitedPlatformViews( - const std::shared_ptr& filter, + const std::shared_ptr& filter, const SkRect& filter_rect) override; // |ExternalViewEmbedder| diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.mm b/shell/platform/darwin/ios/ios_external_view_embedder.mm index f8c2f47c7aefc..ebde36386b3c1 100644 --- a/shell/platform/darwin/ios/ios_external_view_embedder.mm +++ b/shell/platform/darwin/ios/ios_external_view_embedder.mm @@ -12,7 +12,7 @@ namespace flutter { IOSExternalViewEmbedder::IOSExternalViewEmbedder( - const std::shared_ptr& platform_views_controller, + __weak FlutterPlatformViewsController* platform_views_controller, const std::shared_ptr& context) : platform_views_controller_(platform_views_controller), ios_context_(context) { FML_CHECK(ios_context_); @@ -31,7 +31,7 @@ void IOSExternalViewEmbedder::CancelFrame() { TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::CancelFrame"); FML_CHECK(platform_views_controller_); - platform_views_controller_->CancelFrame(); + [platform_views_controller_ cancelFrame]; } // |ExternalViewEmbedder| @@ -42,7 +42,7 @@ // |ExternalViewEmbedder| void IOSExternalViewEmbedder::PrepareFlutterView(SkISize frame_size, double device_pixel_ratio) { FML_CHECK(platform_views_controller_); - platform_views_controller_->BeginFrame(frame_size); + [platform_views_controller_ beginFrameWithSize:frame_size]; } // |ExternalViewEmbedder| @@ -51,7 +51,7 @@ std::unique_ptr params) { TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::PrerollCompositeEmbeddedView"); FML_CHECK(platform_views_controller_); - platform_views_controller_->PrerollCompositeEmbeddedView(view_id, std::move(params)); + [platform_views_controller_ prerollCompositeEmbeddedView:view_id withParams:std::move(params)]; } // |ExternalViewEmbedder| @@ -59,8 +59,10 @@ const fml::RefPtr& raster_thread_merger) { TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::PostPrerollAction"); FML_CHECK(platform_views_controller_); - PostPrerollResult result = platform_views_controller_->PostPrerollAction( - raster_thread_merger, ios_context_->GetBackend() != IOSRenderingBackend::kSkia); + BOOL impeller_enabled = ios_context_->GetBackend() != IOSRenderingBackend::kSkia; + PostPrerollResult result = + [platform_views_controller_ postPrerollActionWithThreadMerger:raster_thread_merger + impellerEnabled:impeller_enabled]; return result; } @@ -68,7 +70,7 @@ DlCanvas* IOSExternalViewEmbedder::CompositeEmbeddedView(int64_t view_id) { TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::CompositeEmbeddedView"); FML_CHECK(platform_views_controller_); - return platform_views_controller_->CompositeEmbeddedView(view_id); + return [platform_views_controller_ compositeEmbeddedViewWithId:view_id]; } // |ExternalViewEmbedder| @@ -83,7 +85,9 @@ // Properly support multi-view in the future. FML_DCHECK(flutter_view_id == kFlutterImplicitViewId); FML_CHECK(platform_views_controller_); - platform_views_controller_->SubmitFrame(context, ios_context_, std::move(frame)); + [platform_views_controller_ submitFrame:std::move(frame) + withIosContext:ios_context_ + grContext:context]; TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::DidSubmitFrame"); } @@ -92,8 +96,10 @@ bool should_resubmit_frame, const fml::RefPtr& raster_thread_merger) { TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::EndFrame"); - platform_views_controller_->EndFrame(should_resubmit_frame, raster_thread_merger, - ios_context_->GetBackend() != IOSRenderingBackend::kSkia); + BOOL impeller_enabled = ios_context_->GetBackend() != IOSRenderingBackend::kSkia; + [platform_views_controller_ endFrameWithResubmit:should_resubmit_frame + threadMerger:raster_thread_merger + impellerEnabled:impeller_enabled]; } // |ExternalViewEmbedder| @@ -108,14 +114,14 @@ // |ExternalViewEmbedder| void IOSExternalViewEmbedder::PushFilterToVisitedPlatformViews( - const std::shared_ptr& filter, + const std::shared_ptr& filter, const SkRect& filter_rect) { - platform_views_controller_->PushFilterToVisitedPlatformViews(filter, filter_rect); + [platform_views_controller_ pushFilterToVisitedPlatformViews:filter withRect:filter_rect]; } // |ExternalViewEmbedder| void IOSExternalViewEmbedder::PushVisitedPlatformView(int64_t view_id) { - platform_views_controller_->PushVisitedPlatformView(view_id); + [platform_views_controller_ pushVisitedPlatformViewId:view_id]; } } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_surface.h b/shell/platform/darwin/ios/ios_surface.h index 96970a465b531..1c74ac1014c18 100644 --- a/shell/platform/darwin/ios/ios_surface.h +++ b/shell/platform/darwin/ios/ios_surface.h @@ -12,7 +12,6 @@ #include "flutter/flow/embedded_views.h" #include "flutter/flow/surface.h" #include "flutter/fml/macros.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" @class CALayer; @@ -20,8 +19,7 @@ namespace flutter { class IOSSurface { public: - static std::unique_ptr Create(std::shared_ptr context, - const fml::scoped_nsobject& layer); + static std::unique_ptr Create(std::shared_ptr context, CALayer* layer); std::shared_ptr GetContext() const; diff --git a/shell/platform/darwin/ios/ios_surface.mm b/shell/platform/darwin/ios/ios_surface.mm index ae27f5fb941c5..437e06585715a 100644 --- a/shell/platform/darwin/ios/ios_surface.mm +++ b/shell/platform/darwin/ios/ios_surface.mm @@ -17,18 +17,18 @@ namespace flutter { std::unique_ptr IOSSurface::Create(std::shared_ptr context, - const fml::scoped_nsobject& layer) { + CALayer* layer) { FML_DCHECK(layer); FML_DCHECK(context); if (@available(iOS METAL_IOS_VERSION_BASELINE, *)) { - if ([layer.get() isKindOfClass:[CAMetalLayer class]]) { + if ([layer isKindOfClass:[CAMetalLayer class]]) { switch (context->GetBackend()) { case IOSRenderingBackend::kSkia: #if !SLIMPELLER return std::make_unique( - fml::scoped_nsobject((CAMetalLayer*)layer.get()), // Metal layer - std::move(context) // context + static_cast(layer), // Metal layer + std::move(context) // context ); #else // !SLIMPELLER FML_LOG(FATAL) << "Impeller opt-out unavailable."; @@ -37,8 +37,8 @@ break; case IOSRenderingBackend::kImpeller: return std::make_unique( - fml::scoped_nsobject((CAMetalLayer*)layer.get()), // Metal layer - std::move(context) // context + static_cast(layer), // Metal layer + std::move(context) // context ); } } diff --git a/shell/platform/darwin/ios/ios_surface_metal_impeller.h b/shell/platform/darwin/ios/ios_surface_metal_impeller.h index 86c5171caac23..527e0f9947bc8 100644 --- a/shell/platform/darwin/ios/ios_surface_metal_impeller.h +++ b/shell/platform/darwin/ios/ios_surface_metal_impeller.h @@ -21,14 +21,13 @@ class SK_API_AVAILABLE_CA_METAL_LAYER IOSSurfaceMetalImpeller final : public IOSSurface, public GPUSurfaceMetalDelegate { public: - IOSSurfaceMetalImpeller(const fml::scoped_nsobject& layer, - const std::shared_ptr& context); + IOSSurfaceMetalImpeller(CAMetalLayer* layer, const std::shared_ptr& context); // |IOSSurface| ~IOSSurfaceMetalImpeller(); private: - fml::scoped_nsobject layer_; + CAMetalLayer* layer_; const std::shared_ptr impeller_context_; std::shared_ptr aiks_context_; bool is_valid_ = false; diff --git a/shell/platform/darwin/ios/ios_surface_metal_impeller.mm b/shell/platform/darwin/ios/ios_surface_metal_impeller.mm index c3a67bb9ae40f..fdca57bfa5c04 100644 --- a/shell/platform/darwin/ios/ios_surface_metal_impeller.mm +++ b/shell/platform/darwin/ios/ios_surface_metal_impeller.mm @@ -15,7 +15,7 @@ namespace flutter { -IOSSurfaceMetalImpeller::IOSSurfaceMetalImpeller(const fml::scoped_nsobject& layer, +IOSSurfaceMetalImpeller::IOSSurfaceMetalImpeller(CAMetalLayer* layer, const std::shared_ptr& context) : IOSSurface(context), GPUSurfaceMetalDelegate(MTLRenderTargetType::kCAMetalLayer), @@ -44,7 +44,7 @@ // |IOSSurface| std::unique_ptr IOSSurfaceMetalImpeller::CreateGPUSurface(GrDirectContext*) { impeller_context_->UpdateOffscreenLayerPixelFormat( - impeller::FromMTLPixelFormat(layer_.get().pixelFormat)); + impeller::FromMTLPixelFormat(layer_.pixelFormat)); return std::make_unique(this, // aiks_context_ // ); @@ -52,17 +52,16 @@ // |GPUSurfaceMetalDelegate| GPUCAMetalLayerHandle IOSSurfaceMetalImpeller::GetCAMetalLayer(const SkISize& frame_info) const { - CAMetalLayer* layer = layer_.get(); const auto drawable_size = CGSizeMake(frame_info.width(), frame_info.height()); - if (!CGSizeEqualToSize(drawable_size, layer.drawableSize)) { - layer.drawableSize = drawable_size; + if (!CGSizeEqualToSize(drawable_size, layer_.drawableSize)) { + layer_.drawableSize = drawable_size; } // Flutter needs to read from the color attachment in cases where there are effects such as // backdrop filters. Flutter plugins that create platform views may also read from the layer. - layer.framebufferOnly = NO; + layer_.framebufferOnly = NO; - return (__bridge GPUCAMetalLayerHandle)layer; + return (__bridge GPUCAMetalLayerHandle)layer_; } // |GPUSurfaceMetalDelegate| diff --git a/shell/platform/darwin/ios/ios_surface_metal_skia.h b/shell/platform/darwin/ios/ios_surface_metal_skia.h index e696bab8c40de..3a3a20bd3d776 100644 --- a/shell/platform/darwin/ios/ios_surface_metal_skia.h +++ b/shell/platform/darwin/ios/ios_surface_metal_skia.h @@ -19,14 +19,13 @@ namespace flutter { class SK_API_AVAILABLE_CA_METAL_LAYER IOSSurfaceMetalSkia final : public IOSSurface, public GPUSurfaceMetalDelegate { public: - IOSSurfaceMetalSkia(const fml::scoped_nsobject& layer, - std::shared_ptr context); + IOSSurfaceMetalSkia(CAMetalLayer* layer, std::shared_ptr context); // |IOSSurface| ~IOSSurfaceMetalSkia(); private: - fml::scoped_nsobject layer_; + CAMetalLayer* layer_; id device_; id command_queue_; bool is_valid_ = false; diff --git a/shell/platform/darwin/ios/ios_surface_metal_skia.mm b/shell/platform/darwin/ios/ios_surface_metal_skia.mm index 6c0b534d56d1a..61266abc7d589 100644 --- a/shell/platform/darwin/ios/ios_surface_metal_skia.mm +++ b/shell/platform/darwin/ios/ios_surface_metal_skia.mm @@ -18,19 +18,13 @@ - (void)flutterPrepareForPresent:(nonnull id)commandBuffer; namespace flutter { -static IOSContextMetalSkia* CastToMetalContext(const std::shared_ptr& context) - __attribute__((cf_audited_transfer)) { - return (IOSContextMetalSkia*)context.get(); -} - -IOSSurfaceMetalSkia::IOSSurfaceMetalSkia(const fml::scoped_nsobject& layer, - std::shared_ptr context) +IOSSurfaceMetalSkia::IOSSurfaceMetalSkia(CAMetalLayer* layer, std::shared_ptr context) : IOSSurface(std::move(context)), GPUSurfaceMetalDelegate(MTLRenderTargetType::kCAMetalLayer), layer_(layer) { is_valid_ = layer_; - auto metal_context = CastToMetalContext(GetContext()); - auto darwin_context = metal_context->GetDarwinContext().get(); + IOSContextMetalSkia* metal_context = static_cast(GetContext().get()); + FlutterDarwinContextMetalSkia* darwin_context = metal_context->GetDarwinContext(); command_queue_ = darwin_context.commandQueue; device_ = darwin_context.device; } @@ -58,25 +52,24 @@ - (void)flutterPrepareForPresent:(nonnull id)commandBuffer; // |GPUSurfaceMetalDelegate| GPUCAMetalLayerHandle IOSSurfaceMetalSkia::GetCAMetalLayer(const SkISize& frame_info) const { - CAMetalLayer* layer = layer_.get(); - layer.device = device_; + layer_.device = device_; - layer.pixelFormat = MTLPixelFormatBGRA8Unorm; + layer_.pixelFormat = MTLPixelFormatBGRA8Unorm; // Flutter needs to read from the color attachment in cases where there are effects such as // backdrop filters. Flutter plugins that create platform views may also read from the layer. - layer.framebufferOnly = NO; + layer_.framebufferOnly = NO; const auto drawable_size = CGSizeMake(frame_info.width(), frame_info.height()); - if (!CGSizeEqualToSize(drawable_size, layer.drawableSize)) { - layer.drawableSize = drawable_size; + if (!CGSizeEqualToSize(drawable_size, layer_.drawableSize)) { + layer_.drawableSize = drawable_size; } // When there are platform views in the scene, the drawable needs to be presented in the same // transaction as the one created for platform views. When the drawable are being presented from // the raster thread, there is no such transaction. - layer.presentsWithTransaction = [[NSThread currentThread] isMainThread]; + layer_.presentsWithTransaction = [[NSThread currentThread] isMainThread]; - return (__bridge GPUCAMetalLayerHandle)layer; + return (__bridge GPUCAMetalLayerHandle)layer_; } // |GPUSurfaceMetalDelegate| diff --git a/shell/platform/darwin/ios/ios_surface_noop.h b/shell/platform/darwin/ios/ios_surface_noop.h index d3902a901ebb4..94b223216e275 100644 --- a/shell/platform/darwin/ios/ios_surface_noop.h +++ b/shell/platform/darwin/ios/ios_surface_noop.h @@ -5,7 +5,6 @@ #ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS_SURFACE_NOOP_H_ #define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS_SURFACE_NOOP_H_ -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #import "flutter/shell/platform/darwin/ios/ios_context.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" diff --git a/shell/platform/darwin/ios/ios_surface_software.h b/shell/platform/darwin/ios/ios_surface_software.h index e1ed6b69518c0..8f0088cb67152 100644 --- a/shell/platform/darwin/ios/ios_surface_software.h +++ b/shell/platform/darwin/ios/ios_surface_software.h @@ -7,7 +7,6 @@ #include "flutter/flow/embedded_views.h" #include "flutter/fml/macros.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/shell/gpu/gpu_surface_software.h" #import "flutter/shell/platform/darwin/ios/ios_context.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" @@ -20,8 +19,7 @@ namespace flutter { class IOSSurfaceSoftware final : public IOSSurface, public GPUSurfaceSoftwareDelegate { public: - IOSSurfaceSoftware(const fml::scoped_nsobject& layer, - std::shared_ptr context); + IOSSurfaceSoftware(CALayer* layer, std::shared_ptr context); ~IOSSurfaceSoftware() override; @@ -41,7 +39,7 @@ class IOSSurfaceSoftware final : public IOSSurface, public GPUSurfaceSoftwareDel bool PresentBackingStore(sk_sp backing_store) override; private: - fml::scoped_nsobject layer_; + CALayer* layer_; sk_sp sk_surface_; FML_DISALLOW_COPY_AND_ASSIGN(IOSSurfaceSoftware); diff --git a/shell/platform/darwin/ios/ios_surface_software.mm b/shell/platform/darwin/ios/ios_surface_software.mm index c67facb1931c2..aba83b7d83bb2 100644 --- a/shell/platform/darwin/ios/ios_surface_software.mm +++ b/shell/platform/darwin/ios/ios_surface_software.mm @@ -19,8 +19,7 @@ namespace flutter { -IOSSurfaceSoftware::IOSSurfaceSoftware(const fml::scoped_nsobject& layer, - std::shared_ptr context) +IOSSurfaceSoftware::IOSSurfaceSoftware(CALayer* layer, std::shared_ptr context) : IOSSurface(std::move(context)), layer_(layer) {} IOSSurfaceSoftware::~IOSSurfaceSoftware() = default; @@ -120,7 +119,7 @@ return false; } - layer_.get().contents = (__bridge id)(static_cast(pixmap_image)); + layer_.contents = (__bridge id) static_cast(pixmap_image); return true; } diff --git a/shell/platform/darwin/ios/platform_message_handler_ios.h b/shell/platform/darwin/ios/platform_message_handler_ios.h index b212e22230728..2c09002760711 100644 --- a/shell/platform/darwin/ios/platform_message_handler_ios.h +++ b/shell/platform/darwin/ios/platform_message_handler_ios.h @@ -5,8 +5,6 @@ #ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_PLATFORM_MESSAGE_HANDLER_IOS_H_ #define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_PLATFORM_MESSAGE_HANDLER_IOS_H_ -#include "flutter/fml/platform/darwin/scoped_block.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/fml/task_runner.h" #include "flutter/shell/common/platform_message_handler.h" #import "flutter/shell/platform/darwin/ios/flutter_task_queue_dispatch.h" @@ -33,8 +31,8 @@ class PlatformMessageHandlerIos : public PlatformMessageHandler { NSObject* task_queue); struct HandlerInfo { - fml::scoped_nsprotocol*> task_queue; - fml::ScopedBlock handler; + NSObject* task_queue; + FlutterBinaryMessageHandler handler; }; private: diff --git a/shell/platform/darwin/ios/platform_message_handler_ios.mm b/shell/platform/darwin/ios/platform_message_handler_ios.mm index e1257b56ffb40..06110e1b5b789 100644 --- a/shell/platform/darwin/ios/platform_message_handler_ios.mm +++ b/shell/platform/darwin/ios/platform_message_handler_ios.mm @@ -80,8 +80,8 @@ - (void)dispatch:(dispatch_block_t)block { }); }; - if (handler_info.task_queue.get()) { - [handler_info.task_queue.get() dispatch:run_handler]; + if (handler_info.task_queue) { + [handler_info.task_queue dispatch:run_handler]; } else { dispatch_async(dispatch_get_main_queue(), run_handler); } @@ -124,11 +124,8 @@ - (void)dispatch:(dispatch_block_t)block { message_handlers_.erase(channel); if (handler) { message_handlers_[channel] = { - .task_queue = - fml::scoped_nsprotocol(static_cast*>(task_queue)), - .handler = - fml::ScopedBlock{ - handler, fml::scoped_policy::OwnershipPolicy::kRetain}, + .task_queue = (NSObject*)task_queue, + .handler = handler, }; } } diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index 299015d785074..4e1da467d22bd 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -9,8 +9,6 @@ #include "flutter/fml/closure.h" #include "flutter/fml/macros.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" -#include "flutter/fml/platform/darwin/weak_nsobject.h" #include "flutter/shell/common/platform_view.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterTexture.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" @@ -42,13 +40,13 @@ class PlatformViewIOS final : public PlatformView { public: PlatformViewIOS(PlatformView::Delegate& delegate, const std::shared_ptr& context, - const std::shared_ptr& platform_views_controller, + __weak FlutterPlatformViewsController* platform_views_controller, const flutter::TaskRunners& task_runners); explicit PlatformViewIOS( PlatformView::Delegate& delegate, IOSRenderingAPI rendering_api, - const std::shared_ptr& platform_views_controller, + __weak FlutterPlatformViewsController* platform_views_controller, const flutter::TaskRunners& task_runners, const std::shared_ptr& worker_task_runner, const std::shared_ptr& is_gpu_disabled_sync_switch); @@ -59,14 +57,14 @@ class PlatformViewIOS final : public PlatformView { * Returns the `FlutterViewController` currently attached to the `FlutterEngine` owning * this PlatformViewIOS. */ - fml::WeakNSObject GetOwnerViewController() const; + FlutterViewController* GetOwnerViewController() const __attribute__((cf_audited_transfer)); /** * Updates the `FlutterViewController` currently attached to the `FlutterEngine` owning * this PlatformViewIOS. This should be updated when the `FlutterEngine` * is given a new `FlutterViewController`. */ - void SetOwnerViewController(const fml::WeakNSObject& owner_controller); + void SetOwnerViewController(__weak FlutterViewController* owner_controller); /** * Called one time per `FlutterViewController` when the `FlutterViewController`'s @@ -133,15 +131,14 @@ class PlatformViewIOS final : public PlatformView { std::function set_semantics_enabled_; }; - fml::WeakNSObject owner_controller_; + __weak FlutterViewController* owner_controller_; // Since the `ios_surface_` is created on the platform thread but // used on the raster thread we need to protect it with a mutex. std::mutex ios_surface_mutex_; std::unique_ptr ios_surface_; std::shared_ptr ios_context_; - const std::shared_ptr& platform_views_controller_; + __weak FlutterPlatformViewsController* platform_views_controller_; AccessibilityBridgeManager accessibility_bridge_; - fml::scoped_nsprotocol text_input_plugin_; ScopedObserver dealloc_view_controller_observer_; std::vector platform_resolved_locale_; std::shared_ptr platform_message_handler_; diff --git a/shell/platform/darwin/ios/platform_view_ios.mm b/shell/platform/darwin/ios/platform_view_ios.mm index 1e58148319e84..b726fd8fc1ee4 100644 --- a/shell/platform/darwin/ios/platform_view_ios.mm +++ b/shell/platform/darwin/ios/platform_view_ios.mm @@ -14,6 +14,8 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" +FLUTTER_ASSERT_ARC + namespace flutter { PlatformViewIOS::AccessibilityBridgeManager::AccessibilityBridgeManager( @@ -39,11 +41,10 @@ accessibility_bridge_.reset(); } -PlatformViewIOS::PlatformViewIOS( - PlatformView::Delegate& delegate, - const std::shared_ptr& context, - const std::shared_ptr& platform_views_controller, - const flutter::TaskRunners& task_runners) +PlatformViewIOS::PlatformViewIOS(PlatformView::Delegate& delegate, + const std::shared_ptr& context, + __weak FlutterPlatformViewsController* platform_views_controller, + const flutter::TaskRunners& task_runners) : PlatformView(delegate, task_runners), ios_context_(context), platform_views_controller_(platform_views_controller), @@ -54,7 +55,7 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} PlatformViewIOS::PlatformViewIOS( PlatformView::Delegate& delegate, IOSRenderingAPI rendering_api, - const std::shared_ptr& platform_views_controller, + __weak FlutterPlatformViewsController* platform_views_controller, const flutter::TaskRunners& task_runners, const std::shared_ptr& worker_task_runner, const std::shared_ptr& is_gpu_disabled_sync_switch) @@ -74,12 +75,11 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} platform_message_handler_->HandlePlatformMessage(std::move(message)); } -fml::WeakNSObject PlatformViewIOS::GetOwnerViewController() const { +FlutterViewController* PlatformViewIOS::GetOwnerViewController() const { return owner_controller_; } -void PlatformViewIOS::SetOwnerViewController( - const fml::WeakNSObject& owner_controller) { +void PlatformViewIOS::SetOwnerViewController(__weak FlutterViewController* owner_controller) { FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); std::lock_guard guard(ios_surface_mutex_); if (ios_surface_ || !owner_controller) { @@ -91,17 +91,17 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} // Add an observer that will clear out the owner_controller_ ivar and // the accessibility_bridge_ in case the view controller is deleted. - dealloc_view_controller_observer_.reset( - [[[NSNotificationCenter defaultCenter] addObserverForName:FlutterViewControllerWillDealloc - object:owner_controller_.get() - queue:[NSOperationQueue mainQueue] - usingBlock:^(NSNotification* note) { - // Implicit copy of 'this' is fine. - accessibility_bridge_.Clear(); - owner_controller_.reset(); - }] retain]); - - if (owner_controller_ && [owner_controller_.get() isViewLoaded]) { + dealloc_view_controller_observer_.reset([[NSNotificationCenter defaultCenter] + addObserverForName:FlutterViewControllerWillDealloc + object:owner_controller_ + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification* note) { + // Implicit copy of 'this' is fine. + accessibility_bridge_.Clear(); + owner_controller_ = nil; + }]); + + if (owner_controller_ && owner_controller_.isViewLoaded) { this->attachView(); } // Do not call `NotifyCreated()` here - let FlutterViewController take care @@ -112,17 +112,16 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} void PlatformViewIOS::attachView() { FML_DCHECK(owner_controller_); - FML_DCHECK(owner_controller_.get().isViewLoaded) - << "FlutterViewController's view should be loaded " - "before attaching to PlatformViewIOS."; - auto flutter_view = static_cast(owner_controller_.get().view); - auto ca_layer = fml::scoped_nsobject{[[flutter_view layer] retain]}; + FML_DCHECK(owner_controller_.isViewLoaded) << "FlutterViewController's view should be loaded " + "before attaching to PlatformViewIOS."; + FlutterView* flutter_view = static_cast(owner_controller_.view); + CALayer* ca_layer = flutter_view.layer; ios_surface_ = IOSSurface::Create(ios_context_, ca_layer); FML_DCHECK(ios_surface_ != nullptr); if (accessibility_bridge_) { accessibility_bridge_.Set(std::make_unique( - owner_controller_.get(), this, [owner_controller_.get() platformViewsController])); + owner_controller_, this, owner_controller_.platformViewsController)); } } @@ -134,8 +133,7 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} void PlatformViewIOS::RegisterExternalTexture(int64_t texture_id, NSObject* texture) { - RegisterTexture(ios_context_->CreateExternalTexture( - texture_id, fml::scoped_nsobject>{[texture retain]})); + RegisterTexture(ios_context_->CreateExternalTexture(texture_id, texture)); } // |PlatformView| @@ -174,7 +172,7 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} } if (enabled && !accessibility_bridge_) { accessibility_bridge_.Set(std::make_unique( - owner_controller_.get(), this, [owner_controller_.get() platformViewsController])); + owner_controller_, this, owner_controller_.platformViewsController)); } else if (!enabled && accessibility_bridge_) { accessibility_bridge_.Clear(); } else { @@ -194,7 +192,7 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} if (accessibility_bridge_) { accessibility_bridge_.get()->UpdateSemantics(std::move(update), actions); [[NSNotificationCenter defaultCenter] postNotificationName:FlutterSemanticsUpdateNotification - object:owner_controller_.get()]; + object:owner_controller_]; } } @@ -210,8 +208,8 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} if (!owner_controller_) { return; } - [owner_controller_.get() platformViewsController]->Reset(); - [[owner_controller_.get() restorationPlugin] reset]; + [owner_controller_.platformViewsController reset]; + [owner_controller_.restorationPlugin reset]; } std::unique_ptr> PlatformViewIOS::ComputePlatformResolvedLocales( @@ -253,7 +251,6 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} PlatformViewIOS::ScopedObserver::~ScopedObserver() { if (observer_) { [[NSNotificationCenter defaultCenter] removeObserver:observer_]; - [observer_ release]; } } @@ -261,7 +258,6 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} if (observer != observer_) { if (observer_) { [[NSNotificationCenter defaultCenter] removeObserver:observer_]; - [observer_ release]; } observer_ = observer; } diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 1224e15e02e5c..0939591bb45ae 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -52,6 +52,8 @@ _flutter_framework_headers_copy_dir = source_set("flutter_framework_source") { visibility = [ ":*" ] + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc sources = [ "framework/Source/AccessibilityBridgeMac.h", @@ -141,8 +143,6 @@ source_set("flutter_framework_source") { "FLUTTER_ENGINE_NO_PROTOTYPES", ] - cflags_objcc = flutter_cflags_objcc_arc - frameworks = [ "Carbon.framework", "Cocoa.framework", @@ -173,6 +173,9 @@ test_fixtures("flutter_desktop_darwin_fixtures") { executable("flutter_desktop_darwin_unittests") { testonly = true + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + ldflags = [ "-ObjC" ] sources = [ "framework/Source/AccessibilityBridgeMacTest.mm", @@ -205,13 +208,10 @@ executable("flutter_desktop_darwin_unittests") { "framework/Source/TestFlutterPlatformView.mm", ] - cflags_objcc = flutter_cflags_objcc_arc - - ldflags = [ "-ObjC" ] - deps = [ ":flutter_desktop_darwin_fixtures", ":flutter_framework_source", + "//flutter/fml", "//flutter/shell/platform/common:common_cpp_accessibility", "//flutter/shell/platform/common:common_cpp_enums", "//flutter/shell/platform/darwin/common:framework_common", @@ -253,7 +253,8 @@ copy("copy_framework_module_map") { copy("copy_framework_privacy_manifest") { visibility = [ ":*" ] sources = [ "framework/PrivacyInfo.xcprivacy" ] - outputs = [ "$_flutter_framework_dir/PrivacyInfo.xcprivacy" ] + outputs = + [ "$_flutter_framework_dir/Versions/A/Resources/PrivacyInfo.xcprivacy" ] } action("copy_framework_headers") { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm index 98abd505d4d9b..5505b460d4d36 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm @@ -29,6 +29,7 @@ @interface FlutterChannelKeyResponder () @implementation FlutterChannelKeyResponder +// Synthesize properties declared in FlutterKeyPrimaryResponder protocol. @synthesize layoutMap; - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel { @@ -131,10 +132,13 @@ - (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { return; } break; - default: { - NSAssert(false, @"Unexpected key event type (got %lu).", event.type); - callback(false); - } + default: + [[unlikely]] { + NSAssert(false, @"Unexpected key event type (got %lu).", event.type); + callback(false); + // This should not happen. Return to suppress clang-tidy warning on `type` being nil. + return; + } } _previouslyPressedFlags = modifierFlags; NSMutableDictionary* keyMessage = [@{ diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index 5feacb1d65272..f3c0106e24d02 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -473,6 +473,7 @@ - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId; @implementation FlutterEmbedderKeyResponder +// Synthesize properties declared in FlutterKeyPrimaryResponder protocol. @synthesize layoutMap; - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 2c79ee079d4b9..1a87c66386094 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -645,16 +645,23 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { static size_t sTaskRunnerIdentifiers = 0; const FlutterTaskRunnerDescription cocoa_task_runner_description = { .struct_size = sizeof(FlutterTaskRunnerDescription), - .user_data = (void*)CFBridgingRetain(self), + // Retain for use in post_task_callback. Released in destruction_callback. + .user_data = (__bridge_retained void*)self, .runs_task_on_current_thread_callback = [](void* user_data) -> bool { return [[NSThread currentThread] isMainThread]; }, .post_task_callback = [](FlutterTask task, uint64_t target_time_nanos, void* user_data) -> void { - [((__bridge FlutterEngine*)(user_data)) postMainThreadTask:task - targetTimeInNanoseconds:target_time_nanos]; + FlutterEngine* engine = (__bridge FlutterEngine*)user_data; + [engine postMainThreadTask:task targetTimeInNanoseconds:target_time_nanos]; }, .identifier = ++sTaskRunnerIdentifiers, + .destruction_callback = + [](void* user_data) { + // Balancing release for the retain when setting user_data above. + FlutterEngine* engine = (__bridge_transfer FlutterEngine*)user_data; + engine = nil; + }, }; const FlutterCustomTaskRunners custom_task_runners = { .struct_size = sizeof(FlutterCustomTaskRunners), @@ -1144,9 +1151,6 @@ - (void)shutDownEngine { NSLog(@"Could not de-initialize the Flutter engine: error %d", result); } - // Balancing release for the retain in the task runner dispatch table. - CFRelease((CFTypeRef)self); - result = _embedderAPI.Shutdown(_engine); if (result != kSuccess) { NSLog(@"Failed to shut down Flutter engine: error %d", result); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMenuPluginTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterMenuPluginTest.mm index 7d32208d93e3f..127bc5d5f29f1 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterMenuPluginTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterMenuPluginTest.mm @@ -25,6 +25,8 @@ @interface FakePluginRegistrar : NSObject @end @implementation FakePluginRegistrar + +// Synthesize properties declared in FlutterPluginRegistrar protocol. @synthesize messenger; @synthesize textures; @synthesize view; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterSurface.mm b/shell/platform/darwin/macos/framework/Source/FlutterSurface.mm index f61f81c5bf0b1..83c4ea2f8ebb6 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterSurface.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterSurface.mm @@ -4,11 +4,14 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurface.h" +#import #import +#import "flutter/fml/platform/darwin/cf_utils.h" + @interface FlutterSurface () { CGSize _size; - IOSurfaceRef _ioSurface; + fml::CFRef _ioSurface; id _texture; // Used for testing. BOOL _isInUseOverride; @@ -44,39 +47,35 @@ - (void)setIsInUseOverride:(BOOL)isInUseOverride { - (instancetype)initWithSize:(CGSize)size device:(id)device { if (self = [super init]) { self->_size = size; - self->_ioSurface = [FlutterSurface createIOSurfaceWithSize:size]; + self->_ioSurface.Reset([FlutterSurface createIOSurfaceWithSize:size]); self->_texture = [FlutterSurface createTextureForIOSurface:_ioSurface size:size device:device]; } return self; } -static void ReleaseSurface(void* surface) { - if (surface != nullptr) { - CFBridgingRelease(surface); - } -} - - (FlutterMetalTexture)asFlutterMetalTexture { - FlutterMetalTexture res; - memset(&res, 0, sizeof(FlutterMetalTexture)); - res.struct_size = sizeof(FlutterMetalTexture); - res.texture = (__bridge void*)_texture; - res.texture_id = self.textureId; - res.user_data = (void*)CFBridgingRetain(self); - res.destruction_callback = ReleaseSurface; - return res; + return FlutterMetalTexture{ + .struct_size = sizeof(FlutterMetalTexture), + .texture_id = self.textureId, + .texture = (__bridge void*)_texture, + // Retain for use in [FlutterSurface fromFlutterMetalTexture]. Released in + // destruction_callback. + .user_data = (__bridge_retained void*)self, + .destruction_callback = + [](void* user_data) { + // Balancing release for the retain when setting user_data above. + FlutterSurface* surface = (__bridge_transfer FlutterSurface*)user_data; + surface = nil; + }, + }; } + (FlutterSurface*)fromFlutterMetalTexture:(const FlutterMetalTexture*)texture { return (__bridge FlutterSurface*)texture->user_data; } -- (void)dealloc { - CFRelease(_ioSurface); -} - + (IOSurfaceRef)createIOSurfaceWithSize:(CGSize)size { - unsigned pixelFormat = 'BGRA'; + unsigned pixelFormat = kCVPixelFormatType_32BGRA; unsigned bytesPerElement = 4; size_t bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width * bytesPerElement); @@ -91,7 +90,7 @@ + (IOSurfaceRef)createIOSurfaceWithSize:(CGSize)size { }; IOSurfaceRef res = IOSurfaceCreate((CFDictionaryRef)options); - IOSurfaceSetValue(res, CFSTR("IOSurfaceColorSpace"), kCGColorSpaceSRGB); + IOSurfaceSetValue(res, kIOSurfaceColorSpace, kCGColorSpaceSRGB); return res; } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizerTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizerTest.mm index a9fafe947f562..efa5fce563847 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizerTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizerTest.mm @@ -28,8 +28,6 @@ @implementation FlutterThreadSynchronizerTestScaffold { FlutterThreadSynchronizer* _synchronizer; } -@synthesize synchronizer = _synchronizer; - - (nullable instancetype)init { self = [super init]; if (self != nil) { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiterTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiterTest.mm index 02448549627ad..b7637104aab63 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiterTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiterTest.mm @@ -7,8 +7,7 @@ #import "flutter/testing/testing.h" -@interface TestDisplayLink : FlutterDisplayLink { -} +@interface TestDisplayLink : FlutterDisplayLink @property(nonatomic) CFTimeInterval nominalOutputRefreshPeriod; @@ -16,20 +15,19 @@ @interface TestDisplayLink : FlutterDisplayLink { @implementation TestDisplayLink +// Synthesize properties declared readonly in FlutterDisplayLink. @synthesize nominalOutputRefreshPeriod = _nominalOutputRefreshPeriod; -@synthesize delegate = _delegate; -@synthesize paused = _paused; - (instancetype)init { if (self = [super init]) { - _paused = YES; + self.paused = YES; } return self; } - (void)tickWithTimestamp:(CFTimeInterval)timestamp targetTimestamp:(CFTimeInterval)targetTimestamp { - [_delegate onDisplayLink:timestamp targetTimestamp:targetTimestamp]; + [self.delegate onDisplayLink:timestamp targetTimestamp:targetTimestamp]; } - (void)invalidate { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index aa1d15708814f..305b64fd3924d 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -9,8 +9,7 @@ #import #include "flutter/common/constants.h" -#include "flutter/shell/platform/embedder/embedder.h" - +#include "flutter/fml/platform/darwin/cf_utils.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" @@ -20,6 +19,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputSemanticsObject.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" +#include "flutter/shell/platform/embedder/embedder.h" #pragma mark - Static types and data. @@ -341,7 +341,9 @@ @implementation FlutterViewController { FlutterThreadSynchronizer* _threadSynchronizer; } +// Synthesize properties declared readonly. @synthesize viewIdentifier = _viewIdentifier; + @dynamic accessibilityBridge; /** @@ -902,17 +904,16 @@ - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey { * It's returned in NSData* to enable auto reference count. */ static NSData* CurrentKeyboardLayoutData() { - TISInputSourceRef source = TISCopyCurrentKeyboardInputSource(); + fml::CFRef source(TISCopyCurrentKeyboardInputSource()); CFTypeRef layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData); if (layout_data == nil) { - CFRelease(source); // TISGetInputSourceProperty returns null with Japanese keyboard layout. // Using TISCopyCurrentKeyboardLayoutInputSource to fix NULL return. // https://github.com/microsoft/node-native-keymap/blob/5f0699ded00179410a14c0e1b0e089fe4df8e130/src/keyboard_mac.mm#L91 - source = TISCopyCurrentKeyboardLayoutInputSource(); + source.Reset(TISCopyCurrentKeyboardLayoutInputSource()); layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData); } - return (__bridge_transfer NSData*)CFRetain(layout_data); + return (__bridge NSData*)layout_data; } - (void)sendKeyEvent:(const FlutterKeyEvent&)event diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm index b63cf439c96a1..cc03fc5c699c2 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm @@ -8,6 +8,7 @@ #import +#include "flutter/fml/platform/darwin/cf_utils.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" @@ -21,6 +22,9 @@ #pragma mark - Test Helper Classes +static const FlutterPointerEvent kDefaultFlutterPointerEvent = {}; +static const FlutterKeyEvent kDefaultFlutterKeyEvent = {}; + // A wrap to convert FlutterKeyEvent to a ObjC class. @interface KeyEventWrapper : NSObject @property(nonatomic) FlutterKeyEvent* data; @@ -339,7 +343,7 @@ - (bool)testKeyEventsAreSentToFramework:(id)engineMock { OCMStub( // NOLINT(google-objc-avoid-throwing-exception) [engineMock binaryMessenger]) .andReturn(binaryMessengerMock); - OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:kDefaultFlutterKeyEvent callback:nil userData:nil]) .andCall([FlutterViewControllerTestObjC class], @@ -375,7 +379,7 @@ - (bool)testKeyEventsAreSentToFramework:(id)engineMock { - (bool)testCtrlTabKeyEventIsPropagated:(id)engineMock { __block bool called = false; __block FlutterKeyEvent last_event; - OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:kDefaultFlutterKeyEvent callback:nil userData:nil]) .andDo((^(NSInvocation* invocation) { @@ -419,7 +423,7 @@ - (bool)testCtrlTabKeyEventIsPropagated:(id)engineMock { - (bool)testKeyEquivalentIsPassedToTextInputPlugin:(id)engineMock { __block bool called = false; __block FlutterKeyEvent last_event; - OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:kDefaultFlutterKeyEvent callback:nil userData:nil]) .andDo((^(NSInvocation* invocation) { @@ -471,7 +475,7 @@ - (bool)testKeyEventsArePropagatedIfNotHandled:(id)engineMock { OCMStub( // NOLINT(google-objc-avoid-throwing-exception) [engineMock binaryMessenger]) .andReturn(binaryMessengerMock); - OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:kDefaultFlutterKeyEvent callback:nil userData:nil]) .andCall([FlutterViewControllerTestObjC class], @@ -545,7 +549,7 @@ - (bool)testFlagsChangedEventsArePropagatedIfNotHandled:(id)engineMock { OCMStub( // NOLINT(google-objc-avoid-throwing-exception) [engineMock binaryMessenger]) .andReturn(binaryMessengerMock); - OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:kDefaultFlutterKeyEvent callback:nil userData:nil]) .andCall([FlutterViewControllerTestObjC class], @@ -598,7 +602,7 @@ - (bool)testKeyEventsAreNotPropagatedIfHandled:(id)engineMock { OCMStub( // NOLINT(google-objc-avoid-throwing-exception) [engineMock binaryMessenger]) .andReturn(binaryMessengerMock); - OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:kDefaultFlutterKeyEvent callback:nil userData:nil]) .andCall([FlutterViewControllerTestObjC class], @@ -655,7 +659,7 @@ - (bool)testKeyboardIsRestartedOnEngineRestart:(id)engineMock { .andReturn(binaryMessengerMock); __block bool called = false; __block FlutterKeyEvent last_event; - OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:kDefaultFlutterKeyEvent callback:nil userData:nil]) .andDo((^(NSInvocation* invocation) { @@ -715,7 +719,7 @@ - (bool)testTrackpadGesturesAreSentToFramework:(id)engineMock { OCMStub([engineMock renderer]).andReturn(renderer_); __block bool called = false; __block FlutterPointerEvent last_event; - OCMStub([[engineMock ignoringNonObjectArgs] sendPointerEvent:FlutterPointerEvent{}]) + OCMStub([[engineMock ignoringNonObjectArgs] sendPointerEvent:kDefaultFlutterPointerEvent]) .andDo((^(NSInvocation* invocation) { FlutterPointerEvent* event; [invocation getArgument:&event atIndex:2]; @@ -1019,29 +1023,27 @@ - (bool)mouseAndGestureEventsAreHandledSeparately:(id)engineMock { // Test for pan events. // Start gesture. - CGEventRef cgEventStart = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0); + fml::CFRef cgEventStart( + CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0)); CGEventSetType(cgEventStart, kCGEventScrollWheel); CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan); CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1); [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]]; - CFRelease(cgEventStart); - CGEventRef cgEventUpdate = CGEventCreateCopy(cgEventStart); + fml::CFRef cgEventUpdate(CGEventCreateCopy(cgEventStart)); CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged); CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1); // pan_x CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2); // pan_y [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]]; - CFRelease(cgEventUpdate); NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00); [viewController mouseEntered:mouseEvent]; [viewController mouseExited:mouseEvent]; // End gesture. - CGEventRef cgEventEnd = CGEventCreateCopy(cgEventStart); + fml::CFRef cgEventEnd(CGEventCreateCopy(cgEventStart)); CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded); [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]]; - CFRelease(cgEventEnd); return true; } @@ -1139,7 +1141,7 @@ - (bool)testModifierKeysAreSynthesizedOnMouseMove:(id)engineMock { // Capture calls to sendKeyEvent __block NSMutableArray* events = [NSMutableArray array]; - OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:kDefaultFlutterKeyEvent callback:nil userData:nil]) .andDo((^(NSInvocation* invocation) { diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index 38c7e2db98e87..5a430ce408fd3 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -144,6 +144,9 @@ template("embedder_source_set") { } if (embedder_enable_metal) { + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + sources += [ "embedder_external_texture_metal.h", "embedder_external_texture_metal.mm", @@ -159,9 +162,6 @@ template("embedder_source_set") { deps += [ "//flutter/impeller/renderer/backend/metal" ] } - cflags_objc = flutter_cflags_objc - cflags_objcc = flutter_cflags_objcc - deps += [ "//flutter/shell/platform/darwin/graphics" ] } @@ -171,6 +171,13 @@ template("embedder_source_set") { "embedder_surface_vulkan.h", ] + if (impeller_supports_rendering) { + sources += [ + "embedder_surface_vulkan_impeller.cc", + "embedder_surface_vulkan_impeller.h", + ] + } + deps += [ "//flutter/flutter_vma:flutter_skia_vma", "//flutter/vulkan/procs", @@ -277,6 +284,8 @@ if (enable_unittests) { "tests/embedder_test.h", "tests/embedder_test_backingstore_producer.cc", "tests/embedder_test_backingstore_producer.h", + "tests/embedder_test_backingstore_producer_software.cc", + "tests/embedder_test_backingstore_producer_software.h", "tests/embedder_test_compositor.cc", "tests/embedder_test_compositor.h", "tests/embedder_test_compositor_software.cc", @@ -306,10 +315,13 @@ if (enable_unittests) { if (test_enable_gl) { sources += [ + "tests/embedder_test_backingstore_producer_gl.cc", + "tests/embedder_test_backingstore_producer_gl.h", "tests/embedder_test_compositor_gl.cc", "tests/embedder_test_compositor_gl.h", "tests/embedder_test_context_gl.cc", "tests/embedder_test_context_gl.h", + "tests/embedder_test_gl.cc", ] public_deps += [ @@ -320,10 +332,13 @@ if (enable_unittests) { if (test_enable_metal) { sources += [ - "tests/embedder_test_compositor_metal.cc", + "tests/embedder_test_backingstore_producer_metal.h", + "tests/embedder_test_backingstore_producer_metal.mm", "tests/embedder_test_compositor_metal.h", - "tests/embedder_test_context_metal.cc", + "tests/embedder_test_compositor_metal.mm", "tests/embedder_test_context_metal.h", + "tests/embedder_test_context_metal.mm", + "tests/embedder_test_metal.mm", ] public_deps += [ "//flutter/testing:metal" ] @@ -331,10 +346,13 @@ if (enable_unittests) { if (test_enable_vulkan) { sources += [ + "tests/embedder_test_backingstore_producer_vulkan.cc", + "tests/embedder_test_backingstore_producer_vulkan.h", "tests/embedder_test_compositor_vulkan.cc", "tests/embedder_test_compositor_vulkan.h", "tests/embedder_test_context_vulkan.cc", "tests/embedder_test_context_vulkan.h", + "tests/embedder_test_vulkan.cc", ] public_deps += [ @@ -368,6 +386,9 @@ if (enable_unittests) { } if (test_enable_metal) { + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + sources += [ "tests/embedder_metal_unittests.mm" ] } diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 2adf56c592e0a..8bec5367a87b4 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -615,7 +615,8 @@ InferVulkanPlatformViewCreationCallback( const flutter::PlatformViewEmbedder::PlatformDispatchTable& platform_dispatch_table, std::unique_ptr - external_view_embedder) { + external_view_embedder, + bool enable_impeller) { if (config->type != kVulkan) { return nullptr; } @@ -655,6 +656,82 @@ InferVulkanPlatformViewCreationCallback( auto proc_addr = vulkan_get_instance_proc_address(vk_instance, "vkGetInstanceProcAddr"); + std::shared_ptr view_embedder = + std::move(external_view_embedder); + +#if IMPELLER_SUPPORTS_RENDERING + if (enable_impeller) { + flutter::EmbedderSurfaceVulkanImpeller::VulkanDispatchTable + vulkan_dispatch_table = { + .get_instance_proc_address = + reinterpret_cast(proc_addr), + .get_next_image = vulkan_get_next_image, + .present_image = vulkan_present_image_callback, + }; + + std::unique_ptr embedder_surface = + std::make_unique( + config->vulkan.version, vk_instance, + config->vulkan.enabled_instance_extension_count, + config->vulkan.enabled_instance_extensions, + config->vulkan.enabled_device_extension_count, + config->vulkan.enabled_device_extensions, + static_cast(config->vulkan.physical_device), + static_cast(config->vulkan.device), + config->vulkan.queue_family_index, + static_cast(config->vulkan.queue), vulkan_dispatch_table, + view_embedder); + + return fml::MakeCopyable( + [embedder_surface = std::move(embedder_surface), + platform_dispatch_table, + external_view_embedder = + std::move(view_embedder)](flutter::Shell& shell) mutable { + return std::make_unique( + shell, // delegate + shell.GetTaskRunners(), // task runners + std::move(embedder_surface), // embedder surface + platform_dispatch_table, // platform dispatch table + std::move(external_view_embedder) // external view embedder + ); + }); + } else { + flutter::EmbedderSurfaceVulkan::VulkanDispatchTable vulkan_dispatch_table = + { + .get_instance_proc_address = + reinterpret_cast(proc_addr), + .get_next_image = vulkan_get_next_image, + .present_image = vulkan_present_image_callback, + }; + + std::unique_ptr embedder_surface = + std::make_unique( + config->vulkan.version, vk_instance, + config->vulkan.enabled_instance_extension_count, + config->vulkan.enabled_instance_extensions, + config->vulkan.enabled_device_extension_count, + config->vulkan.enabled_device_extensions, + static_cast(config->vulkan.physical_device), + static_cast(config->vulkan.device), + config->vulkan.queue_family_index, + static_cast(config->vulkan.queue), vulkan_dispatch_table, + view_embedder); + + return fml::MakeCopyable( + [embedder_surface = std::move(embedder_surface), + platform_dispatch_table, + external_view_embedder = + std::move(view_embedder)](flutter::Shell& shell) mutable { + return std::make_unique( + shell, // delegate + shell.GetTaskRunners(), // task runners + std::move(embedder_surface), // embedder surface + platform_dispatch_table, // platform dispatch table + std::move(external_view_embedder) // external view embedder + ); + }); + } +#else flutter::EmbedderSurfaceVulkan::VulkanDispatchTable vulkan_dispatch_table = { .get_instance_proc_address = reinterpret_cast(proc_addr), @@ -662,9 +739,6 @@ InferVulkanPlatformViewCreationCallback( .present_image = vulkan_present_image_callback, }; - std::shared_ptr view_embedder = - std::move(external_view_embedder); - std::unique_ptr embedder_surface = std::make_unique( config->vulkan.version, vk_instance, @@ -690,6 +764,7 @@ InferVulkanPlatformViewCreationCallback( std::move(external_view_embedder) // external view embedder ); }); +#endif // // IMPELLER_SUPPORTS_RENDERING #else // SHELL_ENABLE_VULKAN FML_LOG(ERROR) << "This Flutter Engine does not support Vulkan rendering."; return nullptr; @@ -762,7 +837,7 @@ InferPlatformViewCreationCallback( case kVulkan: return InferVulkanPlatformViewCreationCallback( config, user_data, platform_dispatch_table, - std::move(external_view_embedder)); + std::move(external_view_embedder), enable_impeller); default: return nullptr; } @@ -1108,21 +1183,21 @@ MakeRenderTargetFromBackingStoreImpeller( } impeller::TextureDescriptor depth_stencil_texture_desc; - depth_stencil_texture_desc.type = - impeller::TextureType::kTexture2DMultisample; depth_stencil_texture_desc.format = impeller::PixelFormat::kD24UnormS8Uint; depth_stencil_texture_desc.size = size; depth_stencil_texture_desc.usage = static_cast( impeller::TextureUsage::kRenderTarget); if (implicit_msaa) { + depth_stencil_texture_desc.type = + impeller::TextureType::kTexture2DMultisample; depth_stencil_texture_desc.sample_count = impeller::SampleCount::kCount4; } else { + depth_stencil_texture_desc.type = impeller::TextureType::kTexture2D; depth_stencil_texture_desc.sample_count = impeller::SampleCount::kCount1; } - auto depth_stencil_tex = std::make_shared( - gl_context.GetReactor(), depth_stencil_texture_desc, - impeller::TextureGLES::IsWrapped::kWrapped); + auto depth_stencil_tex = impeller::TextureGLES::CreatePlaceholder( + gl_context.GetReactor(), depth_stencil_texture_desc); impeller::DepthAttachment depth0; depth0.clear_depth = 0; @@ -1434,11 +1509,16 @@ CreateEmbedderRenderTarget( break; } case kFlutterBackingStoreTypeVulkan: { - auto skia_surface = - MakeSkSurfaceFromBackingStore(context, config, &backing_store.vulkan); - render_target = MakeRenderTargetFromSkSurface( - backing_store, std::move(skia_surface), collect_callback.Release()); - break; + if (enable_impeller) { + FML_LOG(ERROR) << "Unimplemented"; + break; + } else { + auto skia_surface = MakeSkSurfaceFromBackingStore( + context, config, &backing_store.vulkan); + render_target = MakeRenderTargetFromSkSurface( + backing_store, std::move(skia_surface), collect_callback.Release()); + break; + } } }; diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 2873e1889256c..8ca474222d39e 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -164,6 +164,9 @@ typedef enum { kFlutterSemanticsActionSetText = 1 << 21, /// Request that the respective focusable widget gain input focus. kFlutterSemanticsActionFocus = 1 << 22, + /// Request that scrolls the current scrollable container to a given scroll + /// offset. + kFlutterSemanticsActionScrollToOffset = 1 << 23, } FlutterSemanticsAction; /// The set of properties that may be associated with a semantics node. @@ -1651,6 +1654,8 @@ typedef struct { /// A unique identifier for the task runner. If multiple task runners service /// tasks on the same thread, their identifiers must match. size_t identifier; + /// The callback invoked when the task runner is destroyed. + VoidCallback destruction_callback; } FlutterTaskRunnerDescription; typedef struct { diff --git a/shell/platform/embedder/embedder_external_texture_gl.cc b/shell/platform/embedder/embedder_external_texture_gl.cc index 7ad22cdadbda1..852e7390a25ae 100644 --- a/shell/platform/embedder/embedder_external_texture_gl.cc +++ b/shell/platform/embedder/embedder_external_texture_gl.cc @@ -5,7 +5,14 @@ #include "flutter/shell/platform/embedder/embedder_external_texture_gl.h" #include "flutter/fml/logging.h" -#include "include/core/SkCanvas.h" +#include "impeller/core/texture_descriptor.h" +#include "impeller/display_list/aiks_context.h" +#include "impeller/display_list/dl_image_impeller.h" +#include "impeller/geometry/size.h" +#include "impeller/renderer/backend/gles/context_gles.h" +#include "impeller/renderer/backend/gles/handle_gles.h" +#include "impeller/renderer/backend/gles/texture_gles.h" + #include "include/core/SkPaint.h" #include "third_party/skia/include/core/SkAlphaType.h" #include "third_party/skia/include/core/SkColorSpace.h" @@ -62,6 +69,17 @@ sk_sp EmbedderExternalTextureGL::ResolveTexture( GrDirectContext* context, impeller::AiksContext* aiks_context, const SkISize& size) { + if (!!aiks_context) { + return ResolveTextureImpeller(texture_id, aiks_context, size); + } else { + return ResolveTextureSkia(texture_id, context, size); + } +} + +sk_sp EmbedderExternalTextureGL::ResolveTextureSkia( + int64_t texture_id, + GrDirectContext* context, + const SkISize& size) { context->flushAndSubmit(); context->resetContext(kAll_GrBackendState); std::unique_ptr texture = @@ -110,6 +128,48 @@ sk_sp EmbedderExternalTextureGL::ResolveTexture( return DlImage::Make(std::move(image)); } +sk_sp EmbedderExternalTextureGL::ResolveTextureImpeller( + int64_t texture_id, + impeller::AiksContext* aiks_context, + const SkISize& size) { + std::unique_ptr texture = + external_texture_callback_(texture_id, size.width(), size.height()); + + if (!texture) { + return nullptr; + } + + impeller::TextureDescriptor desc; + desc.size = impeller::ISize(texture->width, texture->height); + + impeller::ContextGLES& context = + impeller::ContextGLES::Cast(*aiks_context->GetContext()); + impeller::HandleGLES handle = context.GetReactor()->CreateHandle( + impeller::HandleType::kTexture, texture->target); + std::shared_ptr image = + impeller::TextureGLES::WrapTexture(context.GetReactor(), desc, handle); + + if (!image) { + // In case Skia rejects the image, call the release proc so that + // embedders can perform collection of intermediates. + if (texture->destruction_callback) { + texture->destruction_callback(texture->user_data); + } + FML_LOG(ERROR) << "Could not create external texture"; + return nullptr; + } + if (texture->destruction_callback && + !context.GetReactor()->RegisterCleanupCallback( + handle, + [callback = texture->destruction_callback, + user_data = texture->user_data]() { callback(user_data); })) { + FML_LOG(ERROR) << "Could not register destruction callback"; + return nullptr; + } + + return impeller::DlImageImpeller::Make(image); +} + // |flutter::Texture| void EmbedderExternalTextureGL::OnGrContextCreated() {} diff --git a/shell/platform/embedder/embedder_external_texture_gl.h b/shell/platform/embedder/embedder_external_texture_gl.h index a661a783414bb..0d8853e11db6f 100644 --- a/shell/platform/embedder/embedder_external_texture_gl.h +++ b/shell/platform/embedder/embedder_external_texture_gl.h @@ -31,6 +31,14 @@ class EmbedderExternalTextureGL : public flutter::Texture { impeller::AiksContext* aiks_context, const SkISize& size); + sk_sp ResolveTextureSkia(int64_t texture_id, + GrDirectContext* context, + const SkISize& size); + + sk_sp ResolveTextureImpeller(int64_t texture_id, + impeller::AiksContext* aiks_context, + const SkISize& size); + // |flutter::Texture| void Paint(PaintContext& context, const SkRect& bounds, diff --git a/shell/platform/embedder/embedder_surface_metal_impeller.mm b/shell/platform/embedder/embedder_surface_metal_impeller.mm index 5844e16bc0296..65f239211c2c8 100644 --- a/shell/platform/embedder/embedder_surface_metal_impeller.mm +++ b/shell/platform/embedder/embedder_surface_metal_impeller.mm @@ -20,7 +20,7 @@ #include "impeller/typographer/backends/skia/typographer_context_skia.h" #include "impeller/typographer/typographer_context.h" -FLUTTER_ASSERT_NOT_ARC +FLUTTER_ASSERT_ARC namespace flutter { @@ -41,11 +41,11 @@ impeller_framebuffer_blend_shaders_length), }; context_ = impeller::ContextMTL::Create( - (id)device, // device - (id)command_queue, // command_queue - shader_mappings, // shader_libraries_data - std::make_shared(false), // is_gpu_disabled_sync_switch - "Impeller Library" // library_label + (__bridge id)device, // device + (__bridge id)command_queue, // command_queue + shader_mappings, // shader_libraries_data + std::make_shared(false), // is_gpu_disabled_sync_switch + "Impeller Library" // library_label ); FML_LOG(IMPORTANT) << "Using the Impeller rendering backend (Metal)."; diff --git a/shell/platform/embedder/embedder_surface_metal_skia.mm b/shell/platform/embedder/embedder_surface_metal_skia.mm index f87ac7ccba9d4..eb09ab655b5c7 100644 --- a/shell/platform/embedder/embedder_surface_metal_skia.mm +++ b/shell/platform/embedder/embedder_surface_metal_skia.mm @@ -13,7 +13,8 @@ #include "flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.h" #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" -FLUTTER_ASSERT_NOT_ARC +FLUTTER_ASSERT_ARC + namespace flutter { EmbedderSurfaceMetalSkia::EmbedderSurfaceMetalSkia( @@ -25,11 +26,11 @@ metal_dispatch_table_(std::move(metal_dispatch_table)), external_view_embedder_(std::move(external_view_embedder)) { main_context_ = - [FlutterDarwinContextMetalSkia createGrContext:(id)device - commandQueue:(id)command_queue]; + [FlutterDarwinContextMetalSkia createGrContext:(__bridge id)device + commandQueue:(__bridge id)command_queue]; resource_context_ = - [FlutterDarwinContextMetalSkia createGrContext:(id)device - commandQueue:(id)command_queue]; + [FlutterDarwinContextMetalSkia createGrContext:(__bridge id)device + commandQueue:(__bridge id)command_queue]; valid_ = main_context_ && resource_context_; } diff --git a/shell/platform/embedder/embedder_surface_vulkan_impeller.cc b/shell/platform/embedder/embedder_surface_vulkan_impeller.cc new file mode 100644 index 0000000000000..a590898843eeb --- /dev/null +++ b/shell/platform/embedder/embedder_surface_vulkan_impeller.cc @@ -0,0 +1,124 @@ +// 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. + +#include "flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h" + +#include + +#include "flutter/impeller/entity/vk/entity_shaders_vk.h" +#include "flutter/impeller/entity/vk/framebuffer_blend_shaders_vk.h" +#include "flutter/impeller/entity/vk/modern_shaders_vk.h" +#include "flutter/shell/gpu/gpu_surface_vulkan.h" +#include "impeller/renderer/backend/vulkan/context_vk.h" +#include "include/gpu/ganesh/GrDirectContext.h" +#include "shell/gpu/gpu_surface_vulkan_impeller.h" + +namespace flutter { + +EmbedderSurfaceVulkanImpeller::EmbedderSurfaceVulkanImpeller( + uint32_t version, + VkInstance instance, + size_t instance_extension_count, + const char** instance_extensions, + size_t device_extension_count, + const char** device_extensions, + VkPhysicalDevice physical_device, + VkDevice device, + uint32_t queue_family_index, + VkQueue queue, + const VulkanDispatchTable& vulkan_dispatch_table, + std::shared_ptr external_view_embedder) + : vk_(fml::MakeRefCounted( + vulkan_dispatch_table.get_instance_proc_address)), + vulkan_dispatch_table_(vulkan_dispatch_table), + external_view_embedder_(std::move(external_view_embedder)) { + // Make sure all required members of the dispatch table are checked. + if (!vulkan_dispatch_table_.get_instance_proc_address || + !vulkan_dispatch_table_.get_next_image || + !vulkan_dispatch_table_.present_image) { + return; + } + + std::vector> shader_mappings = { + std::make_shared(impeller_entity_shaders_vk_data, + impeller_entity_shaders_vk_length), + std::make_shared(impeller_modern_shaders_vk_data, + impeller_modern_shaders_vk_length), + std::make_shared( + impeller_framebuffer_blend_shaders_vk_data, + impeller_framebuffer_blend_shaders_vk_length), + }; + impeller::ContextVK::Settings settings; + settings.shader_libraries_data = shader_mappings; + settings.proc_address_callback = + vulkan_dispatch_table.get_instance_proc_address; + + impeller::ContextVK::EmbedderData data; + data.instance = instance; + data.physical_device = physical_device; + data.device = device; + data.queue = queue; + data.queue_family_index = queue_family_index; + data.instance_extensions.reserve(instance_extension_count); + for (auto i = 0u; i < instance_extension_count; i++) { + data.instance_extensions.push_back(std::string{instance_extensions[i]}); + } + data.device_extensions.reserve(device_extension_count); + for (auto i = 0u; i < device_extension_count; i++) { + data.device_extensions.push_back(std::string{device_extensions[i]}); + } + settings.embedder_data = data; + + context_ = impeller::ContextVK::Create(std::move(settings)); + if (!context_) { + FML_LOG(ERROR) << "Failed to initialize Vulkan Context."; + return; + } + + FML_LOG(IMPORTANT) << "Using the Impeller rendering backend (Vulkan)."; + + valid_ = true; +} + +EmbedderSurfaceVulkanImpeller::~EmbedderSurfaceVulkanImpeller() {} + +std::shared_ptr +EmbedderSurfaceVulkanImpeller::CreateImpellerContext() const { + return context_; +} + +// |GPUSurfaceVulkanDelegate| +const vulkan::VulkanProcTable& EmbedderSurfaceVulkanImpeller::vk() { + return *vk_; +} + +// |GPUSurfaceVulkanDelegate| +FlutterVulkanImage EmbedderSurfaceVulkanImpeller::AcquireImage( + const SkISize& size) { + return vulkan_dispatch_table_.get_next_image(size); +} + +// |GPUSurfaceVulkanDelegate| +bool EmbedderSurfaceVulkanImpeller::PresentImage(VkImage image, + VkFormat format) { + return vulkan_dispatch_table_.present_image(image, format); +} + +// |EmbedderSurface| +bool EmbedderSurfaceVulkanImpeller::IsValid() const { + return valid_; +} + +// |EmbedderSurface| +std::unique_ptr EmbedderSurfaceVulkanImpeller::CreateGPUSurface() { + return std::make_unique(this, context_); +} + +// |EmbedderSurface| +sk_sp EmbedderSurfaceVulkanImpeller::CreateResourceContext() + const { + return nullptr; +} + +} // namespace flutter diff --git a/shell/platform/embedder/embedder_surface_vulkan_impeller.h b/shell/platform/embedder/embedder_surface_vulkan_impeller.h new file mode 100644 index 0000000000000..5d1463aa83fcd --- /dev/null +++ b/shell/platform/embedder/embedder_surface_vulkan_impeller.h @@ -0,0 +1,81 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_VULKAN_IMPELLER_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_VULKAN_IMPELLER_H_ + +#include "flutter/shell/common/context_options.h" +#include "flutter/shell/gpu/gpu_surface_vulkan.h" +#include "flutter/shell/gpu/gpu_surface_vulkan_delegate.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/embedder/embedder_external_view_embedder.h" +#include "flutter/shell/platform/embedder/embedder_surface.h" +#include "flutter/vulkan/procs/vulkan_proc_table.h" +#include "impeller/renderer/backend/vulkan/context_vk.h" + +namespace flutter { + +class EmbedderSurfaceVulkanImpeller final : public EmbedderSurface, + public GPUSurfaceVulkanDelegate { + public: + struct VulkanDispatchTable { + PFN_vkGetInstanceProcAddr get_instance_proc_address; // required + std::function + get_next_image; // required + std::function + present_image; // required + }; + + EmbedderSurfaceVulkanImpeller( + uint32_t version, + VkInstance instance, + size_t instance_extension_count, + const char** instance_extensions, + size_t device_extension_count, + const char** device_extensions, + VkPhysicalDevice physical_device, + VkDevice device, + uint32_t queue_family_index, + VkQueue queue, + const VulkanDispatchTable& vulkan_dispatch_table, + std::shared_ptr external_view_embedder); + + ~EmbedderSurfaceVulkanImpeller() override; + + // |GPUSurfaceVulkanDelegate| + const vulkan::VulkanProcTable& vk() override; + + // |GPUSurfaceVulkanDelegate| + FlutterVulkanImage AcquireImage(const SkISize& size) override; + + // |GPUSurfaceVulkanDelegate| + bool PresentImage(VkImage image, VkFormat format) override; + + // |GPUSurfaceVulkanDelegate| + std::shared_ptr CreateImpellerContext() const override; + + private: + bool valid_ = false; + fml::RefPtr vk_; + VulkanDispatchTable vulkan_dispatch_table_; + std::shared_ptr external_view_embedder_; + std::shared_ptr context_; + + // |EmbedderSurface| + bool IsValid() const override; + + // |EmbedderSurface| + std::unique_ptr CreateGPUSurface() override; + + // |EmbedderSurface| + sk_sp CreateResourceContext() const override; + + EmbedderSurfaceVulkanImpeller(const EmbedderSurfaceVulkanImpeller&) = delete; + EmbedderSurfaceVulkanImpeller& operator=( + const EmbedderSurfaceVulkanImpeller&) = delete; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_VULKAN_IMPELLER_H_ diff --git a/shell/platform/embedder/embedder_task_runner.cc b/shell/platform/embedder/embedder_task_runner.cc index abbc340db0faf..061baa9a5e9bb 100644 --- a/shell/platform/embedder/embedder_task_runner.cc +++ b/shell/platform/embedder/embedder_task_runner.cc @@ -18,9 +18,12 @@ EmbedderTaskRunner::EmbedderTaskRunner(DispatchTable table, fml::MessageLoopTaskQueues::GetInstance()->CreateTaskQueue()) { FML_DCHECK(dispatch_table_.post_task_callback); FML_DCHECK(dispatch_table_.runs_task_on_current_thread_callback); + FML_DCHECK(dispatch_table_.destruction_callback); } -EmbedderTaskRunner::~EmbedderTaskRunner() = default; +EmbedderTaskRunner::~EmbedderTaskRunner() { + dispatch_table_.destruction_callback(); +} size_t EmbedderTaskRunner::GetEmbedderIdentifier() const { return embedder_identifier_; diff --git a/shell/platform/embedder/embedder_task_runner.h b/shell/platform/embedder/embedder_task_runner.h index 5c0e1e845d43b..4f7b47bd76ed0 100644 --- a/shell/platform/embedder/embedder_task_runner.h +++ b/shell/platform/embedder/embedder_task_runner.h @@ -39,6 +39,10 @@ class EmbedderTaskRunner final : public fml::TaskRunner { /// thread. /// std::function runs_task_on_current_thread_callback; + + //-------------------------------------------------------------------------- + /// Performs user-designated cleanup on destruction. + std::function destruction_callback; }; //---------------------------------------------------------------------------- diff --git a/shell/platform/embedder/embedder_thread_host.cc b/shell/platform/embedder/embedder_thread_host.cc index 7cca136462ed5..78ad599fbb0e0 100644 --- a/shell/platform/embedder/embedder_thread_host.cc +++ b/shell/platform/embedder/embedder_thread_host.cc @@ -55,11 +55,16 @@ CreateEmbedderTaskRunner(const FlutterTaskRunnerDescription* description) { auto runs_task_on_current_thread_callback_c = description->runs_task_on_current_thread_callback; + VoidCallback destruction_callback_c = [](void* user_data) {}; + if (SAFE_ACCESS(description, destruction_callback, nullptr) != nullptr) { + destruction_callback_c = description->destruction_callback; + } + EmbedderTaskRunner::DispatchTable task_runner_dispatch_table = { - // .post_task_callback - [post_task_callback_c, user_data](EmbedderTaskRunner* task_runner, - uint64_t task_baton, - fml::TimePoint target_time) -> void { + .post_task_callback = [post_task_callback_c, user_data]( + EmbedderTaskRunner* task_runner, + uint64_t task_baton, + fml::TimePoint target_time) -> void { FlutterTask task = { // runner reinterpret_cast(task_runner), @@ -69,10 +74,15 @@ CreateEmbedderTaskRunner(const FlutterTaskRunnerDescription* description) { post_task_callback_c(task, target_time.ToEpochDelta().ToNanoseconds(), user_data); }, - // runs_task_on_current_thread_callback - [runs_task_on_current_thread_callback_c, user_data]() -> bool { + .runs_task_on_current_thread_callback = + [runs_task_on_current_thread_callback_c, user_data]() -> bool { return runs_task_on_current_thread_callback_c(user_data); - }}; + }, + .destruction_callback = + [destruction_callback_c, user_data]() { + destruction_callback_c(user_data); + }, + }; return {true, fml::MakeRefCounted( task_runner_dispatch_table, diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index adc7bb417b05c..dabbb1e96ab7e 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -1595,3 +1595,20 @@ void render_impeller_text_test() { }; PlatformDispatcher.instance.scheduleFrame(); } + +@pragma('vm:entry-point') +// ignore: non_constant_identifier_names +Future render_impeller_image_snapshot_test() async { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + const Color color = Color.fromARGB(255, 0, 0, 123); + canvas.drawPaint(Paint()..color = color); + final Picture picture = recorder.endRecording(); + + final Image image = await picture.toImage(100, 100); + final ByteData? imageData = await image.toByteData(); + final int pixel = imageData!.getInt32(0); + + final bool result = (pixel & 0xFF) == color.alpha && ((pixel >> 8) & 0xFF) == color.blue; + notifyBoolValue(result); +} diff --git a/shell/platform/embedder/platform_view_embedder.h b/shell/platform/embedder/platform_view_embedder.h index f4430589407fd..090e6920df35b 100644 --- a/shell/platform/embedder/platform_view_embedder.h +++ b/shell/platform/embedder/platform_view_embedder.h @@ -26,6 +26,9 @@ #ifdef SHELL_ENABLE_VULKAN #include "flutter/shell/platform/embedder/embedder_surface_vulkan.h" +#ifdef IMPELLER_SUPPORTS_RENDERING +#include "flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h" +#endif // IMPELLER_SUPPORTS_RENDERING #endif namespace flutter { diff --git a/shell/platform/embedder/tests/embedder_a11y_unittests.cc b/shell/platform/embedder/tests/embedder_a11y_unittests.cc index 01cfff7cac374..ae23f1c428e1e 100644 --- a/shell/platform/embedder/tests/embedder_a11y_unittests.cc +++ b/shell/platform/embedder/tests/embedder_a11y_unittests.cc @@ -33,9 +33,9 @@ constexpr static char kTooltip[] = "tooltip"; TEST_F(EmbedderTest, CannotProvideMultipleSemanticsCallbacks) { { - EmbedderConfigBuilder builder( - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); - builder.SetSoftwareRendererConfig(); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); builder.GetProjectArgs().update_semantics_callback = [](const FlutterSemanticsUpdate* update, void* user_data) {}; builder.GetProjectArgs().update_semantics_callback2 = @@ -46,9 +46,9 @@ TEST_F(EmbedderTest, CannotProvideMultipleSemanticsCallbacks) { } { - EmbedderConfigBuilder builder( - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); - builder.SetSoftwareRendererConfig(); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); builder.GetProjectArgs().update_semantics_callback2 = [](const FlutterSemanticsUpdate2* update, void* user_data) {}; builder.GetProjectArgs().update_semantics_node_callback = @@ -61,9 +61,9 @@ TEST_F(EmbedderTest, CannotProvideMultipleSemanticsCallbacks) { } { - EmbedderConfigBuilder builder( - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); - builder.SetSoftwareRendererConfig(); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); builder.GetProjectArgs().update_semantics_callback = [](const FlutterSemanticsUpdate* update, void* user_data) {}; builder.GetProjectArgs().update_semantics_node_callback = @@ -76,9 +76,9 @@ TEST_F(EmbedderTest, CannotProvideMultipleSemanticsCallbacks) { } { - EmbedderConfigBuilder builder( - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); - builder.SetSoftwareRendererConfig(); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); builder.GetProjectArgs().update_semantics_callback2 = [](const FlutterSemanticsUpdate2* update, void* user_data) {}; builder.GetProjectArgs().update_semantics_callback = @@ -98,7 +98,7 @@ TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV3Callbacks) { GTEST_SKIP() << "This test crashes on Fuchsia. https://fxbug.dev/87493 "; #else - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent signal_native_latch; @@ -171,7 +171,7 @@ TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV3Callbacks) { }); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("a11y_main"); auto engine = builder.LaunchEngine(); @@ -277,7 +277,7 @@ TEST_F(EmbedderA11yTest, A11yStringAttributes) { GTEST_SKIP() << "This test crashes on Fuchsia. https://fxbug.dev/87493 "; #else - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent signal_native_latch; @@ -375,7 +375,7 @@ TEST_F(EmbedderA11yTest, A11yStringAttributes) { }); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("a11y_string_attributes"); auto engine = builder.LaunchEngine(); @@ -397,7 +397,7 @@ TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV2Callbacks) { GTEST_SKIP() << "This test crashes on Fuchsia. https://fxbug.dev/87493 "; #else - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent signal_native_latch; @@ -468,7 +468,7 @@ TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV2Callbacks) { }); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("a11y_main"); auto engine = builder.LaunchEngine(); @@ -570,7 +570,7 @@ TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV2Callbacks) { } TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV1Callbacks) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent signal_native_latch; @@ -662,7 +662,7 @@ TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV1Callbacks) { }); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("a11y_main"); auto engine = builder.LaunchEngine(); diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index 3d39cca7ed3f3..fa99c15e5a7e7 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -8,26 +8,9 @@ #include "flutter/runtime/dart_vm.h" #include "flutter/shell/platform/embedder/embedder.h" #include "tests/embedder_test_context.h" -#include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkImage.h" -#ifdef SHELL_ENABLE_GL -#include "flutter/shell/platform/embedder/tests/embedder_test_compositor_gl.h" -#include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" -#endif - -#ifdef SHELL_ENABLE_VULKAN -#include "flutter/shell/platform/embedder/tests/embedder_test_context_vulkan.h" -#include "flutter/vulkan/vulkan_device.h" // nogncheck -#include "vulkan/vulkan_core.h" // nogncheck -#endif - -#ifdef SHELL_ENABLE_METAL -#include "flutter/shell/platform/embedder/tests/embedder_test_context_metal.h" -#endif - -namespace flutter { -namespace testing { +namespace flutter::testing { EmbedderConfigBuilder::EmbedderConfigBuilder( EmbedderTestContext& context, @@ -43,68 +26,6 @@ EmbedderConfigBuilder::EmbedderConfigBuilder( custom_task_runners_.struct_size = sizeof(FlutterCustomTaskRunners); -#ifdef SHELL_ENABLE_GL - opengl_renderer_config_.struct_size = sizeof(FlutterOpenGLRendererConfig); - opengl_renderer_config_.make_current = [](void* context) -> bool { - return reinterpret_cast(context)->GLMakeCurrent(); - }; - opengl_renderer_config_.clear_current = [](void* context) -> bool { - return reinterpret_cast(context)->GLClearCurrent(); - }; - opengl_renderer_config_.present_with_info = - [](void* context, const FlutterPresentInfo* present_info) -> bool { - return reinterpret_cast(context)->GLPresent( - *present_info); - }; - opengl_renderer_config_.fbo_with_frame_info_callback = - [](void* context, const FlutterFrameInfo* frame_info) -> uint32_t { - return reinterpret_cast(context)->GLGetFramebuffer( - *frame_info); - }; - opengl_renderer_config_.populate_existing_damage = nullptr; - opengl_renderer_config_.make_resource_current = [](void* context) -> bool { - return reinterpret_cast(context) - ->GLMakeResourceCurrent(); - }; - opengl_renderer_config_.gl_proc_resolver = [](void* context, - const char* name) -> void* { - return reinterpret_cast(context)->GLGetProcAddress( - name); - }; - opengl_renderer_config_.fbo_reset_after_present = true; - opengl_renderer_config_.surface_transformation = - [](void* context) -> FlutterTransformation { - return reinterpret_cast(context) - ->GetRootSurfaceTransformation(); - }; -#endif - -#ifdef SHELL_ENABLE_METAL - InitializeMetalRendererConfig(); -#endif - -#ifdef SHELL_ENABLE_VULKAN - InitializeVulkanRendererConfig(); -#endif - - software_renderer_config_.struct_size = sizeof(FlutterSoftwareRendererConfig); - software_renderer_config_.surface_present_callback = - [](void* context, const void* allocation, size_t row_bytes, - size_t height) { - auto image_info = - SkImageInfo::MakeN32Premul(SkISize::Make(row_bytes / 4, height)); - SkBitmap bitmap; - if (!bitmap.installPixels(image_info, const_cast(allocation), - row_bytes)) { - FML_LOG(ERROR) << "Could not copy pixels for the software " - "composition from the engine."; - return false; - } - bitmap.setImmutable(); - return reinterpret_cast(context)->Present( - SkImages::RasterFromBitmap(bitmap)); - }; - // The first argument is always the executable name. Don't make tests have to // do this manually. AddCommandLineArgument("embedder_unittest"); @@ -135,93 +56,6 @@ FlutterProjectArgs& EmbedderConfigBuilder::GetProjectArgs() { return project_args_; } -void EmbedderConfigBuilder::SetSoftwareRendererConfig(SkISize surface_size) { - renderer_config_.type = FlutterRendererType::kSoftware; - renderer_config_.software = software_renderer_config_; - context_.SetupSurface(surface_size); -} - -void EmbedderConfigBuilder::SetOpenGLFBOCallBack() { -#ifdef SHELL_ENABLE_GL - // SetOpenGLRendererConfig must be called before this. - FML_CHECK(renderer_config_.type == FlutterRendererType::kOpenGL); - renderer_config_.open_gl.fbo_callback = [](void* context) -> uint32_t { - FlutterFrameInfo frame_info = {}; - // fbo_callback doesn't use the frame size information, only - // fbo_callback_with_frame_info does. - frame_info.struct_size = sizeof(FlutterFrameInfo); - frame_info.size.width = 0; - frame_info.size.height = 0; - return reinterpret_cast(context)->GLGetFramebuffer( - frame_info); - }; -#endif -} - -void EmbedderConfigBuilder::SetOpenGLPresentCallBack() { -#ifdef SHELL_ENABLE_GL - // SetOpenGLRendererConfig must be called before this. - FML_CHECK(renderer_config_.type == FlutterRendererType::kOpenGL); - renderer_config_.open_gl.present = [](void* context) -> bool { - // passing a placeholder fbo_id. - return reinterpret_cast(context)->GLPresent( - FlutterPresentInfo{ - .fbo_id = 0, - }); - }; -#endif -} - -void EmbedderConfigBuilder::SetRendererConfig(EmbedderTestContextType type, - SkISize surface_size) { - switch (type) { - case EmbedderTestContextType::kOpenGLContext: - SetOpenGLRendererConfig(surface_size); - break; - case EmbedderTestContextType::kMetalContext: - SetMetalRendererConfig(surface_size); - break; - case EmbedderTestContextType::kVulkanContext: - SetVulkanRendererConfig(surface_size); - break; - case EmbedderTestContextType::kSoftwareContext: - SetSoftwareRendererConfig(surface_size); - break; - } -} - -void EmbedderConfigBuilder::SetOpenGLRendererConfig(SkISize surface_size) { -#ifdef SHELL_ENABLE_GL - renderer_config_.type = FlutterRendererType::kOpenGL; - renderer_config_.open_gl = opengl_renderer_config_; - context_.SetupSurface(surface_size); -#endif -} - -void EmbedderConfigBuilder::SetMetalRendererConfig(SkISize surface_size) { -#ifdef SHELL_ENABLE_METAL - renderer_config_.type = FlutterRendererType::kMetal; - renderer_config_.metal = metal_renderer_config_; - context_.SetupSurface(surface_size); -#endif -} - -void EmbedderConfigBuilder::SetVulkanRendererConfig( - SkISize surface_size, - std::optional - instance_proc_address_callback) { -#ifdef SHELL_ENABLE_VULKAN - renderer_config_.type = FlutterRendererType::kVulkan; - FlutterVulkanRendererConfig vulkan_renderer_config = vulkan_renderer_config_; - if (instance_proc_address_callback.has_value()) { - vulkan_renderer_config.get_instance_proc_address_callback = - instance_proc_address_callback.value(); - } - renderer_config_.vulkan = vulkan_renderer_config; - context_.SetupSurface(surface_size); -#endif -} - void EmbedderConfigBuilder::SetAssetsPath() { project_args_.assets_path = context_.GetAssetsPath().c_str(); } @@ -336,10 +170,6 @@ void EmbedderConfigBuilder::SetupVsyncCallback() { }; } -FlutterRendererConfig& EmbedderConfigBuilder::GetRendererConfig() { - return renderer_config_; -} - void EmbedderConfigBuilder::SetRenderTaskRunner( const FlutterTaskRunnerDescription* runner) { if (runner == nullptr) { @@ -406,18 +236,7 @@ FlutterCompositor& EmbedderConfigBuilder::GetCompositor() { void EmbedderConfigBuilder::SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType type, FlutterSoftwarePixelFormat software_pixfmt) { - auto& compositor = context_.GetCompositor(); - - auto producer = std::make_unique( - compositor.GetGrContext(), type, software_pixfmt); - -#ifdef SHELL_ENABLE_GL - producer->SetEGLContext(context_.egl_context_); -#endif - - // TODO(wrightgeorge): figure out a better way of plumbing through the - // GrDirectContext - compositor.SetBackingStoreProducer(std::move(producer)); + context_.GetCompositor().SetRenderTargetType(type, software_pixfmt); } UniqueEngine EmbedderConfigBuilder::LaunchEngine() const { @@ -466,11 +285,12 @@ UniqueEngine EmbedderConfigBuilder::SetupEngine(bool run) const { project_args.dart_entrypoint_argc = 0; } - auto result = - run ? FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config_, - &project_args, &context_, &engine) - : FlutterEngineInitialize(FLUTTER_ENGINE_VERSION, &renderer_config_, - &project_args, &context_, &engine); + auto result = run ? FlutterEngineRun(FLUTTER_ENGINE_VERSION, + &context_.GetRendererConfig(), + &project_args, &context_, &engine) + : FlutterEngineInitialize( + FLUTTER_ENGINE_VERSION, &context_.GetRendererConfig(), + &project_args, &context_, &engine); if (result != kSuccess) { return {}; @@ -479,93 +299,4 @@ UniqueEngine EmbedderConfigBuilder::SetupEngine(bool run) const { return UniqueEngine{engine}; } -#ifdef SHELL_ENABLE_METAL - -void EmbedderConfigBuilder::InitializeMetalRendererConfig() { - if (context_.GetContextType() != EmbedderTestContextType::kMetalContext) { - return; - } - - metal_renderer_config_.struct_size = sizeof(metal_renderer_config_); - EmbedderTestContextMetal& metal_context = - reinterpret_cast(context_); - - metal_renderer_config_.device = - metal_context.GetTestMetalContext()->GetMetalDevice(); - metal_renderer_config_.present_command_queue = - metal_context.GetTestMetalContext()->GetMetalCommandQueue(); - metal_renderer_config_.get_next_drawable_callback = - [](void* user_data, const FlutterFrameInfo* frame_info) { - return reinterpret_cast(user_data) - ->GetNextDrawable(frame_info); - }; - metal_renderer_config_.present_drawable_callback = - [](void* user_data, const FlutterMetalTexture* texture) -> bool { - EmbedderTestContextMetal* metal_context = - reinterpret_cast(user_data); - return metal_context->Present(texture->texture_id); - }; - metal_renderer_config_.external_texture_frame_callback = - [](void* user_data, int64_t texture_id, size_t width, size_t height, - FlutterMetalExternalTexture* texture_out) -> bool { - EmbedderTestContextMetal* metal_context = - reinterpret_cast(user_data); - return metal_context->PopulateExternalTexture(texture_id, width, height, - texture_out); - }; -} - -#endif // SHELL_ENABLE_METAL - -#ifdef SHELL_ENABLE_VULKAN - -void EmbedderConfigBuilder::InitializeVulkanRendererConfig() { - if (context_.GetContextType() != EmbedderTestContextType::kVulkanContext) { - return; - } - - vulkan_renderer_config_.struct_size = sizeof(FlutterVulkanRendererConfig); - vulkan_renderer_config_.version = - static_cast(context_) - .vulkan_context_->application_->GetAPIVersion(); - vulkan_renderer_config_.instance = - static_cast(context_) - .vulkan_context_->application_->GetInstance(); - vulkan_renderer_config_.physical_device = - static_cast(context_) - .vulkan_context_->device_->GetPhysicalDeviceHandle(); - vulkan_renderer_config_.device = - static_cast(context_) - .vulkan_context_->device_->GetHandle(); - vulkan_renderer_config_.queue_family_index = - static_cast(context_) - .vulkan_context_->device_->GetGraphicsQueueIndex(); - vulkan_renderer_config_.queue = - static_cast(context_) - .vulkan_context_->device_->GetQueueHandle(); - vulkan_renderer_config_.get_instance_proc_address_callback = - EmbedderTestContextVulkan::InstanceProcAddr; - vulkan_renderer_config_.get_next_image_callback = - [](void* context, - const FlutterFrameInfo* frame_info) -> FlutterVulkanImage { - VkImage image = - reinterpret_cast(context)->GetNextImage( - {static_cast(frame_info->size.width), - static_cast(frame_info->size.height)}); - return { - .struct_size = sizeof(FlutterVulkanImage), - .image = reinterpret_cast(image), - .format = VK_FORMAT_R8G8B8A8_UNORM, - }; - }; - vulkan_renderer_config_.present_image_callback = - [](void* context, const FlutterVulkanImage* image) -> bool { - return reinterpret_cast(context)->PresentImage( - reinterpret_cast(image->image)); - }; -} - -#endif - -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index 82498ac06b17b..8db7072b6762f 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -11,8 +11,7 @@ #include "flutter/shell/platform/embedder/tests/embedder_test.h" #include "flutter/shell/platform/embedder/tests/embedder_test_context_software.h" -namespace flutter { -namespace testing { +namespace flutter::testing { struct UniqueEngineTraits { static FlutterEngine InvalidValue() { return nullptr; } @@ -45,31 +44,6 @@ class EmbedderConfigBuilder { FlutterProjectArgs& GetProjectArgs(); - void SetRendererConfig(EmbedderTestContextType type, SkISize surface_size); - - void SetSoftwareRendererConfig(SkISize surface_size = SkISize::Make(1, 1)); - - void SetOpenGLRendererConfig(SkISize surface_size); - - void SetMetalRendererConfig(SkISize surface_size); - - void SetVulkanRendererConfig( - SkISize surface_size, - std::optional - instance_proc_address_callback = {}); - - // Used to explicitly set an `open_gl.fbo_callback`. Using this method will - // cause your test to fail since the ctor for this class sets - // `open_gl.fbo_callback_with_frame_info`. This method exists as a utility to - // explicitly test this behavior. - void SetOpenGLFBOCallBack(); - - // Used to explicitly set an `open_gl.present`. Using this method will cause - // your test to fail since the ctor for this class sets - // `open_gl.present_with_info`. This method exists as a utility to explicitly - // test this behavior. - void SetOpenGLPresentCallBack(); - void SetAssetsPath(); void SetSnapshots(); @@ -110,7 +84,7 @@ class EmbedderConfigBuilder { FlutterCompositor& GetCompositor(); - FlutterRendererConfig& GetRendererConfig(); + void SetSurface(SkISize surface_size) { context_.SetSurface(surface_size); } void SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType type, @@ -128,19 +102,6 @@ class EmbedderConfigBuilder { private: EmbedderTestContext& context_; FlutterProjectArgs project_args_ = {}; - FlutterRendererConfig renderer_config_ = {}; - FlutterSoftwareRendererConfig software_renderer_config_ = {}; -#ifdef SHELL_ENABLE_GL - FlutterOpenGLRendererConfig opengl_renderer_config_ = {}; -#endif -#ifdef SHELL_ENABLE_VULKAN - void InitializeVulkanRendererConfig(); - FlutterVulkanRendererConfig vulkan_renderer_config_ = {}; -#endif -#ifdef SHELL_ENABLE_METAL - void InitializeMetalRendererConfig(); - FlutterMetalRendererConfig metal_renderer_config_ = {}; -#endif std::string dart_entrypoint_; FlutterCustomTaskRunners custom_task_runners_ = {}; FlutterCompositor compositor_ = {}; @@ -153,7 +114,6 @@ class EmbedderConfigBuilder { FML_DISALLOW_COPY_AND_ASSIGN(EmbedderConfigBuilder); }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONFIG_BUILDER_H_ diff --git a/shell/platform/embedder/tests/embedder_gl_unittests.cc b/shell/platform/embedder/tests/embedder_gl_unittests.cc index 086f36be9b215..11a67df211ee2 100644 --- a/shell/platform/embedder/tests/embedder_gl_unittests.cc +++ b/shell/platform/embedder/tests/embedder_gl_unittests.cc @@ -41,20 +41,14 @@ // CREATE_NATIVE_ENTRY is leaky by design // NOLINTBEGIN(clang-analyzer-core.StackAddressEscape) -namespace flutter { -namespace testing { +namespace flutter::testing { using EmbedderTest = testing::EmbedderTest; -TEST_F(EmbedderTest, CanGetVulkanEmbedderContext) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kVulkanContext); - EmbedderConfigBuilder builder(context); -} - TEST_F(EmbedderTest, CanCreateOpenGLRenderingEngine) { - EmbedderConfigBuilder builder( - GetEmbedderContext(EmbedderTestContextType::kOpenGLContext)); - builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); } @@ -66,9 +60,9 @@ TEST_F(EmbedderTest, CanCreateOpenGLRenderingEngine) { /// TEST_F(EmbedderTest, MustPreventEngineLaunchWhenRequiredCompositorArgsAreAbsent) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetCompositor(); builder.GetCompositor().create_backing_store_callback = nullptr; builder.GetCompositor().collect_backing_store_callback = nullptr; @@ -84,9 +78,9 @@ TEST_F(EmbedderTest, /// render a frame at a later point in time. /// TEST_F(EmbedderTest, LaunchFailsWhenMultiplePresentCallbacks) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetCompositor(); builder.GetCompositor().present_layers_callback = [](const FlutterLayer** layers, size_t layers_count, void* user_data) { @@ -103,10 +97,10 @@ TEST_F(EmbedderTest, LaunchFailsWhenMultiplePresentCallbacks) { /// complete OpenGL textures. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLFramebuffer) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views"); @@ -223,10 +217,10 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLFramebuffer) { /// Layers in a hierarchy containing a platform view should not be cached. The /// other layers in the hierarchy should be, however. TEST_F(EmbedderTest, RasterCacheDisabledWithPlatformViews) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views_with_opacity"); @@ -356,10 +350,10 @@ TEST_F(EmbedderTest, RasterCacheDisabledWithPlatformViews) { /// The RasterCache should normally be enabled. /// TEST_F(EmbedderTest, RasterCacheEnabled) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_with_opacity"); @@ -442,10 +436,10 @@ TEST_F(EmbedderTest, RasterCacheEnabled) { /// the individual layers are OpenGL textures. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLTexture) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views"); @@ -563,10 +557,10 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLTexture) { /// individual layers are software buffers. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToSoftwareBuffer) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views"); @@ -684,10 +678,10 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToSoftwareBuffer) { /// Test the layer structure and pixels rendered when using a custom compositor. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownScene) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); @@ -898,11 +892,11 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownScene) { /// thread merging mechanism must not interfere with the custom compositor. /// TEST_F(EmbedderTest, CustomCompositorMustWorkWithCustomTaskRunner) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views"); @@ -1066,10 +1060,10 @@ TEST_F(EmbedderTest, CustomCompositorMustWorkWithCustomTaskRunner) { /// and a single layer. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithRootLayerOnly) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint( "can_composite_platform_views_with_root_layer_only"); @@ -1148,10 +1142,10 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithRootLayerOnly) { /// and ensure that a redundant layer is not added. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithPlatformLayerOnBottom) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint( "can_composite_platform_views_with_platform_layer_on_bottom"); @@ -1276,10 +1270,10 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithPlatformLayerOnBottom) { /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownSceneWithRootSurfaceTransformation) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); + builder.SetSurface(SkISize::Make(600, 800)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); @@ -1493,12 +1487,12 @@ TEST_F(EmbedderTest, } TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositor) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); auto rendered_scene = context.GetNextSceneImage(); @@ -1519,7 +1513,7 @@ TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositor) { } TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositorWithTransformation) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); const auto root_surface_transformation = SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); @@ -1529,7 +1523,7 @@ TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositorWithTransformation) { EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); - builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); + builder.SetSurface(SkISize::Make(600, 800)); auto rendered_scene = context.GetNextSceneImage(); @@ -1554,11 +1548,9 @@ TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositorWithTransformation) { TEST_P(EmbedderTestMultiBackend, CanRenderGradientWithoutCompositor) { EmbedderTestContextType backend = GetParam(); auto& context = GetEmbedderContext(backend); - EmbedderConfigBuilder builder(context); - builder.SetDartEntrypoint("render_gradient"); - builder.SetRendererConfig(backend, SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); auto rendered_scene = context.GetNextSceneImage(); @@ -1579,7 +1571,7 @@ TEST_P(EmbedderTestMultiBackend, CanRenderGradientWithoutCompositor) { } TEST_F(EmbedderTest, CanRenderGradientWithoutCompositorWithXform) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); const auto root_surface_transformation = SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); @@ -1591,7 +1583,7 @@ TEST_F(EmbedderTest, CanRenderGradientWithoutCompositorWithXform) { const auto surface_size = SkISize::Make(600, 800); builder.SetDartEntrypoint("render_gradient"); - builder.SetOpenGLRendererConfig(surface_size); + builder.SetSurface(surface_size); auto rendered_scene = context.GetNextSceneImage(); @@ -1616,9 +1608,8 @@ TEST_P(EmbedderTestMultiBackend, CanRenderGradientWithCompositor) { auto& context = GetEmbedderContext(backend); EmbedderConfigBuilder builder(context); - builder.SetDartEntrypoint("render_gradient"); - builder.SetRendererConfig(backend, SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetRenderTargetType(GetRenderTargetFromBackend(backend, true)); @@ -1641,7 +1632,7 @@ TEST_P(EmbedderTestMultiBackend, CanRenderGradientWithCompositor) { } TEST_F(EmbedderTest, CanRenderGradientWithCompositorWithXform) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); // This must match the transformation provided in the // |CanRenderGradientWithoutCompositorWithXform| test to ensure that @@ -1654,7 +1645,7 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositorWithXform) { EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("render_gradient"); - builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); + builder.SetSurface(SkISize::Make(600, 800)); builder.SetCompositor(); builder.SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); @@ -1683,9 +1674,8 @@ TEST_P(EmbedderTestMultiBackend, auto& context = GetEmbedderContext(backend); EmbedderConfigBuilder builder(context); - builder.SetDartEntrypoint("render_gradient_on_non_root_backing_store"); - builder.SetRendererConfig(backend, SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetRenderTargetType(GetRenderTargetFromBackend(backend, true)); @@ -1812,7 +1802,7 @@ TEST_P(EmbedderTestMultiBackend, } TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayerWithXform) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); // This must match the transformation provided in the // |CanRenderGradientWithoutCompositorWithXform| test to ensure that @@ -1825,7 +1815,7 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayerWithXform) { EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("render_gradient_on_non_root_backing_store"); - builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); + builder.SetSurface(SkISize::Make(600, 800)); builder.SetCompositor(); builder.SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); @@ -1955,7 +1945,7 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayerWithXform) { } TEST_F(EmbedderTest, VerifyB141980393) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); @@ -1974,7 +1964,7 @@ TEST_F(EmbedderTest, VerifyB141980393) { context.SetRootSurfaceTransformation(root_surface_transformation); // Configure the Flutter project args for the root surface transformation. - builder.SetOpenGLRendererConfig( + builder.SetSurface( SkISize::Make(root_surface_rect.width(), root_surface_rect.height())); // Use a compositor instead of rendering directly to the surface. @@ -2076,10 +2066,10 @@ TEST_F(EmbedderTest, CanCreateEmbedderWithCustomRenderTaskRunner) { task_latch.Signal(); } }); - EmbedderConfigBuilder builder( - GetEmbedderContext(EmbedderTestContextType::kOpenGLContext)); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetRenderTaskRunner( &render_task_runner.GetFlutterTaskRunnerDescription()); @@ -2136,9 +2126,10 @@ TEST_P(EmbedderTestMultiBackend, platform_task_runner->PostTask([&]() { EmbedderTestContextType backend = GetParam(); - EmbedderConfigBuilder builder(GetEmbedderContext(backend)); + auto& context = GetEmbedderContext(backend); + EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); - builder.SetRendererConfig(backend, SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetRenderTaskRunner( &common_task_runner.GetFlutterTaskRunnerDescription()); builder.SetPlatformTaskRunner( @@ -2194,7 +2185,7 @@ TEST_P(EmbedderTestMultiBackend, auto& context = GetEmbedderContext(backend); EmbedderConfigBuilder builder(context); - builder.SetRendererConfig(backend, SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_display_platform_view_with_pixel_ratio"); @@ -2309,10 +2300,10 @@ TEST_P(EmbedderTestMultiBackend, TEST_F( EmbedderTest, CompositorMustBeAbleToRenderKnownScenePixelRatioOnSurfaceWithRootSurfaceXformation) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); + builder.SetSurface(SkISize::Make(600, 800)); builder.SetCompositor(); builder.SetDartEntrypoint("can_display_platform_view_with_pixel_ratio"); @@ -2432,10 +2423,10 @@ TEST_F( TEST_F(EmbedderTest, PushingMutlipleFramesSetsUpNewRecordingCanvasWithCustomCompositor) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); + builder.SetSurface(SkISize::Make(600, 1024)); builder.SetCompositor(); builder.SetDartEntrypoint("push_frames_over_and_over"); @@ -2476,10 +2467,10 @@ TEST_F(EmbedderTest, TEST_F(EmbedderTest, PushingMutlipleFramesSetsUpNewRecordingCanvasWithoutCustomCompositor) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); + builder.SetSurface(SkISize::Make(600, 1024)); builder.SetDartEntrypoint("push_frames_over_and_over"); const auto root_surface_transformation = @@ -2519,7 +2510,7 @@ TEST_P(EmbedderTestMultiBackend, PlatformViewMutatorsAreValid) { auto& context = GetEmbedderContext(backend); EmbedderConfigBuilder builder(context); - builder.SetRendererConfig(backend, SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("platform_view_mutators"); @@ -2625,10 +2616,10 @@ TEST_P(EmbedderTestMultiBackend, PlatformViewMutatorsAreValid) { } TEST_F(EmbedderTest, PlatformViewMutatorsAreValidWithPixelRatio) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("platform_view_mutators_with_pixel_ratio"); @@ -2737,10 +2728,10 @@ TEST_F(EmbedderTest, PlatformViewMutatorsAreValidWithPixelRatio) { TEST_F(EmbedderTest, PlatformViewMutatorsAreValidWithPixelRatioAndRootSurfaceTransformation) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("platform_view_mutators_with_pixel_ratio"); @@ -2854,10 +2845,10 @@ TEST_F(EmbedderTest, } TEST_F(EmbedderTest, EmptySceneIsAcceptable) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("empty_scene"); fml::AutoResetWaitableEvent latch; @@ -2880,10 +2871,10 @@ TEST_F(EmbedderTest, EmptySceneIsAcceptable) { } TEST_F(EmbedderTest, SceneWithNoRootContainerIsAcceptable) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); @@ -2910,10 +2901,10 @@ TEST_F(EmbedderTest, SceneWithNoRootContainerIsAcceptable) { // Verifies that https://skia-review.googlesource.com/c/skia/+/259174 is pulled // into the engine. TEST_F(EmbedderTest, ArcEndCapsAreDrawnCorrectly) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 1024)); + builder.SetSurface(SkISize::Make(800, 1024)); builder.SetCompositor(); builder.SetDartEntrypoint("arc_end_caps_correct"); builder.SetRenderTargetType( @@ -2944,10 +2935,10 @@ TEST_F(EmbedderTest, ArcEndCapsAreDrawnCorrectly) { } TEST_F(EmbedderTest, ClipsAreCorrectlyCalculated) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(400, 300)); + builder.SetSurface(SkISize::Make(400, 300)); builder.SetCompositor(); builder.SetDartEntrypoint("scene_builder_with_clips"); builder.SetRenderTargetType( @@ -3024,10 +3015,10 @@ TEST_F(EmbedderTest, ClipsAreCorrectlyCalculated) { } TEST_F(EmbedderTest, ComplexClipsAreCorrectlyCalculated) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(1024, 600)); + builder.SetSurface(SkISize::Make(1024, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("scene_builder_with_complex_clips"); builder.SetRenderTargetType( @@ -3109,9 +3100,9 @@ TEST_F(EmbedderTest, ComplexClipsAreCorrectlyCalculated) { } TEST_F(EmbedderTest, ObjectsCanBePostedViaPorts) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 1024)); + builder.SetSurface(SkISize::Make(800, 1024)); builder.SetDartEntrypoint("objects_can_be_posted"); // Synchronously acquire the send port from the Dart end. We will be using @@ -3308,10 +3299,10 @@ TEST_F(EmbedderTest, ObjectsCanBePostedViaPorts) { } TEST_F(EmbedderTest, CompositorCanPostZeroLayersForPresentation) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); + builder.SetSurface(SkISize::Make(300, 200)); builder.SetCompositor(); builder.SetDartEntrypoint("empty_scene_posts_zero_layers_to_compositor"); builder.SetRenderTargetType( @@ -3342,10 +3333,10 @@ TEST_F(EmbedderTest, CompositorCanPostZeroLayersForPresentation) { } TEST_F(EmbedderTest, CompositorCanPostOnlyPlatformViews) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); + builder.SetSurface(SkISize::Make(300, 200)); builder.SetCompositor(); builder.SetDartEntrypoint("compositor_can_post_only_platform_views"); builder.SetRenderTargetType( @@ -3406,10 +3397,10 @@ TEST_F(EmbedderTest, CompositorCanPostOnlyPlatformViews) { } TEST_F(EmbedderTest, CompositorRenderTargetsAreRecycled) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); + builder.SetSurface(SkISize::Make(300, 200)); builder.SetCompositor(); builder.SetDartEntrypoint("render_targets_are_recycled"); builder.SetRenderTargetType( @@ -3452,10 +3443,10 @@ TEST_F(EmbedderTest, CompositorRenderTargetsAreRecycled) { } TEST_F(EmbedderTest, CompositorRenderTargetsAreInStableOrder) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); + builder.SetSurface(SkISize::Make(300, 200)); builder.SetCompositor(); builder.SetDartEntrypoint("render_targets_are_in_stable_order"); builder.SetRenderTargetType( @@ -3522,17 +3513,14 @@ TEST_F(EmbedderTest, CompositorRenderTargetsAreInStableOrder) { } TEST_F(EmbedderTest, FrameInfoContainsValidWidthAndHeight) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); - - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); - builder.SetDartEntrypoint("push_frames_over_and_over"); - + auto& context = GetEmbedderContext(); const auto root_surface_transformation = SkMatrix().preTranslate(0, 1024).preRotate(-90, 0, 0); - context.SetRootSurfaceTransformation(root_surface_transformation); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(600, 1024)); + builder.SetDartEntrypoint("push_frames_over_and_over"); auto engine = builder.LaunchEngine(); // Send a window metrics events so frames may be scheduled. @@ -3552,118 +3540,111 @@ TEST_F(EmbedderTest, FrameInfoContainsValidWidthAndHeight) { /* Nothing to do. */ })); - static_cast(context).SetGLGetFBOCallback( - [](FlutterFrameInfo frame_info) { - // width and height are rotated by 90 deg - ASSERT_EQ(frame_info.size.width, event.height); - ASSERT_EQ(frame_info.size.height, event.width); + context.SetGLGetFBOCallback([](FlutterFrameInfo frame_info) { + // width and height are rotated by 90 deg + ASSERT_EQ(frame_info.size.width, event.height); + ASSERT_EQ(frame_info.size.height, event.width); - frame_latch.CountDown(); - }); + frame_latch.CountDown(); + }); frame_latch.Wait(); } TEST_F(EmbedderTest, MustNotRunWithBothFBOCallbacksSet) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); + context.SetOpenGLFBOCallBack(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); - builder.SetOpenGLFBOCallBack(); + builder.SetSurface(SkISize::Make(600, 1024)); auto engine = builder.LaunchEngine(); ASSERT_FALSE(engine.is_valid()); } TEST_F(EmbedderTest, MustNotRunWithBothPresentCallbacksSet) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); + context.SetOpenGLPresentCallBack(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); - builder.SetOpenGLPresentCallBack(); + builder.SetSurface(SkISize::Make(600, 1024)); auto engine = builder.LaunchEngine(); ASSERT_FALSE(engine.is_valid()); } TEST_F(EmbedderTest, MustStillRunWhenPopulateExistingDamageIsNotProvided) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); + context.GetRendererConfig().open_gl.populate_existing_damage = nullptr; EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); - builder.GetRendererConfig().open_gl.populate_existing_damage = nullptr; + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); } TEST_F(EmbedderTest, MustRunWhenPopulateExistingDamageIsProvided) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); - - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); - - builder.GetRendererConfig().open_gl.populate_existing_damage = + auto& context = GetEmbedderContext(); + context.GetRendererConfig().open_gl.populate_existing_damage = [](void* context, const intptr_t id, FlutterDamage* existing_damage) -> void { return reinterpret_cast(context) ->GLPopulateExistingDamage(id, existing_damage); }; + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); } TEST_F(EmbedderTest, MustRunWithPopulateExistingDamageAndFBOCallback) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); - - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); - builder.GetRendererConfig().open_gl.fbo_callback = + auto& context = GetEmbedderContext(); + context.GetRendererConfig().open_gl.fbo_callback = [](void* context) -> uint32_t { return 0; }; - builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = nullptr; - builder.GetRendererConfig().open_gl.populate_existing_damage = + context.GetRendererConfig().open_gl.fbo_with_frame_info_callback = nullptr; + context.GetRendererConfig().open_gl.populate_existing_damage = [](void* context, const intptr_t id, FlutterDamage* existing_damage) -> void { return reinterpret_cast(context) ->GLPopulateExistingDamage(id, existing_damage); }; + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); } TEST_F(EmbedderTest, MustNotRunWhenPopulateExistingDamageButNoOtherFBOCallback) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); - - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); - builder.GetRendererConfig().open_gl.fbo_callback = nullptr; - builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = nullptr; - builder.GetRendererConfig().open_gl.populate_existing_damage = + auto& context = GetEmbedderContext(); + context.GetRendererConfig().open_gl.fbo_callback = nullptr; + context.GetRendererConfig().open_gl.fbo_with_frame_info_callback = nullptr; + context.GetRendererConfig().open_gl.populate_existing_damage = [](void* context, const intptr_t id, FlutterDamage* existing_damage) -> void { return reinterpret_cast(context) ->GLPopulateExistingDamage(id, existing_damage); }; + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_FALSE(engine.is_valid()); } TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); - - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); - builder.SetDartEntrypoint("push_frames_over_and_over"); - + auto& context = GetEmbedderContext(); const auto root_surface_transformation = SkMatrix().preTranslate(0, 1024).preRotate(-90, 0, 0); - context.SetRootSurfaceTransformation(root_surface_transformation); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(600, 1024)); + builder.SetDartEntrypoint("push_frames_over_and_over"); + auto engine = builder.LaunchEngine(); // Send a window metrics events so frames may be scheduled. @@ -3683,9 +3664,8 @@ TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) { /* Nothing to do. */ })); - const uint32_t window_fbo_id = - static_cast(context).GetWindowFBOId(); - static_cast(context).SetGLPresentCallback( + const uint32_t window_fbo_id = context.GetWindowFBOId(); + context.SetGLPresentCallback( [window_fbo_id = window_fbo_id](FlutterPresentInfo present_info) { ASSERT_EQ(present_info.fbo_id, window_fbo_id); @@ -3697,53 +3677,49 @@ TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) { TEST_F(EmbedderTest, PresentInfoReceivesFullDamageWhenExistingDamageIsWholeScreen) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); - - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); - builder.SetDartEntrypoint("render_gradient_retained"); - builder.GetRendererConfig().open_gl.populate_existing_damage = + auto& context = GetEmbedderContext(); + context.GetRendererConfig().open_gl.populate_existing_damage = [](void* context, const intptr_t id, FlutterDamage* existing_damage) -> void { return reinterpret_cast(context) ->GLPopulateExistingDamage(id, existing_damage); }; - // Return existing damage as the entire screen on purpose. - static_cast(context) - .SetGLPopulateExistingDamageCallback( - [](const intptr_t id, FlutterDamage* existing_damage_ptr) { - const size_t num_rects = 1; - // The array must be valid after the callback returns. - static FlutterRect existing_damage_rects[num_rects] = { - FlutterRect{0, 0, 800, 600}}; - existing_damage_ptr->num_rects = num_rects; - existing_damage_ptr->damage = existing_damage_rects; - }); + context.SetGLPopulateExistingDamageCallback( + [](const intptr_t id, FlutterDamage* existing_damage_ptr) { + const size_t num_rects = 1; + // The array must be valid after the callback returns. + static FlutterRect existing_damage_rects[num_rects] = { + FlutterRect{0, 0, 800, 600}}; + existing_damage_ptr->num_rects = num_rects; + existing_damage_ptr->damage = existing_damage_rects; + }); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient_retained"); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); fml::AutoResetWaitableEvent latch; // First frame should be entirely rerendered. - static_cast(context).SetGLPresentCallback( - [&](FlutterPresentInfo present_info) { - const size_t num_rects = 1; - ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); - ASSERT_EQ(present_info.frame_damage.damage->left, 0); - ASSERT_EQ(present_info.frame_damage.damage->top, 0); - ASSERT_EQ(present_info.frame_damage.damage->right, 800); - ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); - - ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); - ASSERT_EQ(present_info.buffer_damage.damage->left, 0); - ASSERT_EQ(present_info.buffer_damage.damage->top, 0); - ASSERT_EQ(present_info.buffer_damage.damage->right, 800); - ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); - - latch.Signal(); - }); + context.SetGLPresentCallback([&](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 800); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 800); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); + + latch.Signal(); + }); // Send a window metrics events so frames may be scheduled. FlutterWindowMetricsEvent event = {}; @@ -3758,23 +3734,22 @@ TEST_F(EmbedderTest, // Because it's the same as the first frame, the second frame damage should // be empty but, because there was a full existing buffer damage, the buffer // damage should be the entire screen. - static_cast(context).SetGLPresentCallback( - [&](FlutterPresentInfo present_info) { - const size_t num_rects = 1; - ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); - ASSERT_EQ(present_info.frame_damage.damage->left, 0); - ASSERT_EQ(present_info.frame_damage.damage->top, 0); - ASSERT_EQ(present_info.frame_damage.damage->right, 0); - ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); - - ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); - ASSERT_EQ(present_info.buffer_damage.damage->left, 0); - ASSERT_EQ(present_info.buffer_damage.damage->top, 0); - ASSERT_EQ(present_info.buffer_damage.damage->right, 800); - ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); - - latch.Signal(); - }); + context.SetGLPresentCallback([&](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 0); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 800); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); + + latch.Signal(); + }); ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess); @@ -3782,53 +3757,49 @@ TEST_F(EmbedderTest, } TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); - - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); - builder.SetDartEntrypoint("render_gradient_retained"); - builder.GetRendererConfig().open_gl.populate_existing_damage = + auto& context = GetEmbedderContext(); + context.GetRendererConfig().open_gl.populate_existing_damage = [](void* context, const intptr_t id, FlutterDamage* existing_damage) -> void { return reinterpret_cast(context) ->GLPopulateExistingDamage(id, existing_damage); }; - // Return no existing damage on purpose. - static_cast(context) - .SetGLPopulateExistingDamageCallback( - [](const intptr_t id, FlutterDamage* existing_damage_ptr) { - const size_t num_rects = 1; - // The array must be valid after the callback returns. - static FlutterRect existing_damage_rects[num_rects] = { - FlutterRect{0, 0, 0, 0}}; - existing_damage_ptr->num_rects = num_rects; - existing_damage_ptr->damage = existing_damage_rects; - }); + context.SetGLPopulateExistingDamageCallback( + [](const intptr_t id, FlutterDamage* existing_damage_ptr) { + const size_t num_rects = 1; + // The array must be valid after the callback returns. + static FlutterRect existing_damage_rects[num_rects] = { + FlutterRect{0, 0, 0, 0}}; + existing_damage_ptr->num_rects = num_rects; + existing_damage_ptr->damage = existing_damage_rects; + }); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient_retained"); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); fml::AutoResetWaitableEvent latch; // First frame should be entirely rerendered. - static_cast(context).SetGLPresentCallback( - [&](FlutterPresentInfo present_info) { - const size_t num_rects = 1; - ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); - ASSERT_EQ(present_info.frame_damage.damage->left, 0); - ASSERT_EQ(present_info.frame_damage.damage->top, 0); - ASSERT_EQ(present_info.frame_damage.damage->right, 800); - ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); - - ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); - ASSERT_EQ(present_info.buffer_damage.damage->left, 0); - ASSERT_EQ(present_info.buffer_damage.damage->top, 0); - ASSERT_EQ(present_info.buffer_damage.damage->right, 800); - ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); - - latch.Signal(); - }); + context.SetGLPresentCallback([&](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 800); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 800); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); + + latch.Signal(); + }); // Send a window metrics events so frames may be scheduled. FlutterWindowMetricsEvent event = {}; @@ -3842,23 +3813,22 @@ TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) { // Because it's the same as the first frame, the second frame should not be // rerendered assuming there is no existing damage. - static_cast(context).SetGLPresentCallback( - [&](FlutterPresentInfo present_info) { - const size_t num_rects = 1; - ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); - ASSERT_EQ(present_info.frame_damage.damage->left, 0); - ASSERT_EQ(present_info.frame_damage.damage->top, 0); - ASSERT_EQ(present_info.frame_damage.damage->right, 0); - ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); - - ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); - ASSERT_EQ(present_info.buffer_damage.damage->left, 0); - ASSERT_EQ(present_info.buffer_damage.damage->top, 0); - ASSERT_EQ(present_info.buffer_damage.damage->right, 0); - ASSERT_EQ(present_info.buffer_damage.damage->bottom, 0); - - latch.Signal(); - }); + context.SetGLPresentCallback([&](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 0); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 0); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 0); + + latch.Signal(); + }); ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess); @@ -3866,53 +3836,49 @@ TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) { } TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); - - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); - builder.SetDartEntrypoint("render_gradient_retained"); - builder.GetRendererConfig().open_gl.populate_existing_damage = + auto& context = GetEmbedderContext(); + context.GetRendererConfig().open_gl.populate_existing_damage = [](void* context, const intptr_t id, FlutterDamage* existing_damage) -> void { return reinterpret_cast(context) ->GLPopulateExistingDamage(id, existing_damage); }; - // Return existing damage as only part of the screen on purpose. - static_cast(context) - .SetGLPopulateExistingDamageCallback( - [&](const intptr_t id, FlutterDamage* existing_damage_ptr) { - const size_t num_rects = 1; - // The array must be valid after the callback returns. - static FlutterRect existing_damage_rects[num_rects] = { - FlutterRect{200, 150, 400, 300}}; - existing_damage_ptr->num_rects = num_rects; - existing_damage_ptr->damage = existing_damage_rects; - }); + context.SetGLPopulateExistingDamageCallback( + [&](const intptr_t id, FlutterDamage* existing_damage_ptr) { + const size_t num_rects = 1; + // The array must be valid after the callback returns. + static FlutterRect existing_damage_rects[num_rects] = { + FlutterRect{200, 150, 400, 300}}; + existing_damage_ptr->num_rects = num_rects; + existing_damage_ptr->damage = existing_damage_rects; + }); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient_retained"); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); fml::AutoResetWaitableEvent latch; // First frame should be entirely rerendered. - static_cast(context).SetGLPresentCallback( - [&](FlutterPresentInfo present_info) { - const size_t num_rects = 1; - ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); - ASSERT_EQ(present_info.frame_damage.damage->left, 0); - ASSERT_EQ(present_info.frame_damage.damage->top, 0); - ASSERT_EQ(present_info.frame_damage.damage->right, 800); - ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); - - ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); - ASSERT_EQ(present_info.buffer_damage.damage->left, 0); - ASSERT_EQ(present_info.buffer_damage.damage->top, 0); - ASSERT_EQ(present_info.buffer_damage.damage->right, 800); - ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); - - latch.Signal(); - }); + context.SetGLPresentCallback([&](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 800); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 800); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); + + latch.Signal(); + }); // Send a window metrics events so frames may be scheduled. FlutterWindowMetricsEvent event = {}; @@ -3927,23 +3893,22 @@ TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) { // Because it's the same as the first frame, the second frame damage should be // empty but, because there was a partial existing damage, the buffer damage // should represent that partial damage area. - static_cast(context).SetGLPresentCallback( - [&](FlutterPresentInfo present_info) { - const size_t num_rects = 1; - ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); - ASSERT_EQ(present_info.frame_damage.damage->left, 0); - ASSERT_EQ(present_info.frame_damage.damage->top, 0); - ASSERT_EQ(present_info.frame_damage.damage->right, 0); - ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); - - ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); - ASSERT_EQ(present_info.buffer_damage.damage->left, 200); - ASSERT_EQ(present_info.buffer_damage.damage->top, 150); - ASSERT_EQ(present_info.buffer_damage.damage->right, 400); - ASSERT_EQ(present_info.buffer_damage.damage->bottom, 300); - - latch.Signal(); - }); + context.SetGLPresentCallback([&](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 0); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 200); + ASSERT_EQ(present_info.buffer_damage.damage->top, 150); + ASSERT_EQ(present_info.buffer_damage.damage->right, 400); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 300); + + latch.Signal(); + }); ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess); @@ -3951,31 +3916,28 @@ TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) { } TEST_F(EmbedderTest, PopulateExistingDamageReceivesValidID) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); - - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); - builder.SetDartEntrypoint("render_gradient_retained"); - builder.GetRendererConfig().open_gl.populate_existing_damage = + auto& context = GetEmbedderContext(); + context.GetRendererConfig().open_gl.populate_existing_damage = [](void* context, const intptr_t id, FlutterDamage* existing_damage) -> void { return reinterpret_cast(context) ->GLPopulateExistingDamage(id, existing_damage); }; + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient_retained"); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); - const uint32_t window_fbo_id = - static_cast(context).GetWindowFBOId(); - static_cast(context) - .SetGLPopulateExistingDamageCallback( - [window_fbo_id = window_fbo_id](intptr_t id, - FlutterDamage* existing_damage) { - ASSERT_EQ(id, window_fbo_id); - existing_damage->num_rects = 0; - existing_damage->damage = nullptr; - }); + const uint32_t window_fbo_id = context.GetWindowFBOId(); + context.SetGLPopulateExistingDamageCallback( + [window_fbo_id = window_fbo_id](intptr_t id, + FlutterDamage* existing_damage) { + ASSERT_EQ(id, window_fbo_id); + existing_damage->num_rects = 0; + existing_damage->damage = nullptr; + }); // Send a window metrics events so frames may be scheduled. FlutterWindowMetricsEvent event = {}; @@ -3988,24 +3950,22 @@ TEST_F(EmbedderTest, PopulateExistingDamageReceivesValidID) { } TEST_F(EmbedderTest, PopulateExistingDamageReceivesInvalidID) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); - - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); - builder.SetDartEntrypoint("render_gradient_retained"); - builder.GetRendererConfig().open_gl.populate_existing_damage = + auto& context = GetEmbedderContext(); + context.GetRendererConfig().open_gl.populate_existing_damage = [](void* context, const intptr_t id, FlutterDamage* existing_damage) -> void { return reinterpret_cast(context) ->GLPopulateExistingDamage(id, existing_damage); }; - // Return a bad FBO ID on purpose. - builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = + context.GetRendererConfig().open_gl.fbo_with_frame_info_callback = [](void* context, const FlutterFrameInfo* frame_info) -> uint32_t { return 123; }; + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient_retained"); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -4014,16 +3974,14 @@ TEST_F(EmbedderTest, PopulateExistingDamageReceivesInvalidID) { /* Nothing to do. */ })); - const uint32_t window_fbo_id = - static_cast(context).GetWindowFBOId(); - static_cast(context) - .SetGLPopulateExistingDamageCallback( - [window_fbo_id = window_fbo_id](intptr_t id, - FlutterDamage* existing_damage) { - ASSERT_NE(id, window_fbo_id); - existing_damage->num_rects = 0; - existing_damage->damage = nullptr; - }); + const uint32_t window_fbo_id = context.GetWindowFBOId(); + context.SetGLPopulateExistingDamageCallback( + [window_fbo_id = window_fbo_id](intptr_t id, + FlutterDamage* existing_damage) { + ASSERT_NE(id, window_fbo_id); + existing_damage->num_rects = 0; + existing_damage->damage = nullptr; + }); // Send a window metrics events so frames may be scheduled. FlutterWindowMetricsEvent event = {}; @@ -4036,10 +3994,10 @@ TEST_F(EmbedderTest, PopulateExistingDamageReceivesInvalidID) { } TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithDisplayId) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("empty_scene"); fml::AutoResetWaitableEvent latch; @@ -4078,10 +4036,10 @@ TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithDisplayId) { } TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithoutDisplayId) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("empty_scene"); fml::AutoResetWaitableEvent latch; @@ -4120,10 +4078,10 @@ TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithoutDisplayId) { } TEST_F(EmbedderTest, SetValidMultiDisplayConfiguration) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("empty_scene"); fml::AutoResetWaitableEvent latch; @@ -4169,10 +4127,10 @@ TEST_F(EmbedderTest, SetValidMultiDisplayConfiguration) { } TEST_F(EmbedderTest, MultipleDisplaysWithSingleDisplayTrueIsInvalid) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("empty_scene"); fml::AutoResetWaitableEvent latch; @@ -4215,10 +4173,10 @@ TEST_F(EmbedderTest, MultipleDisplaysWithSingleDisplayTrueIsInvalid) { } TEST_F(EmbedderTest, MultipleDisplaysWithSameDisplayIdIsInvalid) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("empty_scene"); fml::AutoResetWaitableEvent latch; @@ -4261,10 +4219,10 @@ TEST_F(EmbedderTest, MultipleDisplaysWithSameDisplayIdIsInvalid) { } TEST_F(EmbedderTest, CompositorRenderTargetsNotRecycledWhenAvoidsCacheSet) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); + builder.SetSurface(SkISize::Make(300, 200)); builder.SetCompositor(/*avoid_backing_store_cache=*/true); builder.SetDartEntrypoint("render_targets_are_recycled"); builder.SetRenderTargetType( @@ -4309,10 +4267,10 @@ TEST_F(EmbedderTest, CompositorRenderTargetsNotRecycledWhenAvoidsCacheSet) { } TEST_F(EmbedderTest, SnapshotRenderTargetScalesDownToDriverMax) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); auto max_size = context.GetCompositor().GetGrContext()->maxRenderTargetSize(); @@ -4352,9 +4310,9 @@ TEST_F(EmbedderTest, SnapshotRenderTargetScalesDownToDriverMax) { } TEST_F(EmbedderTest, ObjectsPostedViaPortsServicedOnSecondaryTaskHeap) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 1024)); + builder.SetSurface(SkISize::Make(800, 1024)); builder.SetDartEntrypoint("objects_can_be_posted"); // Synchronously acquire the send port from the Dart end. We will be using @@ -4402,9 +4360,9 @@ TEST_F(EmbedderTest, ObjectsPostedViaPortsServicedOnSecondaryTaskHeap) { } TEST_F(EmbedderTest, CreateInvalidBackingstoreOpenGLTexture) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); @@ -4465,9 +4423,9 @@ TEST_F(EmbedderTest, CreateInvalidBackingstoreOpenGLTexture) { } TEST_F(EmbedderTest, CreateInvalidBackingstoreOpenGLFramebuffer) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); @@ -4528,9 +4486,9 @@ TEST_F(EmbedderTest, CreateInvalidBackingstoreOpenGLFramebuffer) { } TEST_F(EmbedderTest, CreateInvalidBackingstoreOpenGLSurface) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLSurface); @@ -4650,36 +4608,34 @@ TEST_F(EmbedderTest, ExternalTextureGLRefreshedTooOften) { TEST_F( EmbedderTest, PresentInfoReceivesFullScreenDamageWhenPopulateExistingDamageIsNotProvided) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); + context.GetRendererConfig().open_gl.populate_existing_damage = nullptr; EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetDartEntrypoint("render_gradient_retained"); - builder.GetRendererConfig().open_gl.populate_existing_damage = nullptr; - auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); fml::AutoResetWaitableEvent latch; // First frame should be entirely rerendered. - static_cast(context).SetGLPresentCallback( - [&](FlutterPresentInfo present_info) { - const size_t num_rects = 1; - ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); - ASSERT_EQ(present_info.frame_damage.damage->left, 0); - ASSERT_EQ(present_info.frame_damage.damage->top, 0); - ASSERT_EQ(present_info.frame_damage.damage->right, 800); - ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); - - ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); - ASSERT_EQ(present_info.buffer_damage.damage->left, 0); - ASSERT_EQ(present_info.buffer_damage.damage->top, 0); - ASSERT_EQ(present_info.buffer_damage.damage->right, 800); - ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); - - latch.Signal(); - }); + context.SetGLPresentCallback([&](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 800); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 800); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); + + latch.Signal(); + }); // Send a window metrics events so frames may be scheduled. FlutterWindowMetricsEvent event = {}; @@ -4694,23 +4650,22 @@ TEST_F( // Since populate_existing_damage is not provided, the partial repaint // functionality is actually disabled. So, the next frame should be entirely // new frame. - static_cast(context).SetGLPresentCallback( - [&](FlutterPresentInfo present_info) { - const size_t num_rects = 1; - ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); - ASSERT_EQ(present_info.frame_damage.damage->left, 0); - ASSERT_EQ(present_info.frame_damage.damage->top, 0); - ASSERT_EQ(present_info.frame_damage.damage->right, 800); - ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); - - ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); - ASSERT_EQ(present_info.buffer_damage.damage->left, 0); - ASSERT_EQ(present_info.buffer_damage.damage->top, 0); - ASSERT_EQ(present_info.buffer_damage.damage->right, 800); - ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); - - latch.Signal(); - }); + context.SetGLPresentCallback([&](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 800); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 800); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); + + latch.Signal(); + }); ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess); @@ -4719,55 +4674,51 @@ TEST_F( TEST_F(EmbedderTest, PresentInfoReceivesJoinedDamageWhenExistingDamageContainsMultipleRects) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); - - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); - builder.SetDartEntrypoint("render_gradient_retained"); - builder.GetRendererConfig().open_gl.populate_existing_damage = + auto& context = GetEmbedderContext(); + context.GetRendererConfig().open_gl.populate_existing_damage = [](void* context, const intptr_t id, FlutterDamage* existing_damage) -> void { return reinterpret_cast(context) ->GLPopulateExistingDamage(id, existing_damage); }; - // Return existing damage as the entire screen on purpose. - static_cast(context) - .SetGLPopulateExistingDamageCallback( - [](const intptr_t id, FlutterDamage* existing_damage_ptr) { - const size_t num_rects = 2; - // The array must be valid after the callback returns. - static FlutterRect existing_damage_rects[num_rects] = { - FlutterRect{100, 150, 200, 250}, - FlutterRect{200, 250, 300, 350}, - }; - existing_damage_ptr->num_rects = num_rects; - existing_damage_ptr->damage = existing_damage_rects; - }); + context.SetGLPopulateExistingDamageCallback( + [](const intptr_t id, FlutterDamage* existing_damage_ptr) { + const size_t num_rects = 2; + // The array must be valid after the callback returns. + static FlutterRect existing_damage_rects[num_rects] = { + FlutterRect{100, 150, 200, 250}, + FlutterRect{200, 250, 300, 350}, + }; + existing_damage_ptr->num_rects = num_rects; + existing_damage_ptr->damage = existing_damage_rects; + }); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient_retained"); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); fml::AutoResetWaitableEvent latch; // First frame should be entirely rerendered. - static_cast(context).SetGLPresentCallback( - [&](FlutterPresentInfo present_info) { - const size_t num_rects = 1; - ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); - ASSERT_EQ(present_info.frame_damage.damage->left, 0); - ASSERT_EQ(present_info.frame_damage.damage->top, 0); - ASSERT_EQ(present_info.frame_damage.damage->right, 800); - ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); - - ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); - ASSERT_EQ(present_info.buffer_damage.damage->left, 0); - ASSERT_EQ(present_info.buffer_damage.damage->top, 0); - ASSERT_EQ(present_info.buffer_damage.damage->right, 800); - ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); - - latch.Signal(); - }); + context.SetGLPresentCallback([&](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 800); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 800); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); + + latch.Signal(); + }); // Send a window metrics events so frames may be scheduled. FlutterWindowMetricsEvent event = {}; @@ -4782,23 +4733,22 @@ TEST_F(EmbedderTest, // Because it's the same as the first frame, the second frame damage should // be empty but, because there was a full existing buffer damage, the buffer // damage should be the entire screen. - static_cast(context).SetGLPresentCallback( - [&](FlutterPresentInfo present_info) { - const size_t num_rects = 1; - ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); - ASSERT_EQ(present_info.frame_damage.damage->left, 0); - ASSERT_EQ(present_info.frame_damage.damage->top, 0); - ASSERT_EQ(present_info.frame_damage.damage->right, 0); - ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); - - ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); - ASSERT_EQ(present_info.buffer_damage.damage->left, 100); - ASSERT_EQ(present_info.buffer_damage.damage->top, 150); - ASSERT_EQ(present_info.buffer_damage.damage->right, 300); - ASSERT_EQ(present_info.buffer_damage.damage->bottom, 350); - - latch.Signal(); - }); + context.SetGLPresentCallback([&](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 0); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 100); + ASSERT_EQ(present_info.buffer_damage.damage->top, 150); + ASSERT_EQ(present_info.buffer_damage.damage->right, 300); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 350); + + latch.Signal(); + }); ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess); @@ -4806,19 +4756,18 @@ TEST_F(EmbedderTest, } TEST_F(EmbedderTest, CanRenderWithImpellerOpenGL) { - EmbedderTestContextGL& context = static_cast( - GetEmbedderContext(EmbedderTestContextType::kOpenGLContext)); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); bool present_called = false; - static_cast(context).SetGLPresentCallback( + context.SetGLPresentCallback( [&present_called](FlutterPresentInfo present_info) { present_called = true; }); builder.AddCommandLineArgument("--enable-impeller"); builder.SetDartEntrypoint("render_impeller_test"); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); @@ -4865,11 +4814,38 @@ TEST_F(EmbedderTest, CanRenderWithImpellerOpenGL) { ASSERT_FALSE(present_called); } +TEST_F(EmbedderTest, ImpellerOpenGLImageSnapshot) { + auto& context = GetEmbedderContext(); + + bool result = false; + fml::AutoResetWaitableEvent latch; + context.AddNativeCallback("NotifyBoolValue", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + result = tonic::DartConverter::FromDart( + Dart_GetNativeArgument(args, 0)); + latch.Signal(); + })); + + EmbedderConfigBuilder builder(context); + builder.AddCommandLineArgument("--enable-impeller"); + builder.SetDartEntrypoint("render_impeller_image_snapshot_test"); + builder.SetSurface(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetRenderTargetType( + EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + latch.Wait(); + + ASSERT_TRUE(result); +} + TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLSurface) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views"); @@ -4982,10 +4958,10 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLSurface) { } TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownSceneToOpenGLSurfaces) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); @@ -5197,7 +5173,6 @@ INSTANTIATE_TEST_SUITE_P( ::testing::Values(EmbedderTestContextType::kOpenGLContext, EmbedderTestContextType::kVulkanContext)); -} // namespace testing -} // namespace flutter +} // namespace flutter::testing // NOLINTEND(clang-analyzer-core.StackAddressEscape) diff --git a/shell/platform/embedder/tests/embedder_metal_unittests.mm b/shell/platform/embedder/tests/embedder_metal_unittests.mm index 3f5ec9e549daf..67e49d14092f8 100644 --- a/shell/platform/embedder/tests/embedder_metal_unittests.mm +++ b/shell/platform/embedder/tests/embedder_metal_unittests.mm @@ -36,12 +36,11 @@ using EmbedderTest = testing::EmbedderTest; TEST_F(EmbedderTest, CanRenderGradientWithMetal) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetDartEntrypoint("render_gradient"); - builder.SetMetalRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); auto rendered_scene = context.GetNextSceneImage(); @@ -65,7 +64,7 @@ SkISize texture_size, void* texture) { GrMtlTextureInfo info; - info.fTexture.reset([(id)texture retain]); + info.fTexture.retain(texture); GrBackendTexture backend_texture = GrBackendTextures::MakeMtl( texture_size.width(), texture_size.height(), skgpu::Mipmapped::kNo, info); @@ -75,8 +74,7 @@ } TEST_F(EmbedderTest, ExternalTextureMetal) { - EmbedderTestContextMetal& context = reinterpret_cast( - GetEmbedderContext(EmbedderTestContextType::kMetalContext)); + auto& context = GetEmbedderContext(); const auto texture_size = SkISize::Make(800, 600); const int64_t texture_id = 1; @@ -108,7 +106,7 @@ EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("render_texture"); - builder.SetMetalRendererConfig(texture_size); + builder.SetSurface(texture_size); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -129,10 +127,10 @@ } TEST_F(EmbedderTest, MetalCompositorMustBeAbleToRenderPlatformViews) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetMetalRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views"); @@ -240,12 +238,12 @@ } TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositorMetal) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); - builder.SetMetalRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); auto rendered_scene = context.GetNextSceneImage(); @@ -264,10 +262,10 @@ } TEST_F(EmbedderTest, TextureDestructionCallbackCalledWithoutCustomCompositorMetal) { - EmbedderTestContextMetal& context = reinterpret_cast( - GetEmbedderContext(EmbedderTestContextType::kMetalContext)); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); - builder.SetMetalRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetDartEntrypoint("texture_destruction_callback_called_without_custom_compositor"); struct CollectContext { @@ -314,10 +312,10 @@ } TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownSceneMetal) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetMetalRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); @@ -511,9 +509,9 @@ } TEST_F(EmbedderTest, CreateInvalidBackingstoreMetalTexture) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetMetalRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetRenderTargetType(EmbedderTestBackingStoreProducer::RenderTargetType::kMetalTexture); builder.SetDartEntrypoint("invalid_backingstore"); @@ -566,8 +564,7 @@ void Collect() { } TEST_F(EmbedderTest, ExternalTextureMetalRefreshedTooOften) { - EmbedderTestContextMetal& context = reinterpret_cast( - GetEmbedderContext(EmbedderTestContextType::kMetalContext)); + auto& context = GetEmbedderContext(); TestMetalContext* metal_context = context.GetTestMetalContext(); auto metal_texture = metal_context->CreateMetalTexture(SkISize::Make(100, 100)); @@ -615,13 +612,13 @@ void Collect() { } TEST_F(EmbedderTest, CanRenderWithImpellerMetal) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.AddCommandLineArgument("--enable-impeller"); builder.SetDartEntrypoint("render_impeller_test"); - builder.SetMetalRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); auto rendered_scene = context.GetNextSceneImage(); @@ -640,13 +637,13 @@ void Collect() { } TEST_F(EmbedderTest, CanRenderTextWithImpellerMetal) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.AddCommandLineArgument("--enable-impeller"); builder.SetDartEntrypoint("render_impeller_text_test"); - builder.SetMetalRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); auto rendered_scene = context.GetNextSceneImage(); @@ -665,13 +662,13 @@ void Collect() { } TEST_F(EmbedderTest, CanRenderTextWithImpellerAndCompositorMetal) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.AddCommandLineArgument("--enable-impeller"); builder.SetDartEntrypoint("render_impeller_text_test"); - builder.SetMetalRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetRenderTargetType(EmbedderTestBackingStoreProducer::RenderTargetType::kMetalTexture); diff --git a/shell/platform/embedder/tests/embedder_test.cc b/shell/platform/embedder/tests/embedder_test.cc index 0b557d318e1ee..fbed2d29a8566 100644 --- a/shell/platform/embedder/tests/embedder_test.cc +++ b/shell/platform/embedder/tests/embedder_test.cc @@ -3,22 +3,13 @@ // found in the LICENSE file. #include "flutter/shell/platform/embedder/tests/embedder_test.h" -#include "flutter/shell/platform/embedder/tests/embedder_test_context_software.h" -#ifdef SHELL_ENABLE_GL -#include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" -#endif +#include +#include -#ifdef SHELL_ENABLE_METAL -#include "flutter/shell/platform/embedder/tests/embedder_test_context_metal.h" -#endif - -#ifdef SHELL_ENABLE_VULKAN -#include "flutter/shell/platform/embedder/tests/embedder_test_context_vulkan.h" -#endif +#include "flutter/shell/platform/embedder/tests/embedder_test_context_software.h" -namespace flutter { -namespace testing { +namespace flutter::testing { EmbedderTest::EmbedderTest() = default; @@ -26,43 +17,53 @@ std::string EmbedderTest::GetFixturesDirectory() const { return GetFixturesPath(); } -EmbedderTestContext& EmbedderTest::GetEmbedderContext( - EmbedderTestContextType type) { - // Setup the embedder context lazily instead of in the constructor because we - // don't to do all the work if the test won't end up using context. - if (!embedder_contexts_[type]) { - switch (type) { - case EmbedderTestContextType::kSoftwareContext: - embedder_contexts_[type] = - std::make_unique( - GetFixturesDirectory()); - break; -#ifdef SHELL_ENABLE_VULKAN - case EmbedderTestContextType::kVulkanContext: - embedder_contexts_[type] = - std::make_unique(GetFixturesDirectory()); - break; +EmbedderTestContext& EmbedderTest::GetSoftwareContext() { + if (!software_context_) { + software_context_ = + std::make_unique(GetFixturesDirectory()); + } + return *software_context_.get(); +} + +#ifndef SHELL_ENABLE_GL +// Fallback implementation. +// See: flutter/shell/platform/embedder/tests/embedder_test_gl.cc. +EmbedderTestContext& EmbedderTest::GetGLContext() { + FML_LOG(FATAL) << "OpenGL is not supported in this build"; + std::terminate(); +} #endif -#ifdef SHELL_ENABLE_GL - case EmbedderTestContextType::kOpenGLContext: - embedder_contexts_[type] = - std::make_unique(GetFixturesDirectory()); - break; + +#ifndef SHELL_ENABLE_METAL +// Fallback implementation. +// See: flutter/shell/platform/embedder/tests/embedder_test_metal.mm. +EmbedderTestContext& EmbedderTest::GetMetalContext() { + FML_LOG(FATAL) << "Metal is not supported in this build"; + std::terminate(); +} #endif -#ifdef SHELL_ENABLE_METAL - case EmbedderTestContextType::kMetalContext: - embedder_contexts_[type] = - std::make_unique(GetFixturesDirectory()); - break; + +#ifndef SHELL_ENABLE_VULKAN +// Fallback implementation. +// See: flutter/shell/platform/embedder/tests/embedder_test_vulkan.cc. +EmbedderTestContext& EmbedderTest::GetVulkanContext() { + FML_LOG(FATAL) << "Vulkan is not supported in this build"; + std::terminate(); +} #endif - default: - FML_DCHECK(false) << "Invalid context type specified."; - break; - } - } - return *embedder_contexts_[type]; +EmbedderTestContext& EmbedderTestMultiBackend::GetEmbedderContext( + EmbedderTestContextType type) { + switch (type) { + case EmbedderTestContextType::kOpenGLContext: + return GetGLContext(); + case EmbedderTestContextType::kMetalContext: + return GetMetalContext(); + case EmbedderTestContextType::kSoftwareContext: + return GetSoftwareContext(); + case EmbedderTestContextType::kVulkanContext: + return GetVulkanContext(); + } } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_test.h b/shell/platform/embedder/tests/embedder_test.h index eb16637872872..066fd535dac3d 100644 --- a/shell/platform/embedder/tests/embedder_test.h +++ b/shell/platform/embedder/tests/embedder_test.h @@ -14,8 +14,12 @@ #include "flutter/testing/thread_test.h" #include "gtest/gtest.h" -namespace flutter { -namespace testing { +namespace flutter::testing { + +class EmbedderTestContextGL; +class EmbedderTestContextMetal; +class EmbedderTestContextSoftware; +class EmbedderTestContextVulkan; class EmbedderTest : public ThreadTest { public: @@ -23,20 +27,59 @@ class EmbedderTest : public ThreadTest { std::string GetFixturesDirectory() const; - EmbedderTestContext& GetEmbedderContext(EmbedderTestContextType type); + template + T& GetEmbedderContext() { + static_assert(false, "Unsupported test context type"); + } + + template <> + EmbedderTestContextGL& GetEmbedderContext() { + return reinterpret_cast(GetGLContext()); + } + + template <> + EmbedderTestContextMetal& GetEmbedderContext() { + return reinterpret_cast(GetMetalContext()); + } + + template <> + EmbedderTestContextSoftware& + GetEmbedderContext() { + return reinterpret_cast(GetSoftwareContext()); + } - private: - std::map> - embedder_contexts_; + template <> + EmbedderTestContextVulkan& GetEmbedderContext() { + return reinterpret_cast(GetVulkanContext()); + } + + protected: + // We return the base class here and reinterpret_cast in the template + // specializations because we're using forward declarations rather than + // including the headers directly, and thus the relationship between the base + // class and subclasses is unknown to the compiler here. We avoid including + // the headers directly because the Metal headers include Objective-C types, + // and thus cannot be included in pure C++ translation units. + EmbedderTestContext& GetGLContext(); + EmbedderTestContext& GetMetalContext(); + EmbedderTestContext& GetSoftwareContext(); + EmbedderTestContext& GetVulkanContext(); + + std::unique_ptr gl_context_; + std::unique_ptr metal_context_; + std::unique_ptr software_context_; + std::unique_ptr vulkan_context_; FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTest); }; class EmbedderTestMultiBackend : public EmbedderTest, - public ::testing::WithParamInterface {}; + public ::testing::WithParamInterface { + public: + EmbedderTestContext& GetEmbedderContext(EmbedderTestContextType type); +}; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_H_ diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc b/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc index 477493f1be868..3bea53bcb925d 100644 --- a/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc @@ -4,457 +4,18 @@ #include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer.h" -#include "flutter/fml/logging.h" -#include "flutter/shell/platform/embedder/pixel_formats.h" -#include "third_party/skia/include/core/SkColorSpace.h" -#include "third_party/skia/include/core/SkColorType.h" -#include "third_party/skia/include/core/SkImageInfo.h" -#include "third_party/skia/include/core/SkSize.h" -#include "third_party/skia/include/core/SkSurface.h" -#include "third_party/skia/include/gpu/GpuTypes.h" -#include "third_party/skia/include/gpu/ganesh/GrBackendSurface.h" -#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" - -#include -#include -#include - -#ifdef SHELL_ENABLE_VULKAN -#include "third_party/skia/include/gpu/ganesh/vk/GrVkBackendSurface.h" -#include "third_party/skia/include/gpu/ganesh/vk/GrVkTypes.h" -#endif // SHELL_ENABLE_VULKAN - -#ifdef SHELL_ENABLE_METAL -#include "third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendSurface.h" -#include "third_party/skia/include/gpu/ganesh/mtl/GrMtlTypes.h" -#endif // SHELL_ENABLE_METAL - -#ifdef SHELL_ENABLE_GL -#include "flutter/testing/test_gl_surface.h" -#include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h" -#include "third_party/skia/include/gpu/ganesh/gl/GrGLTypes.h" -#endif // SHELL_ENABLE_GL - // TODO(zanderso): https://github.com/flutter/flutter/issues/127701 // NOLINTBEGIN(bugprone-unchecked-optional-access) -namespace flutter { -namespace testing { +namespace flutter::testing { EmbedderTestBackingStoreProducer::EmbedderTestBackingStoreProducer( sk_sp context, - RenderTargetType type, - FlutterSoftwarePixelFormat software_pixfmt) - : context_(std::move(context)), - type_(type), - software_pixfmt_(software_pixfmt) -#ifdef SHELL_ENABLE_GL - , - test_egl_context_(nullptr) -#endif -#ifdef SHELL_ENABLE_METAL - , - test_metal_context_(std::make_unique()) -#endif -#ifdef SHELL_ENABLE_VULKAN - , - test_vulkan_context_(nullptr) -#endif -{ - if (type == RenderTargetType::kSoftwareBuffer && - software_pixfmt_ != kFlutterSoftwarePixelFormatNative32) { - FML_LOG(ERROR) << "Expected pixel format to be the default " - "(kFlutterSoftwarePixelFormatNative32) when" - "backing store producer should produce deprecated v1 " - "software backing " - "stores."; - std::abort(); - }; -} + RenderTargetType type) + : context_(std::move(context)), type_(type) {} EmbedderTestBackingStoreProducer::~EmbedderTestBackingStoreProducer() = default; -#ifdef SHELL_ENABLE_GL -void EmbedderTestBackingStoreProducer::SetEGLContext( - std::shared_ptr context) { - // Ideally this would be set in the constructor, however we can't do that - // without introducing different constructors depending on different graphics - // APIs, which is a bit ugly. - test_egl_context_ = std::move(context); -} -#endif - -bool EmbedderTestBackingStoreProducer::Create( - const FlutterBackingStoreConfig* config, - FlutterBackingStore* renderer_out) { - switch (type_) { - case RenderTargetType::kSoftwareBuffer: - return CreateSoftware(config, renderer_out); - case RenderTargetType::kSoftwareBuffer2: - return CreateSoftware2(config, renderer_out); -#ifdef SHELL_ENABLE_GL - case RenderTargetType::kOpenGLTexture: - return CreateTexture(config, renderer_out); - case RenderTargetType::kOpenGLFramebuffer: - return CreateFramebuffer(config, renderer_out); - case RenderTargetType::kOpenGLSurface: - return CreateSurface(config, renderer_out); -#endif -#ifdef SHELL_ENABLE_METAL - case RenderTargetType::kMetalTexture: - return CreateMTLTexture(config, renderer_out); -#endif -#ifdef SHELL_ENABLE_VULKAN - case RenderTargetType::kVulkanImage: - return CreateVulkanImage(config, renderer_out); -#endif - default: - return false; - } -} - -bool EmbedderTestBackingStoreProducer::CreateFramebuffer( - const FlutterBackingStoreConfig* config, - FlutterBackingStore* backing_store_out) { -#ifdef SHELL_ENABLE_GL - const auto image_info = - SkImageInfo::MakeN32Premul(config->size.width, config->size.height); - - auto surface = - SkSurfaces::RenderTarget(context_.get(), // context - skgpu::Budgeted::kNo, // budgeted - image_info, // image info - 1, // sample count - kBottomLeft_GrSurfaceOrigin, // surface origin - nullptr, // surface properties - false // mipmaps - ); - - if (!surface) { - FML_LOG(ERROR) << "Could not create render target for compositor layer."; - return false; - } - - GrBackendRenderTarget render_target = SkSurfaces::GetBackendRenderTarget( - surface.get(), SkSurfaces::BackendHandleAccess::kDiscardWrite); - - if (!render_target.isValid()) { - FML_LOG(ERROR) << "Backend render target was invalid."; - return false; - } - - GrGLFramebufferInfo framebuffer_info = {}; - if (!GrBackendRenderTargets::GetGLFramebufferInfo(render_target, - &framebuffer_info)) { - FML_LOG(ERROR) << "Could not access backend framebuffer info."; - return false; - } - - auto user_data = new UserData(surface); - - backing_store_out->type = kFlutterBackingStoreTypeOpenGL; - backing_store_out->user_data = user_data; - backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; - backing_store_out->open_gl.framebuffer.target = framebuffer_info.fFormat; - backing_store_out->open_gl.framebuffer.name = framebuffer_info.fFBOID; - backing_store_out->open_gl.framebuffer.user_data = user_data; - backing_store_out->open_gl.framebuffer.destruction_callback = - [](void* user_data) { delete reinterpret_cast(user_data); }; - - return true; -#else - return false; -#endif -} - -bool EmbedderTestBackingStoreProducer::CreateTexture( - const FlutterBackingStoreConfig* config, - FlutterBackingStore* backing_store_out) { -#ifdef SHELL_ENABLE_GL - const auto image_info = - SkImageInfo::MakeN32Premul(config->size.width, config->size.height); - - auto surface = - SkSurfaces::RenderTarget(context_.get(), // context - skgpu::Budgeted::kNo, // budgeted - image_info, // image info - 1, // sample count - kBottomLeft_GrSurfaceOrigin, // surface origin - nullptr, // surface properties - false // mipmaps - ); - - if (!surface) { - FML_LOG(ERROR) << "Could not create render target for compositor layer."; - return false; - } - - GrBackendTexture render_texture = SkSurfaces::GetBackendTexture( - surface.get(), SkSurfaces::BackendHandleAccess::kDiscardWrite); - - if (!render_texture.isValid()) { - FML_LOG(ERROR) << "Backend render texture was invalid."; - return false; - } - - GrGLTextureInfo texture_info = {}; - if (!GrBackendTextures::GetGLTextureInfo(render_texture, &texture_info)) { - FML_LOG(ERROR) << "Could not access backend texture info."; - return false; - } - - auto user_data = new UserData(surface); - - backing_store_out->type = kFlutterBackingStoreTypeOpenGL; - backing_store_out->user_data = user_data; - backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeTexture; - backing_store_out->open_gl.texture.target = texture_info.fTarget; - backing_store_out->open_gl.texture.name = texture_info.fID; - backing_store_out->open_gl.texture.format = texture_info.fFormat; - backing_store_out->open_gl.texture.user_data = user_data; - backing_store_out->open_gl.texture.destruction_callback = - [](void* user_data) { delete reinterpret_cast(user_data); }; - - return true; -#else - return false; -#endif -} - -bool EmbedderTestBackingStoreProducer::CreateSurface( - const FlutterBackingStoreConfig* config, - FlutterBackingStore* backing_store_out) { -#ifdef SHELL_ENABLE_GL - FML_CHECK(test_egl_context_); - auto surface = std::make_unique( - test_egl_context_, - SkSize::Make(config->size.width, config->size.height).toRound()); - - auto make_current = [](void* user_data, bool* invalidate_state) -> bool { - *invalidate_state = false; - return reinterpret_cast(user_data)->gl_surface->MakeCurrent(); - }; - - auto clear_current = [](void* user_data, bool* invalidate_state) -> bool { - *invalidate_state = false; - // return - // reinterpret_cast(user_data)->gl_surface->ClearCurrent(); - return true; - }; - - auto destruction_callback = [](void* user_data) { - delete reinterpret_cast(user_data); - }; - - auto sk_surface = surface->GetOnscreenSurface(); - - auto user_data = new UserData(sk_surface, nullptr, std::move(surface)); - - backing_store_out->type = kFlutterBackingStoreTypeOpenGL; - backing_store_out->user_data = user_data; - backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeSurface; - backing_store_out->open_gl.surface.user_data = user_data; - backing_store_out->open_gl.surface.make_current_callback = make_current; - backing_store_out->open_gl.surface.clear_current_callback = clear_current; - backing_store_out->open_gl.surface.destruction_callback = - destruction_callback; - backing_store_out->open_gl.surface.format = 0x93A1 /* GL_BGRA8_EXT */; - - return true; -#else - return false; -#endif -} - -bool EmbedderTestBackingStoreProducer::CreateSoftware( - const FlutterBackingStoreConfig* config, - FlutterBackingStore* backing_store_out) { - auto surface = SkSurfaces::Raster( - SkImageInfo::MakeN32Premul(config->size.width, config->size.height)); - - if (!surface) { - FML_LOG(ERROR) - << "Could not create the render target for compositor layer."; - return false; - } - - SkPixmap pixmap; - if (!surface->peekPixels(&pixmap)) { - FML_LOG(ERROR) << "Could not peek pixels of pixmap."; - return false; - } - - auto user_data = new UserData(surface); - - backing_store_out->type = kFlutterBackingStoreTypeSoftware; - backing_store_out->user_data = user_data; - backing_store_out->software.allocation = pixmap.addr(); - backing_store_out->software.row_bytes = pixmap.rowBytes(); - backing_store_out->software.height = pixmap.height(); - backing_store_out->software.user_data = user_data; - backing_store_out->software.destruction_callback = [](void* user_data) { - delete reinterpret_cast(user_data); - }; - - return true; -} - -bool EmbedderTestBackingStoreProducer::CreateSoftware2( - const FlutterBackingStoreConfig* config, - FlutterBackingStore* backing_store_out) { - const auto color_info = getSkColorInfo(software_pixfmt_); - if (!color_info) { - return false; - } - - auto surface = SkSurfaces::Raster(SkImageInfo::Make( - SkISize::Make(config->size.width, config->size.height), *color_info)); - if (!surface) { - FML_LOG(ERROR) - << "Could not create the render target for compositor layer."; - return false; - } - - SkPixmap pixmap; - if (!surface->peekPixels(&pixmap)) { - FML_LOG(ERROR) << "Could not peek pixels of pixmap."; - return false; - } - - auto user_data = new UserData(surface); - - backing_store_out->type = kFlutterBackingStoreTypeSoftware2; - backing_store_out->user_data = user_data; - backing_store_out->software2.struct_size = - sizeof(FlutterSoftwareBackingStore2); - backing_store_out->software2.allocation = pixmap.writable_addr(); - backing_store_out->software2.row_bytes = pixmap.rowBytes(); - backing_store_out->software2.height = pixmap.height(); - backing_store_out->software2.user_data = user_data; - backing_store_out->software2.destruction_callback = [](void* user_data) { - delete reinterpret_cast(user_data); - }; - backing_store_out->software2.pixel_format = software_pixfmt_; - - return true; -} - -bool EmbedderTestBackingStoreProducer::CreateMTLTexture( - const FlutterBackingStoreConfig* config, - FlutterBackingStore* backing_store_out) { -#ifdef SHELL_ENABLE_METAL - // TODO(gw280): Use SkSurfaces::RenderTarget instead of generating our - // own MTLTexture and wrapping it. - auto surface_size = SkISize::Make(config->size.width, config->size.height); - auto texture_info = test_metal_context_->CreateMetalTexture(surface_size); - - GrMtlTextureInfo skia_texture_info; - skia_texture_info.fTexture.reset(SkCFSafeRetain(texture_info.texture)); - GrBackendTexture backend_texture = - GrBackendTextures::MakeMtl(surface_size.width(), surface_size.height(), - skgpu::Mipmapped::kNo, skia_texture_info); - - sk_sp surface = SkSurfaces::WrapBackendTexture( - context_.get(), backend_texture, kTopLeft_GrSurfaceOrigin, 1, - kBGRA_8888_SkColorType, nullptr, nullptr); - - if (!surface) { - FML_LOG(ERROR) << "Could not create Skia surface from a Metal texture."; - return false; - } - - backing_store_out->type = kFlutterBackingStoreTypeMetal; - backing_store_out->user_data = surface.get(); - backing_store_out->metal.texture.texture = texture_info.texture; - // The balancing unref is in the destruction callback. - surface->ref(); - backing_store_out->metal.struct_size = sizeof(FlutterMetalBackingStore); - backing_store_out->metal.texture.user_data = surface.get(); - backing_store_out->metal.texture.destruction_callback = [](void* user_data) { - reinterpret_cast(user_data)->unref(); - }; - - return true; -#else - return false; -#endif -} - -bool EmbedderTestBackingStoreProducer::CreateVulkanImage( - const FlutterBackingStoreConfig* config, - FlutterBackingStore* backing_store_out) { -#ifdef SHELL_ENABLE_VULKAN - if (!test_vulkan_context_) { - test_vulkan_context_ = fml::MakeRefCounted(); - } - - auto surface_size = SkISize::Make(config->size.width, config->size.height); - TestVulkanImage* test_image = new TestVulkanImage( - std::move(test_vulkan_context_->CreateImage(surface_size).value())); - - GrVkImageInfo image_info = { - .fImage = test_image->GetImage(), - .fImageTiling = VK_IMAGE_TILING_OPTIMAL, - .fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .fFormat = VK_FORMAT_R8G8B8A8_UNORM, - .fImageUsageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | - VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_SAMPLED_BIT, - .fSampleCount = 1, - .fLevelCount = 1, - }; - auto backend_texture = GrBackendTextures::MakeVk( - surface_size.width(), surface_size.height(), image_info); - - SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry); - - SkSurfaces::TextureReleaseProc release_vktexture = [](void* user_data) { - delete reinterpret_cast(user_data); - }; - - sk_sp surface = SkSurfaces::WrapBackendTexture( - context_.get(), // context - backend_texture, // back-end texture - kTopLeft_GrSurfaceOrigin, // surface origin - 1, // sample count - kRGBA_8888_SkColorType, // color type - SkColorSpace::MakeSRGB(), // color space - &surface_properties, // surface properties - release_vktexture, // texture release proc - test_image // release context - ); - - if (!surface) { - FML_LOG(ERROR) << "Could not create Skia surface from Vulkan image."; - return false; - } - backing_store_out->type = kFlutterBackingStoreTypeVulkan; - - FlutterVulkanImage* image = new FlutterVulkanImage(); - image->image = reinterpret_cast(image_info.fImage); - image->format = VK_FORMAT_R8G8B8A8_UNORM; - backing_store_out->vulkan.image = image; - - // Collect all allocated resources in the destruction_callback. - { - auto user_data = new UserData(surface, image); - backing_store_out->user_data = user_data; - backing_store_out->vulkan.user_data = user_data; - backing_store_out->vulkan.destruction_callback = [](void* user_data) { - UserData* d = reinterpret_cast(user_data); - delete d->image; - delete d; - }; - } - - return true; -#else - return false; -#endif -} - -} // namespace testing -} // namespace flutter +} // namespace flutter::testing // NOLINTEND(bugprone-unchecked-optional-access) diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer.h b/shell/platform/embedder/tests/embedder_test_backingstore_producer.h index 09c89ceb9aee1..dd6b02b436a78 100644 --- a/shell/platform/embedder/tests/embedder_test_backingstore_producer.h +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer.h @@ -6,53 +6,17 @@ #define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_H_ #include + #include "flutter/fml/macros.h" #include "flutter/fml/memory/ref_ptr_internal.h" #include "flutter/shell/platform/embedder/embedder.h" - #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" -#ifdef SHELL_ENABLE_METAL -#include "flutter/testing/test_metal_context.h" -#endif - -#ifdef SHELL_ENABLE_VULKAN -#include "flutter/testing/test_vulkan_context.h" // nogncheck -#endif - -#ifdef SHELL_ENABLE_GL -#include "flutter/testing/test_gl_surface.h" -#endif - -namespace flutter { -namespace testing { +namespace flutter::testing { class EmbedderTestBackingStoreProducer { public: - struct UserData { - UserData() : surface(nullptr), image(nullptr){}; - - explicit UserData(sk_sp surface) - : surface(std::move(surface)), image(nullptr){}; - - UserData(sk_sp surface, FlutterVulkanImage* vk_image) - : surface(std::move(surface)), image(vk_image){}; - - sk_sp surface; - FlutterVulkanImage* image; -#ifdef SHELL_ENABLE_GL - UserData(sk_sp surface, - FlutterVulkanImage* vk_image, - std::unique_ptr gl_surface) - : surface(std::move(surface)), - image(vk_image), - gl_surface(std::move(gl_surface)){}; - - std::unique_ptr gl_surface; -#endif - }; - enum class RenderTargetType { kSoftwareBuffer, kSoftwareBuffer2, @@ -64,60 +28,25 @@ class EmbedderTestBackingStoreProducer { }; EmbedderTestBackingStoreProducer(sk_sp context, - RenderTargetType type, - FlutterSoftwarePixelFormat software_pixfmt = - kFlutterSoftwarePixelFormatNative32); - ~EmbedderTestBackingStoreProducer(); - -#ifdef SHELL_ENABLE_GL - void SetEGLContext(std::shared_ptr context); -#endif - - bool Create(const FlutterBackingStoreConfig* config, - FlutterBackingStore* renderer_out); - - private: - bool CreateFramebuffer(const FlutterBackingStoreConfig* config, - FlutterBackingStore* renderer_out); - - bool CreateTexture(const FlutterBackingStoreConfig* config, - FlutterBackingStore* renderer_out); + RenderTargetType type); + virtual ~EmbedderTestBackingStoreProducer(); - bool CreateSurface(const FlutterBackingStoreConfig* config, - FlutterBackingStore* renderer_out); + virtual bool Create(const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) = 0; - bool CreateSoftware(const FlutterBackingStoreConfig* config, - FlutterBackingStore* backing_store_out); + virtual sk_sp GetSurface( + const FlutterBackingStore* backing_store) const = 0; - bool CreateSoftware2(const FlutterBackingStoreConfig* config, - FlutterBackingStore* backing_store_out); - - bool CreateMTLTexture(const FlutterBackingStoreConfig* config, - FlutterBackingStore* renderer_out); - - bool CreateVulkanImage(const FlutterBackingStoreConfig* config, - FlutterBackingStore* renderer_out); + virtual sk_sp MakeImageSnapshot( + const FlutterBackingStore* backing_store) const = 0; + protected: sk_sp context_; RenderTargetType type_; - FlutterSoftwarePixelFormat software_pixfmt_; - -#ifdef SHELL_ENABLE_GL - std::shared_ptr test_egl_context_; -#endif - -#ifdef SHELL_ENABLE_METAL - std::unique_ptr test_metal_context_; -#endif - -#ifdef SHELL_ENABLE_VULKAN - fml::RefPtr test_vulkan_context_; -#endif FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestBackingStoreProducer); }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_H_ diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer_gl.cc b/shell/platform/embedder/tests/embedder_test_backingstore_producer_gl.cc new file mode 100644 index 0000000000000..aae057f282e00 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer_gl.cc @@ -0,0 +1,214 @@ +// 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. + +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer_gl.h" + +#include "flutter/fml/logging.h" +#include "flutter/testing/test_gl_surface.h" +#include "third_party/skia/include/core/SkColorSpace.h" +#include "third_party/skia/include/gpu/ganesh/GrBackendSurface.h" +#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" +#include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h" +#include "third_party/skia/include/gpu/ganesh/gl/GrGLTypes.h" + +namespace flutter::testing { + +namespace { +struct UserData { + sk_sp surface; + std::unique_ptr gl_surface; +}; +} // namespace + +EmbedderTestBackingStoreProducerGL::EmbedderTestBackingStoreProducerGL( + sk_sp context, + RenderTargetType type, + std::shared_ptr egl_context) + : EmbedderTestBackingStoreProducer(std::move(context), type), + test_egl_context_(std::move(egl_context)) {} + +EmbedderTestBackingStoreProducerGL::~EmbedderTestBackingStoreProducerGL() = + default; + +bool EmbedderTestBackingStoreProducerGL::Create( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + switch (type_) { + case RenderTargetType::kOpenGLTexture: + return CreateTexture(config, backing_store_out); + case RenderTargetType::kOpenGLFramebuffer: + return CreateFramebuffer(config, backing_store_out); + case RenderTargetType::kOpenGLSurface: + return CreateSurface(config, backing_store_out); + default: + return false; + }; +} + +sk_sp EmbedderTestBackingStoreProducerGL::GetSurface( + const FlutterBackingStore* backing_store) const { + UserData* user_data = reinterpret_cast(backing_store->user_data); + return user_data->surface; +} + +sk_sp EmbedderTestBackingStoreProducerGL::MakeImageSnapshot( + const FlutterBackingStore* backing_store) const { + UserData* user_data = reinterpret_cast(backing_store->user_data); + if (user_data->gl_surface != nullptr) { + // This backing store is an OpenGL Surface. + // We need to make it current so we can snapshot it. + user_data->gl_surface->MakeCurrent(); + + // GetRasterSurfaceSnapshot() does two + // gl_surface->makeImageSnapshot()'s. Doing a single + // ->makeImageSnapshot() will not work. + return user_data->gl_surface->GetRasterSurfaceSnapshot(); + } + + // Otherwise, it's a GL Texture or FrameBuffer. + return user_data->surface->makeImageSnapshot(); +} + +bool EmbedderTestBackingStoreProducerGL::CreateFramebuffer( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + const auto image_info = + SkImageInfo::MakeN32Premul(config->size.width, config->size.height); + + auto surface = + SkSurfaces::RenderTarget(context_.get(), // context + skgpu::Budgeted::kNo, // budgeted + image_info, // image info + 1, // sample count + kBottomLeft_GrSurfaceOrigin, // surface origin + nullptr, // surface properties + false // mipmaps + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not create render target for compositor layer."; + return false; + } + + GrBackendRenderTarget render_target = SkSurfaces::GetBackendRenderTarget( + surface.get(), SkSurfaces::BackendHandleAccess::kDiscardWrite); + + if (!render_target.isValid()) { + FML_LOG(ERROR) << "Backend render target was invalid."; + return false; + } + + GrGLFramebufferInfo framebuffer_info = {}; + if (!GrBackendRenderTargets::GetGLFramebufferInfo(render_target, + &framebuffer_info)) { + FML_LOG(ERROR) << "Could not access backend framebuffer info."; + return false; + } + + auto user_data = new UserData{.surface = surface}; + backing_store_out->type = kFlutterBackingStoreTypeOpenGL; + backing_store_out->user_data = user_data; + backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + backing_store_out->open_gl.framebuffer.target = framebuffer_info.fFormat; + backing_store_out->open_gl.framebuffer.name = framebuffer_info.fFBOID; + backing_store_out->open_gl.framebuffer.user_data = user_data; + backing_store_out->open_gl.framebuffer.destruction_callback = + [](void* user_data) { delete reinterpret_cast(user_data); }; + + return true; +} + +bool EmbedderTestBackingStoreProducerGL::CreateTexture( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + const auto image_info = + SkImageInfo::MakeN32Premul(config->size.width, config->size.height); + + auto surface = + SkSurfaces::RenderTarget(context_.get(), // context + skgpu::Budgeted::kNo, // budgeted + image_info, // image info + 1, // sample count + kBottomLeft_GrSurfaceOrigin, // surface origin + nullptr, // surface properties + false // mipmaps + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not create render target for compositor layer."; + return false; + } + + GrBackendTexture render_texture = SkSurfaces::GetBackendTexture( + surface.get(), SkSurfaces::BackendHandleAccess::kDiscardWrite); + + if (!render_texture.isValid()) { + FML_LOG(ERROR) << "Backend render texture was invalid."; + return false; + } + + GrGLTextureInfo texture_info = {}; + if (!GrBackendTextures::GetGLTextureInfo(render_texture, &texture_info)) { + FML_LOG(ERROR) << "Could not access backend texture info."; + return false; + } + + auto user_data = new UserData{.surface = surface}; + backing_store_out->type = kFlutterBackingStoreTypeOpenGL; + backing_store_out->user_data = user_data; + backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeTexture; + backing_store_out->open_gl.texture.target = texture_info.fTarget; + backing_store_out->open_gl.texture.name = texture_info.fID; + backing_store_out->open_gl.texture.format = texture_info.fFormat; + backing_store_out->open_gl.texture.user_data = user_data; + backing_store_out->open_gl.texture.destruction_callback = + [](void* user_data) { delete reinterpret_cast(user_data); }; + + return true; +} + +bool EmbedderTestBackingStoreProducerGL::CreateSurface( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + FML_CHECK(test_egl_context_); + auto surface = std::make_unique( + test_egl_context_, + SkSize::Make(config->size.width, config->size.height).toRound()); + + auto make_current = [](void* user_data, bool* invalidate_state) -> bool { + *invalidate_state = false; + return reinterpret_cast(user_data)->gl_surface->MakeCurrent(); + }; + + auto clear_current = [](void* user_data, bool* invalidate_state) -> bool { + *invalidate_state = false; + // return + // reinterpret_cast(user_data)->gl_surface->ClearCurrent(); + return true; + }; + + auto destruction_callback = [](void* user_data) { + delete reinterpret_cast(user_data); + }; + + auto sk_surface = surface->GetOnscreenSurface(); + + auto user_data = new UserData{ + .surface = sk_surface, + .gl_surface = std::move(surface), + }; + backing_store_out->type = kFlutterBackingStoreTypeOpenGL; + backing_store_out->user_data = user_data; + backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeSurface; + backing_store_out->open_gl.surface.user_data = user_data; + backing_store_out->open_gl.surface.make_current_callback = make_current; + backing_store_out->open_gl.surface.clear_current_callback = clear_current; + backing_store_out->open_gl.surface.destruction_callback = + destruction_callback; + backing_store_out->open_gl.surface.format = 0x93A1 /* GL_BGRA8_EXT */; + + return true; +} + +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer_gl.h b/shell/platform/embedder/tests/embedder_test_backingstore_producer_gl.h new file mode 100644 index 0000000000000..1fe57276468ec --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer_gl.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_GL_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_GL_H_ + +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer.h" + +#include + +#include "flutter/testing/test_gl_context.h" + +namespace flutter::testing { + +class EmbedderTestBackingStoreProducerGL + : public EmbedderTestBackingStoreProducer { + public: + EmbedderTestBackingStoreProducerGL( + sk_sp context, + RenderTargetType type, + std::shared_ptr egl_context); + + virtual ~EmbedderTestBackingStoreProducerGL(); + + bool Create(const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) override; + + sk_sp GetSurface( + const FlutterBackingStore* backing_store) const override; + + sk_sp MakeImageSnapshot( + const FlutterBackingStore* backing_store) const override; + + private: + bool CreateFramebuffer(const FlutterBackingStoreConfig* config, + FlutterBackingStore* renderer_out); + + bool CreateTexture(const FlutterBackingStoreConfig* config, + FlutterBackingStore* renderer_out); + + bool CreateSurface(const FlutterBackingStoreConfig* config, + FlutterBackingStore* renderer_out); + + std::shared_ptr test_egl_context_; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestBackingStoreProducerGL); +}; + +} // namespace flutter::testing + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_GL_H_ diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer_metal.h b/shell/platform/embedder/tests/embedder_test_backingstore_producer_metal.h new file mode 100644 index 0000000000000..1d89d06f64963 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer_metal.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_METAL_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_METAL_H_ + +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer.h" +#include "flutter/testing/test_metal_context.h" + +namespace flutter::testing { + +class EmbedderTestBackingStoreProducerMetal + : public EmbedderTestBackingStoreProducer { + public: + EmbedderTestBackingStoreProducerMetal(sk_sp context, + RenderTargetType type); + + virtual ~EmbedderTestBackingStoreProducerMetal(); + + bool Create(const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) override; + + sk_sp GetSurface( + const FlutterBackingStore* backing_store) const override; + + sk_sp MakeImageSnapshot( + const FlutterBackingStore* backing_store) const override; + + private: + std::unique_ptr test_metal_context_; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestBackingStoreProducerMetal); +}; + +} // namespace flutter::testing + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_METAL_H_ diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer_metal.mm b/shell/platform/embedder/tests/embedder_test_backingstore_producer_metal.mm new file mode 100644 index 0000000000000..42595e92f6a68 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer_metal.mm @@ -0,0 +1,73 @@ +// 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. + +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer_metal.h" + +#include + +#include "flutter/fml/logging.h" +#include "third_party/skia/include/core/SkColorSpace.h" +#include "third_party/skia/include/gpu/ganesh/GrBackendSurface.h" +#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" +#include "third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendSurface.h" +#include "third_party/skia/include/gpu/ganesh/mtl/GrMtlTypes.h" + +namespace flutter::testing { + +EmbedderTestBackingStoreProducerMetal::EmbedderTestBackingStoreProducerMetal( + sk_sp context, + RenderTargetType type) + : EmbedderTestBackingStoreProducer(std::move(context), type), + test_metal_context_(std::make_unique()) {} + +EmbedderTestBackingStoreProducerMetal::~EmbedderTestBackingStoreProducerMetal() = default; + +bool EmbedderTestBackingStoreProducerMetal::Create(const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + // TODO(gw280): Use SkSurfaces::RenderTarget instead of generating our + // own MTLTexture and wrapping it. + auto surface_size = SkISize::Make(config->size.width, config->size.height); + auto texture_info = test_metal_context_->CreateMetalTexture(surface_size); + + GrMtlTextureInfo skia_texture_info; + skia_texture_info.fTexture.reset(SkCFSafeRetain(texture_info.texture)); + GrBackendTexture backend_texture = GrBackendTextures::MakeMtl( + surface_size.width(), surface_size.height(), skgpu::Mipmapped::kNo, skia_texture_info); + + sk_sp surface = + SkSurfaces::WrapBackendTexture(context_.get(), backend_texture, kTopLeft_GrSurfaceOrigin, 1, + kBGRA_8888_SkColorType, nullptr, nullptr); + + if (!surface) { + FML_LOG(ERROR) << "Could not create Skia surface from a Metal texture."; + return false; + } + + backing_store_out->type = kFlutterBackingStoreTypeMetal; + backing_store_out->user_data = surface.get(); + backing_store_out->metal.texture.texture = texture_info.texture; + // The balancing unref is in the destruction callback. + surface->ref(); + backing_store_out->metal.struct_size = sizeof(FlutterMetalBackingStore); + backing_store_out->metal.texture.user_data = surface.get(); + backing_store_out->metal.texture.destruction_callback = [](void* user_data) { + reinterpret_cast(user_data)->unref(); + }; + + return true; +} + +sk_sp EmbedderTestBackingStoreProducerMetal::GetSurface( + const FlutterBackingStore* backing_store) const { + FML_LOG(FATAL) << "Unimplemented."; + std::terminate(); +} + +sk_sp EmbedderTestBackingStoreProducerMetal::MakeImageSnapshot( + const FlutterBackingStore* backing_store) const { + FML_LOG(FATAL) << "Unimplemented."; + std::terminate(); +} + +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer_software.cc b/shell/platform/embedder/tests/embedder_test_backingstore_producer_software.cc new file mode 100644 index 0000000000000..5b753d8277261 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer_software.cc @@ -0,0 +1,138 @@ +// 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. + +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer_software.h" + +#include "flutter/fml/logging.h" +#include "flutter/shell/platform/embedder/pixel_formats.h" +#include "third_party/skia/include/core/SkColorSpace.h" +#include "third_party/skia/include/gpu/ganesh/GrBackendSurface.h" +#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" + +namespace flutter::testing { + +namespace { +struct UserData { + sk_sp surface; +}; +} // namespace + +EmbedderTestBackingStoreProducerSoftware:: + EmbedderTestBackingStoreProducerSoftware( + sk_sp context, + RenderTargetType type, + FlutterSoftwarePixelFormat software_pixfmt) + : EmbedderTestBackingStoreProducer(std::move(context), type), + software_pixfmt_(software_pixfmt) { + if (type == RenderTargetType::kSoftwareBuffer && + software_pixfmt_ != kFlutterSoftwarePixelFormatNative32) { + FML_LOG(ERROR) << "Expected pixel format to be the default " + "(kFlutterSoftwarePixelFormatNative32) when" + "backing store producer should produce deprecated v1 " + "software backing " + "stores."; + std::abort(); + }; +} + +EmbedderTestBackingStoreProducerSoftware:: + ~EmbedderTestBackingStoreProducerSoftware() = default; + +bool EmbedderTestBackingStoreProducerSoftware::Create( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + switch (type_) { + case RenderTargetType::kSoftwareBuffer: + return CreateSoftware(config, backing_store_out); + case RenderTargetType::kSoftwareBuffer2: + return CreateSoftware2(config, backing_store_out); + default: + return false; + } +} + +sk_sp EmbedderTestBackingStoreProducerSoftware::GetSurface( + const FlutterBackingStore* backing_store) const { + UserData* user_data = reinterpret_cast(backing_store->user_data); + return user_data->surface; +} + +sk_sp EmbedderTestBackingStoreProducerSoftware::MakeImageSnapshot( + const FlutterBackingStore* backing_store) const { + auto user_data = reinterpret_cast(backing_store->user_data); + return user_data->surface->makeImageSnapshot(); +} + +bool EmbedderTestBackingStoreProducerSoftware::CreateSoftware( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + auto surface = SkSurfaces::Raster( + SkImageInfo::MakeN32Premul(config->size.width, config->size.height)); + + if (!surface) { + FML_LOG(ERROR) + << "Could not create the render target for compositor layer."; + return false; + } + + SkPixmap pixmap; + if (!surface->peekPixels(&pixmap)) { + FML_LOG(ERROR) << "Could not peek pixels of pixmap."; + return false; + } + + auto user_data = new UserData{.surface = surface}; + backing_store_out->type = kFlutterBackingStoreTypeSoftware; + backing_store_out->user_data = user_data; + backing_store_out->software.allocation = pixmap.addr(); + backing_store_out->software.row_bytes = pixmap.rowBytes(); + backing_store_out->software.height = pixmap.height(); + backing_store_out->software.user_data = user_data; + backing_store_out->software.destruction_callback = [](void* user_data) { + delete reinterpret_cast(user_data); + }; + + return true; +} + +bool EmbedderTestBackingStoreProducerSoftware::CreateSoftware2( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + const auto color_info = getSkColorInfo(software_pixfmt_); + if (!color_info) { + return false; + } + + auto surface = SkSurfaces::Raster(SkImageInfo::Make( + SkISize::Make(config->size.width, config->size.height), *color_info)); + if (!surface) { + FML_LOG(ERROR) + << "Could not create the render target for compositor layer."; + return false; + } + + SkPixmap pixmap; + if (!surface->peekPixels(&pixmap)) { + FML_LOG(ERROR) << "Could not peek pixels of pixmap."; + return false; + } + + auto user_data = new UserData{.surface = surface}; + backing_store_out->type = kFlutterBackingStoreTypeSoftware2; + backing_store_out->user_data = user_data; + backing_store_out->software2.struct_size = + sizeof(FlutterSoftwareBackingStore2); + backing_store_out->software2.allocation = pixmap.writable_addr(); + backing_store_out->software2.row_bytes = pixmap.rowBytes(); + backing_store_out->software2.height = pixmap.height(); + backing_store_out->software2.user_data = user_data; + backing_store_out->software2.destruction_callback = [](void* user_data) { + delete reinterpret_cast(user_data); + }; + backing_store_out->software2.pixel_format = software_pixfmt_; + + return true; +} + +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer_software.h b/shell/platform/embedder/tests/embedder_test_backingstore_producer_software.h new file mode 100644 index 0000000000000..0fdce10136f2e --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer_software.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_SOFTWARE_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_SOFTWARE_H_ + +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer.h" + +namespace flutter::testing { + +class EmbedderTestBackingStoreProducerSoftware + : public EmbedderTestBackingStoreProducer { + public: + EmbedderTestBackingStoreProducerSoftware( + sk_sp context, + RenderTargetType type, + FlutterSoftwarePixelFormat software_pixfmt = + kFlutterSoftwarePixelFormatNative32); + + virtual ~EmbedderTestBackingStoreProducerSoftware(); + + bool Create(const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) override; + + sk_sp GetSurface( + const FlutterBackingStore* backing_store) const override; + + sk_sp MakeImageSnapshot( + const FlutterBackingStore* backing_store) const override; + + private: + bool CreateSoftware(const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out); + + bool CreateSoftware2(const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out); + + FlutterSoftwarePixelFormat software_pixfmt_; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestBackingStoreProducerSoftware); +}; + +} // namespace flutter::testing + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_SOFTWARE_H_ diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer_vulkan.cc b/shell/platform/embedder/tests/embedder_test_backingstore_producer_vulkan.cc new file mode 100644 index 0000000000000..195b68be4d0d8 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer_vulkan.cc @@ -0,0 +1,118 @@ +// 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. + +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer_vulkan.h" + +#include "flutter/fml/logging.h" +#include "third_party/skia/include/core/SkColorSpace.h" +#include "third_party/skia/include/gpu/ganesh/GrBackendSurface.h" +#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" +#include "third_party/skia/include/gpu/ganesh/vk/GrVkBackendSurface.h" +#include "third_party/skia/include/gpu/ganesh/vk/GrVkTypes.h" + +namespace flutter::testing { + +namespace { +struct UserData { + sk_sp surface; + FlutterVulkanImage* image; +}; +} // namespace + +EmbedderTestBackingStoreProducerVulkan::EmbedderTestBackingStoreProducerVulkan( + sk_sp context, + RenderTargetType type) + : EmbedderTestBackingStoreProducer(std::move(context), type), + test_vulkan_context_(nullptr) {} + +EmbedderTestBackingStoreProducerVulkan:: + ~EmbedderTestBackingStoreProducerVulkan() = default; + +bool EmbedderTestBackingStoreProducerVulkan::Create( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + if (!test_vulkan_context_) { + test_vulkan_context_ = fml::MakeRefCounted(); + } + + auto surface_size = SkISize::Make(config->size.width, config->size.height); + auto optional_image = test_vulkan_context_->CreateImage(surface_size); + if (!optional_image.has_value()) { + FML_LOG(ERROR) << "Could not create Vulkan image."; + return false; + } + TestVulkanImage* test_image = new TestVulkanImage(std::move(*optional_image)); + + GrVkImageInfo image_info = { + .fImage = test_image->GetImage(), + .fImageTiling = VK_IMAGE_TILING_OPTIMAL, + .fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .fFormat = VK_FORMAT_R8G8B8A8_UNORM, + .fImageUsageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT, + .fSampleCount = 1, + .fLevelCount = 1, + }; + auto backend_texture = GrBackendTextures::MakeVk( + surface_size.width(), surface_size.height(), image_info); + + SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry); + + SkSurfaces::TextureReleaseProc release_vktexture = [](void* user_data) { + delete reinterpret_cast(user_data); + }; + + sk_sp surface = SkSurfaces::WrapBackendTexture( + context_.get(), // context + backend_texture, // back-end texture + kTopLeft_GrSurfaceOrigin, // surface origin + 1, // sample count + kRGBA_8888_SkColorType, // color type + SkColorSpace::MakeSRGB(), // color space + &surface_properties, // surface properties + release_vktexture, // texture release proc + test_image // release context + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not create Skia surface from Vulkan image."; + return false; + } + backing_store_out->type = kFlutterBackingStoreTypeVulkan; + + FlutterVulkanImage* image = new FlutterVulkanImage(); + image->image = reinterpret_cast(image_info.fImage); + image->format = VK_FORMAT_R8G8B8A8_UNORM; + backing_store_out->vulkan.image = image; + + // Collect all allocated resources in the destruction_callback. + { + auto user_data = new UserData{.surface = surface, .image = image}; + backing_store_out->user_data = user_data; + backing_store_out->vulkan.user_data = user_data; + backing_store_out->vulkan.destruction_callback = [](void* user_data) { + UserData* d = reinterpret_cast(user_data); + delete d->image; + delete d; + }; + } + + return true; +} + +sk_sp EmbedderTestBackingStoreProducerVulkan::GetSurface( + const FlutterBackingStore* backing_store) const { + UserData* user_data = reinterpret_cast(backing_store->user_data); + return user_data->surface; +} + +sk_sp EmbedderTestBackingStoreProducerVulkan::MakeImageSnapshot( + const FlutterBackingStore* backing_store) const { + UserData* user_data = reinterpret_cast(backing_store->user_data); + return user_data->surface->makeImageSnapshot(); +} + +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer_vulkan.h b/shell/platform/embedder/tests/embedder_test_backingstore_producer_vulkan.h new file mode 100644 index 0000000000000..08a11282eb17e --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer_vulkan.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_VULKAN_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_VULKAN_H_ + +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer.h" + +#include "flutter/testing/test_vulkan_context.h" + +namespace flutter::testing { + +class EmbedderTestBackingStoreProducerVulkan + : public EmbedderTestBackingStoreProducer { + public: + EmbedderTestBackingStoreProducerVulkan(sk_sp context, + RenderTargetType type); + + virtual ~EmbedderTestBackingStoreProducerVulkan(); + + bool Create(const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) override; + + sk_sp GetSurface( + const FlutterBackingStore* backing_store) const override; + + sk_sp MakeImageSnapshot( + const FlutterBackingStore* backing_store) const override; + + private: + fml::RefPtr test_vulkan_context_; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestBackingStoreProducerVulkan); +}; + +} // namespace flutter::testing + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_BACKINGSTORE_PRODUCER_VULKAN_H_ diff --git a/shell/platform/embedder/tests/embedder_test_compositor.cc b/shell/platform/embedder/tests/embedder_test_compositor.cc index 66f6d55af0c7f..ab6963b44e722 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor.cc +++ b/shell/platform/embedder/tests/embedder_test_compositor.cc @@ -51,9 +51,9 @@ bool EmbedderTestCompositor::CollectBackingStore( return true; } -void EmbedderTestCompositor::SetBackingStoreProducer( - std::unique_ptr backingstore_producer) { - backingstore_producer_ = std::move(backingstore_producer); +sk_sp EmbedderTestCompositor::GetSurface( + const FlutterBackingStore* backing_store) const { + return backingstore_producer_->GetSurface(backing_store); } sk_sp EmbedderTestCompositor::GetLastComposition() { diff --git a/shell/platform/embedder/tests/embedder_test_compositor.h b/shell/platform/embedder/tests/embedder_test_compositor.h index 04da7759773da..67992deed1a22 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor.h +++ b/shell/platform/embedder/tests/embedder_test_compositor.h @@ -29,8 +29,9 @@ class EmbedderTestCompositor { virtual ~EmbedderTestCompositor(); - void SetBackingStoreProducer( - std::unique_ptr backingstore_producer); + virtual void SetRenderTargetType( + EmbedderTestBackingStoreProducer::RenderTargetType type, + FlutterSoftwarePixelFormat software_pixfmt) = 0; bool CreateBackingStore(const FlutterBackingStoreConfig* config, FlutterBackingStore* backing_store_out); @@ -44,6 +45,8 @@ class EmbedderTestCompositor { void SetPlatformViewRendererCallback( const PlatformViewRendererCallback& callback); + sk_sp GetSurface(const FlutterBackingStore* backing_store) const; + //---------------------------------------------------------------------------- /// @brief Allows tests to install a callback to notify them when the /// entire render tree has been finalized so they can run their diff --git a/shell/platform/embedder/tests/embedder_test_compositor_gl.cc b/shell/platform/embedder/tests/embedder_test_compositor_gl.cc index 29daf8da59969..1404b653dddfc 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor_gl.cc +++ b/shell/platform/embedder/tests/embedder_test_compositor_gl.cc @@ -8,6 +8,8 @@ #include "flutter/fml/logging.h" #include "flutter/shell/platform/embedder/tests/embedder_assertions.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer_gl.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer_software.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" @@ -15,12 +17,40 @@ namespace flutter { namespace testing { EmbedderTestCompositorGL::EmbedderTestCompositorGL( + std::shared_ptr egl_context, SkISize surface_size, sk_sp context) - : EmbedderTestCompositor(surface_size, std::move(context)) {} + : EmbedderTestCompositor(surface_size, std::move(context)), + egl_context_(std::move(egl_context)) {} EmbedderTestCompositorGL::~EmbedderTestCompositorGL() = default; +void EmbedderTestCompositorGL::SetRenderTargetType( + EmbedderTestBackingStoreProducer::RenderTargetType type, + FlutterSoftwarePixelFormat software_pixfmt) { + switch (type) { + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer: + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLSurface: + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture: { + backingstore_producer_ = + std::make_unique(context_, type, + egl_context_); + return; + } + case EmbedderTestBackingStoreProducer::RenderTargetType::kSoftwareBuffer: + case EmbedderTestBackingStoreProducer::RenderTargetType::kSoftwareBuffer2: + backingstore_producer_ = + std::make_unique( + context_, type, software_pixfmt); + return; + case EmbedderTestBackingStoreProducer::RenderTargetType::kMetalTexture: + case EmbedderTestBackingStoreProducer::RenderTargetType::kVulkanImage: + FML_LOG(FATAL) << "Unsupported render target type: " + << static_cast(type); + return; + } +} + bool EmbedderTestCompositorGL::UpdateOffscrenComposition( const FlutterLayer** layers, size_t layers_count) { @@ -59,24 +89,8 @@ bool EmbedderTestCompositorGL::UpdateOffscrenComposition( switch (layer->type) { case kFlutterLayerContentTypeBackingStore: { - auto gl_user_data = - reinterpret_cast( - layer->backing_store->user_data); - - if (gl_user_data->gl_surface != nullptr) { - // This backing store is a OpenGL Surface. - // We need to make it current so we can snapshot it. - - gl_user_data->gl_surface->MakeCurrent(); - - // GetRasterSurfaceSnapshot() does two - // gl_surface->makeImageSnapshot()'s. Doing a single - // ->makeImageSnapshot() will not work. - layer_image = gl_user_data->gl_surface->GetRasterSurfaceSnapshot(); - } else { - layer_image = gl_user_data->surface->makeImageSnapshot(); - } - + layer_image = + backingstore_producer_->MakeImageSnapshot(layer->backing_store); // We don't clear the current surface here because we need the // EGL context to be current for surface->makeImageSnapshot() below. break; diff --git a/shell/platform/embedder/tests/embedder_test_compositor_gl.h b/shell/platform/embedder/tests/embedder_test_compositor_gl.h index 507760d5acc97..b3b9647b05a23 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor_gl.h +++ b/shell/platform/embedder/tests/embedder_test_compositor_gl.h @@ -5,21 +5,30 @@ #ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_COMPOSITOR_GL_H_ #define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_COMPOSITOR_GL_H_ +#include + #include "flutter/fml/macros.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/tests/embedder_test_compositor.h" +#include "flutter/testing/test_gl_context.h" namespace flutter { namespace testing { class EmbedderTestCompositorGL : public EmbedderTestCompositor { public: - EmbedderTestCompositorGL(SkISize surface_size, + EmbedderTestCompositorGL(std::shared_ptr egl_context, + SkISize surface_size, sk_sp context); ~EmbedderTestCompositorGL() override; + void SetRenderTargetType( + EmbedderTestBackingStoreProducer::RenderTargetType type, + FlutterSoftwarePixelFormat software_pixfmt) override; + private: + std::shared_ptr egl_context_; bool UpdateOffscrenComposition(const FlutterLayer** layers, size_t layers_count) override; diff --git a/shell/platform/embedder/tests/embedder_test_compositor_metal.h b/shell/platform/embedder/tests/embedder_test_compositor_metal.h index bbe56272b5358..4bd134ac3b445 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor_metal.h +++ b/shell/platform/embedder/tests/embedder_test_compositor_metal.h @@ -19,6 +19,10 @@ class EmbedderTestCompositorMetal : public EmbedderTestCompositor { ~EmbedderTestCompositorMetal() override; + void SetRenderTargetType( + EmbedderTestBackingStoreProducer::RenderTargetType type, + FlutterSoftwarePixelFormat software_pixfmt) override; + private: bool UpdateOffscrenComposition(const FlutterLayer** layers, size_t layers_count) override; diff --git a/shell/platform/embedder/tests/embedder_test_compositor_metal.cc b/shell/platform/embedder/tests/embedder_test_compositor_metal.mm similarity index 54% rename from shell/platform/embedder/tests/embedder_test_compositor_metal.cc rename to shell/platform/embedder/tests/embedder_test_compositor_metal.mm index 594be6ab94ce7..cb3c2548e988a 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor_metal.cc +++ b/shell/platform/embedder/tests/embedder_test_compositor_metal.mm @@ -8,35 +8,52 @@ #include "flutter/fml/logging.h" #include "flutter/shell/platform/embedder/tests/embedder_assertions.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer_metal.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" namespace flutter { namespace testing { -EmbedderTestCompositorMetal::EmbedderTestCompositorMetal( - SkISize surface_size, - sk_sp context) +EmbedderTestCompositorMetal::EmbedderTestCompositorMetal(SkISize surface_size, + sk_sp context) : EmbedderTestCompositor(surface_size, std::move(context)) {} EmbedderTestCompositorMetal::~EmbedderTestCompositorMetal() = default; -bool EmbedderTestCompositorMetal::UpdateOffscrenComposition( - const FlutterLayer** layers, - size_t layers_count) { +void EmbedderTestCompositorMetal::SetRenderTargetType( + EmbedderTestBackingStoreProducer::RenderTargetType type, + FlutterSoftwarePixelFormat software_pixfmt) { + switch (type) { + case EmbedderTestBackingStoreProducer::RenderTargetType::kMetalTexture: + backingstore_producer_ = + std::make_unique(context_, type); + return; + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer: + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLSurface: + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture: + case EmbedderTestBackingStoreProducer::RenderTargetType::kSoftwareBuffer: + case EmbedderTestBackingStoreProducer::RenderTargetType::kSoftwareBuffer2: + case EmbedderTestBackingStoreProducer::RenderTargetType::kVulkanImage: + FML_LOG(FATAL) << "Unsupported render target type: " << static_cast(type); + return; + } +} + +bool EmbedderTestCompositorMetal::UpdateOffscrenComposition(const FlutterLayer** layers, + size_t layers_count) { last_composition_ = nullptr; const auto image_info = SkImageInfo::MakeN32Premul(surface_size_); - auto surface = - SkSurfaces::RenderTarget(context_.get(), // context - skgpu::Budgeted::kNo, // budgeted - image_info, // image info - 1, // sample count - kTopLeft_GrSurfaceOrigin, // surface origin - nullptr, // surface properties - false // create mipmaps - ); + auto surface = SkSurfaces::RenderTarget(context_.get(), // context + skgpu::Budgeted::kNo, // budgeted + image_info, // image info + 1, // sample count + kTopLeft_GrSurfaceOrigin, // surface origin + nullptr, // surface properties + false // create mipmaps + ); if (!surface) { FML_LOG(ERROR) << "Could not update the off-screen composition."; @@ -60,14 +77,12 @@ bool EmbedderTestCompositorMetal::UpdateOffscrenComposition( switch (layer->type) { case kFlutterLayerContentTypeBackingStore: layer_image = - reinterpret_cast(layer->backing_store->user_data) - ->makeImageSnapshot(); + reinterpret_cast(layer->backing_store->user_data)->makeImageSnapshot(); break; case kFlutterLayerContentTypePlatformView: - layer_image = - platform_view_renderer_callback_ - ? platform_view_renderer_callback_(*layer, context_.get()) - : nullptr; + layer_image = platform_view_renderer_callback_ + ? platform_view_renderer_callback_(*layer, context_.get()) + : nullptr; canvas_offset = SkIPoint::Make(layer->offset.x, layer->offset.y); break; }; @@ -75,8 +90,7 @@ bool EmbedderTestCompositorMetal::UpdateOffscrenComposition( // If the layer is not a platform view but the engine did not specify an // image for the backing store, it is an error. if (!layer_image && layer->type != kFlutterLayerContentTypePlatformView) { - FML_LOG(ERROR) << "Could not snapshot layer in test compositor: " - << *layer; + FML_LOG(ERROR) << "Could not snapshot layer in test compositor: " << *layer; return false; } @@ -85,8 +99,7 @@ bool EmbedderTestCompositorMetal::UpdateOffscrenComposition( if (layer_image) { // The image rendered by Flutter already has the correct offset and // transformation applied. The layers offset is meant for the platform. - canvas->drawImage(layer_image.get(), canvas_offset.x(), - canvas_offset.y()); + canvas->drawImage(layer_image.get(), canvas_offset.x(), canvas_offset.y()); } } diff --git a/shell/platform/embedder/tests/embedder_test_compositor_software.cc b/shell/platform/embedder/tests/embedder_test_compositor_software.cc index 46938205e2c13..369a4f89e676f 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor_software.cc +++ b/shell/platform/embedder/tests/embedder_test_compositor_software.cc @@ -6,6 +6,7 @@ #include "flutter/fml/logging.h" #include "flutter/shell/platform/embedder/tests/embedder_assertions.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer_software.h" #include "third_party/skia/include/core/SkSurface.h" namespace flutter { @@ -17,6 +18,27 @@ EmbedderTestCompositorSoftware::EmbedderTestCompositorSoftware( EmbedderTestCompositorSoftware::~EmbedderTestCompositorSoftware() = default; +void EmbedderTestCompositorSoftware::SetRenderTargetType( + EmbedderTestBackingStoreProducer::RenderTargetType type, + FlutterSoftwarePixelFormat software_pixfmt) { + switch (type) { + case EmbedderTestBackingStoreProducer::RenderTargetType::kSoftwareBuffer: + case EmbedderTestBackingStoreProducer::RenderTargetType::kSoftwareBuffer2: + backingstore_producer_ = + std::make_unique( + context_, type, software_pixfmt); + return; + case EmbedderTestBackingStoreProducer::RenderTargetType::kMetalTexture: + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer: + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLSurface: + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture: + case EmbedderTestBackingStoreProducer::RenderTargetType::kVulkanImage: + FML_LOG(FATAL) << "Unsupported render target type: " + << static_cast(type); + return; + } +} + bool EmbedderTestCompositorSoftware::UpdateOffscrenComposition( const FlutterLayer** layers, size_t layers_count) { @@ -46,13 +68,11 @@ bool EmbedderTestCompositorSoftware::UpdateOffscrenComposition( SkIPoint canvas_offset = SkIPoint::Make(0, 0); switch (layer->type) { - case kFlutterLayerContentTypeBackingStore: + case kFlutterLayerContentTypeBackingStore: { layer_image = - reinterpret_cast( - layer->backing_store->user_data) - ->surface->makeImageSnapshot(); - + backingstore_producer_->MakeImageSnapshot(layer->backing_store); break; + } case kFlutterLayerContentTypePlatformView: layer_image = platform_view_renderer_callback_ ? platform_view_renderer_callback_(*layer, nullptr) diff --git a/shell/platform/embedder/tests/embedder_test_compositor_software.h b/shell/platform/embedder/tests/embedder_test_compositor_software.h index ffd007fff0fdb..d076d29106e3e 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor_software.h +++ b/shell/platform/embedder/tests/embedder_test_compositor_software.h @@ -16,6 +16,10 @@ class EmbedderTestCompositorSoftware : public EmbedderTestCompositor { ~EmbedderTestCompositorSoftware() override; + void SetRenderTargetType( + EmbedderTestBackingStoreProducer::RenderTargetType type, + FlutterSoftwarePixelFormat software_pixfmt) override; + private: bool UpdateOffscrenComposition(const FlutterLayer** layers, size_t layers_count); diff --git a/shell/platform/embedder/tests/embedder_test_compositor_vulkan.cc b/shell/platform/embedder/tests/embedder_test_compositor_vulkan.cc index 18a0fafabd465..480c8c88b7575 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor_vulkan.cc +++ b/shell/platform/embedder/tests/embedder_test_compositor_vulkan.cc @@ -8,7 +8,7 @@ #include "flutter/fml/logging.h" #include "flutter/shell/platform/embedder/tests/embedder_assertions.h" -#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer_vulkan.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" @@ -22,6 +22,27 @@ EmbedderTestCompositorVulkan::EmbedderTestCompositorVulkan( EmbedderTestCompositorVulkan::~EmbedderTestCompositorVulkan() = default; +void EmbedderTestCompositorVulkan::SetRenderTargetType( + EmbedderTestBackingStoreProducer::RenderTargetType type, + FlutterSoftwarePixelFormat software_pixfmt) { + switch (type) { + case EmbedderTestBackingStoreProducer::RenderTargetType::kVulkanImage: + backingstore_producer_ = + std::make_unique(context_, + type); + return; + case EmbedderTestBackingStoreProducer::RenderTargetType::kMetalTexture: + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer: + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLSurface: + case EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture: + case EmbedderTestBackingStoreProducer::RenderTargetType::kSoftwareBuffer: + case EmbedderTestBackingStoreProducer::RenderTargetType::kSoftwareBuffer2: + FML_LOG(FATAL) << "Unsupported render target type: " + << static_cast(type); + return; + } +} + bool EmbedderTestCompositorVulkan::UpdateOffscrenComposition( const FlutterLayer** layers, size_t layers_count) { @@ -61,9 +82,7 @@ bool EmbedderTestCompositorVulkan::UpdateOffscrenComposition( switch (layer->type) { case kFlutterLayerContentTypeBackingStore: layer_image = - reinterpret_cast( - layer->backing_store->user_data) - ->surface->makeImageSnapshot(); + backingstore_producer_->MakeImageSnapshot(layer->backing_store); break; case kFlutterLayerContentTypePlatformView: layer_image = diff --git a/shell/platform/embedder/tests/embedder_test_compositor_vulkan.h b/shell/platform/embedder/tests/embedder_test_compositor_vulkan.h index 608bda52ec858..46d54fe728fef 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor_vulkan.h +++ b/shell/platform/embedder/tests/embedder_test_compositor_vulkan.h @@ -19,6 +19,10 @@ class EmbedderTestCompositorVulkan : public EmbedderTestCompositor { ~EmbedderTestCompositorVulkan() override; + void SetRenderTargetType( + EmbedderTestBackingStoreProducer::RenderTargetType type, + FlutterSoftwarePixelFormat software_pixfmt) override; + private: bool UpdateOffscrenComposition(const FlutterLayer** layers, size_t layers_count) override; diff --git a/shell/platform/embedder/tests/embedder_test_context.cc b/shell/platform/embedder/tests/embedder_test_context.cc index 0f5e3dcf9afe5..0b6959cb022e7 100644 --- a/shell/platform/embedder/tests/embedder_test_context.cc +++ b/shell/platform/embedder/tests/embedder_test_context.cc @@ -96,6 +96,10 @@ void EmbedderTestContext::SetRootSurfaceTransformation(SkMatrix matrix) { root_surface_transformation_ = matrix; } +FlutterRendererConfig& EmbedderTestContext::GetRendererConfig() { + return renderer_config_; +} + void EmbedderTestContext::AddIsolateCreateCallback( const fml::closure& closure) { if (closure) { diff --git a/shell/platform/embedder/tests/embedder_test_context.h b/shell/platform/embedder/tests/embedder_test_context.h index b99d6b3534201..903c47d02a0e7 100644 --- a/shell/platform/embedder/tests/embedder_test_context.h +++ b/shell/platform/embedder/tests/embedder_test_context.h @@ -72,6 +72,8 @@ class EmbedderTestContext { void SetRootSurfaceTransformation(SkMatrix matrix); + FlutterRendererConfig& GetRendererConfig(); + void AddIsolateCreateCallback(const fml::closure& closure); void SetSemanticsUpdateCallback2(SemanticsUpdateCallback2 update_semantics); @@ -116,16 +118,6 @@ class EmbedderTestContext { using NextSceneCallback = std::function image)>; -#ifdef SHELL_ENABLE_VULKAN - // The TestVulkanContext destructor must be called _after_ the compositor is - // freed. - fml::RefPtr vulkan_context_ = nullptr; -#endif - -#ifdef SHELL_ENABLE_GL - std::shared_ptr egl_context_ = nullptr; -#endif - std::string assets_path_; ELFAOTSymbols aot_symbols_; std::unique_ptr vm_snapshot_data_; @@ -135,6 +127,7 @@ class EmbedderTestContext { UniqueAOTData aot_data_; std::vector isolate_create_callbacks_; std::shared_ptr native_resolver_; + FlutterRendererConfig renderer_config_ = {}; SemanticsUpdateCallback2 update_semantics_callback2_; SemanticsUpdateCallback update_semantics_callback_; SemanticsNodeCallback update_semantics_node_callback_; @@ -169,6 +162,8 @@ class EmbedderTestContext { void SetupAOTDataIfNecessary(); + virtual void SetSurface(SkISize surface_size) = 0; + virtual void SetupCompositor() = 0; void FireIsolateCreateCallbacks(); @@ -184,8 +179,6 @@ class EmbedderTestContext { void SetNextSceneCallback(const NextSceneCallback& next_scene_callback); - virtual void SetupSurface(SkISize surface_size) = 0; - FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestContext); }; diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.cc b/shell/platform/embedder/tests/embedder_test_context_gl.cc index ac916ead94f87..055c2f2ace802 100644 --- a/shell/platform/embedder/tests/embedder_test_context_gl.cc +++ b/shell/platform/embedder/tests/embedder_test_context_gl.cc @@ -4,6 +4,7 @@ #include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" +#include #include #include "flutter/fml/make_copyable.h" @@ -16,21 +17,80 @@ #include "third_party/dart/runtime/bin/elf_loader.h" #include "third_party/skia/include/core/SkSurface.h" -namespace flutter { -namespace testing { +namespace flutter::testing { EmbedderTestContextGL::EmbedderTestContextGL(std::string assets_path) - : EmbedderTestContext(std::move(assets_path)) { - egl_context_ = std::make_shared(); + : EmbedderTestContext(std::move(assets_path)), + egl_context_(std::make_shared()) { + renderer_config_.type = FlutterRendererType::kOpenGL; + renderer_config_.open_gl = { + .struct_size = sizeof(FlutterOpenGLRendererConfig), + .make_current = [](void* context) -> bool { + return reinterpret_cast(context) + ->GLMakeCurrent(); + }, + .clear_current = [](void* context) -> bool { + return reinterpret_cast(context) + ->GLClearCurrent(); + }, + .make_resource_current = [](void* context) -> bool { + return reinterpret_cast(context) + ->GLMakeResourceCurrent(); + }, + .fbo_reset_after_present = true, + .surface_transformation = [](void* context) -> FlutterTransformation { + return reinterpret_cast(context) + ->GetRootSurfaceTransformation(); + }, + .gl_proc_resolver = [](void* context, const char* name) -> void* { + return reinterpret_cast(context) + ->GLGetProcAddress(name); + }, + .fbo_with_frame_info_callback = + [](void* context, const FlutterFrameInfo* frame_info) -> uint32_t { + return reinterpret_cast(context) + ->GLGetFramebuffer(*frame_info); + }, + .present_with_info = [](void* context, + const FlutterPresentInfo* present_info) -> bool { + return reinterpret_cast(context)->GLPresent( + *present_info); + }, + .populate_existing_damage = nullptr, + }; } EmbedderTestContextGL::~EmbedderTestContextGL() { SetGLGetFBOCallback(nullptr); } -void EmbedderTestContextGL::SetupSurface(SkISize surface_size) { - FML_CHECK(!gl_surface_); - gl_surface_ = std::make_unique(egl_context_, surface_size); +void EmbedderTestContextGL::SetOpenGLFBOCallBack() { + // SetOpenGLRendererConfig must be called before this. + FML_CHECK(renderer_config_.type == FlutterRendererType::kOpenGL); + + renderer_config_.open_gl.fbo_callback = [](void* context) -> uint32_t { + FlutterFrameInfo frame_info = {}; + // fbo_callback doesn't use the frame size information, only + // fbo_callback_with_frame_info does. + frame_info.struct_size = sizeof(FlutterFrameInfo); + frame_info.size.width = 0; + frame_info.size.height = 0; + return reinterpret_cast(context)->GLGetFramebuffer( + frame_info); + }; +} + +void EmbedderTestContextGL::SetOpenGLPresentCallBack() { + // SetOpenGLRendererConfig must be called before this. + FML_CHECK(renderer_config_.type == FlutterRendererType::kOpenGL); + + renderer_config_.open_gl.present = [](void* context) -> bool { + // passing a placeholder fbo_id. + return reinterpret_cast(context)->GLPresent( + FlutterPresentInfo{ + .fbo_id = 0, + }); + }; } bool EmbedderTestContextGL::GLMakeCurrent() { @@ -63,9 +123,10 @@ bool EmbedderTestContextGL::GLPresent(FlutterPresentInfo present_info) { return gl_surface_->Present(); } -void EmbedderTestContextGL::SetGLGetFBOCallback(GLGetFBOCallback callback) { +void EmbedderTestContextGL::SetGLGetFBOCallback( + const GLGetFBOCallback& callback) { std::scoped_lock lock(gl_callback_mutex_); - gl_get_fbo_callback_ = std::move(callback); + gl_get_fbo_callback_ = callback; } void EmbedderTestContextGL::SetGLPopulateExistingDamageCallback( @@ -135,14 +196,18 @@ uint32_t EmbedderTestContextGL::GetWindowFBOId() const { return gl_surface_->GetWindowFBOId(); } +void EmbedderTestContextGL::SetSurface(SkISize surface_size) { + FML_CHECK(!gl_surface_); + gl_surface_ = std::make_unique(egl_context_, surface_size); +} + void EmbedderTestContextGL::SetupCompositor() { FML_CHECK(!compositor_) << "Already set up a compositor in this context."; FML_CHECK(gl_surface_) << "Set up the GL surface before setting up a compositor."; compositor_ = std::make_unique( - gl_surface_->GetSurfaceSize(), gl_surface_->GetGrContext()); + egl_context_, gl_surface_->GetSurfaceSize(), gl_surface_->GetGrContext()); GLClearCurrent(); } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.h b/shell/platform/embedder/tests/embedder_test_context_gl.h index a7c880803851c..c7f61ca7757e0 100644 --- a/shell/platform/embedder/tests/embedder_test_context_gl.h +++ b/shell/platform/embedder/tests/embedder_test_context_gl.h @@ -6,6 +6,8 @@ #define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_CONTEXT_GL_H_ #include "flutter/shell/platform/embedder/tests/embedder_test_context.h" + +#include "flutter/testing/test_gl_context.h" #include "flutter/testing/test_gl_surface.h" namespace flutter { @@ -28,6 +30,18 @@ class EmbedderTestContextGL : public EmbedderTestContext { // |EmbedderTestContext| EmbedderTestContextType GetContextType() const override; + // Used to explicitly set an `open_gl.fbo_callback`. Using this method will + // cause your test to fail since the ctor for this class sets + // `open_gl.fbo_callback_with_frame_info`. This method exists as a utility to + // explicitly test this behavior. + void SetOpenGLFBOCallBack(); + + // Used to explicitly set an `open_gl.present`. Using this method will cause + // your test to fail since the ctor for this class sets + // `open_gl.present_with_info`. This method exists as a utility to explicitly + // test this behavior. + void SetOpenGLPresentCallBack(); + //---------------------------------------------------------------------------- /// @brief Sets a callback that will be invoked (on the raster task /// runner) when the engine asks the embedder for a new FBO ID at @@ -39,7 +53,7 @@ class EmbedderTestContextGL : public EmbedderTestContext { /// @param[in] callback The callback to set. The previous callback will be /// un-registered. /// - void SetGLGetFBOCallback(GLGetFBOCallback callback); + void SetGLGetFBOCallback(const GLGetFBOCallback& callback); void SetGLPopulateExistingDamageCallback( GLPopulateExistingDamageCallback callback); @@ -64,15 +78,14 @@ class EmbedderTestContextGL : public EmbedderTestContext { void* GLGetProcAddress(const char* name); - protected: - virtual void SetupCompositor() override; - - void SetupCompositorUsingGLSurfaces(); - private: - // This allows the builder to access the hooks. - friend class EmbedderConfigBuilder; + // |EmbedderTestContext| + void SetSurface(SkISize surface_size) override; + + // |EmbedderTestContext| + void SetupCompositor() override; + std::shared_ptr egl_context_; std::unique_ptr gl_surface_; size_t gl_surface_present_count_ = 0; std::mutex gl_callback_mutex_; @@ -80,8 +93,6 @@ class EmbedderTestContextGL : public EmbedderTestContext { GLPresentCallback gl_present_callback_; GLPopulateExistingDamageCallback gl_populate_existing_damage_callback_; - void SetupSurface(SkISize surface_size) override; - bool GLMakeCurrent(); bool GLClearCurrent(); diff --git a/shell/platform/embedder/tests/embedder_test_context_metal.h b/shell/platform/embedder/tests/embedder_test_context_metal.h index eea599fe0c07c..b063db6973be2 100644 --- a/shell/platform/embedder/tests/embedder_test_context_metal.h +++ b/shell/platform/embedder/tests/embedder_test_context_metal.h @@ -35,9 +35,6 @@ class EmbedderTestContextMetal : public EmbedderTestContext { // |EmbedderTestContext| size_t GetSurfacePresentCount() const override; - // |EmbedderTestContext| - void SetupCompositor() override; - void SetExternalTextureCallback( TestExternalTextureCallback external_texture_frame_callback); @@ -61,8 +58,11 @@ class EmbedderTestContextMetal : public EmbedderTestContext { FlutterMetalTexture GetNextDrawable(const FlutterFrameInfo* frame_info); private: - // This allows the builder to access the hooks. - friend class EmbedderConfigBuilder; + // |EmbedderTestContext| + void SetSurface(SkISize surface_size) override; + + // |EmbedderTestContext| + void SetupCompositor() override; TestExternalTextureCallback external_texture_frame_callback_ = nullptr; SkISize surface_size_ = SkISize::MakeEmpty(); @@ -72,8 +72,6 @@ class EmbedderTestContextMetal : public EmbedderTestContext { PresentCallback present_callback_ = nullptr; NextDrawableCallback next_drawable_callback_ = nullptr; - void SetupSurface(SkISize surface_size) override; - FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestContextMetal); }; diff --git a/shell/platform/embedder/tests/embedder_test_context_metal.cc b/shell/platform/embedder/tests/embedder_test_context_metal.mm similarity index 55% rename from shell/platform/embedder/tests/embedder_test_context_metal.cc rename to shell/platform/embedder/tests/embedder_test_context_metal.mm index ec16446e07100..d50016d8ea383 100644 --- a/shell/platform/embedder/tests/embedder_test_context_metal.cc +++ b/shell/platform/embedder/tests/embedder_test_context_metal.mm @@ -11,36 +11,42 @@ #include "flutter/fml/logging.h" #include "flutter/shell/platform/embedder/tests/embedder_test_compositor_metal.h" -namespace flutter { -namespace testing { +namespace flutter::testing { EmbedderTestContextMetal::EmbedderTestContextMetal(std::string assets_path) : EmbedderTestContext(std::move(assets_path)) { metal_context_ = std::make_unique(); + renderer_config_.type = FlutterRendererType::kMetal; + renderer_config_.metal = { + .struct_size = sizeof(FlutterMetalRendererConfig), + .device = (__bridge FlutterMetalDeviceHandle)metal_context_->GetMetalDevice(), + .present_command_queue = + (__bridge FlutterMetalCommandQueueHandle)metal_context_->GetMetalCommandQueue(), + .get_next_drawable_callback = + [](void* user_data, const FlutterFrameInfo* frame_info) { + return reinterpret_cast(user_data)->GetNextDrawable( + frame_info); + }, + .present_drawable_callback = [](void* user_data, const FlutterMetalTexture* texture) -> bool { + return reinterpret_cast(user_data)->Present(texture->texture_id); + }, + .external_texture_frame_callback = [](void* user_data, int64_t texture_id, size_t width, + size_t height, + FlutterMetalExternalTexture* texture_out) -> bool { + return reinterpret_cast(user_data)->PopulateExternalTexture( + texture_id, width, height, texture_out); + }, + }; } EmbedderTestContextMetal::~EmbedderTestContextMetal() {} -void EmbedderTestContextMetal::SetupSurface(SkISize surface_size) { - FML_CHECK(surface_size_.isEmpty()); - surface_size_ = surface_size; - metal_surface_ = TestMetalSurface::Create(*metal_context_, surface_size_); -} - -size_t EmbedderTestContextMetal::GetSurfacePresentCount() const { - return present_count_; -} - EmbedderTestContextType EmbedderTestContextMetal::GetContextType() const { return EmbedderTestContextType::kMetalContext; } -void EmbedderTestContextMetal::SetupCompositor() { - FML_CHECK(!compositor_) << "Already set up a compositor in this context."; - FML_CHECK(metal_surface_) - << "Set up the Metal surface before setting up a compositor."; - compositor_ = std::make_unique( - surface_size_, metal_surface_->GetGrContext()); +size_t EmbedderTestContextMetal::GetSurfacePresentCount() const { + return present_count_; } TestMetalContext* EmbedderTestContextMetal::GetTestMetalContext() { @@ -51,8 +57,7 @@ TestMetalSurface* EmbedderTestContextMetal::GetTestMetalSurface() { return metal_surface_.get(); } -void EmbedderTestContextMetal::SetPresentCallback( - PresentCallback present_callback) { +void EmbedderTestContextMetal::SetPresentCallback(PresentCallback present_callback) { present_callback_ = std::move(present_callback); } @@ -71,11 +76,10 @@ void EmbedderTestContextMetal::SetExternalTextureCallback( external_texture_frame_callback_ = std::move(external_texture_frame_callback); } -bool EmbedderTestContextMetal::PopulateExternalTexture( - int64_t texture_id, - size_t w, - size_t h, - FlutterMetalExternalTexture* output) { +bool EmbedderTestContextMetal::PopulateExternalTexture(int64_t texture_id, + size_t w, + size_t h, + FlutterMetalExternalTexture* output) { if (external_texture_frame_callback_ != nullptr) { return external_texture_frame_callback_(texture_id, w, h, output); } else { @@ -88,8 +92,7 @@ void EmbedderTestContextMetal::SetNextDrawableCallback( next_drawable_callback_ = std::move(next_drawable_callback); } -FlutterMetalTexture EmbedderTestContextMetal::GetNextDrawable( - const FlutterFrameInfo* frame_info) { +FlutterMetalTexture EmbedderTestContextMetal::GetNextDrawable(const FlutterFrameInfo* frame_info) { if (next_drawable_callback_ != nullptr) { return next_drawable_callback_(frame_info); } @@ -98,10 +101,21 @@ FlutterMetalTexture EmbedderTestContextMetal::GetNextDrawable( FlutterMetalTexture texture = {}; texture.struct_size = sizeof(FlutterMetalTexture); texture.texture_id = texture_info.texture_id; - texture.texture = - reinterpret_cast(texture_info.texture); + texture.texture = reinterpret_cast(texture_info.texture); return texture; } -} // namespace testing -} // namespace flutter +void EmbedderTestContextMetal::SetSurface(SkISize surface_size) { + FML_CHECK(surface_size_.isEmpty()); + surface_size_ = surface_size; + metal_surface_ = TestMetalSurface::Create(*metal_context_, surface_size_); +} + +void EmbedderTestContextMetal::SetupCompositor() { + FML_CHECK(!compositor_) << "Already set up a compositor in this context."; + FML_CHECK(metal_surface_) << "Set up the Metal surface before setting up a compositor."; + compositor_ = + std::make_unique(surface_size_, metal_surface_->GetGrContext()); +} + +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_test_context_software.cc b/shell/platform/embedder/tests/embedder_test_context_software.cc index cddeed7656b25..1f06890abb725 100644 --- a/shell/platform/embedder/tests/embedder_test_context_software.cc +++ b/shell/platform/embedder/tests/embedder_test_context_software.cc @@ -13,34 +13,43 @@ #include "flutter/shell/platform/embedder/tests/embedder_test_compositor_software.h" #include "flutter/testing/testing.h" #include "third_party/dart/runtime/bin/elf_loader.h" +#include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkSurface.h" -namespace flutter { -namespace testing { +namespace flutter::testing { EmbedderTestContextSoftware::EmbedderTestContextSoftware( std::string assets_path) - : EmbedderTestContext(std::move(assets_path)) {} - -EmbedderTestContextSoftware::~EmbedderTestContextSoftware() = default; - -bool EmbedderTestContextSoftware::Present(const sk_sp& image) { - software_surface_present_count_++; - - FireRootSurfacePresentCallbackIfPresent([image] { return image; }); - - return true; + : EmbedderTestContext(std::move(assets_path)) { + renderer_config_.type = FlutterRendererType::kSoftware; + renderer_config_.software = { + .struct_size = sizeof(FlutterSoftwareRendererConfig), + .surface_present_callback = + [](void* context, const void* allocation, size_t row_bytes, + size_t height) { + auto image_info = SkImageInfo::MakeN32Premul( + SkISize::Make(row_bytes / 4, height)); + SkBitmap bitmap; + if (!bitmap.installPixels(image_info, const_cast(allocation), + row_bytes)) { + FML_LOG(ERROR) << "Could not copy pixels for the software " + "composition from the engine."; + return false; + } + bitmap.setImmutable(); + return reinterpret_cast(context) + ->Present(SkImages::RasterFromBitmap(bitmap)); + }, + }; } -size_t EmbedderTestContextSoftware::GetSurfacePresentCount() const { - return software_surface_present_count_; -} +EmbedderTestContextSoftware::~EmbedderTestContextSoftware() = default; EmbedderTestContextType EmbedderTestContextSoftware::GetContextType() const { return EmbedderTestContextType::kSoftwareContext; } -void EmbedderTestContextSoftware::SetupSurface(SkISize surface_size) { +void EmbedderTestContextSoftware::SetSurface(SkISize surface_size) { surface_size_ = surface_size; } @@ -49,5 +58,14 @@ void EmbedderTestContextSoftware::SetupCompositor() { compositor_ = std::make_unique(surface_size_); } -} // namespace testing -} // namespace flutter +size_t EmbedderTestContextSoftware::GetSurfacePresentCount() const { + return software_surface_present_count_; +} + +bool EmbedderTestContextSoftware::Present(const sk_sp& image) { + software_surface_present_count_++; + FireRootSurfacePresentCallbackIfPresent([image] { return image; }); + return true; +} + +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_test_context_software.h b/shell/platform/embedder/tests/embedder_test_context_software.h index 25c0ce52c92e5..05396b160352b 100644 --- a/shell/platform/embedder/tests/embedder_test_context_software.h +++ b/shell/platform/embedder/tests/embedder_test_context_software.h @@ -18,21 +18,24 @@ class EmbedderTestContextSoftware : public EmbedderTestContext { ~EmbedderTestContextSoftware() override; - size_t GetSurfacePresentCount() const override; - // |EmbedderTestContext| EmbedderTestContextType GetContextType() const override; - bool Present(const sk_sp& image); + // |EmbedderTestContext| + size_t GetSurfacePresentCount() const override; - protected: - virtual void SetupCompositor() override; + bool Present(const sk_sp& image); private: + // |EmbedderTestContext| + void SetSurface(SkISize surface_size) override; + + // |EmbedderTestContext| + void SetupCompositor() override; + sk_sp surface_; SkISize surface_size_; size_t software_surface_present_count_ = 0; - void SetupSurface(SkISize surface_size) override; FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestContextSoftware); }; diff --git a/shell/platform/embedder/tests/embedder_test_context_vulkan.cc b/shell/platform/embedder/tests/embedder_test_context_vulkan.cc index 4f3678597dda1..09bb264a38616 100644 --- a/shell/platform/embedder/tests/embedder_test_context_vulkan.cc +++ b/shell/platform/embedder/tests/embedder_test_context_vulkan.cc @@ -15,20 +15,52 @@ #include "flutter/vulkan/vulkan_device.h" #include "third_party/skia/include/core/SkSurface.h" -namespace flutter { -namespace testing { +namespace flutter::testing { EmbedderTestContextVulkan::EmbedderTestContextVulkan(std::string assets_path) : EmbedderTestContext(std::move(assets_path)), surface_() { vulkan_context_ = fml::MakeRefCounted(); + renderer_config_.type = FlutterRendererType::kVulkan; + renderer_config_.vulkan = { + .struct_size = sizeof(FlutterVulkanRendererConfig), + .version = vulkan_context_->application_->GetAPIVersion(), + .instance = vulkan_context_->application_->GetInstance(), + .physical_device = vulkan_context_->device_->GetPhysicalDeviceHandle(), + .device = vulkan_context_->device_->GetHandle(), + .queue_family_index = vulkan_context_->device_->GetGraphicsQueueIndex(), + .queue = vulkan_context_->device_->GetQueueHandle(), + .get_instance_proc_address_callback = + EmbedderTestContextVulkan::InstanceProcAddr, + .get_next_image_callback = + [](void* context, + const FlutterFrameInfo* frame_info) -> FlutterVulkanImage { + VkImage image = + reinterpret_cast(context)->GetNextImage( + {static_cast(frame_info->size.width), + static_cast(frame_info->size.height)}); + return { + .struct_size = sizeof(FlutterVulkanImage), + .image = reinterpret_cast(image), + .format = VK_FORMAT_R8G8B8A8_UNORM, + }; + }, + .present_image_callback = [](void* context, + const FlutterVulkanImage* image) -> bool { + return reinterpret_cast(context) + ->PresentImage(reinterpret_cast(image->image)); + }, + }; } EmbedderTestContextVulkan::~EmbedderTestContextVulkan() {} -void EmbedderTestContextVulkan::SetupSurface(SkISize surface_size) { - FML_CHECK(surface_size_.isEmpty()); - surface_size_ = surface_size; - surface_ = TestVulkanSurface::Create(*vulkan_context_, surface_size_); +EmbedderTestContextType EmbedderTestContextVulkan::GetContextType() const { + return EmbedderTestContextType::kVulkanContext; +} + +void EmbedderTestContextVulkan::SetVulkanInstanceProcAddressCallback( + FlutterVulkanInstanceProcAddressCallback callback) { + renderer_config_.vulkan.get_instance_proc_address_callback = callback; } size_t EmbedderTestContextVulkan::GetSurfacePresentCount() const { @@ -46,18 +78,6 @@ bool EmbedderTestContextVulkan::PresentImage(VkImage image) { return true; } -EmbedderTestContextType EmbedderTestContextVulkan::GetContextType() const { - return EmbedderTestContextType::kVulkanContext; -} - -void EmbedderTestContextVulkan::SetupCompositor() { - FML_CHECK(!compositor_) << "Already set up a compositor in this context."; - FML_CHECK(surface_) - << "Set up the Vulkan surface before setting up a compositor."; - compositor_ = std::make_unique( - surface_size_, vulkan_context_->GetGrDirectContext()); -} - void* EmbedderTestContextVulkan::InstanceProcAddr( void* user_data, FlutterVulkanInstanceHandle instance, @@ -68,5 +88,18 @@ void* EmbedderTestContextVulkan::InstanceProcAddr( return reinterpret_cast(proc_addr); } -} // namespace testing -} // namespace flutter +void EmbedderTestContextVulkan::SetSurface(SkISize surface_size) { + FML_CHECK(surface_size_.isEmpty()); + surface_size_ = surface_size; + surface_ = TestVulkanSurface::Create(*vulkan_context_, surface_size_); +} + +void EmbedderTestContextVulkan::SetupCompositor() { + FML_CHECK(!compositor_) << "Already set up a compositor in this context."; + FML_CHECK(surface_) + << "Set up the Vulkan surface before setting up a compositor."; + compositor_ = std::make_unique( + surface_size_, vulkan_context_->GetGrDirectContext()); +} + +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_test_context_vulkan.h b/shell/platform/embedder/tests/embedder_test_context_vulkan.h index e7eb6906d9d16..2fde79996207d 100644 --- a/shell/platform/embedder/tests/embedder_test_context_vulkan.h +++ b/shell/platform/embedder/tests/embedder_test_context_vulkan.h @@ -26,27 +26,33 @@ class EmbedderTestContextVulkan : public EmbedderTestContext { // |EmbedderTestContext| size_t GetSurfacePresentCount() const override; - // |EmbedderTestContext| - void SetupCompositor() override; - VkImage GetNextImage(const SkISize& size); bool PresentImage(VkImage image); + void SetVulkanInstanceProcAddressCallback( + FlutterVulkanInstanceProcAddressCallback callback); + static void* InstanceProcAddr(void* user_data, FlutterVulkanInstanceHandle instance, const char* name); private: + // |EmbedderTestContext| + void SetSurface(SkISize surface_size) override; + + // |EmbedderTestContext| + void SetupCompositor() override; + + // The TestVulkanContext destructor must be called _after_ the compositor is + // freed. + fml::RefPtr vulkan_context_ = nullptr; + std::unique_ptr surface_; SkISize surface_size_ = SkISize::MakeEmpty(); size_t present_count_ = 0; - void SetupSurface(SkISize surface_size) override; - - friend class EmbedderConfigBuilder; - FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestContextVulkan); }; diff --git a/shell/platform/embedder/tests/embedder_test_gl.cc b/shell/platform/embedder/tests/embedder_test_gl.cc new file mode 100644 index 0000000000000..0136947962bb5 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_gl.cc @@ -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. + +#include "flutter/shell/platform/embedder/tests/embedder_test.h" + +#include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" + +namespace flutter::testing { + +EmbedderTestContext& EmbedderTest::GetGLContext() { + if (!gl_context_) { + gl_context_ = + std::make_unique(GetFixturesDirectory()); + } + return *gl_context_.get(); +} + +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_test_metal.mm b/shell/platform/embedder/tests/embedder_test_metal.mm new file mode 100644 index 0000000000000..86edf931e1cb3 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_metal.mm @@ -0,0 +1,18 @@ +// 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. + +#include "flutter/shell/platform/embedder/tests/embedder_test.h" + +#include "flutter/shell/platform/embedder/tests/embedder_test_context_metal.h" + +namespace flutter::testing { + +EmbedderTestContext& EmbedderTest::GetMetalContext() { + if (!metal_context_) { + metal_context_ = std::make_unique(GetFixturesDirectory()); + } + return *metal_context_.get(); +} + +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_test_vulkan.cc b/shell/platform/embedder/tests/embedder_test_vulkan.cc new file mode 100644 index 0000000000000..63b69d2eb2cc7 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_vulkan.cc @@ -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. + +#include "flutter/shell/platform/embedder/tests/embedder_test.h" + +#include "flutter/shell/platform/embedder/tests/embedder_test_context_vulkan.h" + +namespace flutter::testing { + +EmbedderTestContext& EmbedderTest::GetVulkanContext() { + if (!vulkan_context_) { + vulkan_context_ = + std::make_unique(GetFixturesDirectory()); + } + return *vulkan_context_.get(); +} + +} // namespace flutter::testing diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 22cdb54cdcf95..529918c283dcd 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -27,6 +27,7 @@ #include "flutter/shell/platform/embedder/tests/embedder_assertions.h" #include "flutter/shell/platform/embedder/tests/embedder_config_builder.h" #include "flutter/shell/platform/embedder/tests/embedder_test.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer_software.h" #include "flutter/shell/platform/embedder/tests/embedder_unittests_util.h" #include "flutter/testing/assertions_skia.h" #include "flutter/testing/testing.h" @@ -64,11 +65,11 @@ TEST(EmbedderTestNoFixture, MustNotRunWithInvalidArgs) { } TEST_F(EmbedderTest, CanLaunchAndShutdownWithValidProjectArgs) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch; context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); // Wait for the root isolate to launch. @@ -78,9 +79,9 @@ TEST_F(EmbedderTest, CanLaunchAndShutdownWithValidProjectArgs) { // TODO(41999): Disabled because flaky. TEST_F(EmbedderTest, DISABLED_CanLaunchAndShutdownMultipleTimes) { - EmbedderConfigBuilder builder( - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); - builder.SetSoftwareRendererConfig(); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); for (size_t i = 0; i < 3; ++i) { auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -89,14 +90,14 @@ TEST_F(EmbedderTest, DISABLED_CanLaunchAndShutdownMultipleTimes) { } TEST_F(EmbedderTest, CanInvokeCustomEntrypoint) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); static fml::AutoResetWaitableEvent latch; Dart_NativeFunction entrypoint = [](Dart_NativeArguments args) { latch.Signal(); }; context.AddNativeCallback("SayHiFromCustomEntrypoint", entrypoint); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("customEntrypoint"); auto engine = builder.LaunchEngine(); latch.Wait(); @@ -104,7 +105,7 @@ TEST_F(EmbedderTest, CanInvokeCustomEntrypoint) { } TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch1; fml::AutoResetWaitableEvent latch2; @@ -135,7 +136,7 @@ TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { })); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("customEntrypoint1"); auto engine = builder.LaunchEngine(); latch1.Wait(); @@ -145,16 +146,16 @@ TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { } TEST_F(EmbedderTest, CanTerminateCleanly) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("terminateExitCodeHandler"); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); } TEST_F(EmbedderTest, ExecutableNameNotNull) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); // Supply a callback to Dart for the test fixture to pass Platform.executable // back to us. @@ -168,7 +169,7 @@ TEST_F(EmbedderTest, ExecutableNameNotNull) { })); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("executableNameNotNull"); builder.SetExecutableName("/path/to/binary"); auto engine = builder.LaunchEngine(); @@ -179,7 +180,7 @@ TEST_F(EmbedderTest, ImplicitViewNotNull) { // TODO(loicsharma): Update this test when embedders can opt-out // of the implicit view. // See: https://github.com/flutter/flutter/issues/120306 - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); bool implicitViewNotNull = false; fml::AutoResetWaitableEvent latch; @@ -191,7 +192,7 @@ TEST_F(EmbedderTest, ImplicitViewNotNull) { })); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("implicitViewNotNull"); auto engine = builder.LaunchEngine(); latch.Wait(); @@ -202,7 +203,7 @@ TEST_F(EmbedderTest, ImplicitViewNotNull) { std::atomic_size_t EmbedderTestTaskRunner::sEmbedderTaskRunnerIdentifiers = {}; TEST_F(EmbedderTest, CanSpecifyCustomPlatformTaskRunner) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch; // Run the test on its own thread with a message loop so that it can safely @@ -210,6 +211,7 @@ TEST_F(EmbedderTest, CanSpecifyCustomPlatformTaskRunner) { auto platform_task_runner = CreateNewThread("test_platform_thread"); static std::mutex engine_mutex; static bool signaled_once = false; + static std::atomic destruction_callback_called = false; UniqueEngine engine; EmbedderTestTaskRunner test_task_runner( @@ -230,12 +232,14 @@ TEST_F(EmbedderTest, CanSpecifyCustomPlatformTaskRunner) { ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); latch.Signal(); }); + test_task_runner.SetDestructionCallback( + [](void* user_data) { destruction_callback_called = true; }); platform_task_runner->PostTask([&]() { EmbedderConfigBuilder builder(context); const auto task_runner_description = test_task_runner.GetFlutterTaskRunnerDescription(); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetPlatformTaskRunner(&task_runner_description); builder.SetDartEntrypoint("invokePlatformTaskRunner"); std::scoped_lock lock(engine_mutex); @@ -263,6 +267,9 @@ TEST_F(EmbedderTest, CanSpecifyCustomPlatformTaskRunner) { ASSERT_TRUE(signaled_once); signaled_once = false; + + ASSERT_TRUE(destruction_callback_called); + destruction_callback_called = false; } TEST(EmbedderTestNoFixture, CanGetCurrentTimeInNanoseconds) { @@ -274,9 +281,9 @@ TEST(EmbedderTestNoFixture, CanGetCurrentTimeInNanoseconds) { } TEST_F(EmbedderTest, CanReloadSystemFonts) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -285,7 +292,7 @@ TEST_F(EmbedderTest, CanReloadSystemFonts) { } TEST_F(EmbedderTest, IsolateServiceIdSent) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch; fml::Thread thread; @@ -294,7 +301,7 @@ TEST_F(EmbedderTest, IsolateServiceIdSent) { thread.GetTaskRunner()->PostTask([&]() { EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("main"); builder.SetPlatformMessageCallback( [&](const FlutterPlatformMessage* message) { @@ -328,9 +335,9 @@ TEST_F(EmbedderTest, IsolateServiceIdSent) { /// immediately collects the same. /// TEST_F(EmbedderTest, CanCreateAndCollectCallbacks) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("platform_messages_response"); context.AddNativeCallback( "SignalNativeTest", @@ -366,10 +373,9 @@ TEST_F(EmbedderTest, PlatformMessagesCanReceiveResponse) { CreateNewThread()->PostTask([&]() { captures.thread_id = std::this_thread::get_id(); - auto& context = - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("platform_messages_response"); fml::AutoResetWaitableEvent ready; @@ -423,9 +429,9 @@ TEST_F(EmbedderTest, PlatformMessagesCanReceiveResponse) { /// callback with the response is invoked to assert integrity. /// TEST_F(EmbedderTest, PlatformMessagesCanBeSentWithoutResponseHandles) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("platform_messages_no_response"); const std::string message_data = "Hello but don't call me back."; @@ -468,9 +474,9 @@ TEST_F(EmbedderTest, PlatformMessagesCanBeSentWithoutResponseHandles) { /// Tests that a null platform message can be sent. /// TEST_F(EmbedderTest, NullPlatformMessagesCanBeSent) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("null_platform_messages"); fml::AutoResetWaitableEvent ready, message; @@ -510,9 +516,9 @@ TEST_F(EmbedderTest, NullPlatformMessagesCanBeSent) { /// isn't equals to 0. /// TEST_F(EmbedderTest, InvalidPlatformMessages) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -534,10 +540,10 @@ TEST_F(EmbedderTest, InvalidPlatformMessages) { /// using tag "flutter". TEST_F(EmbedderTest, CanSetCustomLogMessageCallback) { fml::AutoResetWaitableEvent callback_latch; - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("custom_logger"); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); context.SetLogMessageCallback( [&callback_latch](const char* tag, const char* message) { EXPECT_EQ(std::string(tag), "flutter"); @@ -553,10 +559,10 @@ TEST_F(EmbedderTest, CanSetCustomLogMessageCallback) { /// Tests that setting a custom log tag works. TEST_F(EmbedderTest, CanSetCustomLogTag) { fml::AutoResetWaitableEvent callback_latch; - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("custom_logger"); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetLogTag("butterfly"); context.SetLogMessageCallback( [&callback_latch](const char* tag, const char* message) { @@ -574,9 +580,9 @@ TEST_F(EmbedderTest, CanSetCustomLogTag) { /// set to true by default in these unit-tests). /// TEST_F(EmbedderTest, VMShutsDownWhenNoEnginesInProcess) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); const auto launch_count = DartVM::GetVMLaunchCount(); { @@ -593,9 +599,9 @@ TEST_F(EmbedderTest, VMShutsDownWhenNoEnginesInProcess) { //------------------------------------------------------------------------------ /// TEST_F(EmbedderTest, DartEntrypointArgs) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.AddDartEntrypointArgument("foo"); builder.AddDartEntrypointArgument("bar"); builder.SetDartEntrypoint("dart_entrypoint_args"); @@ -627,9 +633,9 @@ TEST_F(EmbedderTest, VMAndIsolateSnapshotSizesAreRedundantInAOTMode) { GTEST_SKIP(); return; } - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); // The fixture sets this up correctly. Intentionally mess up the args. builder.GetProjectArgs().vm_snapshot_data_size = 0; @@ -642,10 +648,10 @@ TEST_F(EmbedderTest, VMAndIsolateSnapshotSizesAreRedundantInAOTMode) { } TEST_F(EmbedderTest, CanRenderImplicitView) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("render_implicit_view"); builder.SetRenderTargetType( @@ -674,10 +680,10 @@ TEST_F(EmbedderTest, CanRenderImplicitView) { } TEST_F(EmbedderTest, CanRenderImplicitViewUsingPresentLayersCallback) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(/* avoid_backing_store_cache = */ false, /* use_present_layers_callback = */ true); builder.SetDartEntrypoint("render_implicit_view"); @@ -719,10 +725,10 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownSceneWithSoftwareCompositor) { #endif // FML_OS_MACOSX && FML_ARCH_CPU_ARM64 - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); @@ -938,10 +944,10 @@ TEST_F(EmbedderTest, /// compositor, with a transparent overlay /// TEST_F(EmbedderTest, NoLayerCreatedForTransparentOverlayOnTopOfPlatformLayer) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views_transparent_overlay"); @@ -1075,10 +1081,10 @@ TEST_F(EmbedderTest, NoLayerCreatedForTransparentOverlayOnTopOfPlatformLayer) { /// compositor, with a no overlay /// TEST_F(EmbedderTest, NoLayerCreatedForNoOverlayOnTopOfPlatformLayer) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_composite_platform_views_no_overlay"); @@ -1211,9 +1217,9 @@ TEST_F(EmbedderTest, NoLayerCreatedForNoOverlayOnTopOfPlatformLayer) { /// Test that an engine can be initialized but not run. /// TEST_F(EmbedderTest, CanCreateInitializedEngine) { - EmbedderConfigBuilder builder( - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); - builder.SetSoftwareRendererConfig(); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.InitializeEngine(); ASSERT_TRUE(engine.is_valid()); engine.reset(); @@ -1223,9 +1229,9 @@ TEST_F(EmbedderTest, CanCreateInitializedEngine) { /// Test that an initialized engine can be run exactly once. /// TEST_F(EmbedderTest, CanRunInitializedEngine) { - EmbedderConfigBuilder builder( - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); - builder.SetSoftwareRendererConfig(); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.InitializeEngine(); ASSERT_TRUE(engine.is_valid()); ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); @@ -1238,9 +1244,9 @@ TEST_F(EmbedderTest, CanRunInitializedEngine) { /// Test that an engine can be deinitialized. /// TEST_F(EmbedderTest, CanDeinitializeAnEngine) { - EmbedderConfigBuilder builder( - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); - builder.SetSoftwareRendererConfig(); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.InitializeEngine(); ASSERT_TRUE(engine.is_valid()); ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); @@ -1269,9 +1275,9 @@ TEST_F(EmbedderTest, CanDeinitializeAnEngine) { /// Test that a view can be added to a running engine. /// TEST_F(EmbedderTest, CanAddView) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("window_metrics_event_all_view_ids"); fml::AutoResetWaitableEvent ready_latch, message_latch; @@ -1317,9 +1323,9 @@ TEST_F(EmbedderTest, CanAddView) { /// Test that adding a view schedules a frame. /// TEST_F(EmbedderTest, AddViewSchedulesFrame) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("add_view_schedules_frame"); fml::AutoResetWaitableEvent latch; context.AddNativeCallback( @@ -1362,9 +1368,9 @@ TEST_F(EmbedderTest, AddViewSchedulesFrame) { /// Test that a view that was added can be removed. /// TEST_F(EmbedderTest, CanRemoveView) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("window_metrics_event_all_view_ids"); fml::AutoResetWaitableEvent ready_latch, message_latch; @@ -1423,9 +1429,9 @@ TEST_F(EmbedderTest, CanRemoveView) { /// can *always* be rendered to. Test that this view cannot be removed. /// TEST_F(EmbedderTest, CannotRemoveImplicitView) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -1443,9 +1449,9 @@ TEST_F(EmbedderTest, CannotRemoveImplicitView) { /// Test that a view cannot be added if its ID already exists. /// TEST_F(EmbedderTest, CannotAddDuplicateViews) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("window_metrics_event_all_view_ids"); fml::AutoResetWaitableEvent ready_latch, message_latch; @@ -1515,9 +1521,9 @@ TEST_F(EmbedderTest, CannotAddDuplicateViews) { /// Test that a removed view's ID can be reused to add a new view. /// TEST_F(EmbedderTest, CanReuseViewIds) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("window_metrics_event_all_view_ids"); fml::AutoResetWaitableEvent ready_latch, message_latch; @@ -1580,9 +1586,9 @@ TEST_F(EmbedderTest, CanReuseViewIds) { /// Test that attempting to remove a view that does not exist fails as expected. /// TEST_F(EmbedderTest, CannotRemoveUnknownView) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -1606,9 +1612,9 @@ TEST_F(EmbedderTest, CannotRemoveUnknownView) { /// embedder's and engine's states remain synchronized. /// TEST_F(EmbedderTest, ViewOperationsOrdered) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("window_metrics_event_all_view_ids"); fml::AutoResetWaitableEvent ready_latch; @@ -1753,9 +1759,9 @@ TEST_F(EmbedderTest, ViewOperationsOrdered) { /// Test the engine can present to multiple views. /// TEST_F(EmbedderTest, CanRenderMultipleViews) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetCompositor(); builder.SetDartEntrypoint("render_all_views"); @@ -1832,14 +1838,14 @@ TEST_F(EmbedderTest, CanRenderMultipleViews) { TEST_F(EmbedderTest, BackingStoresCorrespondToTheirViews) { constexpr FlutterViewId kSecondViewId = 123; constexpr FlutterViewId kThirdViewId = 456; - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("render_all_views"); - builder.SetSoftwareRendererConfig(SkISize::Make(800, 600)); + builder.SetSurface(SkISize::Make(800, 600)); builder.SetCompositor(); - EmbedderTestBackingStoreProducer producer( + EmbedderTestBackingStoreProducerSoftware producer( context.GetCompositor().GetGrContext(), EmbedderTestBackingStoreProducer::RenderTargetType::kSoftwareBuffer); @@ -1986,9 +1992,9 @@ TEST_F(EmbedderTest, BackingStoresCorrespondToTheirViews) { } TEST_F(EmbedderTest, CanUpdateLocales) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("can_receive_locale_updates"); fml::AutoResetWaitableEvent latch; context.AddNativeCallback( @@ -2045,11 +2051,11 @@ TEST_F(EmbedderTest, CanUpdateLocales) { } TEST_F(EmbedderTest, LocalizationCallbacksCalled) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch; context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); // Wait for the root isolate to launch. @@ -2077,10 +2083,10 @@ TEST_F(EmbedderTest, CanQueryDartAOTMode) { } TEST_F(EmbedderTest, VerifyB143464703WithSoftwareBackend) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(SkISize::Make(1024, 600)); + builder.SetSurface(SkISize::Make(1024, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("verify_b143464703"); @@ -2201,10 +2207,10 @@ TEST_F(EmbedderTest, VerifyB143464703WithSoftwareBackend) { } TEST_F(EmbedderTest, CanSendLowMemoryNotification) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); @@ -2230,11 +2236,9 @@ TEST_F(EmbedderTest, CanPostTaskToAllNativeThreads) { auto platform_task_runner = CreateNewThread("platform_thread"); platform_task_runner->PostTask([&]() { - auto& context = - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); - + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); engine = builder.LaunchEngine(); @@ -2372,13 +2376,13 @@ TEST_F(EmbedderTest, MustNotRunWithMultipleAOTSources) { GTEST_SKIP(); return; } - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder( context, EmbedderConfigBuilder::InitializationPreference::kMultiAOTInitialize); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_FALSE(engine.is_valid()); @@ -2414,7 +2418,7 @@ TEST_F(EmbedderTest, CanLaunchAndShutdownWithAValidElfSource) { GTEST_SKIP(); return; } - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch; context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); @@ -2423,7 +2427,7 @@ TEST_F(EmbedderTest, CanLaunchAndShutdownWithAValidElfSource) { context, EmbedderConfigBuilder::InitializationPreference::kAOTDataInitialize); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -2457,9 +2461,9 @@ TEST_F(EmbedderTest, CanSuccessfullyPopulateSpecificJITSnapshotCallbacks) { return; } - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); // Construct the location of valid JIT snapshots. const std::string src_path = GetSourcePath(); @@ -2513,9 +2517,9 @@ TEST_F(EmbedderTest, JITSnapshotCallbacksFailWithInvalidLocation) { return; } - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); // Explicitly define the locations of the invalid JIT snapshots builder.GetProjectArgs().vm_snapshot_data = @@ -2550,9 +2554,9 @@ TEST_F(EmbedderTest, CanLaunchEngineWithSpecifiedJITSnapshots) { return; } - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); // Construct the location of valid JIT snapshots. const std::string src_path = GetSourcePath(); @@ -2590,9 +2594,9 @@ TEST_F(EmbedderTest, CanLaunchEngineWithSomeSpecifiedJITSnapshots) { return; } - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); // Construct the location of valid JIT snapshots. const std::string src_path = GetSourcePath(); @@ -2623,9 +2627,9 @@ TEST_F(EmbedderTest, CanLaunchEngineWithInvalidJITSnapshots) { return; } - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); // Explicitly define the locations of the JIT snapshots builder.GetProjectArgs().isolate_snapshot_data = @@ -2649,9 +2653,9 @@ TEST_F(EmbedderTest, CanLaunchEngineWithUnspecifiedJITSnapshots) { return; } - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); ASSERT_EQ(builder.GetProjectArgs().vm_snapshot_data, nullptr); ASSERT_EQ(builder.GetProjectArgs().vm_snapshot_instructions, nullptr); @@ -2663,9 +2667,9 @@ TEST_F(EmbedderTest, CanLaunchEngineWithUnspecifiedJITSnapshots) { } TEST_F(EmbedderTest, InvalidFlutterWindowMetricsEvent) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -2710,14 +2714,13 @@ static void expectSoftwareRenderingOutputMatches( std::string entrypoint, FlutterSoftwarePixelFormat pixfmt, const std::vector& bytes) { - auto& context = - test.GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = test.GetEmbedderContext(); EmbedderConfigBuilder builder(context); fml::AutoResetWaitableEvent latch; bool matches = false; - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetCompositor(); builder.SetDartEntrypoint(std::move(entrypoint)); builder.SetRenderTargetType( @@ -2728,17 +2731,15 @@ static void expectSoftwareRenderingOutputMatches( ASSERT_TRUE(engine.is_valid()); context.GetCompositor().SetNextPresentCallback( - [&matches, &bytes, &latch](FlutterViewId view_id, - const FlutterLayer** layers, - size_t layers_count) { + [&context, &matches, &bytes, &latch](FlutterViewId view_id, + const FlutterLayer** layers, + size_t layers_count) { ASSERT_EQ(layers[0]->type, kFlutterLayerContentTypeBackingStore); ASSERT_EQ(layers[0]->backing_store->type, kFlutterBackingStoreTypeSoftware2); - matches = SurfacePixelDataMatchesBytes( - reinterpret_cast( - layers[0]->backing_store->software2.user_data) - ->surface.get(), - bytes); + sk_sp surface = + context.GetCompositor().GetSurface(layers[0]->backing_store); + matches = SurfacePixelDataMatchesBytes(surface.get(), bytes); latch.Signal(); }); @@ -2935,10 +2936,9 @@ TEST_F(EmbedderTest, KeyDataIsCorrectlySerialized) { UniqueEngine engine; fml::AutoResetWaitableEvent ready; platform_task_runner->PostTask([&]() { - auto& context = - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("key_data_echo"); builder.SetPlatformMessageCallback( [&](const FlutterPlatformMessage* message) { @@ -3057,10 +3057,9 @@ TEST_F(EmbedderTest, KeyDataAreBuffered) { UniqueEngine engine; fml::AutoResetWaitableEvent ready; platform_task_runner->PostTask([&]() { - auto& context = - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("key_data_late_echo"); builder.SetPlatformMessageCallback( [&](const FlutterPlatformMessage* message) { @@ -3158,10 +3157,9 @@ TEST_F(EmbedderTest, KeyDataResponseIsCorrectlyInvoked) { auto platform_task_runner = CreateNewThread("platform_thread"); platform_task_runner->PostTask([&]() { - auto& context = - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("key_data_echo"); context.AddNativeCallback( "SignalNativeTest", @@ -3232,11 +3230,9 @@ TEST_F(EmbedderTest, BackToBackKeyEventResponsesCorrectlyInvoked) { auto platform_task_runner = CreateNewThread("platform_thread"); platform_task_runner->PostTask([&]() { - auto& context = - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); - + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("key_data_echo"); context.AddNativeCallback( "SignalNativeTest", @@ -3319,9 +3315,7 @@ TEST_F(EmbedderTest, VsyncCallbackPostedIntoFuture) { auto platform_task_runner = CreateNewThread("platform_thread"); platform_task_runner->PostTask([&]() { - auto& context = - GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); - + auto& context = GetEmbedderContext(); context.SetVsyncCallback([&](intptr_t baton) { platform_task_runner->PostTask([baton = baton, &engine, &vsync_latch]() { FlutterEngineOnVsync(engine.get(), baton, NanosFromEpoch(16), @@ -3335,7 +3329,7 @@ TEST_F(EmbedderTest, VsyncCallbackPostedIntoFuture) { })); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetupVsyncCallback(); builder.SetDartEntrypoint("empty_scene"); engine = builder.LaunchEngine(); @@ -3364,9 +3358,9 @@ TEST_F(EmbedderTest, VsyncCallbackPostedIntoFuture) { } TEST_F(EmbedderTest, CanScheduleFrame) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("can_schedule_frame"); fml::AutoResetWaitableEvent latch; context.AddNativeCallback( @@ -3392,9 +3386,9 @@ TEST_F(EmbedderTest, CanScheduleFrame) { } TEST_F(EmbedderTest, CanSetNextFrameCallback) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("draw_solid_red"); auto engine = builder.LaunchEngine(); @@ -3479,9 +3473,9 @@ TEST_F(EmbedderTest, EmbedderThreadHostUseCustomThreadConfig) { /// Send a pointer event to Dart and wait until the Dart code signals /// it received the event. TEST_F(EmbedderTest, CanSendPointer) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("pointer_data_packet"); fml::AutoResetWaitableEvent ready_latch, count_latch, message_latch; @@ -3530,9 +3524,9 @@ TEST_F(EmbedderTest, CanSendPointer) { /// Send a pointer event to Dart and wait until the Dart code echos with the /// view ID. TEST_F(EmbedderTest, CanSendPointerEventWithViewId) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("pointer_data_packet_view_id"); fml::AutoResetWaitableEvent ready_latch, add_view_latch, message_latch; @@ -3592,9 +3586,9 @@ TEST_F(EmbedderTest, CanSendPointerEventWithViewId) { } TEST_F(EmbedderTest, WindowMetricsEventDefaultsToImplicitView) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("window_metrics_event_view_id"); fml::AutoResetWaitableEvent ready_latch, message_latch; @@ -3633,9 +3627,9 @@ TEST_F(EmbedderTest, WindowMetricsEventDefaultsToImplicitView) { } TEST_F(EmbedderTest, IgnoresWindowMetricsEventForUnknownView) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("window_metrics_event_view_id"); fml::AutoResetWaitableEvent ready_latch, message_latch; @@ -3689,7 +3683,7 @@ TEST_F(EmbedderTest, IgnoresWindowMetricsEventForUnknownView) { } TEST_F(EmbedderTest, RegisterChannelListener) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch; fml::AutoResetWaitableEvent latch2; @@ -3705,7 +3699,7 @@ TEST_F(EmbedderTest, RegisterChannelListener) { }); EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetDartEntrypoint("channel_listener_response"); auto engine = builder.LaunchEngine(); @@ -3720,7 +3714,7 @@ TEST_F(EmbedderTest, RegisterChannelListener) { } TEST_F(EmbedderTest, PlatformThreadIsolatesWithCustomPlatformTaskRunner) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + auto& context = GetEmbedderContext(); static fml::AutoResetWaitableEvent latch; static std::thread::id ffi_call_thread_id; @@ -3764,7 +3758,7 @@ TEST_F(EmbedderTest, PlatformThreadIsolatesWithCustomPlatformTaskRunner) { EmbedderConfigBuilder builder(context); const auto task_runner_description = test_task_runner.GetFlutterTaskRunnerDescription(); - builder.SetSoftwareRendererConfig(); + builder.SetSurface(SkISize::Make(1, 1)); builder.SetPlatformTaskRunner(&task_runner_description); builder.SetDartEntrypoint("invokePlatformThreadIsolate"); builder.AddCommandLineArgument("--enable-platform-isolates"); diff --git a/shell/platform/embedder/tests/embedder_unittests_util.h b/shell/platform/embedder/tests/embedder_unittests_util.h index 4e1cae51b1df7..1336b3ef34377 100644 --- a/shell/platform/embedder/tests/embedder_unittests_util.h +++ b/shell/platform/embedder/tests/embedder_unittests_util.h @@ -130,9 +130,14 @@ class EmbedderTestTaskRunner { real_task_runner->PostTaskForTime(invoke_task, target_time); }; + task_runner_description_.destruction_callback = [](void* user_data) {}; task_runner_description_.identifier = identifier_; } + void SetDestructionCallback(VoidCallback callback) { + task_runner_description_.destruction_callback = callback; + } + const FlutterTaskRunnerDescription& GetFlutterTaskRunnerDescription() { return task_runner_description_; } diff --git a/shell/platform/embedder/tests/embedder_vk_unittests.cc b/shell/platform/embedder/tests/embedder_vk_unittests.cc index d6209d2ed96d0..6589830bbf1ac 100644 --- a/shell/platform/embedder/tests/embedder_vk_unittests.cc +++ b/shell/platform/embedder/tests/embedder_vk_unittests.cc @@ -95,13 +95,17 @@ static_assert( CheckSameSignature::value); } // namespace +TEST_F(EmbedderTest, CanGetVulkanEmbedderContext) { + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); +} + TEST_F(EmbedderTest, CanSwapOutVulkanCalls) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kVulkanContext); fml::AutoResetWaitableEvent latch; + + auto& context = GetEmbedderContext(); context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); - EmbedderConfigBuilder builder(context); - builder.SetVulkanRendererConfig( - SkISize::Make(1024, 1024), + context.SetVulkanInstanceProcAddressCallback( [](void* user_data, FlutterVulkanInstanceHandle instance, const char* name) -> void* { if (StrcmpFixed(name, "vkGetInstanceProcAddr") == 0) { @@ -114,6 +118,9 @@ TEST_F(EmbedderTest, CanSwapOutVulkanCalls) { return EmbedderTestContextVulkan::InstanceProcAddr(user_data, instance, name); }); + + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1024, 1024)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); // Wait for the root isolate to launch. diff --git a/shell/platform/fuchsia/dart-pkg/fuchsia/pubspec.yaml b/shell/platform/fuchsia/dart-pkg/fuchsia/pubspec.yaml index 85d01ce1515b9..fdacaaf5bde03 100644 --- a/shell/platform/fuchsia/dart-pkg/fuchsia/pubspec.yaml +++ b/shell/platform/fuchsia/dart-pkg/fuchsia/pubspec.yaml @@ -3,4 +3,4 @@ # found in the LICENSE file. environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/shell/platform/fuchsia/dart-pkg/zircon/pubspec.yaml b/shell/platform/fuchsia/dart-pkg/zircon/pubspec.yaml index c75e7f8ff6a4a..ac72505fe6e92 100644 --- a/shell/platform/fuchsia/dart-pkg/zircon/pubspec.yaml +++ b/shell/platform/fuchsia/dart-pkg/zircon/pubspec.yaml @@ -5,7 +5,7 @@ name: zircon environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 # Uncomment block for local testing diff --git a/shell/platform/fuchsia/dart-pkg/zircon_ffi/pubspec.yaml b/shell/platform/fuchsia/dart-pkg/zircon_ffi/pubspec.yaml index e30ca0196f5a7..f1d8c199db0cd 100644 --- a/shell/platform/fuchsia/dart-pkg/zircon_ffi/pubspec.yaml +++ b/shell/platform/fuchsia/dart-pkg/zircon_ffi/pubspec.yaml @@ -1,7 +1,7 @@ name: zircon_ffi environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 dependencies: ffi: ^1.0.0 diff --git a/shell/platform/fuchsia/dart/BUILD.gn b/shell/platform/fuchsia/dart/BUILD.gn index b35defc765d6c..d3002227f93da 100644 --- a/shell/platform/fuchsia/dart/BUILD.gn +++ b/shell/platform/fuchsia/dart/BUILD.gn @@ -103,7 +103,7 @@ dart_library("meta") { } dart_library("args") { - package_root = "$dart_src/third_party/pkg/args" + package_root = "$dart_src/third_party/pkg/core/pkgs/args" package_name = "args" language_version = "2.17" @@ -127,7 +127,7 @@ dart_library("args") { } dart_library("collection") { - package_root = "$dart_src/third_party/pkg/collection" + package_root = "$dart_src/third_party/pkg/core/pkgs/collection" package_name = "collection" pubspec = "$package_root/pubspec.yaml" @@ -167,7 +167,7 @@ dart_library("collection") { } dart_library("logging") { - package_root = "$dart_src/third_party/pkg/logging" + package_root = "$dart_src/third_party/pkg/core/pkgs/logging" package_name = "logging" pubspec = "$package_root/pubspec.yaml" @@ -183,7 +183,7 @@ dart_library("logging") { } dart_library("path") { - package_root = "$dart_src/third_party/pkg/path" + package_root = "$dart_src/third_party/pkg/core/pkgs/path" package_name = "path" pubspec = "$package_root/pubspec.yaml" @@ -230,7 +230,7 @@ dart_library("stack_trace") { } dart_library("matcher") { - package_root = "$dart_src/third_party/pkg/matcher" + package_root = "$dart_src/third_party/pkg/test/pkgs/matcher" package_name = "matcher" pubspec = "$package_root/pubspec.yaml" diff --git a/shell/platform/fuchsia/dart_runner/BUILD.gn b/shell/platform/fuchsia/dart_runner/BUILD.gn index 1a626e1f7af47..c42682d0f541f 100644 --- a/shell/platform/fuchsia/dart_runner/BUILD.gn +++ b/shell/platform/fuchsia/dart_runner/BUILD.gn @@ -137,8 +137,8 @@ runner("dart_aot_runner_bin") { extra_defines += [ "FLUTTER_PROFILE" ] } extra_deps = [ - "$dart_src/runtime:libdart_precompiled_runtime", - "$dart_src/runtime/platform:libdart_platform_precompiled_runtime", + "$dart_src/runtime:libdart_aotruntime", + "$dart_src/runtime/platform:libdart_platform_aotruntime", "embedder:dart_aot_snapshot_cc", ] } @@ -151,8 +151,8 @@ runner("dart_aot_product_runner_bin") { "DART_PRODUCT", ] extra_deps = [ - "$dart_src/runtime:libdart_precompiled_runtime", - "$dart_src/runtime/platform:libdart_platform_precompiled_runtime", + "$dart_src/runtime:libdart_aotruntime", + "$dart_src/runtime/platform:libdart_platform_aotruntime", "embedder:dart_aot_product_snapshot_cc", ] } @@ -170,8 +170,8 @@ template("aot_runner_package") { "vmservice:vmservice_snapshot", "//flutter/shell/platform/fuchsia/runtime/dart/profiler_symbols:dart_aot_runner", - # TODO(kaushikiska): Figure out how to get the profiler symbols for `libdart_precompiled_runtime` - # "$dart_src/runtime:libdart_precompiled_runtime", + # TODO(kaushikiska): Figure out how to get the profiler symbols for `libdart_aotruntime` + # "$dart_src/runtime:libdart_aotruntime", observatory_target, ] } diff --git a/shell/platform/fuchsia/dart_runner/embedder/pubspec.yaml b/shell/platform/fuchsia/dart_runner/embedder/pubspec.yaml index d1ccbe7700dc5..6ef1940491851 100644 --- a/shell/platform/fuchsia/dart_runner/embedder/pubspec.yaml +++ b/shell/platform/fuchsia/dart_runner/embedder/pubspec.yaml @@ -6,5 +6,5 @@ # template in BUILD.gn. environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/shell/platform/fuchsia/dart_runner/vmservice/pubspec.yaml b/shell/platform/fuchsia/dart_runner/vmservice/pubspec.yaml index df2ce1ab10dbf..dfe2136345352 100644 --- a/shell/platform/fuchsia/dart_runner/vmservice/pubspec.yaml +++ b/shell/platform/fuchsia/dart_runner/vmservice/pubspec.yaml @@ -3,5 +3,5 @@ # found in the LICENSE file. environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/shell/platform/fuchsia/flutter/BUILD.gn b/shell/platform/fuchsia/flutter/BUILD.gn index 2ab8725ee6d5a..fad29d220bd6a 100644 --- a/shell/platform/fuchsia/flutter/BUILD.gn +++ b/shell/platform/fuchsia/flutter/BUILD.gn @@ -278,8 +278,8 @@ flutter_runner("aot") { product = false extra_deps = [ - "$dart_src/runtime:libdart_precompiled_runtime", - "$dart_src/runtime/platform:libdart_platform_precompiled_runtime", + "$dart_src/runtime:libdart_aotruntime", + "$dart_src/runtime/platform:libdart_platform_aotruntime", ] } @@ -288,8 +288,8 @@ flutter_runner("aot_product") { product = true extra_deps = [ - "$dart_src/runtime:libdart_precompiled_runtime", - "$dart_src/runtime/platform:libdart_platform_precompiled_runtime", + "$dart_src/runtime:libdart_aotruntime", + "$dart_src/runtime/platform:libdart_platform_aotruntime", ] } diff --git a/shell/platform/fuchsia/flutter/accessibility_bridge.cc b/shell/platform/fuchsia/flutter/accessibility_bridge.cc index b9f443035a37f..987b9303bace1 100644 --- a/shell/platform/fuchsia/flutter/accessibility_bridge.cc +++ b/shell/platform/fuchsia/flutter/accessibility_bridge.cc @@ -161,6 +161,9 @@ std::string NodeActionsToString(const flutter::SemanticsNode& node) { if (node.HasAction(flutter::SemanticsAction::kScrollUp)) { output += "kScrollUp|"; } + if (node.HasAction(flutter::SemanticsAction::kScrollToOffset)) { + output += "kScrollToOffset|"; + } if (node.HasAction(flutter::SemanticsAction::kSetSelection)) { output += "kSetSelection|"; } diff --git a/shell/platform/fuchsia/flutter/flatland_connection.cc b/shell/platform/fuchsia/flutter/flatland_connection.cc index 6a0b6dd22dc1b..0973a7846a86a 100644 --- a/shell/platform/fuchsia/flutter/flatland_connection.cc +++ b/shell/platform/fuchsia/flutter/flatland_connection.cc @@ -222,7 +222,6 @@ void FlatlandConnection::OnNextFrameBegin( const auto now = fml::TimePoint::Now(); std::scoped_lock lock(threadsafe_state_.mutex_); - threadsafe_state_.first_feedback_received_ = true; threadsafe_state_.present_credits_ += values.additional_present_credits(); TRACE_DURATION("flutter", "FlatlandConnection::OnNextFrameBegin", "present_credits", threadsafe_state_.present_credits_); @@ -319,15 +318,16 @@ fml::TimePoint FlatlandConnection::GetNextPresentationTime( bool FlatlandConnection::MaybeRunInitialVsyncCallback( const fml::TimePoint& now, FireCallbackCallback& callback) { - if (!threadsafe_state_.first_feedback_received_) { - TRACE_DURATION("flutter", - "FlatlandConnection::MaybeRunInitialVsyncCallback"); - const auto frame_end = now + kInitialFlatlandVsyncOffset; - threadsafe_state_.last_presentation_time_ = frame_end; - callback(now, frame_end); - return true; + // Only sent maybe_run_initial_vsync once. + if (threadsafe_state_.initial_vsync_callback_ran_) { + return false; } - return false; + TRACE_DURATION("flutter", "FlatlandConnection::MaybeRunInitialVsyncCallback"); + const auto frame_end = now + kInitialFlatlandVsyncOffset; + threadsafe_state_.last_presentation_time_ = frame_end; + threadsafe_state_.initial_vsync_callback_ran_ = true; + callback(now, frame_end); + return true; } // This method may be called from the raster or UI thread, but it is safe diff --git a/shell/platform/fuchsia/flutter/flatland_connection.h b/shell/platform/fuchsia/flutter/flatland_connection.h index 37cb47bd05e69..e41c945f7fd66 100644 --- a/shell/platform/fuchsia/flutter/flatland_connection.h +++ b/shell/platform/fuchsia/flutter/flatland_connection.h @@ -137,7 +137,7 @@ class FlatlandConnection final { fml::TimePoint last_presentation_time_; FireCallbackCallback pending_fire_callback_; uint32_t present_credits_ = 1; - bool first_feedback_received_ = false; + bool initial_vsync_callback_ran_ = false; } threadsafe_state_; // Acquire fences sent to Flatland. diff --git a/shell/platform/fuchsia/flutter/kernel/pubspec.yaml b/shell/platform/fuchsia/flutter/kernel/pubspec.yaml index 85d01ce1515b9..fdacaaf5bde03 100644 --- a/shell/platform/fuchsia/flutter/kernel/pubspec.yaml +++ b/shell/platform/fuchsia/flutter/kernel/pubspec.yaml @@ -3,4 +3,4 @@ # found in the LICENSE file. environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/pubspec.yaml b/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/pubspec.yaml index 89f565db2969b..999c2898734e2 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/pubspec.yaml +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/pubspec.yaml @@ -5,4 +5,4 @@ name: child_view2 environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/pubspec.yaml b/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/pubspec.yaml index 1ee7f0c30401c..418f4a5f0179a 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/pubspec.yaml +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/pubspec.yaml @@ -5,4 +5,4 @@ name: parent-view2 environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/shell/platform/fuchsia/flutter/tests/integration/mouse-input/mouse-input-view/pubspec.yaml b/shell/platform/fuchsia/flutter/tests/integration/mouse-input/mouse-input-view/pubspec.yaml index 40e52bb3bbc54..1b4c748f4ed7c 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/mouse-input/mouse-input-view/pubspec.yaml +++ b/shell/platform/fuchsia/flutter/tests/integration/mouse-input/mouse-input-view/pubspec.yaml @@ -5,4 +5,4 @@ name: mouse-input-view environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/shell/platform/fuchsia/flutter/tests/integration/text-input/text-input-view/pubspec.yaml b/shell/platform/fuchsia/flutter/tests/integration/text-input/text-input-view/pubspec.yaml index 21d9b7d9c0917..1c7aebb632d23 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/text-input/text-input-view/pubspec.yaml +++ b/shell/platform/fuchsia/flutter/tests/integration/text-input/text-input-view/pubspec.yaml @@ -5,4 +5,4 @@ name: text-input-view environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/pubspec.yaml b/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/pubspec.yaml index e929bdaaa7518..88d86708587c5 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/pubspec.yaml +++ b/shell/platform/fuchsia/flutter/tests/integration/touch-input/embedding-flutter-view/pubspec.yaml @@ -5,4 +5,4 @@ name: embedding-flutter-view environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/pubspec.yaml b/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/pubspec.yaml index 7fc83f8c32fd0..4ba22fab5b134 100644 --- a/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/pubspec.yaml +++ b/shell/platform/fuchsia/flutter/tests/integration/touch-input/touch-input-view/pubspec.yaml @@ -5,4 +5,4 @@ name: touch-input-view environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/shell/platform/fuchsia/runtime/dart/profiler_symbols/pubspec.yaml b/shell/platform/fuchsia/runtime/dart/profiler_symbols/pubspec.yaml index 7076e2a537f44..50ff8e43b8af2 100644 --- a/shell/platform/fuchsia/runtime/dart/profiler_symbols/pubspec.yaml +++ b/shell/platform/fuchsia/runtime/dart/profiler_symbols/pubspec.yaml @@ -4,7 +4,7 @@ name: profiler_symbols environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 version: 0 description: Extracts a minimal symbols table for the Dart VM profiler author: Dart Team diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index b00bab8cedbca..b2cdd11aed99a 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -7,6 +7,7 @@ #include #include +#include #include #include #include diff --git a/shell/platform/linux/.clang-tidy b/shell/platform/linux/.clang-tidy index 42d8632c23f39..d31e07c442d77 100644 --- a/shell/platform/linux/.clang-tidy +++ b/shell/platform/linux/.clang-tidy @@ -3,3 +3,9 @@ InheritParentConfig: true # EnumCastOutOfRange warns about some common usages of GTK macros Checks: >- -clang-analyzer-optin.core.EnumCastOutOfRange + +CheckOptions: + - key: readability-identifier-naming.EnumConstantCase + value: "UPPER_CASE" + - key: readability-identifier-naming.EnumConstantPrefix + value: "" diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 691358221fdb7..b71b0c74f2479 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -92,6 +92,7 @@ source_set("flutter_linux_sources") { "fl_method_channel_private.h", "fl_method_codec_private.h", "fl_plugin_registrar_private.h", + "fl_pointer_manager.h", "fl_window_state_monitor.h", "key_mapping.h", ] @@ -115,6 +116,8 @@ source_set("flutter_linux_sources") { "fl_key_channel_responder.cc", "fl_key_embedder_responder.cc", "fl_key_event.cc", + "fl_key_event_channel.cc", + "fl_keyboard_channel.cc", "fl_keyboard_handler.cc", "fl_keyboard_layout.cc", "fl_keyboard_manager.cc", @@ -125,18 +128,21 @@ source_set("flutter_linux_sources") { "fl_method_channel.cc", "fl_method_codec.cc", "fl_method_response.cc", + "fl_mouse_cursor_channel.cc", "fl_mouse_cursor_handler.cc", "fl_pixel_buffer_texture.cc", + "fl_platform_channel.cc", "fl_platform_handler.cc", "fl_plugin_registrar.cc", "fl_plugin_registry.cc", + "fl_pointer_manager.cc", "fl_renderable.cc", "fl_renderer.cc", "fl_renderer_gdk.cc", "fl_renderer_headless.cc", "fl_scrolling_manager.cc", - "fl_scrolling_view_delegate.cc", "fl_settings.cc", + "fl_settings_channel.cc", "fl_settings_handler.cc", "fl_settings_portal.cc", "fl_socket_accessible.cc", @@ -145,6 +151,7 @@ source_set("flutter_linux_sources") { "fl_string_codec.cc", "fl_task_runner.cc", "fl_task_runner.h", + "fl_text_input_channel.cc", "fl_text_input_handler.cc", "fl_text_input_view_delegate.cc", "fl_texture.cc", @@ -210,6 +217,7 @@ executable("flutter_linux_unittests") { "fl_dart_project_test.cc", "fl_engine_test.cc", "fl_event_channel_test.cc", + "fl_framebuffer_test.cc", "fl_gnome_settings_test.cc", "fl_json_message_codec_test.cc", "fl_json_method_codec_test.cc", @@ -223,8 +231,10 @@ executable("flutter_linux_unittests") { "fl_method_codec_test.cc", "fl_method_response_test.cc", "fl_pixel_buffer_texture_test.cc", + "fl_platform_channel_test.cc", "fl_platform_handler_test.cc", "fl_plugin_registrar_test.cc", + "fl_pointer_manager_test.cc", "fl_renderer_test.cc", "fl_scrolling_manager_test.cc", "fl_settings_handler_test.cc", @@ -240,11 +250,10 @@ executable("flutter_linux_unittests") { "fl_view_test.cc", "fl_window_state_monitor_test.cc", "key_mapping_test.cc", + "testing/fl_mock_binary_messenger.cc", "testing/fl_test.cc", "testing/fl_test_gtk_logs.cc", "testing/fl_test_gtk_logs.h", - "testing/mock_binary_messenger.cc", - "testing/mock_binary_messenger_response_handle.cc", "testing/mock_engine.cc", "testing/mock_epoxy.cc", "testing/mock_im_context.cc", @@ -253,7 +262,6 @@ executable("flutter_linux_unittests") { "testing/mock_renderer.cc", "testing/mock_settings.cc", "testing/mock_signal_handler.cc", - "testing/mock_text_input_handler.cc", "testing/mock_text_input_view_delegate.cc", "testing/mock_texture_registrar.cc", "testing/mock_window.cc", diff --git a/shell/platform/linux/fl_accessible_node.cc b/shell/platform/linux/fl_accessible_node.cc index 962ff0cc354fa..37d54833fd906 100644 --- a/shell/platform/linux/fl_accessible_node.cc +++ b/shell/platform/linux/fl_accessible_node.cc @@ -81,7 +81,7 @@ struct FlAccessibleNodePrivate { FlutterSemanticsFlag flags; }; -enum { kProp0, kPropEngine, kPropId, kPropLast }; +enum { PROP_0, PROP_ENGINE, PROP_ID, PROP_LAST }; #define FL_ACCESSIBLE_NODE_GET_PRIVATE(node) \ ((FlAccessibleNodePrivate*)fl_accessible_node_get_instance_private( \ @@ -145,13 +145,13 @@ static void fl_accessible_node_set_property(GObject* object, GParamSpec* pspec) { FlAccessibleNodePrivate* priv = FL_ACCESSIBLE_NODE_GET_PRIVATE(object); switch (prop_id) { - case kPropEngine: + case PROP_ENGINE: g_assert(priv->engine == nullptr); priv->engine = FL_ENGINE(g_value_get_object(value)); g_object_add_weak_pointer(object, reinterpret_cast(&priv->engine)); break; - case kPropId: + case PROP_ID: priv->id = g_value_get_int(value); break; default: @@ -448,13 +448,13 @@ static void fl_accessible_node_class_init(FlAccessibleNodeClass* klass) { fl_accessible_node_perform_action_impl; g_object_class_install_property( - G_OBJECT_CLASS(klass), kPropEngine, + G_OBJECT_CLASS(klass), PROP_ENGINE, g_param_spec_object( "engine", "engine", "Flutter engine", fl_engine_get_type(), static_cast(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS))); g_object_class_install_property( - G_OBJECT_CLASS(klass), kPropId, + G_OBJECT_CLASS(klass), PROP_ID, g_param_spec_int( "id", "id", "Accessibility node ID", 0, G_MAXINT, 0, static_cast(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | diff --git a/shell/platform/linux/fl_application.cc b/shell/platform/linux/fl_application.cc index 203d8a12df5e0..721d53eac3700 100644 --- a/shell/platform/linux/fl_application.cc +++ b/shell/platform/linux/fl_application.cc @@ -22,9 +22,9 @@ struct FlApplicationPrivate { ((FlApplicationPrivate*)fl_application_get_instance_private( \ FL_APPLICATION(app))) -enum { kSignalRegisterPlugins, kSignalCreateWindow, kSignalLastSignal }; +enum { SIGNAL_REGISTER_PLUGINS, SIGNAL_CREATE_WINDOW, LAST_SIGNAL }; -static guint fl_application_signals[kSignalLastSignal]; +static guint fl_application_signals[LAST_SIGNAL]; G_DEFINE_TYPE_WITH_CODE(FlApplication, fl_application, @@ -95,14 +95,14 @@ static void fl_application_activate(GApplication* application) { gtk_widget_show(GTK_WIDGET(view)); GtkWindow* window; - g_signal_emit(self, fl_application_signals[kSignalCreateWindow], 0, view, + g_signal_emit(self, fl_application_signals[SIGNAL_CREATE_WINDOW], 0, view, &window); // Make the resources for the view so rendering can start. // We'll show the view when we have the first frame. gtk_widget_realize(GTK_WIDGET(view)); - g_signal_emit(self, fl_application_signals[kSignalRegisterPlugins], 0, + g_signal_emit(self, fl_application_signals[SIGNAL_REGISTER_PLUGINS], 0, FL_PLUGIN_REGISTRY(view)); } @@ -150,11 +150,11 @@ static void fl_application_class_init(FlApplicationClass* klass) { klass->register_plugins = fl_application_register_plugins; klass->create_window = fl_application_create_window; - fl_application_signals[kSignalRegisterPlugins] = g_signal_new( + fl_application_signals[SIGNAL_REGISTER_PLUGINS] = g_signal_new( "register-plugins", fl_application_get_type(), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FlApplicationClass, register_plugins), nullptr, nullptr, nullptr, G_TYPE_NONE, 1, fl_plugin_registry_get_type()); - fl_application_signals[kSignalCreateWindow] = g_signal_new( + fl_application_signals[SIGNAL_CREATE_WINDOW] = g_signal_new( "create-window", fl_application_get_type(), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FlApplicationClass, create_window), g_signal_accumulator_first_wins, nullptr, nullptr, GTK_TYPE_WINDOW, 1, diff --git a/shell/platform/linux/fl_application_test.cc b/shell/platform/linux/fl_application_test.cc index 73b5dc1173f35..c889382b9c1d5 100644 --- a/shell/platform/linux/fl_application_test.cc +++ b/shell/platform/linux/fl_application_test.cc @@ -7,11 +7,23 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_application.h" TEST(FlApplicationTest, ConstructorArgs) { - g_autoptr(FlApplication) app = fl_application_new( - "com.example.TestApplication", G_APPLICATION_FLAGS_NONE); + g_autoptr(FlApplication) app = + fl_application_new("com.example.TestApplication", +#ifdef GLIB_VERSION_2_74 + G_APPLICATION_DEFAULT_FLAGS +#else + G_APPLICATION_FLAGS_NONE +#endif + ); EXPECT_STREQ(g_application_get_application_id(G_APPLICATION(app)), "com.example.TestApplication"); + +#ifdef GLIB_VERSION_2_74 + EXPECT_EQ(g_application_get_flags(G_APPLICATION(app)), + G_APPLICATION_DEFAULT_FLAGS); +#else EXPECT_EQ(g_application_get_flags(G_APPLICATION(app)), G_APPLICATION_FLAGS_NONE); +#endif } diff --git a/shell/platform/linux/fl_basic_message_channel.cc b/shell/platform/linux/fl_basic_message_channel.cc index 7d2b5afe60a6a..b7552be5814a8 100644 --- a/shell/platform/linux/fl_basic_message_channel.cc +++ b/shell/platform/linux/fl_basic_message_channel.cc @@ -103,8 +103,8 @@ static void message_cb(FlBinaryMessenger* messenger, static void message_response_cb(GObject* object, GAsyncResult* result, gpointer user_data) { - GTask* task = G_TASK(user_data); - g_task_return_pointer(task, result, g_object_unref); + g_autoptr(GTask) task = G_TASK(user_data); + g_task_return_pointer(task, g_object_ref(result), g_object_unref); } // Called when the channel handler is closed. @@ -239,7 +239,7 @@ G_MODULE_EXPORT void fl_basic_message_channel_send(FlBasicMessageChannel* self, fl_message_codec_encode_message(self->codec, message, &error); if (data == nullptr) { if (task != nullptr) { - g_task_return_error(task, error); + g_task_return_error(task, g_error_copy(error)); } return; } @@ -257,8 +257,12 @@ G_MODULE_EXPORT FlValue* fl_basic_message_channel_send_finish( g_return_val_if_fail(FL_IS_BASIC_MESSAGE_CHANNEL(self), nullptr); g_return_val_if_fail(g_task_is_valid(result, self), nullptr); - g_autoptr(GTask) task = G_TASK(result); - GAsyncResult* r = G_ASYNC_RESULT(g_task_propagate_pointer(task, nullptr)); + GTask* task = G_TASK(result); + g_autoptr(GAsyncResult) r = + G_ASYNC_RESULT(g_task_propagate_pointer(task, error)); + if (r == nullptr) { + return nullptr; + } g_autoptr(GBytes) message = fl_binary_messenger_send_on_channel_finish(self->messenger, r, error); diff --git a/shell/platform/linux/fl_basic_message_channel_test.cc b/shell/platform/linux/fl_basic_message_channel_test.cc index 4cdcca136759f..a718486ee4716 100644 --- a/shell/platform/linux/fl_basic_message_channel_test.cc +++ b/shell/platform/linux/fl_basic_message_channel_test.cc @@ -43,7 +43,7 @@ TEST(FlBasicMessageChannelTest, SendMessageWithoutResponse) { g_bytes_new(message->message, message->message_size); g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new(); - FlValue* message_value = fl_message_codec_decode_message( + g_autoptr(FlValue) message_value = fl_message_codec_decode_message( FL_MESSAGE_CODEC(codec), message_bytes, nullptr); EXPECT_EQ(fl_value_get_type(message_value), FL_VALUE_TYPE_STRING); EXPECT_STREQ(fl_value_get_string(message_value), "Hello World!"); @@ -222,3 +222,35 @@ TEST(FlBasicMessageChannelTest, SendNullMessageWithResponse) { // Blocks here until null_message_response_cb is called. g_main_loop_run(loop); } + +// Called when the message response is received in the CustomType test. +static void custom_type_response_cb(GObject* object, + GAsyncResult* result, + gpointer user_data) { + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) message = fl_basic_message_channel_send_finish( + FL_BASIC_MESSAGE_CHANNEL(object), result, &error); + EXPECT_EQ(message, nullptr); + EXPECT_NE(error, nullptr); + EXPECT_STREQ(error->message, "Custom value not implemented"); + + g_main_loop_quit(static_cast(user_data)); +} + +// Checks sending a message with a custom type generates an error. +TEST(FlBasicMessageChannelTest, CustomType) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + // Attempt to send an integer with the string codec. + g_autoptr(FlEngine) engine = make_mock_engine(); + g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine); + g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new(); + g_autoptr(FlBasicMessageChannel) channel = fl_basic_message_channel_new( + messenger, "test/echo", FL_MESSAGE_CODEC(codec)); + g_autoptr(FlValue) message = fl_value_new_custom(42, nullptr, nullptr); + fl_basic_message_channel_send(channel, message, nullptr, + custom_type_response_cb, loop); + + // Blocks here until custom_type_response_cb is called. + g_main_loop_run(loop); +} diff --git a/shell/platform/linux/fl_binary_messenger.cc b/shell/platform/linux/fl_binary_messenger.cc index 16dcd2b86bbc1..4453456ec1113 100644 --- a/shell/platform/linux/fl_binary_messenger.cc +++ b/shell/platform/linux/fl_binary_messenger.cc @@ -260,8 +260,8 @@ static gboolean send_response(FlBinaryMessenger* messenger, static void platform_message_ready_cb(GObject* object, GAsyncResult* result, gpointer user_data) { - GTask* task = G_TASK(user_data); - g_task_return_pointer(task, result, g_object_unref); + g_autoptr(GTask) task = G_TASK(user_data); + g_task_return_pointer(task, g_object_ref(result), g_object_unref); } static void send_on_channel(FlBinaryMessenger* messenger, @@ -290,8 +290,12 @@ static GBytes* send_on_channel_finish(FlBinaryMessenger* messenger, FlBinaryMessengerImpl* self = FL_BINARY_MESSENGER_IMPL(messenger); g_return_val_if_fail(g_task_is_valid(result, self), FALSE); - g_autoptr(GTask) task = G_TASK(result); - GAsyncResult* r = G_ASYNC_RESULT(g_task_propagate_pointer(task, nullptr)); + GTask* task = G_TASK(result); + g_autoptr(GAsyncResult) r = + G_ASYNC_RESULT(g_task_propagate_pointer(task, error)); + if (r == nullptr) { + return nullptr; + } g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); if (engine == nullptr) { diff --git a/shell/platform/linux/fl_binary_messenger_test.cc b/shell/platform/linux/fl_binary_messenger_test.cc index 133f6e4b57c20..8f03eb514057c 100644 --- a/shell/platform/linux/fl_binary_messenger_test.cc +++ b/shell/platform/linux/fl_binary_messenger_test.cc @@ -16,9 +16,34 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" #include "flutter/shell/platform/linux/testing/fl_test.h" -#include "flutter/shell/platform/linux/testing/mock_binary_messenger_response_handle.h" #include "flutter/shell/platform/linux/testing/mock_renderer.h" +G_DECLARE_FINAL_TYPE(FlFakeBinaryMessengerResponseHandle, + fl_fake_binary_messenger_response_handle, + FL, + FAKE_BINARY_MESSENGER_RESPONSE_HANDLE, + FlBinaryMessengerResponseHandle) + +struct _FlFakeBinaryMessengerResponseHandle { + FlBinaryMessengerResponseHandle parent_instance; +}; + +G_DEFINE_TYPE(FlFakeBinaryMessengerResponseHandle, + fl_fake_binary_messenger_response_handle, + fl_binary_messenger_response_handle_get_type()); + +static void fl_fake_binary_messenger_response_handle_class_init( + FlFakeBinaryMessengerResponseHandleClass* klass) {} + +static void fl_fake_binary_messenger_response_handle_init( + FlFakeBinaryMessengerResponseHandle* self) {} + +FlFakeBinaryMessengerResponseHandle* +fl_fake_binary_messenger_response_handle_new() { + return FL_FAKE_BINARY_MESSENGER_RESPONSE_HANDLE( + g_object_new(fl_fake_binary_messenger_response_handle_get_type(), NULL)); +} + G_DECLARE_FINAL_TYPE(FlFakeBinaryMessenger, fl_fake_binary_messenger, FL, @@ -55,7 +80,7 @@ static gboolean send_message_cb(gpointer user_data) { g_autoptr(GBytes) message = g_bytes_new(text, strlen(text)); self->message_handler(FL_BINARY_MESSENGER(self), "CHANNEL", message, FL_BINARY_MESSENGER_RESPONSE_HANDLE( - fl_mock_binary_messenger_response_handle_new()), + fl_fake_binary_messenger_response_handle_new()), self->message_handler_user_data); return FALSE; @@ -83,7 +108,7 @@ static gboolean send_response(FlBinaryMessenger* messenger, GError** error) { FlFakeBinaryMessenger* self = FL_FAKE_BINARY_MESSENGER(messenger); - EXPECT_TRUE(FL_IS_MOCK_BINARY_MESSENGER_RESPONSE_HANDLE(response_handle)); + EXPECT_TRUE(FL_IS_FAKE_BINARY_MESSENGER_RESPONSE_HANDLE(response_handle)); g_autofree gchar* text = g_strndup(static_cast(g_bytes_get_data(response, nullptr)), diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index c56193e1d504b..0cc9497102e37 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -15,6 +15,7 @@ #include "flutter/shell/platform/linux/fl_dart_project_private.h" #include "flutter/shell/platform/linux/fl_engine_private.h" #include "flutter/shell/platform/linux/fl_pixel_buffer_texture_private.h" +#include "flutter/shell/platform/linux/fl_platform_handler.h" #include "flutter/shell/platform/linux/fl_plugin_registrar_private.h" #include "flutter/shell/platform/linux/fl_renderer.h" #include "flutter/shell/platform/linux/fl_renderer_gdk.h" @@ -38,14 +39,38 @@ struct _FlEngine { // Thread the GLib main loop is running on. GThread* thread; + // The project this engine is running. FlDartProject* project; + + // Renders the Flutter app. FlRenderer* renderer; + + // Messenger used to send and receive platform messages. FlBinaryMessenger* binary_messenger; + + // Implements the flutter/settings channel. FlSettingsHandler* settings_handler; + + // Implements the flutter/platform channel. + FlPlatformHandler* platform_handler; + + // Implements the flutter/mousecursor channel. + FlMouseCursorHandler* mouse_cursor_handler; + + // Manages textures rendered by native code. FlTextureRegistrar* texture_registrar; + + // Schedules tasks to be run on the appropriate thread. FlTaskRunner* task_runner; + + // Ahead of time data used to make engine run faster. FlutterEngineAOTData aot_data; + + // The Flutter engine. FLUTTER_API_SYMBOL(FlutterEngine) engine; + + // Function table for engine API, used to intercept engine calls for testing + // purposes. FlutterEngineProcTable embedder_api; // Next ID to use for a view. @@ -67,9 +92,9 @@ G_DEFINE_QUARK(fl_engine_error_quark, fl_engine_error) static void fl_engine_plugin_registry_iface_init( FlPluginRegistryInterface* iface); -enum { kSignalOnPreEngineRestart, kSignalLastSignal }; +enum { SIGNAL_ON_PRE_ENGINE_RESTART, LAST_SIGNAL }; -static guint fl_engine_signals[kSignalLastSignal]; +static guint fl_engine_signals[LAST_SIGNAL]; G_DEFINE_TYPE_WITH_CODE( FlEngine, @@ -78,7 +103,7 @@ G_DEFINE_TYPE_WITH_CODE( G_IMPLEMENT_INTERFACE(fl_plugin_registry_get_type(), fl_engine_plugin_registry_iface_init)) -enum { kProp0, kPropBinaryMessenger, kPropLast }; +enum { PROP_0, PROP_BINARY_MESSENGER, PROP_LAST }; // Parse a locale into its components. static void parse_locale(const gchar* locale, @@ -361,7 +386,7 @@ static void fl_engine_update_semantics_cb(const FlutterSemanticsUpdate2* update, static void fl_engine_on_pre_engine_restart_cb(void* user_data) { FlEngine* self = FL_ENGINE(user_data); - g_signal_emit(self, fl_engine_signals[kSignalOnPreEngineRestart], 0); + g_signal_emit(self, fl_engine_signals[SIGNAL_ON_PRE_ENGINE_RESTART], 0); } // Called when a response to a sent platform message is received from the @@ -395,7 +420,7 @@ static void fl_engine_set_property(GObject* object, GParamSpec* pspec) { FlEngine* self = FL_ENGINE(object); switch (prop_id) { - case kPropBinaryMessenger: + case PROP_BINARY_MESSENGER: g_set_object(&self->binary_messenger, FL_BINARY_MESSENGER(g_value_get_object(value))); break; @@ -426,6 +451,8 @@ static void fl_engine_dispose(GObject* object) { g_clear_object(&self->texture_registrar); g_clear_object(&self->binary_messenger); g_clear_object(&self->settings_handler); + g_clear_object(&self->platform_handler); + g_clear_object(&self->mouse_cursor_handler); g_clear_object(&self->task_runner); if (self->platform_message_handler_destroy_notify) { @@ -450,14 +477,14 @@ static void fl_engine_class_init(FlEngineClass* klass) { G_OBJECT_CLASS(klass)->set_property = fl_engine_set_property; g_object_class_install_property( - G_OBJECT_CLASS(klass), kPropBinaryMessenger, + G_OBJECT_CLASS(klass), PROP_BINARY_MESSENGER, g_param_spec_object( "binary-messenger", "messenger", "Binary messenger", fl_binary_messenger_get_type(), static_cast(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS))); - fl_engine_signals[kSignalOnPreEngineRestart] = g_signal_new( + fl_engine_signals[SIGNAL_ON_PRE_ENGINE_RESTART] = g_signal_new( "on-pre-engine-restart", fl_engine_get_type(), G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr, G_TYPE_NONE, 0); } @@ -534,11 +561,12 @@ gboolean fl_engine_start(FlEngine* self, GError** error) { custom_task_runners.platform_task_runner = &platform_task_runner; custom_task_runners.render_task_runner = &platform_task_runner; - g_autoptr(GPtrArray) command_line_args = fl_engine_get_switches(self); - // FlutterProjectArgs expects a full argv, so when processing it for flags - // the first item is treated as the executable and ignored. Add a dummy value - // so that all switches are used. + g_autoptr(GPtrArray) command_line_args = + g_ptr_array_new_with_free_func(g_free); g_ptr_array_insert(command_line_args, 0, g_strdup("flutter")); + for (const auto& env_switch : flutter::GetSwitchesFromEnvironment()) { + g_ptr_array_add(command_line_args, g_strdup(env_switch.c_str())); + } gchar** dart_entrypoint_args = fl_dart_project_get_dart_entrypoint_arguments(self->project); @@ -604,6 +632,10 @@ gboolean fl_engine_start(FlEngine* self, GError** error) { self->settings_handler = fl_settings_handler_new(self); fl_settings_handler_start(self->settings_handler, settings); + self->platform_handler = fl_platform_handler_new(self->binary_messenger); + self->mouse_cursor_handler = + fl_mouse_cursor_handler_new(self->binary_messenger); + result = self->embedder_api.UpdateSemanticsEnabled(self->engine, TRUE); if (result != kSuccess) { g_warning("Failed to enable accessibility features on Flutter engine"); @@ -1021,10 +1053,12 @@ void fl_engine_update_accessibility_features(FlEngine* self, int32_t flags) { self->engine, static_cast(flags)); } -GPtrArray* fl_engine_get_switches(FlEngine* self) { - GPtrArray* switches = g_ptr_array_new_with_free_func(g_free); - for (const auto& env_switch : flutter::GetSwitchesFromEnvironment()) { - g_ptr_array_add(switches, g_strdup(env_switch.c_str())); - } - return switches; +void fl_engine_request_app_exit(FlEngine* self) { + g_return_if_fail(FL_IS_ENGINE(self)); + fl_platform_handler_request_app_exit(self->platform_handler); +} + +FlMouseCursorHandler* fl_engine_get_mouse_cursor_handler(FlEngine* self) { + g_return_val_if_fail(FL_IS_ENGINE(self), nullptr); + return self->mouse_cursor_handler; } diff --git a/shell/platform/linux/fl_engine_private.h b/shell/platform/linux/fl_engine_private.h index 6980ae4019db1..b6fce3056d9e7 100644 --- a/shell/platform/linux/fl_engine_private.h +++ b/shell/platform/linux/fl_engine_private.h @@ -8,6 +8,7 @@ #include #include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/linux/fl_mouse_cursor_handler.h" #include "flutter/shell/platform/linux/fl_renderer.h" #include "flutter/shell/platform/linux/fl_task_runner.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h" @@ -21,9 +22,7 @@ G_BEGIN_DECLS */ typedef enum { - // NOLINTBEGIN(readability-identifier-naming) FL_ENGINE_ERROR_FAILED, - // NOLINTEND(readability-identifier-naming) } FlEngineError; GQuark fl_engine_error_quark(void) G_GNUC_CONST; @@ -417,14 +416,22 @@ gboolean fl_engine_unregister_external_texture(FlEngine* engine, void fl_engine_update_accessibility_features(FlEngine* engine, int32_t flags); /** - * fl_engine_get_switches: - * @project: an #FlEngine. + * fl_engine_request_app_exit: + * @engine: an #FlEngine. + * + * Request the application exits. + */ +void fl_engine_request_app_exit(FlEngine* engine); + +/** + * fl_engine_get_mouse_cursor_handler: + * @engine: an #FlEngine. * - * Determines the switches that should be passed to the Flutter engine. + * Gets the mouse cursor handler used by this engine. * - * Returns: an array of switches to pass to the Flutter engine. + * Returns: a #FlMouseCursorHandler. */ -GPtrArray* fl_engine_get_switches(FlEngine* engine); +FlMouseCursorHandler* fl_engine_get_mouse_cursor_handler(FlEngine* engine); G_END_DECLS diff --git a/shell/platform/linux/fl_engine_test.cc b/shell/platform/linux/fl_engine_test.cc index 6a84f8d4c7320..62fcb48974f15 100644 --- a/shell/platform/linux/fl_engine_test.cc +++ b/shell/platform/linux/fl_engine_test.cc @@ -537,17 +537,6 @@ TEST(FlEngineTest, EmptyLocales) { } } -TEST(FlEngineTest, SwitchesEmpty) { - g_autoptr(FlEngine) engine = make_mock_engine(); - - // Clear the main environment variable, since test order is not guaranteed. - unsetenv("FLUTTER_ENGINE_SWITCHES"); - - g_autoptr(GPtrArray) switches = fl_engine_get_switches(engine); - - EXPECT_EQ(switches->len, 0U); -} - static void add_view_cb(GObject* object, GAsyncResult* result, gpointer user_data) { @@ -759,25 +748,4 @@ TEST(FlEngineTest, RemoveViewEngineError) { g_main_loop_run(loop); } -#ifndef FLUTTER_RELEASE -TEST(FlEngineTest, Switches) { - g_autoptr(FlEngine) engine = make_mock_engine(); - - setenv("FLUTTER_ENGINE_SWITCHES", "2", 1); - setenv("FLUTTER_ENGINE_SWITCH_1", "abc", 1); - setenv("FLUTTER_ENGINE_SWITCH_2", "foo=\"bar, baz\"", 1); - - g_autoptr(GPtrArray) switches = fl_engine_get_switches(engine); - EXPECT_EQ(switches->len, 2U); - EXPECT_STREQ(static_cast(g_ptr_array_index(switches, 0)), - "--abc"); - EXPECT_STREQ(static_cast(g_ptr_array_index(switches, 1)), - "--foo=\"bar, baz\""); - - unsetenv("FLUTTER_ENGINE_SWITCHES"); - unsetenv("FLUTTER_ENGINE_SWITCH_1"); - unsetenv("FLUTTER_ENGINE_SWITCH_2"); -} -#endif // !FLUTTER_RELEASE - // NOLINTEND(clang-analyzer-core.StackAddressEscape) diff --git a/shell/platform/linux/fl_framebuffer.cc b/shell/platform/linux/fl_framebuffer.cc index 947b540301cc4..6ec4797605a38 100644 --- a/shell/platform/linux/fl_framebuffer.cc +++ b/shell/platform/linux/fl_framebuffer.cc @@ -63,6 +63,19 @@ FlFramebuffer* fl_framebuffer_new(GLint format, size_t width, size_t height) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, provider->texture_id, 0); + GLuint depth_stencil; + glGenRenderbuffers(1, &depth_stencil); + glBindRenderbuffer(GL_RENDERBUFFER, depth_stencil); + glRenderbufferStorage(GL_RENDERBUFFER, // target + GL_DEPTH24_STENCIL8, // internal format + width, // width + height // height + ); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, depth_stencil); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, depth_stencil); + return provider; } diff --git a/shell/platform/linux/fl_framebuffer_test.cc b/shell/platform/linux/fl_framebuffer_test.cc new file mode 100644 index 0000000000000..de565c59ecf2d --- /dev/null +++ b/shell/platform/linux/fl_framebuffer_test.cc @@ -0,0 +1,28 @@ +// 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. + +#include "gtest/gtest.h" + +#include "flutter/common/constants.h" +#include "flutter/fml/logging.h" +#include "flutter/shell/platform/linux/fl_framebuffer.h" +#include "flutter/shell/platform/linux/testing/mock_epoxy.h" + +TEST(FlFramebufferTest, HasDepthStencil) { + ::testing::NiceMock epoxy; + + g_autoptr(FlFramebuffer) framebuffer = fl_framebuffer_new(GL_RGB, 100, 100); + + GLint depth_type = GL_NONE; + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, + &depth_type); + EXPECT_NE(depth_type, GL_NONE); + + GLint stencil_type = GL_NONE; + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, + &stencil_type); + EXPECT_NE(stencil_type, GL_NONE); +} diff --git a/shell/platform/linux/fl_gnome_settings.cc b/shell/platform/linux/fl_gnome_settings.cc index e20b1bb053091..da958a3898b8b 100644 --- a/shell/platform/linux/fl_gnome_settings.cc +++ b/shell/platform/linux/fl_gnome_settings.cc @@ -22,7 +22,7 @@ struct _FlGnomeSettings { GSettings* interface_settings; }; -enum { kProp0, kPropInterfaceSettings, kPropLast }; +enum { PROP_0, PROP_INTERFACE_SETTINGS, PROP_LAST }; static void fl_gnome_settings_iface_init(FlSettingsInterface* iface); @@ -106,7 +106,7 @@ static void fl_gnome_settings_set_property(GObject* object, GParamSpec* pspec) { FlGnomeSettings* self = FL_GNOME_SETTINGS(object); switch (prop_id) { - case kPropInterfaceSettings: + case PROP_INTERFACE_SETTINGS: fl_gnome_settings_set_interface_settings( self, G_SETTINGS(g_value_get_object(value))); break; @@ -130,7 +130,7 @@ static void fl_gnome_settings_class_init(FlGnomeSettingsClass* klass) { object_class->set_property = fl_gnome_settings_set_property; g_object_class_install_property( - object_class, kPropInterfaceSettings, + object_class, PROP_INTERFACE_SETTINGS, g_param_spec_object( kInterfaceSettings, kInterfaceSettings, kDesktopInterfaceSchema, g_settings_get_type(), diff --git a/shell/platform/linux/fl_key_channel_responder.cc b/shell/platform/linux/fl_key_channel_responder.cc index 757cc5d893937..faf43fdfb65e9 100644 --- a/shell/platform/linux/fl_key_channel_responder.cc +++ b/shell/platform/linux/fl_key_channel_responder.cc @@ -7,95 +7,12 @@ #include #include -#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" +#include "flutter/shell/platform/linux/fl_key_event_channel.h" -static constexpr char kChannelName[] = "flutter/keyevent"; -static constexpr char kTypeKey[] = "type"; -static constexpr char kTypeValueUp[] = "keyup"; -static constexpr char kTypeValueDown[] = "keydown"; -static constexpr char kKeymapKey[] = "keymap"; -static constexpr char kKeyCodeKey[] = "keyCode"; -static constexpr char kScanCodeKey[] = "scanCode"; -static constexpr char kModifiersKey[] = "modifiers"; -static constexpr char kToolkitKey[] = "toolkit"; -static constexpr char kSpecifiedLogicalKey[] = "specifiedLogicalKey"; -static constexpr char kUnicodeScalarValuesKey[] = "unicodeScalarValues"; - -static constexpr char kGtkToolkit[] = "gtk"; -static constexpr char kLinuxKeymap[] = "linux"; - -/* Declare and define FlKeyChannelUserData */ - -/** - * FlKeyChannelUserData: - * The user_data used when #FlKeyChannelResponder sends message through the - * channel. - */ -G_DECLARE_FINAL_TYPE(FlKeyChannelUserData, - fl_key_channel_user_data, - FL, - KEY_CHANNEL_USER_DATA, - GObject); - -struct _FlKeyChannelUserData { - GObject parent_instance; - - // The current responder. - GWeakRef responder; - // The callback provided by the caller #FlKeyboardHandler. - FlKeyChannelResponderAsyncCallback callback; - // The user_data provided by the caller #FlKeyboardHandler. - gpointer user_data; -}; - -// Definition for FlKeyChannelUserData private class. -G_DEFINE_TYPE(FlKeyChannelUserData, fl_key_channel_user_data, G_TYPE_OBJECT) - -// Dispose method for FlKeyChannelUserData private class. -static void fl_key_channel_user_data_dispose(GObject* object) { - g_return_if_fail(FL_IS_KEY_CHANNEL_USER_DATA(object)); - FlKeyChannelUserData* self = FL_KEY_CHANNEL_USER_DATA(object); - - g_weak_ref_clear(&self->responder); - - G_OBJECT_CLASS(fl_key_channel_user_data_parent_class)->dispose(object); -} - -// Class initialization method for FlKeyChannelUserData private class. -static void fl_key_channel_user_data_class_init( - FlKeyChannelUserDataClass* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_key_channel_user_data_dispose; -} - -// Instance initialization method for FlKeyChannelUserData private class. -static void fl_key_channel_user_data_init(FlKeyChannelUserData* self) {} - -// Creates a new FlKeyChannelUserData private class with all information. -// -// The callback and the user_data might be nullptr. -static FlKeyChannelUserData* fl_key_channel_user_data_new( - FlKeyChannelResponder* responder, - FlKeyChannelResponderAsyncCallback callback, - gpointer user_data) { - FlKeyChannelUserData* self = FL_KEY_CHANNEL_USER_DATA( - g_object_new(fl_key_channel_user_data_get_type(), nullptr)); - - g_weak_ref_init(&self->responder, responder); - self->callback = callback; - self->user_data = user_data; - return self; -} - -/* Define FlKeyChannelResponder */ - -// Definition of the FlKeyChannelResponder GObject class. struct _FlKeyChannelResponder { GObject parent_instance; - FlBasicMessageChannel* channel; - - FlKeyChannelResponderMock* mock; + FlKeyEventChannel* channel; }; G_DEFINE_TYPE(FlKeyChannelResponder, fl_key_channel_responder, G_TYPE_OBJECT) @@ -105,31 +22,17 @@ G_DEFINE_TYPE(FlKeyChannelResponder, fl_key_channel_responder, G_TYPE_OBJECT) static void handle_response(GObject* object, GAsyncResult* result, gpointer user_data) { - g_autoptr(FlKeyChannelUserData) data = FL_KEY_CHANNEL_USER_DATA(user_data); - - g_autoptr(FlKeyChannelResponder) self = - FL_KEY_CHANNEL_RESPONDER(g_weak_ref_get(&data->responder)); - if (self == nullptr) { - return; - } + g_autoptr(GTask) task = G_TASK(user_data); + gboolean handled = FALSE; g_autoptr(GError) error = nullptr; - FlBasicMessageChannel* messageChannel = FL_BASIC_MESSAGE_CHANNEL(object); - FlValue* message = - fl_basic_message_channel_send_finish(messageChannel, result, &error); - if (self->mock != nullptr && self->mock->value_converter != nullptr) { - message = self->mock->value_converter(message); - } - bool handled = false; - if (error != nullptr) { + if (!fl_key_event_channel_send_finish(object, result, &handled, &error)) { g_warning("Unable to retrieve framework response: %s", error->message); - } else { - g_autoptr(FlValue) handled_value = - fl_value_lookup_string(message, "handled"); - handled = fl_value_get_bool(handled_value); } - data->callback(handled, data->user_data); + gboolean* return_value = g_new0(gboolean, 1); + *return_value = handled; + g_task_return_pointer(task, return_value, g_free); } // Disposes of an FlKeyChannelResponder instance. @@ -152,40 +55,33 @@ static void fl_key_channel_responder_init(FlKeyChannelResponder* self) {} // Creates a new FlKeyChannelResponder instance, with a messenger used to send // messages to the framework, and an FlTextInputHandler that is used to handle -// key events that the framework doesn't handle. Mainly for testing purposes, it -// also takes an optional callback to call when a response is received, and an -// optional channel name to use when sending messages. +// key events that the framework doesn't handle. FlKeyChannelResponder* fl_key_channel_responder_new( - FlBinaryMessenger* messenger, - FlKeyChannelResponderMock* mock) { + FlBinaryMessenger* messenger) { g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); FlKeyChannelResponder* self = FL_KEY_CHANNEL_RESPONDER( g_object_new(fl_key_channel_responder_get_type(), nullptr)); - self->mock = mock; - g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); - const char* channel_name = - mock == nullptr ? kChannelName : mock->channel_name; - self->channel = fl_basic_message_channel_new(messenger, channel_name, - FL_MESSAGE_CODEC(codec)); + self->channel = fl_key_event_channel_new(messenger); return self; } -void fl_key_channel_responder_handle_event( - FlKeyChannelResponder* self, - FlKeyEvent* event, - uint64_t specified_logical_key, - FlKeyChannelResponderAsyncCallback callback, - gpointer user_data) { +void fl_key_channel_responder_handle_event(FlKeyChannelResponder* self, + FlKeyEvent* event, + uint64_t specified_logical_key, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { g_return_if_fail(event != nullptr); g_return_if_fail(callback != nullptr); - const gchar* type = - fl_key_event_get_is_press(event) ? kTypeValueDown : kTypeValueUp; + FlKeyEventType type = fl_key_event_get_is_press(event) + ? FL_KEY_EVENT_TYPE_KEYDOWN + : FL_KEY_EVENT_TYPE_KEYUP; int64_t scan_code = fl_key_event_get_keycode(event); - int64_t unicode_scarlar_values = + int64_t unicode_scalar_values = gdk_keyval_to_unicode(fl_key_event_get_keyval(event)); // For most modifier keys, GTK keeps track of the "pressed" state of the @@ -234,29 +130,25 @@ void fl_key_channel_responder_handle_event( state |= (shift_lock_pressed || caps_lock_pressed) ? GDK_LOCK_MASK : 0x0; state |= num_lock_pressed ? GDK_MOD2_MASK : 0x0; - g_autoptr(FlValue) message = fl_value_new_map(); - fl_value_set_string_take(message, kTypeKey, fl_value_new_string(type)); - fl_value_set_string_take(message, kKeymapKey, - fl_value_new_string(kLinuxKeymap)); - fl_value_set_string_take(message, kScanCodeKey, fl_value_new_int(scan_code)); - fl_value_set_string_take(message, kToolkitKey, - fl_value_new_string(kGtkToolkit)); - fl_value_set_string_take(message, kKeyCodeKey, - fl_value_new_int(fl_key_event_get_keyval(event))); - fl_value_set_string_take(message, kModifiersKey, fl_value_new_int(state)); - if (unicode_scarlar_values != 0) { - fl_value_set_string_take(message, kUnicodeScalarValuesKey, - fl_value_new_int(unicode_scarlar_values)); - } + fl_key_event_channel_send( + self->channel, type, scan_code, fl_key_event_get_keyval(event), state, + unicode_scalar_values, specified_logical_key, nullptr, handle_response, + g_task_new(self, cancellable, callback, user_data)); +} - if (specified_logical_key != 0) { - fl_value_set_string_take(message, kSpecifiedLogicalKey, - fl_value_new_int(specified_logical_key)); +gboolean fl_key_channel_responder_handle_event_finish( + FlKeyChannelResponder* self, + GAsyncResult* result, + gboolean* handled, + GError** error) { + g_return_val_if_fail(g_task_is_valid(result, self), FALSE); + + g_autofree gboolean* return_value = + static_cast(g_task_propagate_pointer(G_TASK(result), error)); + if (return_value == nullptr) { + return FALSE; } - FlKeyChannelUserData* data = - fl_key_channel_user_data_new(self, callback, user_data); - // Send the message off to the framework for handling (or not). - fl_basic_message_channel_send(self->channel, message, nullptr, - handle_response, data); + *handled = *return_value; + return TRUE; } diff --git a/shell/platform/linux/fl_key_channel_responder.h b/shell/platform/linux/fl_key_channel_responder.h index 66a47d22bd1ac..198fa1b883bc2 100644 --- a/shell/platform/linux/fl_key_channel_responder.h +++ b/shell/platform/linux/fl_key_channel_responder.h @@ -7,30 +7,6 @@ #include "flutter/shell/platform/linux/fl_key_event.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h" - -typedef FlValue* (*FlValueConverter)(FlValue*); - -/** - * FlKeyChannelResponderMock: - * - * Allows mocking of FlKeyChannelResponder methods and values. Only used in - * unittests. - */ -typedef struct _FlKeyChannelResponderMock { - /** - * FlKeyChannelResponderMock::value_converter: - * If #value_converter is not nullptr, then this function is applied to the - * reply of the message, whose return value is taken as the message reply. - */ - FlValueConverter value_converter; - - /** - * FlKeyChannelResponderMock::channel_name: - * Mocks the channel name to send the message. - */ - const char* channel_name; -} FlKeyChannelResponderMock; G_BEGIN_DECLS @@ -40,18 +16,6 @@ G_DECLARE_FINAL_TYPE(FlKeyChannelResponder, KEY_CHANNEL_RESPONDER, GObject); -/** - * FlKeyChannelResponderAsyncCallback: - * @event: whether the event has been handled. - * @user_data: the same value as user_data sent by - * #fl_key_responder_handle_event. - * - * The signature for a callback with which a #FlKeyChannelResponder - *asynchronously reports whether the responder handles the event. - **/ -typedef void (*FlKeyChannelResponderAsyncCallback)(bool handled, - gpointer user_data); - /** * FlKeyChannelResponder: * @@ -64,15 +28,13 @@ typedef void (*FlKeyChannelResponderAsyncCallback)(bool handled, /** * fl_key_channel_responder_new: * @messenger: the messenger that the message channel should be built on. - * @mock: options to mock several functionalities. Only used in unittests. * * Creates a new #FlKeyChannelResponder. * * Returns: a new #FlKeyChannelResponder. */ FlKeyChannelResponder* fl_key_channel_responder_new( - FlBinaryMessenger* messenger, - FlKeyChannelResponderMock* mock = nullptr); + FlBinaryMessenger* messenger); /** * fl_key_channel_responder_handle_event: @@ -80,21 +42,37 @@ FlKeyChannelResponder* fl_key_channel_responder_new( * @event: the event to be handled. Must not be null. The object is managed by * callee and must not be assumed available after this function. * @specified_logical_key: - * @callback: the callback to report the result. It should be called exactly - * once. Must not be null. - * @user_data: a value that will be sent back in the callback. Can be null. + * @cancellable: (allow-none): a #GCancellable or %NULL. + * @callback: (scope async): a #GAsyncReadyCallback to call when the event has + * been processed. + * @user_data: (closure): user data to pass to @callback. + * + * Let the responder handle an event. + */ +void fl_key_channel_responder_handle_event(FlKeyChannelResponder* responder, + FlKeyEvent* event, + uint64_t specified_logical_key, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +/** + * fl_key_channel_responder_handle_event_finish: + * @responder: an #FlKeyChannelResponder. + * @result: a #GAsyncResult. + * @handled: location to write if this event was handled by the platform. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Completes request started with fl_key_channel_responder_handle_event(). * - * Let the responder handle an event, expecting the responder to report - * whether to handle the event. The result will be reported by invoking - * `callback` exactly once, which might happen after - * `fl_key_channel_responder_handle_event` or during it. + * Returns %TRUE on success. */ -void fl_key_channel_responder_handle_event( +gboolean fl_key_channel_responder_handle_event_finish( FlKeyChannelResponder* responder, - FlKeyEvent* event, - uint64_t specified_logical_key, - FlKeyChannelResponderAsyncCallback callback, - gpointer user_data); + GAsyncResult* result, + gboolean* handled, + GError** error); G_END_DECLS diff --git a/shell/platform/linux/fl_key_channel_responder_test.cc b/shell/platform/linux/fl_key_channel_responder_test.cc index 2c68d40b0d12a..62307139232b5 100644 --- a/shell/platform/linux/fl_key_channel_responder_test.cc +++ b/shell/platform/linux/fl_key_channel_responder_test.cc @@ -7,64 +7,87 @@ #include "gtest/gtest.h" #include "flutter/shell/platform/linux/fl_binary_messenger_private.h" -#include "flutter/shell/platform/linux/fl_engine_private.h" -#include "flutter/shell/platform/linux/testing/fl_test.h" +#include "flutter/shell/platform/linux/testing/fl_mock_binary_messenger.h" -static const char* expected_value = nullptr; -static gboolean expected_handled = FALSE; +typedef struct { + const gchar* expected_message; + gboolean handled; +} KeyEventData; -static FlValue* echo_response_cb(FlValue* echoed_value) { - gchar* text = fl_value_to_string(echoed_value); - EXPECT_STREQ(text, expected_value); - g_free(text); +static FlValue* key_event_cb(FlMockBinaryMessenger* messenger, + FlValue* message, + gpointer user_data) { + KeyEventData* data = static_cast(user_data); - FlValue* value = fl_value_new_map(); - fl_value_set_string_take(value, "handled", - fl_value_new_bool(expected_handled)); - return value; + g_autofree gchar* message_string = fl_value_to_string(message); + EXPECT_STREQ(message_string, data->expected_message); + + FlValue* response = fl_value_new_map(); + fl_value_set_string_take(response, "handled", + fl_value_new_bool(data->handled)); + + free(data); + + return response; } -static void responder_callback(bool handled, gpointer user_data) { - EXPECT_EQ(handled, expected_handled); - g_main_loop_quit(static_cast(user_data)); +static void set_key_event_channel(FlMockBinaryMessenger* messenger, + const gchar* expected_message, + gboolean handled) { + KeyEventData* data = g_new0(KeyEventData, 1); + data->expected_message = expected_message; + data->handled = handled; + fl_mock_binary_messenger_set_json_message_channel( + messenger, "flutter/keyevent", key_event_cb, data); } // Test sending a letter "A"; TEST(FlKeyChannelResponderTest, SendKeyEvent) { g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); - g_autoptr(FlEngine) engine = make_mock_engine(); - g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine); - FlKeyChannelResponderMock mock{ - .value_converter = echo_response_cb, - .channel_name = "test/echo", - }; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlKeyChannelResponder) responder = - fl_key_channel_responder_new(messenger, &mock); + fl_key_channel_responder_new(FL_BINARY_MESSENGER(messenger)); + set_key_event_channel( + messenger, + "{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, " + "modifiers: 0, unicodeScalarValues: 65}", + FALSE); g_autoptr(FlKeyEvent) event1 = fl_key_event_new( 12345, TRUE, 0x04, GDK_KEY_A, static_cast(0), 0); - fl_key_channel_responder_handle_event(responder, event1, 0, - responder_callback, loop); - expected_value = - "{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, " - "modifiers: 0, unicodeScalarValues: 65}"; - expected_handled = FALSE; - - // Blocks here until echo_response_cb is called. + fl_key_channel_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_channel_responder_handle_event_finish( + FL_KEY_CHANNEL_RESPONDER(object), result, &handled, nullptr)); + EXPECT_FALSE(handled); + g_main_loop_quit(static_cast(user_data)); + }, + loop); g_main_loop_run(loop); + set_key_event_channel( + messenger, + "{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, " + "modifiers: 0, unicodeScalarValues: 65}", + FALSE); g_autoptr(FlKeyEvent) event2 = fl_key_event_new( 23456, FALSE, 0x04, GDK_KEY_A, static_cast(0), 0); - fl_key_channel_responder_handle_event(responder, event2, 0, - responder_callback, loop); - expected_value = - "{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, " - "modifiers: 0, unicodeScalarValues: 65}"; - expected_handled = FALSE; - - // Blocks here until echo_response_cb is called. + fl_key_channel_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_channel_responder_handle_event_finish( + FL_KEY_CHANNEL_RESPONDER(object), result, &handled, nullptr)); + EXPECT_FALSE(handled); + g_main_loop_quit(static_cast(user_data)); + }, + loop); g_main_loop_run(loop); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } void test_lock_event(guint key_code, @@ -72,34 +95,41 @@ void test_lock_event(guint key_code, const char* up_expected) { g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); - g_autoptr(FlEngine) engine = make_mock_engine(); - g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine); - FlKeyChannelResponderMock mock{ - .value_converter = echo_response_cb, - .channel_name = "test/echo", - }; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlKeyChannelResponder) responder = - fl_key_channel_responder_new(messenger, &mock); + fl_key_channel_responder_new(FL_BINARY_MESSENGER(messenger)); + set_key_event_channel(messenger, down_expected, FALSE); g_autoptr(FlKeyEvent) event1 = fl_key_event_new( 12345, TRUE, 0x04, key_code, static_cast(0), 0); - fl_key_channel_responder_handle_event(responder, event1, 0, - responder_callback, loop); - expected_value = down_expected; - expected_handled = FALSE; - - // Blocks here until echo_response_cb is called. + fl_key_channel_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_channel_responder_handle_event_finish( + FL_KEY_CHANNEL_RESPONDER(object), result, &handled, nullptr)); + EXPECT_FALSE(handled); + g_main_loop_quit(static_cast(user_data)); + }, + loop); g_main_loop_run(loop); - expected_value = up_expected; - expected_handled = FALSE; + set_key_event_channel(messenger, up_expected, FALSE); g_autoptr(FlKeyEvent) event2 = fl_key_event_new( 12346, FALSE, 0x04, key_code, static_cast(0), 0); - fl_key_channel_responder_handle_event(responder, event2, 0, - responder_callback, loop); - - // Blocks here until echo_response_cb is called. + fl_key_channel_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_channel_responder_handle_event_finish( + FL_KEY_CHANNEL_RESPONDER(object), result, &handled, nullptr)); + EXPECT_FALSE(handled); + g_main_loop_quit(static_cast(user_data)); + }, + loop); g_main_loop_run(loop); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } // Test sending a "NumLock" keypress. @@ -132,50 +162,58 @@ TEST(FlKeyChannelResponderTest, SendShiftLockKeyEvent) { TEST(FlKeyChannelResponderTest, TestKeyEventHandledByFramework) { g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); - g_autoptr(FlEngine) engine = make_mock_engine(); - g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine); - FlKeyChannelResponderMock mock{ - .value_converter = echo_response_cb, - .channel_name = "test/echo", - }; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlKeyChannelResponder) responder = - fl_key_channel_responder_new(messenger, &mock); + fl_key_channel_responder_new(FL_BINARY_MESSENGER(messenger)); + set_key_event_channel( + messenger, + "{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, " + "keyCode: 65, modifiers: 0, unicodeScalarValues: 65}", + TRUE); g_autoptr(FlKeyEvent) event = fl_key_event_new( 12345, TRUE, 0x04, GDK_KEY_A, static_cast(0), 0); - fl_key_channel_responder_handle_event(responder, event, 0, responder_callback, - loop); - expected_handled = TRUE; - expected_value = - "{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, " - "keyCode: 65, modifiers: 0, unicodeScalarValues: 65}"; - - // Blocks here until echo_response_cb is called. + fl_key_channel_responder_handle_event( + responder, event, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_channel_responder_handle_event_finish( + FL_KEY_CHANNEL_RESPONDER(object), result, &handled, nullptr)); + EXPECT_TRUE(handled); + g_main_loop_quit(static_cast(user_data)); + }, + loop); g_main_loop_run(loop); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlKeyChannelResponderTest, UseSpecifiedLogicalKey) { g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); - g_autoptr(FlEngine) engine = make_mock_engine(); - g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine); - FlKeyChannelResponderMock mock{ - .value_converter = echo_response_cb, - .channel_name = "test/echo", - }; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlKeyChannelResponder) responder = - fl_key_channel_responder_new(messenger, &mock); + fl_key_channel_responder_new(FL_BINARY_MESSENGER(messenger)); - g_autoptr(FlKeyEvent) event = fl_key_event_new( - 12345, TRUE, 0x04, GDK_KEY_A, static_cast(0), 0); - fl_key_channel_responder_handle_event(responder, event, 888, - responder_callback, loop); - expected_handled = TRUE; - expected_value = + set_key_event_channel( + messenger, "{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, " "keyCode: 65, modifiers: 0, unicodeScalarValues: 65, " - "specifiedLogicalKey: 888}"; - - // Blocks here until echo_response_cb is called. + "specifiedLogicalKey: 888}", + TRUE); + g_autoptr(FlKeyEvent) event = fl_key_event_new( + 12345, TRUE, 0x04, GDK_KEY_A, static_cast(0), 0); + fl_key_channel_responder_handle_event( + responder, event, 888, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_channel_responder_handle_event_finish( + FL_KEY_CHANNEL_RESPONDER(object), result, &handled, nullptr)); + EXPECT_TRUE(handled); + g_main_loop_quit(static_cast(user_data)); + }, + loop); g_main_loop_run(loop); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } diff --git a/shell/platform/linux/fl_key_embedder_responder.cc b/shell/platform/linux/fl_key_embedder_responder.cc index 4118b6c1ddaa2..1f714acdc59b2 100644 --- a/shell/platform/linux/fl_key_embedder_responder.cc +++ b/shell/platform/linux/fl_key_embedder_responder.cc @@ -125,9 +125,9 @@ static FlKeyEmbedderUserData* fl_key_embedder_user_data_new( namespace { typedef enum { - kStateLogicUndecided, - kStateLogicNormal, - kStateLogicReversed, + STATE_LOGIC_INFERRENCE_UNDECIDED, + STATE_LOGIC_INFERRENCE_NORMAL, + STATE_LOGIC_INFERRENCE_REVERSED, } StateLogicInferrence; } @@ -246,7 +246,7 @@ FlKeyEmbedderResponder* fl_key_embedder_responder_new( self->pressing_records = g_hash_table_new(g_direct_hash, g_direct_equal); self->mapping_records = g_hash_table_new(g_direct_hash, g_direct_equal); self->lock_records = 0; - self->caps_lock_state_logic_inferrence = kStateLogicUndecided; + self->caps_lock_state_logic_inferrence = STATE_LOGIC_INFERRENCE_UNDECIDED; self->modifier_bit_to_checked_keys = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); @@ -557,7 +557,8 @@ static void update_caps_lock_state_logic_inferrence( bool is_down_event, bool enabled_by_state, int stage_by_record) { - if (self->caps_lock_state_logic_inferrence != kStateLogicUndecided) { + if (self->caps_lock_state_logic_inferrence != + STATE_LOGIC_INFERRENCE_UNDECIDED) { return; } if (!is_down_event) { @@ -567,9 +568,9 @@ static void update_caps_lock_state_logic_inferrence( stage_by_record, is_down_event, enabled_by_state, false); if ((stage_by_event == 0 && stage_by_record == 2) || (stage_by_event == 2 && stage_by_record == 0)) { - self->caps_lock_state_logic_inferrence = kStateLogicReversed; + self->caps_lock_state_logic_inferrence = STATE_LOGIC_INFERRENCE_REVERSED; } else { - self->caps_lock_state_logic_inferrence = kStateLogicNormal; + self->caps_lock_state_logic_inferrence = STATE_LOGIC_INFERRENCE_NORMAL; } } @@ -639,11 +640,11 @@ static void synchronize_lock_states_loop_body(gpointer key, update_caps_lock_state_logic_inferrence(self, context->is_down, enabled_by_state, stage_by_record); g_return_if_fail(self->caps_lock_state_logic_inferrence != - kStateLogicUndecided); + STATE_LOGIC_INFERRENCE_UNDECIDED); } const bool reverse_state_logic = checked_key->is_caps_lock && - self->caps_lock_state_logic_inferrence == kStateLogicReversed; + self->caps_lock_state_logic_inferrence == STATE_LOGIC_INFERRENCE_REVERSED; const int stage_by_event = this_key_is_event_key ? find_stage_by_self_event(stage_by_record, context->is_down, diff --git a/shell/platform/linux/fl_key_embedder_responder.h b/shell/platform/linux/fl_key_embedder_responder.h index 1905d4b3fa329..18e1f16ca536d 100644 --- a/shell/platform/linux/fl_key_embedder_responder.h +++ b/shell/platform/linux/fl_key_embedder_responder.h @@ -8,8 +8,6 @@ #include "flutter/shell/platform/linux/fl_engine_private.h" #include "flutter/shell/platform/linux/fl_key_event.h" -constexpr int kMaxConvertedKeyData = 3; - // The signature of a function that FlKeyEmbedderResponder calls on every key // event. // diff --git a/shell/platform/linux/fl_key_event_channel.cc b/shell/platform/linux/fl_key_event_channel.cc new file mode 100644 index 0000000000000..bd9fedc9f10f2 --- /dev/null +++ b/shell/platform/linux/fl_key_event_channel.cc @@ -0,0 +1,122 @@ +// 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. + +#include "flutter/shell/platform/linux/fl_key_event_channel.h" + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" + +static constexpr char kChannelName[] = "flutter/keyevent"; +static constexpr char kTypeKey[] = "type"; +static constexpr char kTypeValueUp[] = "keyup"; +static constexpr char kTypeValueDown[] = "keydown"; +static constexpr char kKeymapKey[] = "keymap"; +static constexpr char kKeyCodeKey[] = "keyCode"; +static constexpr char kScanCodeKey[] = "scanCode"; +static constexpr char kModifiersKey[] = "modifiers"; +static constexpr char kToolkitKey[] = "toolkit"; +static constexpr char kSpecifiedLogicalKey[] = "specifiedLogicalKey"; +static constexpr char kUnicodeScalarValuesKey[] = "unicodeScalarValues"; + +static constexpr char kGtkToolkit[] = "gtk"; +static constexpr char kLinuxKeymap[] = "linux"; + +static constexpr int64_t kUnicodeScalarValuesUnset = 0; +static constexpr int64_t kSpecifiedLogicalKeyUnset = 0; + +struct _FlKeyEventChannel { + GObject parent_instance; + + FlBasicMessageChannel* channel; +}; + +G_DEFINE_TYPE(FlKeyEventChannel, fl_key_event_channel, G_TYPE_OBJECT) + +static void fl_key_event_channel_dispose(GObject* object) { + FlKeyEventChannel* self = FL_KEY_EVENT_CHANNEL(object); + + g_clear_object(&self->channel); + + G_OBJECT_CLASS(fl_key_event_channel_parent_class)->dispose(object); +} + +static void fl_key_event_channel_class_init(FlKeyEventChannelClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_key_event_channel_dispose; +} + +static void fl_key_event_channel_init(FlKeyEventChannel* self) {} + +FlKeyEventChannel* fl_key_event_channel_new(FlBinaryMessenger* messenger) { + g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + + FlKeyEventChannel* self = FL_KEY_EVENT_CHANNEL( + g_object_new(fl_key_event_channel_get_type(), nullptr)); + + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + self->channel = fl_basic_message_channel_new(messenger, kChannelName, + FL_MESSAGE_CODEC(codec)); + + return self; +} + +void fl_key_event_channel_send(FlKeyEventChannel* self, + FlKeyEventType type, + int64_t scan_code, + int64_t key_code, + int64_t modifiers, + int64_t unicode_scalar_values, + int64_t specified_logical_key, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_KEY_EVENT_CHANNEL(self)); + + const gchar* type_string; + switch (type) { + case FL_KEY_EVENT_TYPE_KEYUP: + type_string = kTypeValueUp; + break; + case FL_KEY_EVENT_TYPE_KEYDOWN: + type_string = kTypeValueDown; + break; + default: + g_assert_not_reached(); + } + + g_autoptr(FlValue) message = fl_value_new_map(); + fl_value_set_string_take(message, kTypeKey, fl_value_new_string(type_string)); + fl_value_set_string_take(message, kKeymapKey, + fl_value_new_string(kLinuxKeymap)); + fl_value_set_string_take(message, kScanCodeKey, fl_value_new_int(scan_code)); + fl_value_set_string_take(message, kToolkitKey, + fl_value_new_string(kGtkToolkit)); + fl_value_set_string_take(message, kKeyCodeKey, fl_value_new_int(key_code)); + fl_value_set_string_take(message, kModifiersKey, fl_value_new_int(modifiers)); + if (unicode_scalar_values != kUnicodeScalarValuesUnset) { + fl_value_set_string_take(message, kUnicodeScalarValuesKey, + fl_value_new_int(unicode_scalar_values)); + } + if (specified_logical_key != kSpecifiedLogicalKeyUnset) { + fl_value_set_string_take(message, kSpecifiedLogicalKey, + fl_value_new_int(specified_logical_key)); + } + fl_basic_message_channel_send(self->channel, message, cancellable, callback, + user_data); +} + +gboolean fl_key_event_channel_send_finish(GObject* object, + GAsyncResult* result, + gboolean* handled, + GError** error) { + g_autoptr(FlValue) message = fl_basic_message_channel_send_finish( + FL_BASIC_MESSAGE_CHANNEL(object), result, error); + if (message == nullptr) { + return FALSE; + } + + FlValue* handled_value = fl_value_lookup_string(message, "handled"); + *handled = fl_value_get_bool(handled_value); + + return TRUE; +} diff --git a/shell/platform/linux/fl_key_event_channel.h b/shell/platform/linux/fl_key_event_channel.h new file mode 100644 index 0000000000000..ae8bd38f12e47 --- /dev/null +++ b/shell/platform/linux/fl_key_event_channel.h @@ -0,0 +1,86 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_CHANNEL_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_CHANNEL_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlKeyEventChannel, + fl_key_event_channel, + FL, + KEY_EVENT_CHANNEL, + GObject); + +/** + * FlKeyEventChannel: + * + * #FlKeyEventChannel is a channel that implements the shell side + * of SystemChannels.keyEvent from the Flutter services library. + */ + +typedef enum { + FL_KEY_EVENT_TYPE_KEYUP, + FL_KEY_EVENT_TYPE_KEYDOWN, +} FlKeyEventType; + +/** + * fl_key_event_channel_new: + * @messenger: an #FlBinaryMessenger + * + * Creates a new channel that implements SystemChannels.keyEvent from the + * Flutter services library. + * + * Returns: a new #FlKeyEventChannel. + */ +FlKeyEventChannel* fl_key_event_channel_new(FlBinaryMessenger* messenger); + +/** + * fl_key_event_channel_send: + * @channel: an #FlKeyEventChannel + * @type: event type. + * @scan_code: scan code. + * @key_code: key code. + * @modifiers: modifiers. + * @unicode_scarlar_values: + * @specified_logical_key: + * @cancellable: (allow-none): a #GCancellable or %NULL. + * @callback: (scope async): a #GAsyncReadyCallback to call when the method + * returns. + * @user_data: (closure): user data to pass to @callback. + * + * Send a key event to the platform. + */ +void fl_key_event_channel_send(FlKeyEventChannel* channel, + FlKeyEventType type, + int64_t scan_code, + int64_t key_code, + int64_t modifiers, + int64_t unicode_scarlar_values, + int64_t specified_logical_key, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +/** + * fl_key_event_channel_send_finish: + * @object: + * @result: a #GAsyncResult. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Completes request started with fl_key_event_channel_send(). + * + * Returns: %TRUE on success. + */ +gboolean fl_key_event_channel_send_finish(GObject* object, + GAsyncResult* result, + gboolean* handled, + GError** error); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_CHANNEL_H_ diff --git a/shell/platform/linux/fl_keyboard_channel.cc b/shell/platform/linux/fl_keyboard_channel.cc new file mode 100644 index 0000000000000..8b7bc2706dc4d --- /dev/null +++ b/shell/platform/linux/fl_keyboard_channel.cc @@ -0,0 +1,84 @@ +// 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. + +#include "flutter/shell/platform/linux/fl_keyboard_channel.h" + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" + +static constexpr char kChannelName[] = "flutter/keyboard"; + +static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState"; + +struct _FlKeyboardChannel { + GObject parent_instance; + + FlMethodChannel* channel; + + FlKeyboardChannelVTable* vtable; + + gpointer user_data; +}; + +G_DEFINE_TYPE(FlKeyboardChannel, fl_keyboard_channel, G_TYPE_OBJECT) + +static FlMethodResponse* get_keyboard_state(FlKeyboardChannel* self) { + g_autoptr(FlValue) result = self->vtable->get_keyboard_state(self->user_data); + return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); +} + +// Called when a method call is received from Flutter. +static void method_call_cb(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data) { + FlKeyboardChannel* self = FL_KEYBOARD_CHANNEL(user_data); + + const gchar* method = fl_method_call_get_name(method_call); + g_autoptr(FlMethodResponse) response = nullptr; + if (strcmp(method, kGetKeyboardStateMethod) == 0) { + response = get_keyboard_state(self); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + g_autoptr(GError) error = nullptr; + if (!fl_method_call_respond(method_call, response, &error)) { + g_warning("Failed to send method call response: %s", error->message); + } +} + +static void fl_keyboard_channel_dispose(GObject* object) { + FlKeyboardChannel* self = FL_KEYBOARD_CHANNEL(object); + + g_clear_object(&self->channel); + + G_OBJECT_CLASS(fl_keyboard_channel_parent_class)->dispose(object); +} + +static void fl_keyboard_channel_class_init(FlKeyboardChannelClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_keyboard_channel_dispose; +} + +static void fl_keyboard_channel_init(FlKeyboardChannel* self) {} + +FlKeyboardChannel* fl_keyboard_channel_new(FlBinaryMessenger* messenger, + FlKeyboardChannelVTable* vtable, + gpointer user_data) { + g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + g_return_val_if_fail(vtable != nullptr, nullptr); + + FlKeyboardChannel* self = FL_KEYBOARD_CHANNEL( + g_object_new(fl_keyboard_channel_get_type(), nullptr)); + + self->vtable = vtable; + self->user_data = user_data; + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + self->channel = + fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self, + nullptr); + + return self; +} diff --git a/shell/platform/linux/fl_keyboard_channel.h b/shell/platform/linux/fl_keyboard_channel.h new file mode 100644 index 0000000000000..69c2b590f9fe5 --- /dev/null +++ b/shell/platform/linux/fl_keyboard_channel.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_CHANNEL_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_CHANNEL_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlKeyboardChannel, + fl_keyboard_channel, + FL, + KEYBOARD_CHANNEL, + GObject); + +/** + * FlKeyboardChannel: + * + * #FlKeyboardChannel is a channel that implements the shell side + * of SystemChannels.keyboard from the Flutter services library. + */ + +typedef struct { + FlValue* (*get_keyboard_state)(gpointer user_data); +} FlKeyboardChannelVTable; + +/** + * fl_keyboard_channel_new: + * @messenger: an #FlBinaryMessenger + * @vtable: callbacks for incoming method calls. + * @user_data: data to pass in callbacks. + * + * Creates a new channel that implements SystemChannels.keyboard from the + * Flutter services library. + * + * Returns: a new #FlKeyboardChannel + */ +FlKeyboardChannel* fl_keyboard_channel_new(FlBinaryMessenger* messenger, + FlKeyboardChannelVTable* vtable, + gpointer user_data); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_CHANNEL_H_ diff --git a/shell/platform/linux/fl_keyboard_handler.cc b/shell/platform/linux/fl_keyboard_handler.cc index 266b9b1e007ca..9a1478da7f3d5 100644 --- a/shell/platform/linux/fl_keyboard_handler.cc +++ b/shell/platform/linux/fl_keyboard_handler.cc @@ -4,32 +4,27 @@ #include "flutter/shell/platform/linux/fl_keyboard_handler.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" - -static constexpr char kChannelName[] = "flutter/keyboard"; -static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState"; +#include "flutter/shell/platform/linux/fl_keyboard_channel.h" struct _FlKeyboardHandler { GObject parent_instance; - GWeakRef view_delegate; + FlKeyboardManager* keyboard_manager; // The channel used by the framework to query the keyboard pressed state. - FlMethodChannel* channel; + FlKeyboardChannel* channel; }; G_DEFINE_TYPE(FlKeyboardHandler, fl_keyboard_handler, G_TYPE_OBJECT); // Returns the keyboard pressed state. -static FlMethodResponse* get_keyboard_state(FlKeyboardHandler* self) { - g_autoptr(FlValue) result = fl_value_new_map(); +static FlValue* get_keyboard_state(gpointer user_data) { + FlKeyboardHandler* self = FL_KEYBOARD_HANDLER(user_data); - g_autoptr(FlKeyboardViewDelegate) view_delegate = - FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); + FlValue* result = fl_value_new_map(); GHashTable* pressing_records = - fl_keyboard_view_delegate_get_keyboard_state(view_delegate); + fl_keyboard_manager_get_pressed_state(self->keyboard_manager); g_hash_table_foreach( pressing_records, @@ -42,34 +37,14 @@ static FlMethodResponse* get_keyboard_state(FlKeyboardHandler* self) { fl_value_new_int(logical_key)); }, result); - return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); -} - -// Called when a method call on flutter/keyboard is received from Flutter. -static void method_call_handler(FlMethodChannel* channel, - FlMethodCall* method_call, - gpointer user_data) { - FlKeyboardHandler* self = FL_KEYBOARD_HANDLER(user_data); - const gchar* method = fl_method_call_get_name(method_call); - - g_autoptr(FlMethodResponse) response = nullptr; - if (strcmp(method, kGetKeyboardStateMethod) == 0) { - response = get_keyboard_state(self); - } else { - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - } - - g_autoptr(GError) error = nullptr; - if (!fl_method_call_respond(method_call, response, &error)) { - g_warning("Failed to send method call response: %s", error->message); - } + return result; } static void fl_keyboard_handler_dispose(GObject* object) { FlKeyboardHandler* self = FL_KEYBOARD_HANDLER(object); - g_weak_ref_clear(&self->view_delegate); + g_clear_object(&self->keyboard_manager); g_clear_object(&self->channel); G_OBJECT_CLASS(fl_keyboard_handler_parent_class)->dispose(object); @@ -79,23 +54,21 @@ static void fl_keyboard_handler_class_init(FlKeyboardHandlerClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_keyboard_handler_dispose; } +static FlKeyboardChannelVTable keyboard_channel_vtable = { + .get_keyboard_state = get_keyboard_state}; + static void fl_keyboard_handler_init(FlKeyboardHandler* self) {} FlKeyboardHandler* fl_keyboard_handler_new( FlBinaryMessenger* messenger, - FlKeyboardViewDelegate* view_delegate) { - g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(view_delegate), nullptr); - + FlKeyboardManager* keyboard_manager) { FlKeyboardHandler* self = FL_KEYBOARD_HANDLER( g_object_new(fl_keyboard_handler_get_type(), nullptr)); - g_weak_ref_init(&self->view_delegate, view_delegate); + self->keyboard_manager = FL_KEYBOARD_MANAGER(g_object_ref(keyboard_manager)); // Setup the flutter/keyboard channel. - g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); self->channel = - fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); - fl_method_channel_set_method_call_handler(self->channel, method_call_handler, - self, nullptr); + fl_keyboard_channel_new(messenger, &keyboard_channel_vtable, self); return self; } diff --git a/shell/platform/linux/fl_keyboard_handler.h b/shell/platform/linux/fl_keyboard_handler.h index d2631215d8e2d..f711186824c90 100644 --- a/shell/platform/linux/fl_keyboard_handler.h +++ b/shell/platform/linux/fl_keyboard_handler.h @@ -7,7 +7,7 @@ #include -#include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h" +#include "flutter/shell/platform/linux/fl_keyboard_manager.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" G_BEGIN_DECLS @@ -26,16 +26,15 @@ G_DECLARE_FINAL_TYPE(FlKeyboardHandler, /** * fl_keyboard_handler_new: - * @view_delegate: An interface that the handler requires to communicate with - * the platform. Usually implemented by FlView. + * @messenger: a #FlBinaryMessenger. + * @keyboard_manager: a #FlKeyboardManager. * * Create a new #FlKeyboardHandler. * * Returns: a new #FlKeyboardHandler. */ -FlKeyboardHandler* fl_keyboard_handler_new( - FlBinaryMessenger* messenger, - FlKeyboardViewDelegate* view_delegate); +FlKeyboardHandler* fl_keyboard_handler_new(FlBinaryMessenger* messenger, + FlKeyboardManager* keyboard_manager); G_END_DECLS diff --git a/shell/platform/linux/fl_keyboard_handler_test.cc b/shell/platform/linux/fl_keyboard_handler_test.cc index a4d92784a1f02..5cb1f6c86b210 100644 --- a/shell/platform/linux/fl_keyboard_handler_test.cc +++ b/shell/platform/linux/fl_keyboard_handler_test.cc @@ -4,9 +4,10 @@ #include "flutter/shell/platform/linux/fl_keyboard_handler.h" +#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" #include "flutter/shell/platform/linux/fl_method_codec_private.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" -#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h" +#include "flutter/shell/platform/linux/testing/fl_mock_binary_messenger.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -26,19 +27,6 @@ G_DECLARE_FINAL_TYPE(FlMockKeyboardHandlerDelegate, G_END_DECLS -MATCHER_P(MethodSuccessResponse, result, "") { - g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); - g_autoptr(FlMethodResponse) response = - fl_method_codec_decode_response(FL_METHOD_CODEC(codec), arg, nullptr); - fl_method_response_get_result(response, nullptr); - if (fl_value_equal(fl_method_response_get_result(response, nullptr), - result)) { - return true; - } - *result_listener << ::testing::PrintToString(response); - return false; -} - struct _FlMockKeyboardHandlerDelegate { GObject parent_instance; }; @@ -60,19 +48,8 @@ static void fl_mock_keyboard_handler_delegate_init( static void fl_mock_keyboard_handler_delegate_class_init( FlMockKeyboardHandlerDelegateClass* klass) {} -static GHashTable* fl_mock_view_keyboard_get_keyboard_state( - FlKeyboardViewDelegate* view_delegate) { - GHashTable* result = g_hash_table_new(g_direct_hash, g_direct_equal); - g_hash_table_insert(result, reinterpret_cast(kMockPhysicalKey), - reinterpret_cast(kMockLogicalKey)); - - return result; -} - static void fl_mock_keyboard_handler_delegate_keyboard_view_delegate_iface_init( - FlKeyboardViewDelegateInterface* iface) { - iface->get_keyboard_state = fl_mock_view_keyboard_get_keyboard_state; -} + FlKeyboardViewDelegateInterface* iface) {} static FlMockKeyboardHandlerDelegate* fl_mock_keyboard_handler_delegate_new() { FlMockKeyboardHandlerDelegate* self = FL_MOCK_KEYBOARD_HANDLER_DELEGATE( @@ -85,25 +62,48 @@ static FlMockKeyboardHandlerDelegate* fl_mock_keyboard_handler_delegate_new() { } TEST(FlKeyboardHandlerTest, KeyboardChannelGetPressedState) { - ::testing::NiceMock messenger; - - g_autoptr(FlKeyboardHandler) handler = fl_keyboard_handler_new( - messenger, - FL_KEYBOARD_VIEW_DELEGATE(fl_mock_keyboard_handler_delegate_new())); + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); + g_autoptr(FlEngine) engine = + FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", + FL_BINARY_MESSENGER(messenger), nullptr)); + g_autoptr(FlMockKeyboardHandlerDelegate) view_delegate = + fl_mock_keyboard_handler_delegate_new(); + g_autoptr(FlKeyboardManager) manager = + fl_keyboard_manager_new(engine, FL_KEYBOARD_VIEW_DELEGATE(view_delegate)); + fl_keyboard_manager_set_get_pressed_state_handler( + manager, + [](gpointer user_data) { + GHashTable* result = g_hash_table_new(g_direct_hash, g_direct_equal); + g_hash_table_insert(result, + reinterpret_cast(kMockPhysicalKey), + reinterpret_cast(kMockLogicalKey)); + + return result; + }, + nullptr); + g_autoptr(FlKeyboardHandler) handler = + fl_keyboard_handler_new(FL_BINARY_MESSENGER(messenger), manager); EXPECT_NE(handler, nullptr); - g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); - g_autoptr(GBytes) message = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), kGetKeyboardStateMethod, nullptr, nullptr); - - g_autoptr(FlValue) response = fl_value_new_map(); - fl_value_set_take(response, fl_value_new_int(kMockPhysicalKey), - fl_value_new_int(kMockLogicalKey)); - EXPECT_CALL(messenger, - fl_binary_messenger_send_response( - ::testing::Eq(messenger), ::testing::_, - MethodSuccessResponse(response), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage(kKeyboardChannelName, message); + gboolean called = FALSE; + fl_mock_binary_messenger_invoke_standard_method( + messenger, kKeyboardChannelName, kGetKeyboardStateMethod, nullptr, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + + g_autoptr(FlValue) expected_result = fl_value_new_map(); + fl_value_set_take(expected_result, fl_value_new_int(kMockPhysicalKey), + fl_value_new_int(kMockLogicalKey)); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } diff --git a/shell/platform/linux/fl_keyboard_manager.cc b/shell/platform/linux/fl_keyboard_manager.cc index 2f6f240a0a491..0a4925737a750 100644 --- a/shell/platform/linux/fl_keyboard_manager.cc +++ b/shell/platform/linux/fl_keyboard_manager.cc @@ -21,10 +21,10 @@ /* Declarations of private classes */ -G_DECLARE_FINAL_TYPE(FlKeyboardManagerUserData, - fl_keyboard_manager_user_data, +G_DECLARE_FINAL_TYPE(FlKeyboardManagerData, + fl_keyboard_manager_data, FL, - KEYBOARD_MANAGER_USER_DATA, + KEYBOARD_MANAGER_DATA, GObject); /* End declarations */ @@ -62,52 +62,50 @@ void debug_format_layout_data(std::string& debug_layout_data, } // namespace -/* Define FlKeyboardManagerUserData */ +/* Define FlKeyboardManagerData */ /** - * FlKeyboardManagerUserData: + * FlKeyboardManagerData: * The user_data used when #FlKeyboardManager sends event to * responders. */ -struct _FlKeyboardManagerUserData { +struct _FlKeyboardManagerData { GObject parent_instance; // The owner manager. GWeakRef manager; - uint64_t sequence_id; + + FlKeyboardPendingEvent* pending; }; -G_DEFINE_TYPE(FlKeyboardManagerUserData, - fl_keyboard_manager_user_data, - G_TYPE_OBJECT) +G_DEFINE_TYPE(FlKeyboardManagerData, fl_keyboard_manager_data, G_TYPE_OBJECT) -static void fl_keyboard_manager_user_data_dispose(GObject* object) { - g_return_if_fail(FL_IS_KEYBOARD_MANAGER_USER_DATA(object)); - FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA(object); +static void fl_keyboard_manager_data_dispose(GObject* object) { + g_return_if_fail(FL_IS_KEYBOARD_MANAGER_DATA(object)); + FlKeyboardManagerData* self = FL_KEYBOARD_MANAGER_DATA(object); g_weak_ref_clear(&self->manager); - G_OBJECT_CLASS(fl_keyboard_manager_user_data_parent_class)->dispose(object); + G_OBJECT_CLASS(fl_keyboard_manager_data_parent_class)->dispose(object); } -static void fl_keyboard_manager_user_data_class_init( - FlKeyboardManagerUserDataClass* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_user_data_dispose; +static void fl_keyboard_manager_data_class_init( + FlKeyboardManagerDataClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_data_dispose; } -static void fl_keyboard_manager_user_data_init( - FlKeyboardManagerUserData* self) {} +static void fl_keyboard_manager_data_init(FlKeyboardManagerData* self) {} -// Creates a new FlKeyboardManagerUserData private class with all information. -static FlKeyboardManagerUserData* fl_keyboard_manager_user_data_new( +// Creates a new FlKeyboardManagerData private class with all information. +static FlKeyboardManagerData* fl_keyboard_manager_data_new( FlKeyboardManager* manager, - uint64_t sequence_id) { - FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA( - g_object_new(fl_keyboard_manager_user_data_get_type(), nullptr)); + FlKeyboardPendingEvent* pending) { + FlKeyboardManagerData* self = FL_KEYBOARD_MANAGER_DATA( + g_object_new(fl_keyboard_manager_data_get_type(), nullptr)); g_weak_ref_init(&self->manager, manager); - self->sequence_id = sequence_id; + self->pending = FL_KEYBOARD_PENDING_EVENT(g_object_ref(pending)); return self; } @@ -116,14 +114,22 @@ static FlKeyboardManagerUserData* fl_keyboard_manager_user_data_new( struct _FlKeyboardManager { GObject parent_instance; + GWeakRef engine; + GWeakRef view_delegate; + FlKeyboardManagerSendKeyEventHandler send_key_event_handler; + gpointer send_key_event_handler_user_data; + FlKeyboardManagerLookupKeyHandler lookup_key_handler; gpointer lookup_key_handler_user_data; FlKeyboardManagerRedispatchEventHandler redispatch_handler; gpointer redispatch_handler_user_data; + FlKeyboardManagerGetPressedStateHandler get_pressed_state_handler; + gpointer get_pressed_state_handler_user_data; + FlKeyEmbedderResponder* key_embedder_responder; FlKeyChannelResponder* key_channel_responder; @@ -140,9 +146,6 @@ struct _FlKeyboardManager { // Its elements are unreferenced when removed. GPtrArray* pending_redispatches; - // The last sequence ID used. Increased by 1 by every use. - uint64_t last_sequence_id; - // Record the derived layout. // // It is cleared when the platform reports a layout switch. Each entry, @@ -164,6 +167,8 @@ struct _FlKeyboardManager { GdkKeymap* keymap; gulong keymap_keys_changed_cb_id; // Signal connection ID for // keymap-keys-changed + + GCancellable* cancellable; }; G_DEFINE_TYPE(FlKeyboardManager, fl_keyboard_manager, G_TYPE_OBJECT); @@ -197,15 +202,6 @@ static gboolean g_ptr_array_find_with_equal_func1(GPtrArray* haystack, return FALSE; } -// Compare a #FlKeyboardPendingEvent with the given sequence_id. -static gboolean compare_pending_by_sequence_id(gconstpointer a, - gconstpointer b) { - FlKeyboardPendingEvent* pending = - FL_KEYBOARD_PENDING_EVENT(const_cast(a)); - uint64_t sequence_id = *reinterpret_cast(b); - return fl_keyboard_pending_event_get_sequence_id(pending) == sequence_id; -} - // Compare a #FlKeyboardPendingEvent with the given hash. static gboolean compare_pending_by_hash(gconstpointer a, gconstpointer b) { FlKeyboardPendingEvent* pending = @@ -234,50 +230,23 @@ static bool fl_keyboard_manager_remove_redispatched(FlKeyboardManager* self, } // The callback used by a responder after the event was dispatched. -static void responder_handle_event_callback(bool handled, - gpointer user_data_ptr, - gboolean is_embedder) { - g_return_if_fail(FL_IS_KEYBOARD_MANAGER_USER_DATA(user_data_ptr)); - FlKeyboardManagerUserData* user_data = - FL_KEYBOARD_MANAGER_USER_DATA(user_data_ptr); - - g_autoptr(FlKeyboardManager) self = - FL_KEYBOARD_MANAGER(g_weak_ref_get(&user_data->manager)); - if (self == nullptr) { - return; - } - +static void responder_handle_event_callback(FlKeyboardManager* self, + FlKeyboardPendingEvent* pending) { g_autoptr(FlKeyboardViewDelegate) view_delegate = FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); if (view_delegate == nullptr) { return; } - guint result_index = -1; - gboolean found = g_ptr_array_find_with_equal_func1( - self->pending_responds, &user_data->sequence_id, - compare_pending_by_sequence_id, &result_index); - g_return_if_fail(found); - FlKeyboardPendingEvent* pending = FL_KEYBOARD_PENDING_EVENT( - g_ptr_array_index(self->pending_responds, result_index)); - g_return_if_fail(pending != nullptr); - if (is_embedder) { - fl_keyboard_pending_event_mark_embedder_replied(pending, handled); - } else { - fl_keyboard_pending_event_mark_channel_replied(pending, handled); - } // All responders have replied. if (fl_keyboard_pending_event_is_complete(pending)) { - g_object_unref(user_data_ptr); - gpointer removed = - g_ptr_array_remove_index_fast(self->pending_responds, result_index); - g_return_if_fail(removed == pending); + g_ptr_array_remove(self->pending_responds, pending); bool should_redispatch = !fl_keyboard_pending_event_get_any_handled(pending) && !fl_keyboard_view_delegate_text_filter_key_press( view_delegate, fl_keyboard_pending_event_get_event(pending)); if (should_redispatch) { - g_ptr_array_add(self->pending_redispatches, pending); + g_ptr_array_add(self->pending_redispatches, g_object_ref(pending)); FlKeyEvent* event = fl_keyboard_pending_event_get_event(pending); if (self->redispatch_handler != nullptr) { self->redispatch_handler(event, self->redispatch_handler_user_data); @@ -288,20 +257,49 @@ static void responder_handle_event_callback(bool handled, event_type == GDK_KEY_RELEASE); gdk_event_put(fl_key_event_get_origin(event)); } - } else { - g_object_unref(pending); } } } static void responder_handle_embedder_event_callback(bool handled, - gpointer user_data_ptr) { - responder_handle_event_callback(handled, user_data_ptr, TRUE); + gpointer user_data) { + g_autoptr(FlKeyboardManagerData) data = FL_KEYBOARD_MANAGER_DATA(user_data); + + fl_keyboard_pending_event_mark_embedder_replied(data->pending, handled); + + g_autoptr(FlKeyboardManager) self = + FL_KEYBOARD_MANAGER(g_weak_ref_get(&data->manager)); + if (self == nullptr) { + return; + } + + responder_handle_event_callback(self, data->pending); } -static void responder_handle_channel_event_callback(bool handled, - gpointer user_data_ptr) { - responder_handle_event_callback(handled, user_data_ptr, FALSE); +static void responder_handle_channel_event_cb(GObject* object, + GAsyncResult* result, + gpointer user_data) { + g_autoptr(FlKeyboardManagerData) data = FL_KEYBOARD_MANAGER_DATA(user_data); + + g_autoptr(GError) error = nullptr; + gboolean handled; + if (!fl_key_channel_responder_handle_event_finish( + FL_KEY_CHANNEL_RESPONDER(object), result, &handled, &error)) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning("Failed to handle key event in platform: %s", error->message); + } + return; + } + + g_autoptr(FlKeyboardManager) self = + FL_KEYBOARD_MANAGER(g_weak_ref_get(&data->manager)); + if (self == nullptr) { + return; + } + + fl_keyboard_pending_event_mark_channel_replied(data->pending, handled); + + responder_handle_event_callback(self, data->pending); } static uint16_t convert_key_to_char(FlKeyboardManager* self, @@ -410,6 +408,9 @@ static void guarantee_layout(FlKeyboardManager* self, FlKeyEvent* event) { static void fl_keyboard_manager_dispose(GObject* object) { FlKeyboardManager* self = FL_KEYBOARD_MANAGER(object); + g_cancellable_cancel(self->cancellable); + + g_weak_ref_clear(&self->engine); g_weak_ref_clear(&self->view_delegate); self->keycode_to_goals.reset(); @@ -425,6 +426,7 @@ static void fl_keyboard_manager_dispose(GObject* object) { g_signal_handler_disconnect(self->keymap, self->keymap_keys_changed_cb_id); self->keymap_keys_changed_cb_id = 0; } + g_clear_object(&self->cancellable); G_OBJECT_CLASS(fl_keyboard_manager_parent_class)->dispose(object); } @@ -450,37 +452,41 @@ static void fl_keyboard_manager_init(FlKeyboardManager* self) { self->pending_responds = g_ptr_array_new(); self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref); - self->last_sequence_id = 1; - self->keymap = gdk_keymap_get_for_display(gdk_display_get_default()); self->keymap_keys_changed_cb_id = g_signal_connect_swapped( self->keymap, "keys-changed", G_CALLBACK(keymap_keys_changed_cb), self); + self->cancellable = g_cancellable_new(); } FlKeyboardManager* fl_keyboard_manager_new( - FlBinaryMessenger* messenger, + FlEngine* engine, FlKeyboardViewDelegate* view_delegate) { g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(view_delegate), nullptr); FlKeyboardManager* self = FL_KEYBOARD_MANAGER( g_object_new(fl_keyboard_manager_get_type(), nullptr)); + g_weak_ref_init(&self->engine, engine); g_weak_ref_init(&self->view_delegate, view_delegate); self->key_embedder_responder = fl_key_embedder_responder_new( [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, void* callback_user_data, void* send_key_event_user_data) { FlKeyboardManager* self = FL_KEYBOARD_MANAGER(send_key_event_user_data); - g_autoptr(FlKeyboardViewDelegate) view_delegate = - FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); - if (view_delegate == nullptr) { - return; + if (self->send_key_event_handler != nullptr) { + self->send_key_event_handler(event, callback, callback_user_data, + self->send_key_event_handler_user_data); + } else { + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine != nullptr) { + fl_engine_send_key_event(engine, event, callback, + callback_user_data); + } } - fl_keyboard_view_delegate_send_key_event(view_delegate, event, callback, - callback_user_data); }, self); - self->key_channel_responder = fl_key_channel_responder_new(messenger); + self->key_channel_responder = + fl_key_channel_responder_new(fl_engine_get_binary_messenger(engine)); return self; } @@ -497,21 +503,20 @@ gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* self, return FALSE; } - FlKeyboardPendingEvent* pending = - fl_keyboard_pending_event_new(event, ++self->last_sequence_id); + FlKeyboardPendingEvent* pending = fl_keyboard_pending_event_new(event); g_ptr_array_add(self->pending_responds, pending); - FlKeyboardManagerUserData* user_data = fl_keyboard_manager_user_data_new( - self, fl_keyboard_pending_event_get_sequence_id(pending)); + g_autoptr(FlKeyboardManagerData) data = + fl_keyboard_manager_data_new(self, pending); uint64_t specified_logical_key = fl_keyboard_layout_get_logical_key( self->derived_layout, fl_key_event_get_group(event), fl_key_event_get_keycode(event)); fl_key_embedder_responder_handle_event( self->key_embedder_responder, event, specified_logical_key, - responder_handle_embedder_event_callback, user_data); + responder_handle_embedder_event_callback, g_object_ref(data)); fl_key_channel_responder_handle_event( self->key_channel_responder, event, specified_logical_key, - responder_handle_channel_event_callback, user_data); + self->cancellable, responder_handle_channel_event_cb, g_object_ref(data)); return TRUE; } @@ -532,8 +537,22 @@ void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* self, GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* self) { g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), nullptr); - return fl_key_embedder_responder_get_pressed_state( - self->key_embedder_responder); + if (self->get_pressed_state_handler != nullptr) { + return self->get_pressed_state_handler( + self->get_pressed_state_handler_user_data); + } else { + return fl_key_embedder_responder_get_pressed_state( + self->key_embedder_responder); + } +} + +void fl_keyboard_manager_set_send_key_event_handler( + FlKeyboardManager* self, + FlKeyboardManagerSendKeyEventHandler send_key_event_handler, + gpointer user_data) { + g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self)); + self->send_key_event_handler = send_key_event_handler; + self->send_key_event_handler_user_data = user_data; } void fl_keyboard_manager_set_lookup_key_handler( @@ -558,3 +577,12 @@ void fl_keyboard_manager_set_redispatch_handler( self->redispatch_handler = redispatch_handler; self->redispatch_handler_user_data = user_data; } + +void fl_keyboard_manager_set_get_pressed_state_handler( + FlKeyboardManager* self, + FlKeyboardManagerGetPressedStateHandler get_pressed_state_handler, + gpointer user_data) { + g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self)); + self->get_pressed_state_handler = get_pressed_state_handler; + self->get_pressed_state_handler_user_data = user_data; +} diff --git a/shell/platform/linux/fl_keyboard_manager.h b/shell/platform/linux/fl_keyboard_manager.h index 9701342091eef..9a96860d1a248 100644 --- a/shell/platform/linux/fl_keyboard_manager.h +++ b/shell/platform/linux/fl_keyboard_manager.h @@ -8,7 +8,7 @@ #include #include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" G_BEGIN_DECLS @@ -36,7 +36,7 @@ G_DECLARE_FINAL_TYPE(FlKeyboardManager, /** * fl_keyboard_manager_new: - * @messenger: an #FlBinaryMessenger. + * @engine: an #FlEngine. * @view_delegate: An interface that the manager requires to communicate with * the platform. Usually implemented by FlView. * @@ -45,7 +45,7 @@ G_DECLARE_FINAL_TYPE(FlKeyboardManager, * Returns: a new #FlKeyboardManager. */ FlKeyboardManager* fl_keyboard_manager_new( - FlBinaryMessenger* messenger, + FlEngine* engine, FlKeyboardViewDelegate* view_delegate); /** @@ -94,6 +94,23 @@ void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* manager, */ GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* manager); +typedef void (*FlKeyboardManagerSendKeyEventHandler)( + const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, + void* callback_user_data, + gpointer user_data); + +/** + * fl_keyboard_manager_set_send_key_event_handler: + * @manager: the #FlKeyboardManager self. + * + * Set the handler for sending events, for testing purposes only. + */ +void fl_keyboard_manager_set_send_key_event_handler( + FlKeyboardManager* manager, + FlKeyboardManagerSendKeyEventHandler send_key_event_handler, + gpointer user_data); + typedef guint (*FlKeyboardManagerLookupKeyHandler)(const GdkKeymapKey* key, gpointer user_data); @@ -131,6 +148,20 @@ void fl_keyboard_manager_set_redispatch_handler( FlKeyboardManagerRedispatchEventHandler redispatch_handler, gpointer user_data); +typedef GHashTable* (*FlKeyboardManagerGetPressedStateHandler)( + gpointer user_data); + +/** + * fl_keyboard_manager_set_get_pressed_state_handler: + * @manager: the #FlKeyboardManager self. + * + * Set the handler for gettting the keyboard state, for testing purposes only. + */ +void fl_keyboard_manager_set_get_pressed_state_handler( + FlKeyboardManager* manager, + FlKeyboardManagerGetPressedStateHandler get_pressed_state_handler, + gpointer user_data); + G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_ diff --git a/shell/platform/linux/fl_keyboard_manager_test.cc b/shell/platform/linux/fl_keyboard_manager_test.cc index 7718acbad097b..e1044fa44bcd2 100644 --- a/shell/platform/linux/fl_keyboard_manager_test.cc +++ b/shell/platform/linux/fl_keyboard_manager_test.cc @@ -16,7 +16,6 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" #include "flutter/shell/platform/linux/testing/fl_test.h" #include "flutter/shell/platform/linux/testing/mock_keymap.h" -#include "flutter/shell/platform/linux/testing/mock_text_input_handler.h" #include "flutter/testing/testing.h" #include "gmock/gmock.h" @@ -26,7 +25,7 @@ // stacktrace wouldn't print where the function is called in the unit tests. #define EXPECT_KEY_EVENT(RECORD, TYPE, PHYSICAL, LOGICAL, CHAR, SYNTHESIZED) \ - EXPECT_EQ((RECORD).type, CallRecord::kKeyCallEmbedder); \ + EXPECT_EQ((RECORD).type, CallRecord::KEY_CALL_EMBEDDER); \ EXPECT_EQ((RECORD).event->type, (TYPE)); \ EXPECT_EQ((RECORD).event->physical, (PHYSICAL)); \ EXPECT_EQ((RECORD).event->logical, (LOGICAL)); \ @@ -34,7 +33,7 @@ EXPECT_EQ((RECORD).event->synthesized, (SYNTHESIZED)); #define VERIFY_DOWN(OUT_LOGICAL, OUT_CHAR) \ - EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder); \ + EXPECT_EQ(call_records[0].type, CallRecord::KEY_CALL_EMBEDDER); \ EXPECT_EQ(call_records[0].event->type, kFlutterKeyEventTypeDown); \ EXPECT_EQ(call_records[0].event->logical, (OUT_LOGICAL)); \ EXPECT_STREQ(call_records[0].event->character, (OUT_CHAR)); \ @@ -79,8 +78,8 @@ typedef std::function RedispatchHandler; // An instance of `CallRecord` might not have all the fields filled. typedef struct { enum { - kKeyCallEmbedder, - kKeyCallChannel, + KEY_CALL_EMBEDDER, + KEY_CALL_CHANNEL, } type; AsyncKeyCallback callback; @@ -233,6 +232,10 @@ static void fl_mock_binary_messenger_set_warns_on_channel_overflow( // Mock implementation. Do nothing. } +static void fl_mock_binary_messenger_shutdown(FlBinaryMessenger* messenger) { + // Mock implementation. Do nothing. +} + static void fl_mock_key_binary_messenger_iface_init( FlBinaryMessengerInterface* iface) { iface->set_message_handler_on_channel = @@ -257,6 +260,7 @@ static void fl_mock_key_binary_messenger_iface_init( iface->resize_channel = fl_mock_binary_messenger_resize_channel; iface->set_warns_on_channel_overflow = fl_mock_binary_messenger_set_warns_on_channel_overflow; + iface->shutdown = fl_mock_binary_messenger_shutdown; } static FlMockKeyBinaryMessenger* fl_mock_key_binary_messenger_new() { @@ -279,9 +283,6 @@ static void fl_mock_key_binary_messenger_set_callback_handler( struct _FlMockViewDelegate { GObject parent_instance; - - FlMockKeyBinaryMessenger* messenger; - EmbedderCallHandler embedder_handler; bool text_filter_result; }; @@ -297,30 +298,7 @@ G_DEFINE_TYPE_WITH_CODE( static void fl_mock_view_delegate_init(FlMockViewDelegate* self) {} -static void fl_mock_view_delegate_dispose(GObject* object) { - FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(object); - - g_clear_object(&self->messenger); - - G_OBJECT_CLASS(fl_mock_view_delegate_parent_class)->dispose(object); -} - -static void fl_mock_view_delegate_class_init(FlMockViewDelegateClass* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_mock_view_delegate_dispose; -} - -static void fl_mock_view_keyboard_send_key_event( - FlKeyboardViewDelegate* view_delegate, - const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, - void* user_data) { - FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate); - self->embedder_handler(event, [callback, user_data](bool handled) { - if (callback != nullptr) { - callback(handled, user_data); - } - }); -} +static void fl_mock_view_delegate_class_init(FlMockViewDelegateClass* klass) {} static gboolean fl_mock_view_keyboard_text_filter_key_press( FlKeyboardViewDelegate* view_delegate, @@ -331,7 +309,6 @@ static gboolean fl_mock_view_keyboard_text_filter_key_press( static void fl_mock_view_keyboard_delegate_iface_init( FlKeyboardViewDelegateInterface* iface) { - iface->send_key_event = fl_mock_view_keyboard_send_key_event; iface->text_filter_key_press = fl_mock_view_keyboard_text_filter_key_press; } @@ -342,16 +319,9 @@ static FlMockViewDelegate* fl_mock_view_delegate_new() { // Added to stop compiler complaining about an unused function. FL_IS_MOCK_VIEW_DELEGATE(self); - self->messenger = fl_mock_key_binary_messenger_new(); - return self; } -static void fl_mock_view_set_embedder_handler(FlMockViewDelegate* self, - EmbedderCallHandler handler) { - self->embedder_handler = std::move(handler); -} - static void fl_mock_view_set_text_filter_result(FlMockViewDelegate* self, bool result) { self->text_filter_result = result; @@ -362,14 +332,31 @@ static void fl_mock_view_set_text_filter_result(FlMockViewDelegate* self, class KeyboardTester { public: KeyboardTester() { + messenger_ = fl_mock_key_binary_messenger_new(); + view_ = fl_mock_view_delegate_new(); respondToEmbedderCallsWith(false); respondToChannelCallsWith(false); respondToTextInputWith(false); setLayout(kLayoutUs); - manager_ = fl_keyboard_manager_new(FL_BINARY_MESSENGER(view_->messenger), - FL_KEYBOARD_VIEW_DELEGATE(view_)); + engine_ = FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", + FL_BINARY_MESSENGER(messenger_), nullptr)); + manager_ = + fl_keyboard_manager_new(engine_, FL_KEYBOARD_VIEW_DELEGATE(view_)); + fl_keyboard_manager_set_send_key_event_handler( + manager_, + [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, + void* callback_user_data, gpointer user_data) { + KeyboardTester* self = reinterpret_cast(user_data); + self->embedder_handler_(event, + [callback, callback_user_data](bool handled) { + if (callback != nullptr) { + callback(handled, callback_user_data); + } + }); + }, + this); fl_keyboard_manager_set_lookup_key_handler( manager_, [](const GdkKeymapKey* key, gpointer user_data) { @@ -394,6 +381,8 @@ class KeyboardTester { ~KeyboardTester() { g_clear_object(&view_); + g_clear_object(&messenger_); + g_clear_object(&engine_); g_clear_object(&manager_); g_clear_pointer(&redispatched_events_, g_ptr_array_unref); } @@ -432,53 +421,51 @@ class KeyboardTester { } void respondToEmbedderCallsWith(bool response) { - fl_mock_view_set_embedder_handler( - view_, [response, this](const FlutterKeyEvent* event, - const AsyncKeyCallback& callback) { - EXPECT_FALSE(during_redispatch_); - callback(response); - }); + embedder_handler_ = [response, this](const FlutterKeyEvent* event, + const AsyncKeyCallback& callback) { + EXPECT_FALSE(during_redispatch_); + callback(response); + }; } void recordEmbedderCallsTo(std::vector& storage) { - fl_mock_view_set_embedder_handler( - view_, [&storage, this](const FlutterKeyEvent* event, - AsyncKeyCallback callback) { - EXPECT_FALSE(during_redispatch_); - auto new_event = std::make_unique(*event); - char* new_event_character = cloneString(event->character); - new_event->character = new_event_character; - storage.push_back(CallRecord{ - .type = CallRecord::kKeyCallEmbedder, - .callback = std::move(callback), - .event = std::move(new_event), - .event_character = std::unique_ptr(new_event_character), - }); - }); + embedder_handler_ = [&storage, this](const FlutterKeyEvent* event, + AsyncKeyCallback callback) { + EXPECT_FALSE(during_redispatch_); + auto new_event = std::make_unique(*event); + char* new_event_character = cloneString(event->character); + new_event->character = new_event_character; + storage.push_back(CallRecord{ + .type = CallRecord::KEY_CALL_EMBEDDER, + .callback = std::move(callback), + .event = std::move(new_event), + .event_character = std::unique_ptr(new_event_character), + }); + }; } void respondToEmbedderCallsWithAndRecordsTo( bool response, std::vector& storage) { - fl_mock_view_set_embedder_handler( - view_, [&storage, response, this](const FlutterKeyEvent* event, - const AsyncKeyCallback& callback) { - EXPECT_FALSE(during_redispatch_); - auto new_event = std::make_unique(*event); - char* new_event_character = cloneString(event->character); - new_event->character = new_event_character; - storage.push_back(CallRecord{ - .type = CallRecord::kKeyCallEmbedder, - .event = std::move(new_event), - .event_character = std::unique_ptr(new_event_character), - }); - callback(response); - }); + embedder_handler_ = [&storage, response, this]( + const FlutterKeyEvent* event, + const AsyncKeyCallback& callback) { + EXPECT_FALSE(during_redispatch_); + auto new_event = std::make_unique(*event); + char* new_event_character = cloneString(event->character); + new_event->character = new_event_character; + storage.push_back(CallRecord{ + .type = CallRecord::KEY_CALL_EMBEDDER, + .event = std::move(new_event), + .event_character = std::unique_ptr(new_event_character), + }); + callback(response); + }; } void respondToChannelCallsWith(bool response) { fl_mock_key_binary_messenger_set_callback_handler( - view_->messenger, [response, this](const AsyncKeyCallback& callback) { + messenger_, [response, this](const AsyncKeyCallback& callback) { EXPECT_FALSE(during_redispatch_); callback(response); }); @@ -486,10 +473,10 @@ class KeyboardTester { void recordChannelCallsTo(std::vector& storage) { fl_mock_key_binary_messenger_set_callback_handler( - view_->messenger, [&storage, this](AsyncKeyCallback callback) { + messenger_, [&storage, this](AsyncKeyCallback callback) { EXPECT_FALSE(during_redispatch_); storage.push_back(CallRecord{ - .type = CallRecord::kKeyCallChannel, + .type = CallRecord::KEY_CALL_CHANNEL, .callback = std::move(callback), }); }); @@ -519,10 +506,13 @@ class KeyboardTester { private: FlMockViewDelegate* view_; + FlMockKeyBinaryMessenger* messenger_ = nullptr; + FlEngine* engine_ = nullptr; FlKeyboardManager* manager_ = nullptr; GPtrArray* redispatched_events_ = nullptr; bool during_redispatch_ = false; const MockLayoutData* layout_data_; + EmbedderCallHandler embedder_handler_; static gboolean _flushChannelMessagesCb(gpointer data) { g_autoptr(GMainLoop) loop = reinterpret_cast(data); @@ -713,8 +703,8 @@ TEST(FlKeyboardManagerTest, WithTwoAsyncDelegates) { EXPECT_EQ(redispatched->len, 0u); EXPECT_EQ(call_records.size(), 2u); - EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder); - EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel); + EXPECT_EQ(call_records[0].type, CallRecord::KEY_CALL_EMBEDDER); + EXPECT_EQ(call_records[1].type, CallRecord::KEY_CALL_CHANNEL); call_records[0].callback(true); call_records[1].callback(false); @@ -733,8 +723,8 @@ TEST(FlKeyboardManagerTest, WithTwoAsyncDelegates) { EXPECT_EQ(redispatched->len, 0u); EXPECT_EQ(call_records.size(), 2u); - EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder); - EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel); + EXPECT_EQ(call_records[0].type, CallRecord::KEY_CALL_EMBEDDER); + EXPECT_EQ(call_records[1].type, CallRecord::KEY_CALL_CHANNEL); call_records[0].callback(false); call_records[1].callback(false); diff --git a/shell/platform/linux/fl_keyboard_pending_event.cc b/shell/platform/linux/fl_keyboard_pending_event.cc index 7b19cca9fcdb7..5a24ae85ac439 100644 --- a/shell/platform/linux/fl_keyboard_pending_event.cc +++ b/shell/platform/linux/fl_keyboard_pending_event.cc @@ -19,9 +19,6 @@ struct _FlKeyboardPendingEvent { // The target event. FlKeyEvent* event; - // Unique ID to identify pending responds. - uint64_t sequence_id; - // True if the embedder responder has replied. bool embedder_replied; @@ -58,13 +55,11 @@ static void fl_keyboard_pending_event_init(FlKeyboardPendingEvent* self) {} // Creates a new FlKeyboardPendingEvent by providing the target event, // the sequence ID, and the number of responders that will reply. -FlKeyboardPendingEvent* fl_keyboard_pending_event_new(FlKeyEvent* event, - uint64_t sequence_id) { +FlKeyboardPendingEvent* fl_keyboard_pending_event_new(FlKeyEvent* event) { FlKeyboardPendingEvent* self = FL_KEYBOARD_PENDING_EVENT( g_object_new(fl_keyboard_pending_event_get_type(), nullptr)); self->event = FL_KEY_EVENT(g_object_ref(event)); - self->sequence_id = sequence_id; self->hash = fl_key_event_hash(self->event); return self; @@ -75,12 +70,6 @@ FlKeyEvent* fl_keyboard_pending_event_get_event(FlKeyboardPendingEvent* self) { return self->event; } -uint64_t fl_keyboard_pending_event_get_sequence_id( - FlKeyboardPendingEvent* self) { - g_return_val_if_fail(FL_IS_KEYBOARD_PENDING_EVENT(self), 0); - return self->sequence_id; -} - uint64_t fl_keyboard_pending_event_get_hash(FlKeyboardPendingEvent* self) { g_return_val_if_fail(FL_IS_KEYBOARD_PENDING_EVENT(self), 0); return self->hash; diff --git a/shell/platform/linux/fl_keyboard_pending_event.h b/shell/platform/linux/fl_keyboard_pending_event.h index e419598d17147..31827c3c76bdf 100644 --- a/shell/platform/linux/fl_keyboard_pending_event.h +++ b/shell/platform/linux/fl_keyboard_pending_event.h @@ -15,14 +15,10 @@ G_DECLARE_FINAL_TYPE(FlKeyboardPendingEvent, KEYBOARD_PENDING_EVENT, GObject); -FlKeyboardPendingEvent* fl_keyboard_pending_event_new(FlKeyEvent* event, - uint64_t sequence_id); +FlKeyboardPendingEvent* fl_keyboard_pending_event_new(FlKeyEvent* event); FlKeyEvent* fl_keyboard_pending_event_get_event(FlKeyboardPendingEvent* event); -uint64_t fl_keyboard_pending_event_get_sequence_id( - FlKeyboardPendingEvent* event); - uint64_t fl_keyboard_pending_event_get_hash(FlKeyboardPendingEvent* event); void fl_keyboard_pending_event_mark_embedder_replied( diff --git a/shell/platform/linux/fl_keyboard_view_delegate.cc b/shell/platform/linux/fl_keyboard_view_delegate.cc index effdd3980876f..27ec02f363e73 100644 --- a/shell/platform/linux/fl_keyboard_view_delegate.cc +++ b/shell/platform/linux/fl_keyboard_view_delegate.cc @@ -11,17 +11,6 @@ G_DEFINE_INTERFACE(FlKeyboardViewDelegate, static void fl_keyboard_view_delegate_default_init( FlKeyboardViewDelegateInterface* iface) {} -void fl_keyboard_view_delegate_send_key_event(FlKeyboardViewDelegate* self, - const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, - void* user_data) { - g_return_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self)); - g_return_if_fail(event != nullptr); - - FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->send_key_event( - self, event, callback, user_data); -} - gboolean fl_keyboard_view_delegate_text_filter_key_press( FlKeyboardViewDelegate* self, FlKeyEvent* event) { @@ -31,10 +20,3 @@ gboolean fl_keyboard_view_delegate_text_filter_key_press( return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->text_filter_key_press( self, event); } - -GHashTable* fl_keyboard_view_delegate_get_keyboard_state( - FlKeyboardViewDelegate* self) { - g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self), nullptr); - - return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->get_keyboard_state(self); -} diff --git a/shell/platform/linux/fl_keyboard_view_delegate.h b/shell/platform/linux/fl_keyboard_view_delegate.h index 4e7ca39069a98..edb706f773721 100644 --- a/shell/platform/linux/fl_keyboard_view_delegate.h +++ b/shell/platform/linux/fl_keyboard_view_delegate.h @@ -34,34 +34,10 @@ G_DECLARE_INTERFACE(FlKeyboardViewDelegate, struct _FlKeyboardViewDelegateInterface { GTypeInterface g_iface; - void (*send_key_event)(FlKeyboardViewDelegate* delegate, - const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, - void* user_data); - gboolean (*text_filter_key_press)(FlKeyboardViewDelegate* delegate, FlKeyEvent* event); - - GHashTable* (*get_keyboard_state)(FlKeyboardViewDelegate* delegate); }; -/** - * fl_keyboard_view_delegate_send_key_event: - * - * Handles `FlKeyboardHandler`'s request to send a `FlutterKeyEvent` through the - * embedder API to the framework. - * - * The ownership of the `event` is kept by the keyboard handler, and the `event` - * might be immediately destroyed after this function returns. - * - * The `callback` must eventually be called exactly once with the event result - * and the `user_data`. - */ -void fl_keyboard_view_delegate_send_key_event(FlKeyboardViewDelegate* delegate, - const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, - void* user_data); - /** * fl_keyboard_view_delegate_text_filter_key_press: * @@ -74,16 +50,6 @@ gboolean fl_keyboard_view_delegate_text_filter_key_press( FlKeyboardViewDelegate* delegate, FlKeyEvent* event); -/** - * fl_keyboard_view_delegate_get_keyboard_state: - * - * Returns the keyboard pressed state. The hash table contains one entry per - * pressed keys, mapping from the logical key to the physical key.* - * - */ -GHashTable* fl_keyboard_view_delegate_get_keyboard_state( - FlKeyboardViewDelegate* delegate); - G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_VIEW_DELEGATE_H_ diff --git a/shell/platform/linux/fl_method_channel.cc b/shell/platform/linux/fl_method_channel.cc index 266bfa7d812d7..1fe5277e0866f 100644 --- a/shell/platform/linux/fl_method_channel.cc +++ b/shell/platform/linux/fl_method_channel.cc @@ -63,8 +63,8 @@ static void message_cb(FlBinaryMessenger* messenger, static void message_response_cb(GObject* object, GAsyncResult* result, gpointer user_data) { - GTask* task = G_TASK(user_data); - g_task_return_pointer(task, result, g_object_unref); + g_autoptr(GTask) task = G_TASK(user_data); + g_task_return_pointer(task, g_object_ref(result), g_object_unref); } // Called when the channel handler is closed. @@ -178,7 +178,7 @@ G_MODULE_EXPORT void fl_method_channel_invoke_method( fl_method_codec_encode_method_call(self->codec, method, args, &error); if (message == nullptr) { if (task != nullptr) { - g_task_return_error(task, error); + g_task_return_error(task, g_error_copy(error)); } return; } @@ -196,8 +196,12 @@ G_MODULE_EXPORT FlMethodResponse* fl_method_channel_invoke_method_finish( g_return_val_if_fail(FL_IS_METHOD_CHANNEL(self), nullptr); g_return_val_if_fail(g_task_is_valid(result, self), nullptr); - g_autoptr(GTask) task = G_TASK(result); - GAsyncResult* r = G_ASYNC_RESULT(g_task_propagate_pointer(task, nullptr)); + GTask* task = G_TASK(result); + g_autoptr(GAsyncResult) r = + G_ASYNC_RESULT(g_task_propagate_pointer(task, error)); + if (r == nullptr) { + return nullptr; + } g_autoptr(GBytes) response = fl_binary_messenger_send_on_channel_finish(self->messenger, r, error); @@ -216,34 +220,13 @@ gboolean fl_method_channel_respond( g_return_val_if_fail(FL_IS_METHOD_CHANNEL(self), FALSE); g_return_val_if_fail(FL_IS_BINARY_MESSENGER_RESPONSE_HANDLE(response_handle), FALSE); - g_return_val_if_fail(FL_IS_METHOD_SUCCESS_RESPONSE(response) || - FL_IS_METHOD_ERROR_RESPONSE(response) || - FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response), - FALSE); + g_return_val_if_fail(FL_IS_METHOD_RESPONSE(response), FALSE); - g_autoptr(GBytes) message = nullptr; - if (FL_IS_METHOD_SUCCESS_RESPONSE(response)) { - FlMethodSuccessResponse* r = FL_METHOD_SUCCESS_RESPONSE(response); - message = fl_method_codec_encode_success_envelope( - self->codec, fl_method_success_response_get_result(r), error); - if (message == nullptr) { - return FALSE; - } - } else if (FL_IS_METHOD_ERROR_RESPONSE(response)) { - FlMethodErrorResponse* r = FL_METHOD_ERROR_RESPONSE(response); - message = fl_method_codec_encode_error_envelope( - self->codec, fl_method_error_response_get_code(r), - fl_method_error_response_get_message(r), - fl_method_error_response_get_details(r), error); - if (message == nullptr) { - return FALSE; - } - } else if (FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response)) { - message = nullptr; - } else { - g_assert_not_reached(); + g_autoptr(GBytes) message = + fl_method_codec_encode_response(self->codec, response, error); + if (message == nullptr) { + return FALSE; } - return fl_binary_messenger_send_response(self->messenger, response_handle, message, error); } diff --git a/shell/platform/linux/fl_method_channel_test.cc b/shell/platform/linux/fl_method_channel_test.cc index 7aa0c6572cd0f..c7d366cfde60b 100644 --- a/shell/platform/linux/fl_method_channel_test.cc +++ b/shell/platform/linux/fl_method_channel_test.cc @@ -744,3 +744,36 @@ TEST(FlMethodChannelTest, DisposeAReplacedMethodChannel) { EXPECT_EQ(user_data1.count, 101); EXPECT_EQ(user_data2.count, 102); } + +// Called when the method call response is received in the CustomType +// test. +static void custom_type_response_cb(GObject* object, + GAsyncResult* result, + gpointer user_data) { + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish( + FL_METHOD_CHANNEL(object), result, &error); + EXPECT_EQ(response, nullptr); + EXPECT_NE(error, nullptr); + EXPECT_STREQ(error->message, "Custom value not implemented"); + + g_main_loop_quit(static_cast(user_data)); +} + +// Checks invoking a method with a custom type generates an error. +TEST(FlMethodChannelTest, CustomType) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = fl_method_channel_new( + messenger, "test/standard-method", FL_METHOD_CODEC(codec)); + + g_autoptr(FlValue) args = fl_value_new_custom(42, nullptr, nullptr); + fl_method_channel_invoke_method(channel, "Echo", args, nullptr, + custom_type_response_cb, loop); + + // Blocks here until custom_type_response_cb is called. + g_main_loop_run(loop); +} diff --git a/shell/platform/linux/fl_method_codec.cc b/shell/platform/linux/fl_method_codec.cc index 5b6d87c47a39a..a0c023ca759c3 100644 --- a/shell/platform/linux/fl_method_codec.cc +++ b/shell/platform/linux/fl_method_codec.cc @@ -59,6 +59,32 @@ GBytes* fl_method_codec_encode_error_envelope(FlMethodCodec* self, self, code, message, details, error); } +GBytes* fl_method_codec_encode_response(FlMethodCodec* self, + FlMethodResponse* response, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CODEC(self), nullptr); + g_return_val_if_fail(FL_IS_METHOD_SUCCESS_RESPONSE(response) || + FL_IS_METHOD_ERROR_RESPONSE(response) || + FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response), + nullptr); + + if (FL_IS_METHOD_SUCCESS_RESPONSE(response)) { + FlMethodSuccessResponse* r = FL_METHOD_SUCCESS_RESPONSE(response); + return fl_method_codec_encode_success_envelope( + self, fl_method_success_response_get_result(r), error); + } else if (FL_IS_METHOD_ERROR_RESPONSE(response)) { + FlMethodErrorResponse* r = FL_METHOD_ERROR_RESPONSE(response); + return fl_method_codec_encode_error_envelope( + self, fl_method_error_response_get_code(r), + fl_method_error_response_get_message(r), + fl_method_error_response_get_details(r), error); + } else if (FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response)) { + return g_bytes_new(nullptr, 0); + } else { + g_assert_not_reached(); + } +} + FlMethodResponse* fl_method_codec_decode_response(FlMethodCodec* self, GBytes* message, GError** error) { diff --git a/shell/platform/linux/fl_method_codec_private.h b/shell/platform/linux/fl_method_codec_private.h index 8912093adc29c..1880b09cd0983 100644 --- a/shell/platform/linux/fl_method_codec_private.h +++ b/shell/platform/linux/fl_method_codec_private.h @@ -85,6 +85,21 @@ GBytes* fl_method_codec_encode_error_envelope(FlMethodCodec* codec, FlValue* details, GError** error); +/** + * fl_method_codec_encode_response: + * @codec: an #FlMethodCodec. + * @response: response to encode. + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL. + * + * Encodes a response to a method call. + * + * Returns: a new #FlMethodResponse or %NULL on error. + */ +GBytes* fl_method_codec_encode_response(FlMethodCodec* codec, + FlMethodResponse* response, + GError** error); + /** * fl_method_codec_decode_response: * @codec: an #FlMethodCodec. @@ -92,8 +107,7 @@ GBytes* fl_method_codec_encode_error_envelope(FlMethodCodec* codec, * @error: (allow-none): #GError location to store the error occurring, or * %NULL. * - * Decodes a response to a method call. If the call resulted in an error then - * @error_code is set, otherwise it is %NULL. + * Decodes a response to a method call. * * Returns: a new #FlMethodResponse or %NULL on error. */ diff --git a/shell/platform/linux/fl_mouse_cursor_channel.cc b/shell/platform/linux/fl_mouse_cursor_channel.cc new file mode 100644 index 0000000000000..6314f7b498ba1 --- /dev/null +++ b/shell/platform/linux/fl_mouse_cursor_channel.cc @@ -0,0 +1,104 @@ +// 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. + +#include "flutter/shell/platform/linux/fl_mouse_cursor_channel.h" + +#include + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" + +static constexpr char kChannelName[] = "flutter/mousecursor"; +static constexpr char kBadArgumentsError[] = "Bad Arguments"; +static constexpr char kActivateSystemCursorMethod[] = "activateSystemCursor"; +static constexpr char kKindKey[] = "kind"; + +struct _FlMouseCursorChannel { + GObject parent_instance; + + FlMethodChannel* channel; + + FlMouseCursorChannelVTable* vtable; + + gpointer user_data; +}; + +G_DEFINE_TYPE(FlMouseCursorChannel, fl_mouse_cursor_channel, G_TYPE_OBJECT) + +// Sets the mouse cursor. +static FlMethodResponse* activate_system_cursor(FlMouseCursorChannel* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Argument map missing or malformed", nullptr)); + } + + FlValue* kind_value = fl_value_lookup_string(args, kKindKey); + const gchar* kind = nullptr; + if (fl_value_get_type(kind_value) == FL_VALUE_TYPE_STRING) { + kind = fl_value_get_string(kind_value); + } + + self->vtable->activate_system_cursor(kind, self->user_data); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Called when a method call is received from Flutter. +static void method_call_cb(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data) { + FlMouseCursorChannel* self = FL_MOUSE_CURSOR_CHANNEL(user_data); + + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + g_autoptr(FlMethodResponse) response = nullptr; + if (strcmp(method, kActivateSystemCursorMethod) == 0) { + response = activate_system_cursor(self, args); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + g_autoptr(GError) error = nullptr; + if (!fl_method_call_respond(method_call, response, &error)) { + g_warning("Failed to send method call response: %s", error->message); + } +} + +static void fl_mouse_cursor_channel_dispose(GObject* object) { + FlMouseCursorChannel* self = FL_MOUSE_CURSOR_CHANNEL(object); + + g_clear_object(&self->channel); + + G_OBJECT_CLASS(fl_mouse_cursor_channel_parent_class)->dispose(object); +} + +static void fl_mouse_cursor_channel_class_init( + FlMouseCursorChannelClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_mouse_cursor_channel_dispose; +} + +static void fl_mouse_cursor_channel_init(FlMouseCursorChannel* self) {} + +FlMouseCursorChannel* fl_mouse_cursor_channel_new( + FlBinaryMessenger* messenger, + FlMouseCursorChannelVTable* vtable, + gpointer user_data) { + g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + + FlMouseCursorChannel* self = FL_MOUSE_CURSOR_CHANNEL( + g_object_new(fl_mouse_cursor_channel_get_type(), nullptr)); + + self->vtable = vtable; + self->user_data = user_data; + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + self->channel = + fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self, + nullptr); + + return self; +} diff --git a/shell/platform/linux/fl_mouse_cursor_channel.h b/shell/platform/linux/fl_mouse_cursor_channel.h new file mode 100644 index 0000000000000..bc8a6b968cc0f --- /dev/null +++ b/shell/platform/linux/fl_mouse_cursor_channel.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_MOUSE_CURSOR_CHANNEL_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_MOUSE_CURSOR_CHANNEL_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlMouseCursorChannel, + fl_mouse_cursor_channel, + FL, + MOUSE_CURSOR_CHANNEL, + GObject); + +/** + * FlMouseCursorChannel: + * + * #FlMouseCursorChannel is a cursor channel that implements the shell + * side of SystemChannels.mouseCursor from the Flutter services library. + */ + +typedef struct { + void (*activate_system_cursor)(const gchar* kind, gpointer user_data); +} FlMouseCursorChannelVTable; + +/** + * fl_mouse_cursor_channel_new: + * @messenger: an #FlBinaryMessenger. + * @vtable: callbacks for incoming method calls. + * @user_data: data to pass in callbacks. + * + * Creates a new channel that implements SystemChannels.mouseCursor from the + * Flutter services library. + * + * Returns: a new #FlMouseCursorChannel. + */ +FlMouseCursorChannel* fl_mouse_cursor_channel_new( + FlBinaryMessenger* messenger, + FlMouseCursorChannelVTable* vtable, + gpointer user_data); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_MOUSE_CURSOR_CHANNEL_H_ diff --git a/shell/platform/linux/fl_mouse_cursor_handler.cc b/shell/platform/linux/fl_mouse_cursor_handler.cc index d8fdc8c395ab1..d70fb17acd7ad 100644 --- a/shell/platform/linux/fl_mouse_cursor_handler.cc +++ b/shell/platform/linux/fl_mouse_cursor_handler.cc @@ -4,29 +4,27 @@ #include "flutter/shell/platform/linux/fl_mouse_cursor_handler.h" -#include #include -#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" - -static constexpr char kChannelName[] = "flutter/mousecursor"; -static constexpr char kBadArgumentsError[] = "Bad Arguments"; -static constexpr char kActivateSystemCursorMethod[] = "activateSystemCursor"; -static constexpr char kKindKey[] = "kind"; +#include "flutter/shell/platform/linux/fl_mouse_cursor_channel.h" static constexpr char kFallbackCursor[] = "default"; struct _FlMouseCursorHandler { GObject parent_instance; - FlMethodChannel* channel; - - GWeakRef view; + FlMouseCursorChannel* channel; GHashTable* system_cursor_table; + + // The current cursor. + gchar* cursor_name; }; +enum { SIGNAL_CURSOR_CHANGED, LAST_SIGNAL }; + +static guint fl_mouse_cursor_handler_signals[LAST_SIGNAL]; + G_DEFINE_TYPE(FlMouseCursorHandler, fl_mouse_cursor_handler, G_TYPE_OBJECT) // Insert a new entry into a hashtable from strings to strings. @@ -85,18 +83,8 @@ static void populate_system_cursor_table(GHashTable* table) { } // Sets the mouse cursor. -FlMethodResponse* activate_system_cursor(FlMouseCursorHandler* self, - FlValue* args) { - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - return FL_METHOD_RESPONSE(fl_method_error_response_new( - kBadArgumentsError, "Argument map missing or malformed", nullptr)); - } - - FlValue* kind_value = fl_value_lookup_string(args, kKindKey); - const gchar* kind = nullptr; - if (fl_value_get_type(kind_value) == FL_VALUE_TYPE_STRING) { - kind = fl_value_get_string(kind_value); - } +static void activate_system_cursor(const gchar* kind, gpointer user_data) { + FlMouseCursorHandler* self = FL_MOUSE_CURSOR_HANDLER(user_data); if (self->system_cursor_table == nullptr) { self->system_cursor_table = g_hash_table_new(g_str_hash, g_str_equal); @@ -109,46 +97,19 @@ FlMethodResponse* activate_system_cursor(FlMouseCursorHandler* self, cursor_name = kFallbackCursor; } - g_autoptr(FlView) view = FL_VIEW(g_weak_ref_get(&self->view)); - if (view != nullptr) { - GdkWindow* window = - gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(view))); - g_autoptr(GdkCursor) cursor = - gdk_cursor_new_from_name(gdk_window_get_display(window), cursor_name); - gdk_window_set_cursor(window, cursor); - } - - return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); -} - -// Called when a method call is received from Flutter. -static void method_call_cb(FlMethodChannel* channel, - FlMethodCall* method_call, - gpointer user_data) { - FlMouseCursorHandler* self = FL_MOUSE_CURSOR_HANDLER(user_data); - - const gchar* method = fl_method_call_get_name(method_call); - FlValue* args = fl_method_call_get_args(method_call); + g_free(self->cursor_name); + self->cursor_name = g_strdup(cursor_name); - g_autoptr(FlMethodResponse) response = nullptr; - if (strcmp(method, kActivateSystemCursorMethod) == 0) { - response = activate_system_cursor(self, args); - } else { - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - } - - g_autoptr(GError) error = nullptr; - if (!fl_method_call_respond(method_call, response, &error)) { - g_warning("Failed to send method call response: %s", error->message); - } + g_signal_emit(self, fl_mouse_cursor_handler_signals[SIGNAL_CURSOR_CHANGED], + 0); } static void fl_mouse_cursor_handler_dispose(GObject* object) { FlMouseCursorHandler* self = FL_MOUSE_CURSOR_HANDLER(object); g_clear_object(&self->channel); - g_weak_ref_clear(&self->view); g_clear_pointer(&self->system_cursor_table, g_hash_table_unref); + g_clear_pointer(&self->cursor_name, g_free); G_OBJECT_CLASS(fl_mouse_cursor_handler_parent_class)->dispose(object); } @@ -156,23 +117,35 @@ static void fl_mouse_cursor_handler_dispose(GObject* object) { static void fl_mouse_cursor_handler_class_init( FlMouseCursorHandlerClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_mouse_cursor_handler_dispose; + + fl_mouse_cursor_handler_signals[SIGNAL_CURSOR_CHANGED] = + g_signal_new("cursor-changed", fl_mouse_cursor_handler_get_type(), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +static void fl_mouse_cursor_handler_init(FlMouseCursorHandler* self) { + self->cursor_name = g_strdup(""); } -static void fl_mouse_cursor_handler_init(FlMouseCursorHandler* self) {} +static FlMouseCursorChannelVTable mouse_cursor_vtable = { + .activate_system_cursor = activate_system_cursor, +}; -FlMouseCursorHandler* fl_mouse_cursor_handler_new(FlBinaryMessenger* messenger, - FlView* view) { +FlMouseCursorHandler* fl_mouse_cursor_handler_new( + FlBinaryMessenger* messenger) { g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); FlMouseCursorHandler* self = FL_MOUSE_CURSOR_HANDLER( g_object_new(fl_mouse_cursor_handler_get_type(), nullptr)); - g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); self->channel = - fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); - fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self, - nullptr); - g_weak_ref_init(&self->view, view); + fl_mouse_cursor_channel_new(messenger, &mouse_cursor_vtable, self); return self; } + +const gchar* fl_mouse_cursor_handler_get_cursor_name( + FlMouseCursorHandler* self) { + g_return_val_if_fail(FL_IS_MOUSE_CURSOR_HANDLER(self), nullptr); + return self->cursor_name; +} diff --git a/shell/platform/linux/fl_mouse_cursor_handler.h b/shell/platform/linux/fl_mouse_cursor_handler.h index 7ec7b6412fa1b..88942a94c10f6 100644 --- a/shell/platform/linux/fl_mouse_cursor_handler.h +++ b/shell/platform/linux/fl_mouse_cursor_handler.h @@ -8,7 +8,6 @@ #include #include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" G_BEGIN_DECLS @@ -28,15 +27,24 @@ G_DECLARE_FINAL_TYPE(FlMouseCursorHandler, /** * fl_mouse_cursor_handler_new: * @messenger: an #FlBinaryMessenger. - * @view: an #FlView to control. * * Creates a new handler that implements SystemChannels.mouseCursor from the * Flutter services library. * * Returns: a new #FlMouseCursorHandler. */ -FlMouseCursorHandler* fl_mouse_cursor_handler_new(FlBinaryMessenger* messenger, - FlView* view); +FlMouseCursorHandler* fl_mouse_cursor_handler_new(FlBinaryMessenger* messenger); + +/** + * fl_mouse_cursor_handler_get_cursor_name: + * @handler: an #FlMouseCursorHandler. + * + * Get the name of the current mouse cursor. + * + * Returns: a mouse cursor name. + */ +const gchar* fl_mouse_cursor_handler_get_cursor_name( + FlMouseCursorHandler* handler); G_END_DECLS diff --git a/shell/platform/linux/fl_platform_channel.cc b/shell/platform/linux/fl_platform_channel.cc new file mode 100644 index 0000000000000..cd5fb904d5062 --- /dev/null +++ b/shell/platform/linux/fl_platform_channel.cc @@ -0,0 +1,347 @@ +// 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. + +#include "flutter/shell/platform/linux/fl_platform_channel.h" + +#include + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" + +static constexpr char kChannelName[] = "flutter/platform"; +static constexpr char kBadArgumentsError[] = "Bad Arguments"; +static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData"; +static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData"; +static constexpr char kClipboardHasStringsMethod[] = "Clipboard.hasStrings"; +static constexpr char kExitApplicationMethod[] = "System.exitApplication"; +static constexpr char kRequestAppExitMethod[] = "System.requestAppExit"; +static constexpr char kInitializationCompleteMethod[] = + "System.initializationComplete"; +static constexpr char kPlaySoundMethod[] = "SystemSound.play"; +static constexpr char kSystemNavigatorPopMethod[] = "SystemNavigator.pop"; +static constexpr char kTextKey[] = "text"; +static constexpr char kValueKey[] = "value"; + +static constexpr char kExitTypeKey[] = "type"; +static constexpr char kExitTypeCancelable[] = "cancelable"; +static constexpr char kExitTypeRequired[] = "required"; + +static constexpr char kExitResponseKey[] = "response"; +static constexpr char kExitResponseCancel[] = "cancel"; +static constexpr char kExitResponseExit[] = "exit"; + +struct _FlPlatformChannel { + GObject parent_instance; + + FlMethodChannel* channel; + + // Handlers for incoming method calls. + FlPlatformChannelVTable* vtable; + + // User data to pass to method call handlers. + gpointer user_data; +}; + +G_DEFINE_TYPE(FlPlatformChannel, fl_platform_channel, G_TYPE_OBJECT) + +static FlMethodResponse* clipboard_set_data(FlPlatformChannel* self, + FlMethodCall* method_call) { + FlValue* args = fl_method_call_get_args(method_call); + + if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Argument map missing or malformed", nullptr)); + } + + FlValue* text_value = fl_value_lookup_string(args, kTextKey); + if (text_value == nullptr || + fl_value_get_type(text_value) != FL_VALUE_TYPE_STRING) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Missing clipboard text", nullptr)); + } + const gchar* text = fl_value_get_string(text_value); + + return self->vtable->clipboard_set_data(method_call, text, self->user_data); +} + +static FlMethodResponse* clipboard_get_data(FlPlatformChannel* self, + FlMethodCall* method_call) { + FlValue* args = fl_method_call_get_args(method_call); + + if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Expected string", nullptr)); + } + const gchar* format = fl_value_get_string(args); + + return self->vtable->clipboard_get_data(method_call, format, self->user_data); +} + +static FlMethodResponse* clipboard_has_strings(FlPlatformChannel* self, + FlMethodCall* method_call) { + return self->vtable->clipboard_has_strings(method_call, self->user_data); +} + +// Get the exit response from a System.requestAppExit method call. +FlPlatformChannelExitResponse get_exit_response(FlMethodResponse* response) { + if (response == nullptr) { + return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT; + } + + g_autoptr(GError) error = nullptr; + FlValue* result = fl_method_response_get_result(response, &error); + if (result == nullptr) { + g_warning("Error returned from System.requestAppExit: %s", error->message); + return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT; + } + if (fl_value_get_type(result) != FL_VALUE_TYPE_MAP) { + g_warning("System.requestAppExit result argument map missing or malformed"); + return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT; + } + + FlValue* response_value = fl_value_lookup_string(result, kExitResponseKey); + if (fl_value_get_type(response_value) != FL_VALUE_TYPE_STRING) { + g_warning("Invalid response from System.requestAppExit"); + return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT; + } + const char* response_string = fl_value_get_string(response_value); + + if (strcmp(response_string, kExitResponseCancel) == 0) { + return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_CANCEL; + } else if (strcmp(response_string, kExitResponseExit) == 0) { + return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT; + } + + // If something went wrong, then just exit. + return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT; +} + +static FlMethodResponse* system_exit_application(FlPlatformChannel* self, + FlMethodCall* method_call) { + FlValue* args = fl_method_call_get_args(method_call); + if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Argument map missing or malformed", nullptr)); + } + + FlValue* type_value = fl_value_lookup_string(args, kExitTypeKey); + if (type_value == nullptr || + fl_value_get_type(type_value) != FL_VALUE_TYPE_STRING) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Missing type argument", nullptr)); + } + const char* type_string = fl_value_get_string(type_value); + FlPlatformChannelExitType type; + if (strcmp(type_string, kExitTypeCancelable) == 0) { + type = FL_PLATFORM_CHANNEL_EXIT_TYPE_CANCELABLE; + } else if (strcmp(type_string, kExitTypeRequired) == 0) { + type = FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED; + } else { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Invalid exit type", nullptr)); + } + + return self->vtable->system_exit_application(method_call, type, + self->user_data); +} + +static FlMethodResponse* system_initialization_complete( + FlPlatformChannel* self) { + self->vtable->system_initialization_complete(self->user_data); + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +static FlMethodResponse* system_sound_play(FlPlatformChannel* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Expected string", nullptr)); + } + const gchar* type = fl_value_get_string(args); + + self->vtable->system_sound_play(type, self->user_data); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +static FlMethodResponse* system_navigator_pop(FlPlatformChannel* self) { + self->vtable->system_navigator_pop(self->user_data); + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +static void method_call_cb(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data) { + FlPlatformChannel* self = FL_PLATFORM_CHANNEL(user_data); + + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + g_autoptr(FlMethodResponse) response = nullptr; + if (strcmp(method, kSetClipboardDataMethod) == 0) { + response = clipboard_set_data(self, method_call); + } else if (strcmp(method, kGetClipboardDataMethod) == 0) { + response = clipboard_get_data(self, method_call); + } else if (strcmp(method, kClipboardHasStringsMethod) == 0) { + response = clipboard_has_strings(self, method_call); + } else if (strcmp(method, kExitApplicationMethod) == 0) { + response = system_exit_application(self, method_call); + } else if (strcmp(method, kInitializationCompleteMethod) == 0) { + response = system_initialization_complete(self); + } else if (strcmp(method, kPlaySoundMethod) == 0) { + response = system_sound_play(self, args); + } else if (strcmp(method, kSystemNavigatorPopMethod) == 0) { + response = system_navigator_pop(self); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + if (response != nullptr) { + g_autoptr(GError) error = nullptr; + if (!fl_method_call_respond(method_call, response, &error)) { + g_warning("Failed to send method call response: %s", error->message); + } + } +} + +static void fl_platform_channel_dispose(GObject* object) { + FlPlatformChannel* self = FL_PLATFORM_CHANNEL(object); + + g_clear_object(&self->channel); + + G_OBJECT_CLASS(fl_platform_channel_parent_class)->dispose(object); +} + +static void fl_platform_channel_class_init(FlPlatformChannelClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_platform_channel_dispose; +} + +static void fl_platform_channel_init(FlPlatformChannel* self) {} + +FlPlatformChannel* fl_platform_channel_new(FlBinaryMessenger* messenger, + FlPlatformChannelVTable* vtable, + gpointer user_data) { + g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + g_return_val_if_fail(vtable != nullptr, nullptr); + + FlPlatformChannel* self = FL_PLATFORM_CHANNEL( + g_object_new(fl_platform_channel_get_type(), nullptr)); + + self->vtable = vtable; + self->user_data = user_data; + + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + self->channel = + fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self, + nullptr); + + return self; +} + +void fl_platform_channel_system_request_app_exit(FlPlatformChannel* self, + FlPlatformChannelExitType type, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_PLATFORM_CHANNEL(self)); + + g_autoptr(FlValue) args = fl_value_new_map(); + const gchar* type_string; + switch (type) { + case FL_PLATFORM_CHANNEL_EXIT_TYPE_CANCELABLE: + type_string = kExitTypeCancelable; + break; + case FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED: + type_string = kExitTypeRequired; + break; + default: + g_assert_not_reached(); + } + fl_value_set_string_take(args, kExitTypeKey, + fl_value_new_string(type_string)); + fl_method_channel_invoke_method(self->channel, kRequestAppExitMethod, args, + cancellable, callback, user_data); +} + +gboolean fl_platform_channel_system_request_app_exit_finish( + GObject* object, + GAsyncResult* result, + FlPlatformChannelExitResponse* exit_response, + GError** error) { + g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish( + FL_METHOD_CHANNEL(object), result, error); + if (response == nullptr) { + return FALSE; + } + + *exit_response = get_exit_response(response); + + return TRUE; +} + +void fl_platform_channel_respond_clipboard_get_data(FlMethodCall* method_call, + const gchar* text) { + g_autoptr(FlValue) result = nullptr; + if (text != nullptr) { + result = fl_value_new_map(); + fl_value_set_string_take(result, kTextKey, fl_value_new_string(text)); + } + + g_autoptr(FlMethodResponse) response = + FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + + g_autoptr(GError) error = nullptr; + if (!fl_method_call_respond(method_call, response, &error)) { + g_warning("Failed to send response to %s: %s", kGetClipboardDataMethod, + error->message); + } +} + +void fl_platform_channel_respond_clipboard_has_strings( + FlMethodCall* method_call, + gboolean has_strings) { + g_autoptr(FlValue) result = fl_value_new_map(); + fl_value_set_string_take(result, kValueKey, fl_value_new_bool(has_strings)); + + g_autoptr(FlMethodResponse) response = + FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + + g_autoptr(GError) error = nullptr; + if (!fl_method_call_respond(method_call, response, &error)) { + g_warning("Failed to send response to %s: %s", kClipboardHasStringsMethod, + error->message); + } +} + +void fl_platform_channel_respond_system_exit_application( + FlMethodCall* method_call, + FlPlatformChannelExitResponse exit_response) { + g_autoptr(FlMethodResponse) response = + fl_platform_channel_make_system_request_app_exit_response(exit_response); + g_autoptr(GError) error = nullptr; + if (!fl_method_call_respond(method_call, response, &error)) { + g_warning("Failed to send response to System.exitApplication: %s", + error->message); + } +} + +FlMethodResponse* fl_platform_channel_make_system_request_app_exit_response( + FlPlatformChannelExitResponse exit_response) { + g_autoptr(FlValue) exit_result = fl_value_new_map(); + const gchar* exit_response_string; + switch (exit_response) { + case FL_PLATFORM_CHANNEL_EXIT_RESPONSE_CANCEL: + exit_response_string = kExitResponseCancel; + break; + case FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT: + exit_response_string = kExitResponseExit; + break; + default: + g_assert_not_reached(); + } + fl_value_set_string_take(exit_result, kExitResponseKey, + fl_value_new_string(exit_response_string)); + return FL_METHOD_RESPONSE(fl_method_success_response_new(exit_result)); +} diff --git a/shell/platform/linux/fl_platform_channel.h b/shell/platform/linux/fl_platform_channel.h new file mode 100644 index 0000000000000..3dd5e424b44f7 --- /dev/null +++ b/shell/platform/linux/fl_platform_channel.h @@ -0,0 +1,108 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_CHANNEL_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_CHANNEL_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_call.h" + +G_BEGIN_DECLS + +typedef enum { + FL_PLATFORM_CHANNEL_EXIT_TYPE_CANCELABLE, + FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED, +} FlPlatformChannelExitType; + +typedef enum { + FL_PLATFORM_CHANNEL_EXIT_RESPONSE_CANCEL, + FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT, +} FlPlatformChannelExitResponse; + +G_DECLARE_FINAL_TYPE(FlPlatformChannel, + fl_platform_channel, + FL, + PLATFORM_CHANNEL, + GObject); + +/** + * FlPlatformChannel: + * + * #FlPlatformChannel is a channel that implements the shell side + * of SystemChannels.platform from the Flutter services library. + */ + +typedef struct { + FlMethodResponse* (*clipboard_set_data)(FlMethodCall* method_call, + const gchar* text, + gpointer user_data); + FlMethodResponse* (*clipboard_get_data)(FlMethodCall* method_call, + const gchar* format, + gpointer user_data); + FlMethodResponse* (*clipboard_has_strings)(FlMethodCall* method_call, + gpointer user_data); + FlMethodResponse* (*system_exit_application)(FlMethodCall* method_call, + FlPlatformChannelExitType type, + gpointer user_data); + void (*system_initialization_complete)(gpointer user_data); + void (*system_sound_play)(const gchar* type, gpointer user_data); + void (*system_navigator_pop)(gpointer user_data); +} FlPlatformChannelVTable; + +/** + * fl_platform_channel_new: + * @messenger: an #FlBinaryMessenger + * @vtable: callbacks for incoming method calls. + * @user_data: data to pass in callbacks. + * + * Creates a new channel that implements SystemChannels.platform from the + * Flutter services library. + * + * Returns: a new #FlPlatformChannel + */ +FlPlatformChannel* fl_platform_channel_new(FlBinaryMessenger* messenger, + FlPlatformChannelVTable* vtable, + gpointer user_data); + +/** + * fl_platform_channel_system_request_app_exit: + * @channel: an #FlPlatformChannel + * + * Request the application exits (i.e. due to the window being requested to be + * closed). + * + * Calling this will only send an exit request to the framework if the framework + * has already indicated that it is ready to receive requests by sending a + * "System.initializationComplete" method call on the platform channel. Calls + * before initialization is complete will result in an immediate exit. + */ +void fl_platform_channel_system_request_app_exit(FlPlatformChannel* channel, + FlPlatformChannelExitType type, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean fl_platform_channel_system_request_app_exit_finish( + GObject* object, + GAsyncResult* result, + FlPlatformChannelExitResponse* exit_response, + GError** error); + +void fl_platform_channel_respond_system_exit_application( + FlMethodCall* method_call, + FlPlatformChannelExitResponse exit_response); + +void fl_platform_channel_respond_clipboard_get_data(FlMethodCall* method_call, + const gchar* text); + +void fl_platform_channel_respond_clipboard_has_strings( + FlMethodCall* method_call, + gboolean has_strings); + +FlMethodResponse* fl_platform_channel_make_system_request_app_exit_response( + FlPlatformChannelExitResponse exit_response); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_CHANNEL_H_ diff --git a/shell/platform/linux/fl_platform_channel_test.cc b/shell/platform/linux/fl_platform_channel_test.cc new file mode 100644 index 0000000000000..45aeeef2a0531 --- /dev/null +++ b/shell/platform/linux/fl_platform_channel_test.cc @@ -0,0 +1,43 @@ +// 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. + +#include "flutter/shell/platform/linux/fl_platform_channel.h" +#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" +#include "flutter/shell/platform/linux/testing/fl_test.h" +#include "gtest/gtest.h" + +static void exit_method_response_cb(GObject* object, + GAsyncResult* result, + gpointer user_data) { + g_autoptr(GError) error = nullptr; + FlPlatformChannelExitResponse response; + gboolean success = fl_platform_channel_system_request_app_exit_finish( + object, result, &response, &error); + + EXPECT_TRUE(success); + EXPECT_EQ(response, FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT); + + g_main_loop_quit(static_cast(user_data)); +} + +TEST(FlPlatformChannelTest, ExitResponse) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = fl_method_channel_new( + messenger, "test/standard-method", FL_METHOD_CODEC(codec)); + + g_autoptr(FlValue) args = fl_value_new_map(); + fl_value_set_string_take(args, "response", fl_value_new_string("exit")); + + fl_method_channel_invoke_method(channel, "Echo", args, nullptr, + exit_method_response_cb, loop); + + // Blocks here until method_response_cb is called. + g_main_loop_run(loop); +} diff --git a/shell/platform/linux/fl_platform_handler.cc b/shell/platform/linux/fl_platform_handler.cc index 28db474174956..5be3470f0a1e1 100644 --- a/shell/platform/linux/fl_platform_handler.cc +++ b/shell/platform/linux/fl_platform_handler.cc @@ -7,33 +7,12 @@ #include #include -#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h" +#include "flutter/shell/platform/linux/fl_platform_channel.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" -static constexpr char kChannelName[] = "flutter/platform"; -static constexpr char kBadArgumentsError[] = "Bad Arguments"; +static constexpr char kInProgressError[] = "In Progress"; static constexpr char kUnknownClipboardFormatError[] = "Unknown Clipboard Format"; -static constexpr char kInProgressError[] = "In Progress"; -static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData"; -static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData"; -static constexpr char kClipboardHasStringsMethod[] = "Clipboard.hasStrings"; -static constexpr char kExitApplicationMethod[] = "System.exitApplication"; -static constexpr char kRequestAppExitMethod[] = "System.requestAppExit"; -static constexpr char kInitializationCompleteMethod[] = - "System.initializationComplete"; -static constexpr char kPlaySoundMethod[] = "SystemSound.play"; -static constexpr char kSystemNavigatorPopMethod[] = "SystemNavigator.pop"; -static constexpr char kTextKey[] = "text"; -static constexpr char kValueKey[] = "value"; - -static constexpr char kExitTypeKey[] = "type"; -static constexpr char kExitTypeCancelable[] = "cancelable"; -static constexpr char kExitTypeRequired[] = "required"; - -static constexpr char kExitResponseKey[] = "response"; -static constexpr char kExitResponseCancel[] = "cancel"; -static constexpr char kExitResponseExit[] = "exit"; static constexpr char kTextPlainFormat[] = "text/plain"; @@ -43,38 +22,23 @@ static constexpr char kSoundTypeClick[] = "SystemSoundType.click"; struct _FlPlatformHandler { GObject parent_instance; - FlMethodChannel* channel; + FlPlatformChannel* channel; + FlMethodCall* exit_application_method_call; - GCancellable* cancellable; + bool app_initialization_complete; + + GCancellable* cancellable; }; G_DEFINE_TYPE(FlPlatformHandler, fl_platform_handler, G_TYPE_OBJECT) -// Sends the method call response to Flutter. -static void send_response(FlMethodCall* method_call, - FlMethodResponse* response) { - g_autoptr(GError) error = nullptr; - if (!fl_method_call_respond(method_call, response, &error)) { - g_warning("Failed to send method call response: %s", error->message); - } -} - // Called when clipboard text received. static void clipboard_text_cb(GtkClipboard* clipboard, const gchar* text, gpointer user_data) { g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data); - - g_autoptr(FlValue) result = nullptr; - if (text != nullptr) { - result = fl_value_new_map(); - fl_value_set_string_take(result, kTextKey, fl_value_new_string(text)); - } - - g_autoptr(FlMethodResponse) response = - FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - send_response(method_call, response); + fl_platform_channel_respond_clipboard_get_data(method_call, text); } // Called when clipboard text received during has_strings. @@ -82,50 +46,25 @@ static void clipboard_text_has_strings_cb(GtkClipboard* clipboard, const gchar* text, gpointer user_data) { g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data); - - g_autoptr(FlValue) result = fl_value_new_map(); - fl_value_set_string_take( - result, kValueKey, - fl_value_new_bool(text != nullptr && strlen(text) > 0)); - - g_autoptr(FlMethodResponse) response = - FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - send_response(method_call, response); + fl_platform_channel_respond_clipboard_has_strings( + method_call, text != nullptr && strlen(text) > 0); } // Called when Flutter wants to copy to the clipboard. -static FlMethodResponse* clipboard_set_data(FlPlatformHandler* self, - FlValue* args) { - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - return FL_METHOD_RESPONSE(fl_method_error_response_new( - kBadArgumentsError, "Argument map missing or malformed", nullptr)); - } - - FlValue* text_value = fl_value_lookup_string(args, kTextKey); - if (text_value == nullptr || - fl_value_get_type(text_value) != FL_VALUE_TYPE_STRING) { - return FL_METHOD_RESPONSE(fl_method_error_response_new( - kBadArgumentsError, "Missing clipboard text", nullptr)); - } - +static FlMethodResponse* clipboard_set_data(FlMethodCall* method_call, + const gchar* text, + gpointer user_data) { GtkClipboard* clipboard = gtk_clipboard_get_default(gdk_display_get_default()); - gtk_clipboard_set_text(clipboard, fl_value_get_string(text_value), -1); + gtk_clipboard_set_text(clipboard, text, -1); return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } // Called when Flutter wants to paste from the clipboard. -static FlMethodResponse* clipboard_get_data_async(FlPlatformHandler* self, - FlMethodCall* method_call) { - FlValue* args = fl_method_call_get_args(method_call); - - if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) { - return FL_METHOD_RESPONSE(fl_method_error_response_new( - kBadArgumentsError, "Expected string", nullptr)); - } - - const gchar* format = fl_value_get_string(args); +static FlMethodResponse* clipboard_get_data(FlMethodCall* method_call, + const gchar* format, + gpointer user_data) { if (strcmp(format, kTextPlainFormat) != 0) { return FL_METHOD_RESPONSE(fl_method_error_response_new( kUnknownClipboardFormatError, "GTK clipboard API only supports text", @@ -143,9 +82,8 @@ static FlMethodResponse* clipboard_get_data_async(FlPlatformHandler* self, // Called when Flutter wants to know if the content of the clipboard is able to // be pasted, without actually accessing the clipboard content itself. -static FlMethodResponse* clipboard_has_strings_async( - FlPlatformHandler* self, - FlMethodCall* method_call) { +static FlMethodResponse* clipboard_has_strings(FlMethodCall* method_call, + gpointer user_data) { GtkClipboard* clipboard = gtk_clipboard_get_default(gdk_display_get_default()); gtk_clipboard_request_text(clipboard, clipboard_text_has_strings_cb, @@ -155,31 +93,6 @@ static FlMethodResponse* clipboard_has_strings_async( return nullptr; } -// Get the exit response from a System.requestAppExit method call. -static gchar* get_exit_response(FlMethodResponse* response) { - if (response == nullptr) { - return nullptr; - } - - g_autoptr(GError) error = nullptr; - FlValue* result = fl_method_response_get_result(response, &error); - if (result == nullptr) { - g_warning("Error returned from System.requestAppExit: %s", error->message); - return nullptr; - } - if (fl_value_get_type(result) != FL_VALUE_TYPE_MAP) { - g_warning("System.requestAppExit result argument map missing or malformed"); - return nullptr; - } - - FlValue* response_value = fl_value_lookup_string(result, kExitResponseKey); - if (fl_value_get_type(response_value) != FL_VALUE_TYPE_STRING) { - g_warning("Invalid response from System.requestAppExit"); - return nullptr; - } - return g_strdup(fl_value_get_string(response_value)); -} - // Quit this application static void quit_application() { GApplication* app = g_application_get_default(); @@ -212,88 +125,56 @@ static void request_app_exit_response_cb(GObject* object, FlPlatformHandler* self = FL_PLATFORM_HANDLER(user_data); g_autoptr(GError) error = nullptr; - g_autoptr(FlMethodResponse) method_response = - fl_method_channel_invoke_method_finish(FL_METHOD_CHANNEL(object), result, - &error); - g_autofree gchar* exit_response = nullptr; - if (method_response == nullptr) { + FlPlatformChannelExitResponse exit_response; + if (!fl_platform_channel_system_request_app_exit_finish( + object, result, &exit_response, &error)) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { return; } g_warning("Failed to complete System.requestAppExit: %s", error->message); - } else { - exit_response = get_exit_response(method_response); - } - // If something went wrong, then just exit. - if (exit_response == nullptr) { - exit_response = g_strdup(kExitResponseExit); + quit_application(); + return; } - if (g_str_equal(exit_response, kExitResponseExit)) { + if (exit_response == FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT) { quit_application(); - } else if (g_str_equal(exit_response, kExitResponseCancel)) { - // Canceled - no action to take. } // If request was due to a request from Flutter, pass result back. if (self->exit_application_method_call != nullptr) { - g_autoptr(FlValue) exit_result = fl_value_new_map(); - fl_value_set_string_take(exit_result, kExitResponseKey, - fl_value_new_string(exit_response)); - g_autoptr(FlMethodResponse) exit_response = - FL_METHOD_RESPONSE(fl_method_success_response_new(exit_result)); - if (!fl_method_call_respond(self->exit_application_method_call, - exit_response, &error)) { - g_warning("Failed to send response to System.exitApplication: %s", - error->message); - } - g_clear_object(&self->exit_application_method_call); + fl_platform_channel_respond_system_exit_application( + self->exit_application_method_call, exit_response); } } // Send a request to Flutter to exit the application, but only if it's ready for // a request. -static void request_app_exit(FlPlatformHandler* self, const char* type) { - g_autoptr(FlValue) args = fl_value_new_map(); +static void request_app_exit(FlPlatformHandler* self, + FlPlatformChannelExitType type) { if (!self->app_initialization_complete || - g_str_equal(type, kExitTypeRequired)) { + type == FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED) { quit_application(); return; } - fl_value_set_string_take(args, kExitTypeKey, fl_value_new_string(type)); - fl_method_channel_invoke_method(self->channel, kRequestAppExitMethod, args, - self->cancellable, - request_app_exit_response_cb, self); + fl_platform_channel_system_request_app_exit( + self->channel, type, self->cancellable, request_app_exit_response_cb, + self); } // Called when the Dart app has finished initialization and is ready to handle // requests. For the Flutter framework, this means after the ServicesBinding has // been initialized and it sends a System.initializationComplete message. -static FlMethodResponse* system_intitialization_complete( - FlPlatformHandler* self, - FlMethodCall* method_call) { +static void system_initialization_complete(gpointer user_data) { + FlPlatformHandler* self = FL_PLATFORM_HANDLER(user_data); self->app_initialization_complete = TRUE; - return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } // Called when Flutter wants to exit the application. -static FlMethodResponse* system_exit_application(FlPlatformHandler* self, - FlMethodCall* method_call) { - FlValue* args = fl_method_call_get_args(method_call); - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - return FL_METHOD_RESPONSE(fl_method_error_response_new( - kBadArgumentsError, "Argument map missing or malformed", nullptr)); - } - - FlValue* type_value = fl_value_lookup_string(args, kExitTypeKey); - if (type_value == nullptr || - fl_value_get_type(type_value) != FL_VALUE_TYPE_STRING) { - return FL_METHOD_RESPONSE(fl_method_error_response_new( - kBadArgumentsError, "Missing type argument", nullptr)); - } - const char* type = fl_value_get_string(type_value); - +static FlMethodResponse* system_exit_application(FlMethodCall* method_call, + FlPlatformChannelExitType type, + gpointer user_data) { + FlPlatformHandler* self = FL_PLATFORM_HANDLER(user_data); // Save method call to respond to when our request to Flutter completes. if (self->exit_application_method_call != nullptr) { return FL_METHOD_RESPONSE(fl_method_error_response_new( @@ -305,12 +186,10 @@ static FlMethodResponse* system_exit_application(FlPlatformHandler* self, // Requested to immediately quit if the app hasn't yet signaled that it is // ready to handle requests, or if the type of exit requested is "required". if (!self->app_initialization_complete || - g_str_equal(type, kExitTypeRequired)) { + type == FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED) { quit_application(); - g_autoptr(FlValue) exit_result = fl_value_new_map(); - fl_value_set_string_take(exit_result, kExitResponseKey, - fl_value_new_string(kExitResponseExit)); - return FL_METHOD_RESPONSE(fl_method_success_response_new(exit_result)); + return fl_platform_channel_make_system_request_app_exit_response( + FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT); } // Send the request back to Flutter to follow the standard process. @@ -321,14 +200,7 @@ static FlMethodResponse* system_exit_application(FlPlatformHandler* self, } // Called when Flutter wants to play a sound. -static FlMethodResponse* system_sound_play(FlPlatformHandler* self, - FlValue* args) { - if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) { - return FL_METHOD_RESPONSE(fl_method_error_response_new( - kBadArgumentsError, "Expected string", nullptr)); - } - - const gchar* type = fl_value_get_string(args); +static void system_sound_play(const gchar* type, gpointer user_data) { if (strcmp(type, kSoundTypeAlert) == 0) { GdkDisplay* display = gdk_display_get_default(); if (display != nullptr) { @@ -339,47 +211,11 @@ static FlMethodResponse* system_sound_play(FlPlatformHandler* self, } else { g_warning("Ignoring unknown sound type %s in SystemSound.play.\n", type); } - - return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } // Called when Flutter wants to quit the application. -static FlMethodResponse* system_navigator_pop(FlPlatformHandler* self) { +static void system_navigator_pop(gpointer user_data) { quit_application(); - return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); -} - -// Called when a method call is received from Flutter. -static void method_call_cb(FlMethodChannel* channel, - FlMethodCall* method_call, - gpointer user_data) { - FlPlatformHandler* self = FL_PLATFORM_HANDLER(user_data); - - const gchar* method = fl_method_call_get_name(method_call); - FlValue* args = fl_method_call_get_args(method_call); - - g_autoptr(FlMethodResponse) response = nullptr; - if (strcmp(method, kSetClipboardDataMethod) == 0) { - response = clipboard_set_data(self, args); - } else if (strcmp(method, kGetClipboardDataMethod) == 0) { - response = clipboard_get_data_async(self, method_call); - } else if (strcmp(method, kClipboardHasStringsMethod) == 0) { - response = clipboard_has_strings_async(self, method_call); - } else if (strcmp(method, kExitApplicationMethod) == 0) { - response = system_exit_application(self, method_call); - } else if (strcmp(method, kInitializationCompleteMethod) == 0) { - response = system_intitialization_complete(self, method_call); - } else if (strcmp(method, kPlaySoundMethod) == 0) { - response = system_sound_play(self, args); - } else if (strcmp(method, kSystemNavigatorPopMethod) == 0) { - response = system_navigator_pop(self); - } else { - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - } - - if (response != nullptr) { - send_response(method_call, response); - } } static void fl_platform_handler_dispose(GObject* object) { @@ -402,17 +238,24 @@ static void fl_platform_handler_init(FlPlatformHandler* self) { self->cancellable = g_cancellable_new(); } +static FlPlatformChannelVTable platform_channel_vtable = { + .clipboard_set_data = clipboard_set_data, + .clipboard_get_data = clipboard_get_data, + .clipboard_has_strings = clipboard_has_strings, + .system_exit_application = system_exit_application, + .system_initialization_complete = system_initialization_complete, + .system_sound_play = system_sound_play, + .system_navigator_pop = system_navigator_pop, +}; + FlPlatformHandler* fl_platform_handler_new(FlBinaryMessenger* messenger) { g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); FlPlatformHandler* self = FL_PLATFORM_HANDLER( g_object_new(fl_platform_handler_get_type(), nullptr)); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); self->channel = - fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); - fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self, - nullptr); + fl_platform_channel_new(messenger, &platform_channel_vtable, self); self->app_initialization_complete = FALSE; return self; @@ -421,5 +264,5 @@ FlPlatformHandler* fl_platform_handler_new(FlBinaryMessenger* messenger) { void fl_platform_handler_request_app_exit(FlPlatformHandler* self) { g_return_if_fail(FL_IS_PLATFORM_HANDLER(self)); // Request a cancellable exit. - request_app_exit(self, kExitTypeCancelable); + request_app_exit(self, FL_PLATFORM_CHANNEL_EXIT_TYPE_CANCELABLE); } diff --git a/shell/platform/linux/fl_platform_handler_test.cc b/shell/platform/linux/fl_platform_handler_test.cc index 98369d227beff..4de202e1eb047 100644 --- a/shell/platform/linux/fl_platform_handler_test.cc +++ b/shell/platform/linux/fl_platform_handler_test.cc @@ -5,90 +5,14 @@ #include #include "flutter/shell/platform/linux/fl_binary_messenger_private.h" -#include "flutter/shell/platform/linux/fl_method_codec_private.h" #include "flutter/shell/platform/linux/fl_platform_handler.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h" +#include "flutter/shell/platform/linux/testing/fl_mock_binary_messenger.h" #include "flutter/shell/platform/linux/testing/fl_test.h" -#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h" -#include "flutter/testing/testing.h" #include "gmock/gmock.h" #include "gtest/gtest.h" -MATCHER_P(SuccessResponse, result, "") { - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(FlMethodResponse) response = - fl_method_codec_decode_response(FL_METHOD_CODEC(codec), arg, nullptr); - if (fl_value_equal(fl_method_response_get_result(response, nullptr), - result)) { - return true; - } - *result_listener << ::testing::PrintToString(response); - return false; -} - -class MethodCallMatcher { - public: - using is_gtest_matcher = void; - - explicit MethodCallMatcher(::testing::Matcher name, - ::testing::Matcher args) - : name_(std::move(name)), args_(std::move(args)) {} - - bool MatchAndExplain(GBytes* method_call, - ::testing::MatchResultListener* result_listener) const { - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GError) error = nullptr; - g_autofree gchar* name = nullptr; - g_autoptr(FlValue) args = nullptr; - gboolean result = fl_method_codec_decode_method_call( - FL_METHOD_CODEC(codec), method_call, &name, &args, &error); - if (!result) { - *result_listener << ::testing::PrintToString(error->message); - return false; - } - if (!name_.MatchAndExplain(name, result_listener)) { - *result_listener << " where the name doesn't match: \"" << name << "\""; - return false; - } - if (!args_.MatchAndExplain(args, result_listener)) { - *result_listener << " where the args don't match: " - << ::testing::PrintToString(args); - return false; - } - return true; - } - - void DescribeTo(std::ostream* os) const { - *os << "method name "; - name_.DescribeTo(os); - *os << " and args "; - args_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const { - *os << "method name "; - name_.DescribeNegationTo(os); - *os << " or args "; - args_.DescribeNegationTo(os); - } - - private: - ::testing::Matcher name_; - ::testing::Matcher args_; -}; - -static ::testing::Matcher MethodCall( - const std::string& name, - ::testing::Matcher args) { - return MethodCallMatcher(::testing::StrEq(name), std::move(args)); -} - -MATCHER_P(FlValueEq, value, "equal to " + ::testing::PrintToString(value)) { - return fl_value_equal(arg, value); -} - G_DECLARE_FINAL_TYPE(FlTestApplication, fl_test_application, FL, @@ -116,26 +40,35 @@ static void fl_test_application_startup(GApplication* application) { static void fl_test_application_activate(GApplication* application) { G_APPLICATION_CLASS(fl_test_application_parent_class)->activate(application); - ::testing::NiceMock messenger; - g_autoptr(FlPlatformHandler) handler = fl_platform_handler_new(messenger); + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); + g_autoptr(FlPlatformHandler) handler = + fl_platform_handler_new(FL_BINARY_MESSENGER(messenger)); EXPECT_NE(handler, nullptr); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - - g_autoptr(FlValue) exit_result = fl_value_new_map(); - fl_value_set_string_take(exit_result, "response", - fl_value_new_string("exit")); - EXPECT_CALL(messenger, - fl_binary_messenger_send_response( - ::testing::Eq(messenger), ::testing::_, - SuccessResponse(exit_result), ::testing::_)) - .WillOnce(::testing::Return(true)); // Request app exit. + gboolean called = FALSE; g_autoptr(FlValue) args = fl_value_new_map(); fl_value_set_string_take(args, "type", fl_value_new_string("required")); - g_autoptr(GBytes) message = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "System.exitApplication", args, nullptr); - messenger.ReceiveMessage("flutter/platform", message); + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/platform", "System.exitApplication", args, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + + g_autoptr(FlValue) expected_result = fl_value_new_map(); + fl_value_set_string_take(expected_result, "response", + fl_value_new_string("exit")); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } static void fl_test_application_dispose(GObject* object) { @@ -171,59 +104,107 @@ FlTestApplication* fl_test_application_new(gboolean* dispose_called) { } TEST(FlPlatformHandlerTest, PlaySound) { - ::testing::NiceMock messenger; - - g_autoptr(FlPlatformHandler) handler = fl_platform_handler_new(messenger); + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); + g_autoptr(FlPlatformHandler) handler = + fl_platform_handler_new(FL_BINARY_MESSENGER(messenger)); EXPECT_NE(handler, nullptr); + gboolean called = FALSE; g_autoptr(FlValue) args = fl_value_new_string("SystemSoundType.alert"); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) message = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "SystemSound.play", args, nullptr); - - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/platform", message); + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/platform", "SystemSound.play", args, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + + g_autoptr(FlValue) expected_result = fl_value_new_null(); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlPlatformHandlerTest, ExitApplication) { - ::testing::NiceMock messenger; + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); - g_autoptr(FlPlatformHandler) handler = fl_platform_handler_new(messenger); + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); + g_autoptr(FlPlatformHandler) handler = + fl_platform_handler_new(FL_BINARY_MESSENGER(messenger)); EXPECT_NE(handler, nullptr); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - - g_autoptr(FlValue) null = fl_value_new_null(); - ON_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillByDefault(testing::Return(TRUE)); // Indicate that the binding is initialized. - g_autoptr(GError) error = nullptr; - g_autoptr(GBytes) init_message = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "System.initializationComplete", nullptr, &error); - messenger.ReceiveMessage("flutter/platform", init_message); - - g_autoptr(FlValue) request_args = fl_value_new_map(); - fl_value_set_string_take(request_args, "type", - fl_value_new_string("cancelable")); - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/platform"), - MethodCall("System.requestAppExit", FlValueEq(request_args)), - ::testing::_, ::testing::_, ::testing::_)); + gboolean called = FALSE; + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/platform", "System.initializationComplete", nullptr, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + + g_autoptr(FlValue) expected_result = fl_value_new_null(); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); + + gboolean request_exit_called = FALSE; + fl_mock_binary_messenger_set_json_method_channel( + messenger, "flutter/platform", + [](FlMockBinaryMessenger* messenger, const gchar* name, FlValue* args, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_STREQ(name, "System.requestAppExit"); + + g_autoptr(FlValue) expected_args = fl_value_new_map(); + fl_value_set_string_take(expected_args, "type", + fl_value_new_string("cancelable")); + EXPECT_TRUE(fl_value_equal(args, expected_args)); + + // Cancel so it doesn't try and exit this app (i.e. the current test) + g_autoptr(FlValue) result = fl_value_new_map(); + fl_value_set_string_take(result, "response", + fl_value_new_string("cancel")); + return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + }, + &request_exit_called); g_autoptr(FlValue) args = fl_value_new_map(); fl_value_set_string_take(args, "type", fl_value_new_string("cancelable")); - g_autoptr(GBytes) message = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "System.exitApplication", args, nullptr); - messenger.ReceiveMessage("flutter/platform", message); + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/platform", "System.exitApplication", args, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + + g_autoptr(FlValue) expected_result = fl_value_new_map(); + fl_value_set_string_take(expected_result, "response", + fl_value_new_string("cancel")); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + + g_main_loop_quit(static_cast(user_data)); + }, + loop); + + g_main_loop_run(loop); + + EXPECT_TRUE(request_exit_called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlPlatformHandlerTest, ExitApplicationDispose) { diff --git a/shell/platform/linux/fl_pointer_manager.cc b/shell/platform/linux/fl_pointer_manager.cc new file mode 100644 index 0000000000000..67f360bbd4b2a --- /dev/null +++ b/shell/platform/linux/fl_pointer_manager.cc @@ -0,0 +1,203 @@ +// 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. + +#include "flutter/shell/platform/linux/fl_pointer_manager.h" + +#include "flutter/shell/platform/linux/fl_engine_private.h" + +static constexpr int kMicrosecondsPerMillisecond = 1000; + +struct _FlPointerManager { + GObject parent_instance; + + // Engine to send pointer events to. + GWeakRef engine; + + // ID to mark events with. + FlutterViewId view_id; + + // TRUE if the mouse pointer is inside the view, used for generating missing + // add events. + gboolean pointer_inside; + + // Pointer button state recorded for sending status updates. + int64_t button_state; +}; + +G_DEFINE_TYPE(FlPointerManager, fl_pointer_manager, G_TYPE_OBJECT); + +// Generates a mouse pointer event if the pointer appears inside the window. +static void ensure_pointer_added(FlPointerManager* self, + guint event_time, + FlutterPointerDeviceKind device_kind, + gdouble x, + gdouble y) { + if (self->pointer_inside) { + return; + } + self->pointer_inside = TRUE; + + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { + return; + } + + fl_engine_send_mouse_pointer_event( + engine, self->view_id, kAdd, event_time * kMicrosecondsPerMillisecond, x, + y, device_kind, 0, 0, self->button_state); +} + +static void fl_pointer_manager_dispose(GObject* object) { + FlPointerManager* self = FL_POINTER_MANAGER(object); + + g_weak_ref_clear(&self->engine); + + G_OBJECT_CLASS(fl_pointer_manager_parent_class)->dispose(object); +} + +static void fl_pointer_manager_class_init(FlPointerManagerClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_pointer_manager_dispose; +} + +static void fl_pointer_manager_init(FlPointerManager* self) {} + +FlPointerManager* fl_pointer_manager_new(FlutterViewId view_id, + FlEngine* engine) { + FlPointerManager* self = + FL_POINTER_MANAGER(g_object_new(fl_pointer_manager_get_type(), nullptr)); + + self->view_id = view_id; + g_weak_ref_init(&self->engine, engine); + + return self; +} + +gboolean fl_pointer_manager_handle_button_press( + FlPointerManager* self, + guint event_time, + FlutterPointerDeviceKind device_kind, + gdouble x, + gdouble y, + int64_t button) { + g_return_val_if_fail(FL_IS_POINTER_MANAGER(self), FALSE); + + ensure_pointer_added(self, event_time, device_kind, x, y); + + // Drop the event if Flutter already thinks the button is down. + if ((self->button_state & button) != 0) { + return FALSE; + } + + int old_button_state = self->button_state; + FlutterPointerPhase phase = kMove; + self->button_state ^= button; + phase = old_button_state == 0 ? kDown : kMove; + + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { + return FALSE; + } + + fl_engine_send_mouse_pointer_event( + engine, self->view_id, phase, event_time * kMicrosecondsPerMillisecond, x, + y, device_kind, 0, 0, self->button_state); + + return TRUE; +} + +gboolean fl_pointer_manager_handle_button_release( + FlPointerManager* self, + guint event_time, + FlutterPointerDeviceKind device_kind, + gdouble x, + gdouble y, + int64_t button) { + g_return_val_if_fail(FL_IS_POINTER_MANAGER(self), FALSE); + + // Drop the event if Flutter already thinks the button is up. + if ((self->button_state & button) == 0) { + return FALSE; + } + + FlutterPointerPhase phase = kMove; + self->button_state ^= button; + + phase = self->button_state == 0 ? kUp : kMove; + + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { + return FALSE; + } + + fl_engine_send_mouse_pointer_event( + engine, self->view_id, phase, event_time * kMicrosecondsPerMillisecond, x, + y, device_kind, 0, 0, self->button_state); + + return TRUE; +} + +gboolean fl_pointer_manager_handle_motion(FlPointerManager* self, + guint event_time, + FlutterPointerDeviceKind device_kind, + gdouble x, + gdouble y) { + g_return_val_if_fail(FL_IS_POINTER_MANAGER(self), FALSE); + + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { + return FALSE; + } + + ensure_pointer_added(self, event_time, device_kind, x, y); + + fl_engine_send_mouse_pointer_event( + engine, self->view_id, self->button_state != 0 ? kMove : kHover, + event_time * kMicrosecondsPerMillisecond, x, y, device_kind, 0, 0, + self->button_state); + + return TRUE; +} + +gboolean fl_pointer_manager_handle_enter(FlPointerManager* self, + guint event_time, + FlutterPointerDeviceKind device_kind, + gdouble x, + gdouble y) { + g_return_val_if_fail(FL_IS_POINTER_MANAGER(self), FALSE); + + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { + return FALSE; + } + + ensure_pointer_added(self, event_time, device_kind, x, y); + + return TRUE; +} + +gboolean fl_pointer_manager_handle_leave(FlPointerManager* self, + guint event_time, + FlutterPointerDeviceKind device_kind, + gdouble x, + gdouble y) { + g_return_val_if_fail(FL_IS_POINTER_MANAGER(self), FALSE); + + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { + return FALSE; + } + + // Don't remove pointer while button is down; In case of dragging outside of + // window with mouse grab active Gtk will send another leave notify on + // release. + if (self->pointer_inside && self->button_state == 0) { + fl_engine_send_mouse_pointer_event(engine, self->view_id, kRemove, + event_time * kMicrosecondsPerMillisecond, + x, y, device_kind, 0, 0, + self->button_state); + self->pointer_inside = FALSE; + } + + return TRUE; +} diff --git a/shell/platform/linux/fl_pointer_manager.h b/shell/platform/linux/fl_pointer_manager.h new file mode 100644 index 0000000000000..2647512bd2cdb --- /dev/null +++ b/shell/platform/linux/fl_pointer_manager.h @@ -0,0 +1,118 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_POINTER_MANAGER_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_POINTER_MANAGER_H_ + +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlPointerManager, + fl_pointer_manager, + FL, + POINTER_MANAGER, + GObject); + +/** + * fl_pointer_manager_new: + * @view_id: view ID to report events for. + * @engine: an #FlEngine. + * + * Create a new #FlPointerManager. + * + * Returns: a new #FlPointerManager. + */ +FlPointerManager* fl_pointer_manager_new(FlutterViewId view_id, + FlEngine* engine); + +/** + * fl_pointer_manager_handle_button_press: + * @manager: an #FlPointerManager. + * @event_time: event time in milliseconds. + * @device_kind: kind of device generating the event. + * @x: x co-ordinate of event. + * @y: y co-ordinate of event. + * @button: button being pressed. + * + * Returns %TRUE if this event was handled. + */ +gboolean fl_pointer_manager_handle_button_press( + FlPointerManager* manager, + guint event_time, + FlutterPointerDeviceKind device_kind, + gdouble x, + gdouble y, + int64_t button); + +/** + * fl_pointer_manager_handle_button_release: + * @manager: an #FlPointerManager. + * @event_time: event time in milliseconds. + * @device_kind: kind of device generating the event. + * @x: x co-ordinate of event. + * @y: y co-ordinate of event. + * @button: button being released. + * + * Returns %TRUE if this event was handled. + */ +gboolean fl_pointer_manager_handle_button_release( + FlPointerManager* manager, + guint event_time, + FlutterPointerDeviceKind device_kind, + gdouble x, + gdouble y, + int64_t button); + +/** + * fl_pointer_manager_handle_motion: + * @manager: an #FlPointerManager. + * @event_time: event time in milliseconds. + * @device_kind: kind of device generating the event. + * @x: x co-ordinate of event. + * @y: y co-ordinate of event. + * + * Returns %TRUE if this event was handled. + */ +gboolean fl_pointer_manager_handle_motion(FlPointerManager* manager, + guint event_time, + FlutterPointerDeviceKind device_kind, + gdouble x, + gdouble y); + +/** + * fl_pointer_manager_handle_enter: + * @manager: an #FlPointerManager. + * @event_time: event time in milliseconds. + * @device_kind: kind of device generating the event. + * @x: x co-ordinate of event. + * @y: y co-ordinate of event. + * + * Returns %TRUE if this event was handled. + */ +gboolean fl_pointer_manager_handle_enter(FlPointerManager* manager, + guint event_time, + FlutterPointerDeviceKind device_kind, + gdouble x, + gdouble y); + +/** + * fl_pointer_manager_handle_leave: + * @manager: an #FlPointerManager. + * @event_time: event time in milliseconds. + * @device_kind: kind of device generating the event. + * @x: x co-ordinate of event. + * @y: y co-ordinate of event. + * + * Returns %TRUE if this event was handled. + */ +gboolean fl_pointer_manager_handle_leave(FlPointerManager* manager, + guint event_time, + FlutterPointerDeviceKind device_kind, + gdouble x, + gdouble y); +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_POINTER_MANAGER_H_ diff --git a/shell/platform/linux/fl_pointer_manager_test.cc b/shell/platform/linux/fl_pointer_manager_test.cc new file mode 100644 index 0000000000000..503397e441acf --- /dev/null +++ b/shell/platform/linux/fl_pointer_manager_test.cc @@ -0,0 +1,448 @@ +// 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. + +#include "flutter/shell/platform/linux/fl_pointer_manager.h" +#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" +#include "flutter/shell/platform/linux/fl_engine_private.h" +#include "flutter/shell/platform/linux/testing/fl_test.h" + +#include "gtest/gtest.h" + +static void log_pointer_events( + FlEngine* engine, + std::vector& pointer_events) { + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + embedder_api->SendPointerEvent = MOCK_ENGINE_PROC( + SendPointerEvent, + ([&pointer_events](auto engine, const FlutterPointerEvent* events, + size_t events_count) { + for (size_t i = 0; i < events_count; i++) { + pointer_events.push_back(events[i]); + } + + return kSuccess; + })); +} + +TEST(FlPointerManagerTest, EnterLeave) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + fl_pointer_manager_handle_enter(manager, 1234, kFlutterPointerDeviceKindMouse, + 1.0, 2.0); + fl_pointer_manager_handle_leave(manager, 1235, kFlutterPointerDeviceKindMouse, + 3.0, 4.0); + + EXPECT_EQ(pointer_events.size(), 2u); + + EXPECT_EQ(pointer_events[0].timestamp, 1234000u); + EXPECT_EQ(pointer_events[0].x, 1.0); + EXPECT_EQ(pointer_events[0].y, 2.0); + EXPECT_EQ(pointer_events[0].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[0].buttons, 0); + EXPECT_EQ(pointer_events[0].view_id, 42); + + EXPECT_EQ(pointer_events[1].timestamp, 1235000u); + EXPECT_EQ(pointer_events[1].x, 3.0); + EXPECT_EQ(pointer_events[1].y, 4.0); + EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[1].buttons, 0); + EXPECT_EQ(pointer_events[1].view_id, 42); +} + +TEST(FlPointerManagerTest, EnterEnter) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + fl_pointer_manager_handle_enter(manager, 1234, kFlutterPointerDeviceKindMouse, + 1.0, 2.0); + // Duplicate enter is ignored + fl_pointer_manager_handle_enter(manager, 1235, kFlutterPointerDeviceKindMouse, + 3.0, 4.0); + + EXPECT_EQ(pointer_events.size(), 1u); + + EXPECT_EQ(pointer_events[0].timestamp, 1234000u); + EXPECT_EQ(pointer_events[0].x, 1.0); + EXPECT_EQ(pointer_events[0].y, 2.0); + EXPECT_EQ(pointer_events[0].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[0].buttons, 0); + EXPECT_EQ(pointer_events[0].view_id, 42); +} + +TEST(FlPointerManagerTest, EnterLeaveLeave) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + fl_pointer_manager_handle_enter(manager, 1234, kFlutterPointerDeviceKindMouse, + 1.0, 2.0); + fl_pointer_manager_handle_leave(manager, 1235, kFlutterPointerDeviceKindMouse, + 3.0, 4.0); + // Duplicate leave is ignored + fl_pointer_manager_handle_leave(manager, 1235, kFlutterPointerDeviceKindMouse, + 5.0, 6.0); + + EXPECT_EQ(pointer_events.size(), 2u); + + EXPECT_EQ(pointer_events[0].timestamp, 1234000u); + EXPECT_EQ(pointer_events[0].x, 1.0); + EXPECT_EQ(pointer_events[0].y, 2.0); + EXPECT_EQ(pointer_events[0].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[0].buttons, 0); + EXPECT_EQ(pointer_events[0].view_id, 42); + + EXPECT_EQ(pointer_events[1].timestamp, 1235000u); + EXPECT_EQ(pointer_events[1].x, 3.0); + EXPECT_EQ(pointer_events[1].y, 4.0); + EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[1].buttons, 0); + EXPECT_EQ(pointer_events[1].view_id, 42); +} + +TEST(FlPointerManagerTest, EnterButtonPress) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + fl_pointer_manager_handle_enter(manager, 1234, kFlutterPointerDeviceKindMouse, + 1.0, 2.0); + fl_pointer_manager_handle_button_press( + manager, 1235, kFlutterPointerDeviceKindMouse, 4.0, 8.0, + kFlutterPointerButtonMousePrimary); + + EXPECT_EQ(pointer_events.size(), 2u); + + EXPECT_EQ(pointer_events[0].timestamp, 1234000u); + EXPECT_EQ(pointer_events[0].x, 1.0); + EXPECT_EQ(pointer_events[0].y, 2.0); + EXPECT_EQ(pointer_events[0].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[0].buttons, 0); + EXPECT_EQ(pointer_events[0].view_id, 42); + + EXPECT_EQ(pointer_events[1].timestamp, 1235000u); + EXPECT_EQ(pointer_events[1].x, 4.0); + EXPECT_EQ(pointer_events[1].y, 8.0); + EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[1].buttons, kFlutterPointerButtonMousePrimary); + EXPECT_EQ(pointer_events[1].view_id, 42); +} + +TEST(FlPointerManagerTest, NoEnterButtonPress) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + fl_pointer_manager_handle_button_press( + manager, 1234, kFlutterPointerDeviceKindMouse, 4.0, 8.0, + kFlutterPointerButtonMousePrimary); + + EXPECT_EQ(pointer_events.size(), 2u); + + // Synthetic enter events + EXPECT_EQ(pointer_events[0].timestamp, 1234000u); + EXPECT_EQ(pointer_events[0].x, 4.0); + EXPECT_EQ(pointer_events[0].y, 8.0); + EXPECT_EQ(pointer_events[0].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[0].buttons, 0); + EXPECT_EQ(pointer_events[0].view_id, 42); + + EXPECT_EQ(pointer_events[1].timestamp, 1234000u); + EXPECT_EQ(pointer_events[1].x, 4.0); + EXPECT_EQ(pointer_events[1].y, 8.0); + EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[1].buttons, kFlutterPointerButtonMousePrimary); + EXPECT_EQ(pointer_events[1].view_id, 42); +} + +TEST(FlPointerManagerTest, ButtonPressButtonRelease) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + fl_pointer_manager_handle_button_press( + manager, 1234, kFlutterPointerDeviceKindMouse, 4.0, 8.0, + kFlutterPointerButtonMousePrimary); + fl_pointer_manager_handle_button_release( + manager, 1235, kFlutterPointerDeviceKindMouse, 5.0, 9.0, + kFlutterPointerButtonMousePrimary); + + EXPECT_EQ(pointer_events.size(), 3u); + + // Ignore first synthetic enter event + EXPECT_EQ(pointer_events[1].timestamp, 1234000u); + EXPECT_EQ(pointer_events[1].x, 4.0); + EXPECT_EQ(pointer_events[1].y, 8.0); + EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[1].buttons, kFlutterPointerButtonMousePrimary); + EXPECT_EQ(pointer_events[1].view_id, 42); + EXPECT_EQ(pointer_events[2].timestamp, 1235000u); + EXPECT_EQ(pointer_events[2].x, 5.0); + EXPECT_EQ(pointer_events[2].y, 9.0); + EXPECT_EQ(pointer_events[2].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[2].buttons, 0); + EXPECT_EQ(pointer_events[2].view_id, 42); +} + +TEST(FlPointerManagerTest, ButtonPressButtonReleaseThreeButtons) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + // Press buttons 1-2-3, release 3-2-1 + fl_pointer_manager_handle_button_press( + manager, 1234, kFlutterPointerDeviceKindMouse, 1.0, 2.0, + kFlutterPointerButtonMousePrimary); + fl_pointer_manager_handle_button_press( + manager, 1235, kFlutterPointerDeviceKindMouse, 3.0, 4.0, + kFlutterPointerButtonMouseSecondary); + fl_pointer_manager_handle_button_press(manager, 1236, + kFlutterPointerDeviceKindMouse, 5.0, + 6.0, kFlutterPointerButtonMouseMiddle); + fl_pointer_manager_handle_button_release( + manager, 1237, kFlutterPointerDeviceKindMouse, 7.0, 8.0, + kFlutterPointerButtonMouseMiddle); + fl_pointer_manager_handle_button_release( + manager, 1238, kFlutterPointerDeviceKindMouse, 9.0, 10.0, + kFlutterPointerButtonMouseSecondary); + fl_pointer_manager_handle_button_release( + manager, 1239, kFlutterPointerDeviceKindMouse, 11.0, 12.0, + kFlutterPointerButtonMousePrimary); + + EXPECT_EQ(pointer_events.size(), 7u); + + // Ignore first synthetic enter event + EXPECT_EQ(pointer_events[1].timestamp, 1234000u); + EXPECT_EQ(pointer_events[1].x, 1.0); + EXPECT_EQ(pointer_events[1].y, 2.0); + EXPECT_EQ(pointer_events[1].buttons, kFlutterPointerButtonMousePrimary); + EXPECT_EQ(pointer_events[2].timestamp, 1235000u); + EXPECT_EQ(pointer_events[2].x, 3.0); + EXPECT_EQ(pointer_events[2].y, 4.0); + EXPECT_EQ(pointer_events[2].buttons, kFlutterPointerButtonMousePrimary | + kFlutterPointerButtonMouseSecondary); + EXPECT_EQ(pointer_events[3].timestamp, 1236000u); + EXPECT_EQ(pointer_events[3].x, 5.0); + EXPECT_EQ(pointer_events[3].y, 6.0); + EXPECT_EQ(pointer_events[3].buttons, kFlutterPointerButtonMousePrimary | + kFlutterPointerButtonMouseSecondary | + kFlutterPointerButtonMouseMiddle); + EXPECT_EQ(pointer_events[4].timestamp, 1237000u); + EXPECT_EQ(pointer_events[4].x, 7.0); + EXPECT_EQ(pointer_events[4].y, 8.0); + EXPECT_EQ(pointer_events[4].buttons, kFlutterPointerButtonMousePrimary | + kFlutterPointerButtonMouseSecondary); + EXPECT_EQ(pointer_events[5].timestamp, 1238000u); + EXPECT_EQ(pointer_events[5].x, 9.0); + EXPECT_EQ(pointer_events[5].y, 10.0); + EXPECT_EQ(pointer_events[5].buttons, kFlutterPointerButtonMousePrimary); + EXPECT_EQ(pointer_events[6].timestamp, 1239000u); + EXPECT_EQ(pointer_events[6].x, 11.0); + EXPECT_EQ(pointer_events[6].y, 12.0); + EXPECT_EQ(pointer_events[6].buttons, 0); +} + +TEST(FlPointerManagerTest, ButtonPressButtonPressButtonRelease) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + fl_pointer_manager_handle_button_press( + manager, 1234, kFlutterPointerDeviceKindMouse, 4.0, 8.0, + kFlutterPointerButtonMousePrimary); + // Ignore duplicate press + fl_pointer_manager_handle_button_press( + manager, 1234, kFlutterPointerDeviceKindMouse, 6.0, 10.0, + kFlutterPointerButtonMousePrimary); + fl_pointer_manager_handle_button_release( + manager, 1235, kFlutterPointerDeviceKindMouse, 5.0, 9.0, + kFlutterPointerButtonMousePrimary); + + EXPECT_EQ(pointer_events.size(), 3u); + + // Ignore first synthetic enter event + EXPECT_EQ(pointer_events[1].timestamp, 1234000u); + EXPECT_EQ(pointer_events[1].x, 4.0); + EXPECT_EQ(pointer_events[1].y, 8.0); + EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[1].buttons, kFlutterPointerButtonMousePrimary); + EXPECT_EQ(pointer_events[1].view_id, 42); + EXPECT_EQ(pointer_events[2].timestamp, 1235000u); + EXPECT_EQ(pointer_events[2].x, 5.0); + EXPECT_EQ(pointer_events[2].y, 9.0); + EXPECT_EQ(pointer_events[2].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[2].buttons, 0); + EXPECT_EQ(pointer_events[2].view_id, 42); +} + +TEST(FlPointerManagerTest, ButtonPressButtonReleaseButtonRelease) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + fl_pointer_manager_handle_button_press( + manager, 1234, kFlutterPointerDeviceKindMouse, 4.0, 8.0, + kFlutterPointerButtonMousePrimary); + fl_pointer_manager_handle_button_release( + manager, 1235, kFlutterPointerDeviceKindMouse, 5.0, 9.0, + kFlutterPointerButtonMousePrimary); + // Ignore duplicate release + fl_pointer_manager_handle_button_release( + manager, 1235, kFlutterPointerDeviceKindMouse, 6.0, 10.0, + kFlutterPointerButtonMousePrimary); + + EXPECT_EQ(pointer_events.size(), 3u); + + // Ignore first synthetic enter event + EXPECT_EQ(pointer_events[1].timestamp, 1234000u); + EXPECT_EQ(pointer_events[1].x, 4.0); + EXPECT_EQ(pointer_events[1].y, 8.0); + EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[1].buttons, kFlutterPointerButtonMousePrimary); + EXPECT_EQ(pointer_events[1].view_id, 42); + EXPECT_EQ(pointer_events[2].timestamp, 1235000u); + EXPECT_EQ(pointer_events[2].x, 5.0); + EXPECT_EQ(pointer_events[2].y, 9.0); + EXPECT_EQ(pointer_events[2].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[2].buttons, 0); + EXPECT_EQ(pointer_events[2].view_id, 42); +} + +TEST(FlPointerManagerTest, NoButtonPressButtonRelease) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + // Release without associated press, will be ignored + fl_pointer_manager_handle_button_release( + manager, 1235, kFlutterPointerDeviceKindMouse, 5.0, 9.0, + kFlutterPointerButtonMousePrimary); + + EXPECT_EQ(pointer_events.size(), 0u); +} + +TEST(FlPointerManagerTest, Motion) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + fl_pointer_manager_handle_motion(manager, 1234, + kFlutterPointerDeviceKindMouse, 1.0, 2.0); + fl_pointer_manager_handle_motion(manager, 1235, + kFlutterPointerDeviceKindMouse, 3.0, 4.0); + fl_pointer_manager_handle_motion(manager, 1236, + kFlutterPointerDeviceKindMouse, 5.0, 6.0); + + EXPECT_EQ(pointer_events.size(), 4u); + + // Ignore first synthetic enter event + EXPECT_EQ(pointer_events[1].timestamp, 1234000u); + EXPECT_EQ(pointer_events[1].x, 1.0); + EXPECT_EQ(pointer_events[1].y, 2.0); + EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[1].buttons, 0); + EXPECT_EQ(pointer_events[1].view_id, 42); + EXPECT_EQ(pointer_events[2].timestamp, 1235000u); + EXPECT_EQ(pointer_events[2].x, 3.0); + EXPECT_EQ(pointer_events[2].y, 4.0); + EXPECT_EQ(pointer_events[2].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[2].buttons, 0); + EXPECT_EQ(pointer_events[2].view_id, 42); + EXPECT_EQ(pointer_events[3].timestamp, 1236000u); + EXPECT_EQ(pointer_events[3].x, 5.0); + EXPECT_EQ(pointer_events[3].y, 6.0); + EXPECT_EQ(pointer_events[3].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[3].buttons, 0); + EXPECT_EQ(pointer_events[3].view_id, 42); +} + +TEST(FlPointerManagerTest, Drag) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + fl_pointer_manager_handle_motion(manager, 1234, + kFlutterPointerDeviceKindMouse, 1.0, 2.0); + fl_pointer_manager_handle_button_press( + manager, 1235, kFlutterPointerDeviceKindMouse, 3.0, 4.0, + kFlutterPointerButtonMousePrimary); + fl_pointer_manager_handle_motion(manager, 1236, + kFlutterPointerDeviceKindMouse, 5.0, 6.0); + fl_pointer_manager_handle_button_release( + manager, 1237, kFlutterPointerDeviceKindMouse, 7.0, 8.0, + kFlutterPointerButtonMousePrimary); + fl_pointer_manager_handle_motion(manager, 1238, + kFlutterPointerDeviceKindMouse, 9.0, 10.0); + + EXPECT_EQ(pointer_events.size(), 6u); + + // Ignore first synthetic enter event + EXPECT_EQ(pointer_events[1].timestamp, 1234000u); + EXPECT_EQ(pointer_events[1].x, 1.0); + EXPECT_EQ(pointer_events[1].y, 2.0); + EXPECT_EQ(pointer_events[1].buttons, 0); + EXPECT_EQ(pointer_events[1].view_id, 42); + EXPECT_EQ(pointer_events[2].timestamp, 1235000u); + EXPECT_EQ(pointer_events[2].x, 3.0); + EXPECT_EQ(pointer_events[2].y, 4.0); + EXPECT_EQ(pointer_events[2].buttons, kFlutterPointerButtonMousePrimary); + EXPECT_EQ(pointer_events[2].view_id, 42); + EXPECT_EQ(pointer_events[3].timestamp, 1236000u); + EXPECT_EQ(pointer_events[3].x, 5.0); + EXPECT_EQ(pointer_events[3].y, 6.0); + EXPECT_EQ(pointer_events[3].buttons, kFlutterPointerButtonMousePrimary); + EXPECT_EQ(pointer_events[3].view_id, 42); + EXPECT_EQ(pointer_events[4].timestamp, 1237000u); + EXPECT_EQ(pointer_events[4].x, 7.0); + EXPECT_EQ(pointer_events[4].y, 8.0); + EXPECT_EQ(pointer_events[4].buttons, 0); + EXPECT_EQ(pointer_events[4].view_id, 42); + EXPECT_EQ(pointer_events[5].timestamp, 1238000u); + EXPECT_EQ(pointer_events[5].x, 9.0); + EXPECT_EQ(pointer_events[5].y, 10.0); + EXPECT_EQ(pointer_events[5].buttons, 0); + EXPECT_EQ(pointer_events[5].view_id, 42); +} + +TEST(FlPointerManagerTest, DeviceKind) { + g_autoptr(FlEngine) engine = make_mock_engine(); + std::vector pointer_events; + log_pointer_events(engine, pointer_events); + + g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine); + fl_pointer_manager_handle_enter(manager, 1234, + kFlutterPointerDeviceKindTrackpad, 1.0, 2.0); + fl_pointer_manager_handle_button_press( + manager, 1235, kFlutterPointerDeviceKindTrackpad, 1.0, 2.0, + kFlutterPointerButtonMousePrimary); + fl_pointer_manager_handle_motion(manager, 1238, + kFlutterPointerDeviceKindTrackpad, 3.0, 4.0); + fl_pointer_manager_handle_button_release( + manager, 1237, kFlutterPointerDeviceKindTrackpad, 3.0, 4.0, + kFlutterPointerButtonMousePrimary); + fl_pointer_manager_handle_leave(manager, 1235, + kFlutterPointerDeviceKindTrackpad, 3.0, 4.0); + + EXPECT_EQ(pointer_events.size(), 5u); + + EXPECT_EQ(pointer_events[0].device_kind, kFlutterPointerDeviceKindTrackpad); + EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindTrackpad); + EXPECT_EQ(pointer_events[2].device_kind, kFlutterPointerDeviceKindTrackpad); + EXPECT_EQ(pointer_events[3].device_kind, kFlutterPointerDeviceKindTrackpad); + EXPECT_EQ(pointer_events[4].device_kind, kFlutterPointerDeviceKindTrackpad); +} diff --git a/shell/platform/linux/fl_renderer.cc b/shell/platform/linux/fl_renderer.cc index 63e0a8186ba60..2465390a2951a 100644 --- a/shell/platform/linux/fl_renderer.cc +++ b/shell/platform/linux/fl_renderer.cc @@ -89,6 +89,12 @@ static gboolean is_nvidia() { return strstr(vendor, "NVIDIA") != nullptr; } +// Check if running on an Vivante Corporation driver. +static gboolean is_vivante() { + const gchar* vendor = reinterpret_cast(glGetString(GL_VENDOR)); + return strstr(vendor, "Vivante Corporation") != nullptr; +} + // Returns the log for the given OpenGL shader. Must be freed by the caller. static gchar* get_shader_log(GLuint shader) { GLint log_length; @@ -568,11 +574,12 @@ void fl_renderer_setup(FlRenderer* self) { g_return_if_fail(FL_IS_RENDERER(self)); - // Note: NVIDIA is temporarily disabled due to + // Note: NVIDIA and Vivante are temporarily disabled due to // https://github.com/flutter/flutter/issues/152099 priv->has_gl_framebuffer_blit = - !is_nvidia() && (epoxy_gl_version() >= 30 || - epoxy_has_gl_extension("GL_EXT_framebuffer_blit")); + !is_nvidia() && !is_vivante() && + (epoxy_gl_version() >= 30 || + epoxy_has_gl_extension("GL_EXT_framebuffer_blit")); if (!priv->has_gl_framebuffer_blit) { setup_shader(self); diff --git a/shell/platform/linux/fl_renderer.h b/shell/platform/linux/fl_renderer.h index b107442ea4d43..b59f5e4297f1a 100644 --- a/shell/platform/linux/fl_renderer.h +++ b/shell/platform/linux/fl_renderer.h @@ -20,9 +20,7 @@ G_BEGIN_DECLS */ typedef enum { - // NOLINTBEGIN(readability-identifier-naming) FL_RENDERER_ERROR_FAILED, - // NOLINTEND(readability-identifier-naming) } FlRendererError; GQuark fl_renderer_error_quark(void) G_GNUC_CONST; diff --git a/shell/platform/linux/fl_scrolling_manager.cc b/shell/platform/linux/fl_scrolling_manager.cc index 7bfe1a263b97c..e49924a2cae37 100644 --- a/shell/platform/linux/fl_scrolling_manager.cc +++ b/shell/platform/linux/fl_scrolling_manager.cc @@ -3,13 +3,17 @@ // found in the LICENSE file. #include "flutter/shell/platform/linux/fl_scrolling_manager.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/linux/fl_engine_private.h" static constexpr int kMicrosecondsPerMillisecond = 1000; struct _FlScrollingManager { GObject parent_instance; - GWeakRef view_delegate; + GWeakRef engine; + + FlutterViewId view_id; gdouble last_x; gdouble last_y; @@ -29,7 +33,7 @@ G_DEFINE_TYPE(FlScrollingManager, fl_scrolling_manager, G_TYPE_OBJECT); static void fl_scrolling_manager_dispose(GObject* object) { FlScrollingManager* self = FL_SCROLLING_MANAGER(object); - g_weak_ref_clear(&self->view_delegate); + g_weak_ref_clear(&self->engine); G_OBJECT_CLASS(fl_scrolling_manager_parent_class)->dispose(object); } @@ -40,14 +44,15 @@ static void fl_scrolling_manager_class_init(FlScrollingManagerClass* klass) { static void fl_scrolling_manager_init(FlScrollingManager* self) {} -FlScrollingManager* fl_scrolling_manager_new( - FlScrollingViewDelegate* view_delegate) { - g_return_val_if_fail(FL_IS_SCROLLING_VIEW_DELEGATE(view_delegate), nullptr); +FlScrollingManager* fl_scrolling_manager_new(FlEngine* engine, + FlutterViewId view_id) { + g_return_val_if_fail(FL_IS_ENGINE(engine), nullptr); FlScrollingManager* self = FL_SCROLLING_MANAGER( g_object_new(fl_scrolling_manager_get_type(), nullptr)); - g_weak_ref_init(&self->view_delegate, view_delegate); + g_weak_ref_init(&self->engine, engine); + self->view_id = view_id; self->pan_started = FALSE; self->zoom_started = FALSE; self->rotate_started = FALSE; @@ -68,9 +73,8 @@ void fl_scrolling_manager_handle_scroll_event(FlScrollingManager* self, gint scale_factor) { g_return_if_fail(FL_IS_SCROLLING_MANAGER(self)); - g_autoptr(FlScrollingViewDelegate) view_delegate = - FL_SCROLLING_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); - if (view_delegate == nullptr) { + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { return; } @@ -110,8 +114,8 @@ void fl_scrolling_manager_handle_scroll_event(FlScrollingManager* self, scroll_delta_x *= -1; scroll_delta_y *= -1; if (gdk_event_is_scroll_stop_event(event)) { - fl_scrolling_view_delegate_send_pointer_pan_zoom_event( - view_delegate, event_time * kMicrosecondsPerMillisecond, + fl_engine_send_pointer_pan_zoom_event( + engine, self->view_id, event_time * kMicrosecondsPerMillisecond, event_x * scale_factor, event_y * scale_factor, kPanZoomEnd, self->pan_x, self->pan_y, 0, 0); self->pan_started = FALSE; @@ -119,24 +123,24 @@ void fl_scrolling_manager_handle_scroll_event(FlScrollingManager* self, if (!self->pan_started) { self->pan_x = 0; self->pan_y = 0; - fl_scrolling_view_delegate_send_pointer_pan_zoom_event( - view_delegate, event_time * kMicrosecondsPerMillisecond, + fl_engine_send_pointer_pan_zoom_event( + engine, self->view_id, event_time * kMicrosecondsPerMillisecond, event_x * scale_factor, event_y * scale_factor, kPanZoomStart, 0, 0, 0, 0); self->pan_started = TRUE; } self->pan_x += scroll_delta_x; self->pan_y += scroll_delta_y; - fl_scrolling_view_delegate_send_pointer_pan_zoom_event( - view_delegate, event_time * kMicrosecondsPerMillisecond, + fl_engine_send_pointer_pan_zoom_event( + engine, self->view_id, event_time * kMicrosecondsPerMillisecond, event_x * scale_factor, event_y * scale_factor, kPanZoomUpdate, self->pan_x, self->pan_y, 1, 0); } } else { self->last_x = event_x * scale_factor; self->last_y = event_y * scale_factor; - fl_scrolling_view_delegate_send_mouse_pointer_event( - view_delegate, + fl_engine_send_mouse_pointer_event( + engine, self->view_id, FlutterPointerPhase::kMove /* arbitrary value, phase will be ignored as this is a discrete scroll event */ , @@ -149,9 +153,8 @@ void fl_scrolling_manager_handle_scroll_event(FlScrollingManager* self, void fl_scrolling_manager_handle_rotation_begin(FlScrollingManager* self) { g_return_if_fail(FL_IS_SCROLLING_MANAGER(self)); - g_autoptr(FlScrollingViewDelegate) view_delegate = - FL_SCROLLING_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); - if (view_delegate == nullptr) { + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { return; } @@ -159,8 +162,8 @@ void fl_scrolling_manager_handle_rotation_begin(FlScrollingManager* self) { if (!self->zoom_started) { self->scale = 1; self->rotation = 0; - fl_scrolling_view_delegate_send_pointer_pan_zoom_event( - view_delegate, g_get_real_time(), self->last_x, self->last_y, + fl_engine_send_pointer_pan_zoom_event( + engine, self->view_id, g_get_real_time(), self->last_x, self->last_y, kPanZoomStart, 0, 0, 0, 0); } } @@ -169,31 +172,29 @@ void fl_scrolling_manager_handle_rotation_update(FlScrollingManager* self, gdouble rotation) { g_return_if_fail(FL_IS_SCROLLING_MANAGER(self)); - g_autoptr(FlScrollingViewDelegate) view_delegate = - FL_SCROLLING_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); - if (view_delegate == nullptr) { + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { return; } self->rotation = rotation; - fl_scrolling_view_delegate_send_pointer_pan_zoom_event( - view_delegate, g_get_real_time(), self->last_x, self->last_y, + fl_engine_send_pointer_pan_zoom_event( + engine, self->view_id, g_get_real_time(), self->last_x, self->last_y, kPanZoomUpdate, 0, 0, self->scale, self->rotation); } void fl_scrolling_manager_handle_rotation_end(FlScrollingManager* self) { g_return_if_fail(FL_IS_SCROLLING_MANAGER(self)); - g_autoptr(FlScrollingViewDelegate) view_delegate = - FL_SCROLLING_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); - if (view_delegate == nullptr) { + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { return; } self->rotate_started = FALSE; if (!self->zoom_started) { - fl_scrolling_view_delegate_send_pointer_pan_zoom_event( - view_delegate, g_get_real_time(), self->last_x, self->last_y, + fl_engine_send_pointer_pan_zoom_event( + engine, self->view_id, g_get_real_time(), self->last_x, self->last_y, kPanZoomEnd, 0, 0, 0, 0); } } @@ -201,9 +202,8 @@ void fl_scrolling_manager_handle_rotation_end(FlScrollingManager* self) { void fl_scrolling_manager_handle_zoom_begin(FlScrollingManager* self) { g_return_if_fail(FL_IS_SCROLLING_MANAGER(self)); - g_autoptr(FlScrollingViewDelegate) view_delegate = - FL_SCROLLING_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); - if (view_delegate == nullptr) { + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { return; } @@ -211,8 +211,8 @@ void fl_scrolling_manager_handle_zoom_begin(FlScrollingManager* self) { if (!self->rotate_started) { self->scale = 1; self->rotation = 0; - fl_scrolling_view_delegate_send_pointer_pan_zoom_event( - view_delegate, g_get_real_time(), self->last_x, self->last_y, + fl_engine_send_pointer_pan_zoom_event( + engine, self->view_id, g_get_real_time(), self->last_x, self->last_y, kPanZoomStart, 0, 0, 0, 0); } } @@ -221,31 +221,29 @@ void fl_scrolling_manager_handle_zoom_update(FlScrollingManager* self, gdouble scale) { g_return_if_fail(FL_IS_SCROLLING_MANAGER(self)); - g_autoptr(FlScrollingViewDelegate) view_delegate = - FL_SCROLLING_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); - if (view_delegate == nullptr) { + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { return; } self->scale = scale; - fl_scrolling_view_delegate_send_pointer_pan_zoom_event( - view_delegate, g_get_real_time(), self->last_x, self->last_y, + fl_engine_send_pointer_pan_zoom_event( + engine, self->view_id, g_get_real_time(), self->last_x, self->last_y, kPanZoomUpdate, 0, 0, self->scale, self->rotation); } void fl_scrolling_manager_handle_zoom_end(FlScrollingManager* self) { g_return_if_fail(FL_IS_SCROLLING_MANAGER(self)); - g_autoptr(FlScrollingViewDelegate) view_delegate = - FL_SCROLLING_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); - if (view_delegate == nullptr) { + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine == nullptr) { return; } self->zoom_started = FALSE; if (!self->rotate_started) { - fl_scrolling_view_delegate_send_pointer_pan_zoom_event( - view_delegate, g_get_real_time(), self->last_x, self->last_y, + fl_engine_send_pointer_pan_zoom_event( + engine, self->view_id, g_get_real_time(), self->last_x, self->last_y, kPanZoomEnd, 0, 0, 0, 0); } } diff --git a/shell/platform/linux/fl_scrolling_manager.h b/shell/platform/linux/fl_scrolling_manager.h index db862317ba5df..551a38e89943d 100644 --- a/shell/platform/linux/fl_scrolling_manager.h +++ b/shell/platform/linux/fl_scrolling_manager.h @@ -7,7 +7,8 @@ #include -#include "flutter/shell/platform/linux/fl_scrolling_view_delegate.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" G_BEGIN_DECLS @@ -19,15 +20,15 @@ G_DECLARE_FINAL_TYPE(FlScrollingManager, /** * fl_scrolling_manager_new: - * @view_delegate: An interface that the manager requires to communicate with - * the platform. Usually implemented by FlView. + * @engine: an #FlEngine. + * @view_id: the view being managed. * * Create a new #FlScrollingManager. * * Returns: a new #FlScrollingManager. */ -FlScrollingManager* fl_scrolling_manager_new( - FlScrollingViewDelegate* view_delegate); +FlScrollingManager* fl_scrolling_manager_new(FlEngine* engine, + FlutterViewId view_id); /** * fl_scrolling_manager_set_last_mouse_position: diff --git a/shell/platform/linux/fl_scrolling_manager_test.cc b/shell/platform/linux/fl_scrolling_manager_test.cc index 6445b531712d5..05fe2adc31967 100644 --- a/shell/platform/linux/fl_scrolling_manager_test.cc +++ b/shell/platform/linux/fl_scrolling_manager_test.cc @@ -3,247 +3,14 @@ // found in the LICENSE file. #include "flutter/shell/platform/linux/fl_scrolling_manager.h" +#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" +#include "flutter/shell/platform/linux/fl_engine_private.h" +#include "flutter/shell/platform/linux/testing/fl_test.h" #include #include #include "gtest/gtest.h" -namespace { -typedef std::function - MousePointerCallHandler; -typedef std::function - PointerPanZoomCallHandler; - -typedef struct { - FlutterPointerPhase phase; - size_t timestamp; - double x; - double y; - FlutterPointerDeviceKind device_kind; - double scroll_delta_x; - double scroll_delta_y; - int64_t buttons; -} MousePointerEventRecord; - -typedef struct { - size_t timestamp; - double x; - double y; - FlutterPointerPhase phase; - double pan_x; - double pan_y; - double scale; - double rotation; -} PointerPanZoomEventRecord; - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlMockScrollingViewDelegate, - fl_mock_scrolling_view_delegate, - FL, - MOCK_SCROLLING_VIEW_DELEGATE, - GObject) - -G_END_DECLS - -/***** FlMockScrollingViewDelegate *****/ - -struct _FlMockScrollingViewDelegate { - GObject parent_instance; -}; - -struct FlMockScrollingViewDelegatePrivate { - MousePointerCallHandler mouse_handler; - PointerPanZoomCallHandler pan_zoom_handler; -}; - -static void fl_mock_view_scroll_delegate_iface_init( - FlScrollingViewDelegateInterface* iface); - -G_DEFINE_TYPE_WITH_CODE( - FlMockScrollingViewDelegate, - fl_mock_scrolling_view_delegate, - G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE(fl_scrolling_view_delegate_get_type(), - fl_mock_view_scroll_delegate_iface_init); - G_ADD_PRIVATE(FlMockScrollingViewDelegate)) - -#define FL_MOCK_SCROLLING_VIEW_DELEGATE_GET_PRIVATE(obj) \ - static_cast( \ - fl_mock_scrolling_view_delegate_get_instance_private( \ - FL_MOCK_SCROLLING_VIEW_DELEGATE(obj))) - -static void fl_mock_scrolling_view_delegate_init( - FlMockScrollingViewDelegate* self) { - FlMockScrollingViewDelegatePrivate* priv = - FL_MOCK_SCROLLING_VIEW_DELEGATE_GET_PRIVATE(self); - - new (priv) FlMockScrollingViewDelegatePrivate(); -} - -static void fl_mock_scrolling_view_delegate_dispose(GObject* object) { - FL_MOCK_SCROLLING_VIEW_DELEGATE_GET_PRIVATE(object) - ->~FlMockScrollingViewDelegatePrivate(); - - G_OBJECT_CLASS(fl_mock_scrolling_view_delegate_parent_class)->dispose(object); -} - -static void fl_mock_scrolling_view_delegate_class_init( - FlMockScrollingViewDelegateClass* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_mock_scrolling_view_delegate_dispose; -} - -static void fl_mock_view_send_mouse_pointer_event( - FlScrollingViewDelegate* delegate, - FlutterPointerPhase phase, - size_t timestamp, - double x, - double y, - FlutterPointerDeviceKind device_kind, - double scroll_delta_x, - double scroll_delta_y, - int64_t buttons) { - FlMockScrollingViewDelegatePrivate* priv = - FL_MOCK_SCROLLING_VIEW_DELEGATE_GET_PRIVATE(delegate); - priv->mouse_handler(phase, timestamp, x, y, device_kind, scroll_delta_x, - scroll_delta_y, buttons); -} - -static void fl_mock_view_send_pointer_pan_zoom_event( - FlScrollingViewDelegate* delegate, - size_t timestamp, - double x, - double y, - FlutterPointerPhase phase, - double pan_x, - double pan_y, - double scale, - double rotation) { - FlMockScrollingViewDelegatePrivate* priv = - FL_MOCK_SCROLLING_VIEW_DELEGATE_GET_PRIVATE(delegate); - priv->pan_zoom_handler(timestamp, x, y, phase, pan_x, pan_y, scale, rotation); -} - -static void fl_mock_view_scroll_delegate_iface_init( - FlScrollingViewDelegateInterface* iface) { - iface->send_mouse_pointer_event = fl_mock_view_send_mouse_pointer_event; - iface->send_pointer_pan_zoom_event = fl_mock_view_send_pointer_pan_zoom_event; -} - -static FlMockScrollingViewDelegate* fl_mock_scrolling_view_delegate_new() { - FlMockScrollingViewDelegate* self = FL_MOCK_SCROLLING_VIEW_DELEGATE( - g_object_new(fl_mock_scrolling_view_delegate_get_type(), nullptr)); - - // Added to stop compiler complaining about an unused function. - FL_IS_MOCK_SCROLLING_VIEW_DELEGATE(self); - - return self; -} - -static void fl_mock_scrolling_view_set_mouse_handler( - FlMockScrollingViewDelegate* self, - MousePointerCallHandler handler) { - FlMockScrollingViewDelegatePrivate* priv = - FL_MOCK_SCROLLING_VIEW_DELEGATE_GET_PRIVATE(self); - - priv->mouse_handler = std::move(handler); -} - -static void fl_mock_scrolling_view_set_pan_zoom_handler( - FlMockScrollingViewDelegate* self, - PointerPanZoomCallHandler handler) { - FlMockScrollingViewDelegatePrivate* priv = - FL_MOCK_SCROLLING_VIEW_DELEGATE_GET_PRIVATE(self); - - priv->pan_zoom_handler = std::move(handler); -} - -/***** End FlMockScrollingViewDelegate *****/ - -class ScrollingTester { - public: - ScrollingTester() { - view_ = fl_mock_scrolling_view_delegate_new(); - manager_ = fl_scrolling_manager_new(FL_SCROLLING_VIEW_DELEGATE(view_)); - fl_mock_scrolling_view_set_mouse_handler( - view_, - [](FlutterPointerPhase phase, size_t timestamp, double x, double y, - FlutterPointerDeviceKind device_kind, double scroll_delta_x, - double scroll_delta_y, int64_t buttons) { - // do nothing - }); - fl_mock_scrolling_view_set_pan_zoom_handler( - view_, - [](size_t timestamp, double x, double y, FlutterPointerPhase phase, - double pan_x, double pan_y, double scale, double rotation) { - // do nothing - }); - } - - ~ScrollingTester() { - g_clear_object(&view_); - g_clear_object(&manager_); - } - - FlScrollingManager* manager() { return manager_; } - - void recordMousePointerCallsTo( - std::vector& storage) { - fl_mock_scrolling_view_set_mouse_handler( - view_, [&storage](FlutterPointerPhase phase, size_t timestamp, double x, - double y, FlutterPointerDeviceKind device_kind, - double scroll_delta_x, double scroll_delta_y, - int64_t buttons) { - storage.push_back(MousePointerEventRecord{ - .phase = phase, - .timestamp = timestamp, - .x = x, - .y = y, - .device_kind = device_kind, - .scroll_delta_x = scroll_delta_x, - .scroll_delta_y = scroll_delta_y, - .buttons = buttons, - }); - }); - } - - void recordPointerPanZoomCallsTo( - std::vector& storage) { - fl_mock_scrolling_view_set_pan_zoom_handler( - view_, [&storage](size_t timestamp, double x, double y, - FlutterPointerPhase phase, double pan_x, double pan_y, - double scale, double rotation) { - storage.push_back(PointerPanZoomEventRecord{ - .timestamp = timestamp, - .x = x, - .y = y, - .phase = phase, - .pan_x = pan_x, - .pan_y = pan_y, - .scale = scale, - .rotation = rotation, - }); - }); - } - - private: - FlMockScrollingViewDelegate* view_; - FlScrollingManager* manager_; -}; // Disgusting hack but could not find any way to create a GdkDevice struct _FakeGdkDevice { @@ -262,12 +29,23 @@ GdkDevice* makeFakeDevice(GdkInputSource source) { return reinterpret_cast(device); } -TEST(FlScrollingManagerTest, DiscreteDirectionional) { - ScrollingTester tester; - std::vector mouse_records; - std::vector pan_zoom_records; - tester.recordMousePointerCallsTo(mouse_records); - tester.recordPointerPanZoomCallsTo(pan_zoom_records); +TEST(FlScrollingManagerTest, DiscreteDirectional) { + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + std::vector pointer_events; + embedder_api->SendPointerEvent = MOCK_ENGINE_PROC( + SendPointerEvent, + ([&pointer_events](auto engine, const FlutterPointerEvent* events, + size_t events_count) { + for (size_t i = 0; i < events_count; i++) { + pointer_events.push_back(events[i]); + } + + return kSuccess; + })); + + g_autoptr(FlScrollingManager) manager = fl_scrolling_manager_new(engine, 0); + GdkDevice* mouse = makeFakeDevice(GDK_SOURCE_MOUSE); GdkEventScroll* event = reinterpret_cast(gdk_event_new(GDK_SCROLL)); @@ -276,57 +54,64 @@ TEST(FlScrollingManagerTest, DiscreteDirectionional) { event->y = 8.0; event->device = mouse; event->direction = GDK_SCROLL_UP; - fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0); - EXPECT_EQ(pan_zoom_records.size(), 0u); - EXPECT_EQ(mouse_records.size(), 1u); - EXPECT_EQ(mouse_records[0].x, 4.0); - EXPECT_EQ(mouse_records[0].y, 8.0); - EXPECT_EQ(mouse_records[0].device_kind, kFlutterPointerDeviceKindMouse); - EXPECT_EQ(mouse_records[0].timestamp, + fl_scrolling_manager_handle_scroll_event(manager, event, 1.0); + EXPECT_EQ(pointer_events.size(), 1u); + EXPECT_EQ(pointer_events[0].x, 4.0); + EXPECT_EQ(pointer_events[0].y, 8.0); + EXPECT_EQ(pointer_events[0].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[0].timestamp, 1000lu); // Milliseconds -> Microseconds - EXPECT_EQ(mouse_records[0].scroll_delta_x, 0); - EXPECT_EQ(mouse_records[0].scroll_delta_y, 53 * -1.0); + EXPECT_EQ(pointer_events[0].scroll_delta_x, 0); + EXPECT_EQ(pointer_events[0].scroll_delta_y, 53 * -1.0); event->direction = GDK_SCROLL_DOWN; - fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0); - EXPECT_EQ(pan_zoom_records.size(), 0u); - EXPECT_EQ(mouse_records.size(), 2u); - EXPECT_EQ(mouse_records[1].x, 4.0); - EXPECT_EQ(mouse_records[1].y, 8.0); - EXPECT_EQ(mouse_records[1].device_kind, kFlutterPointerDeviceKindMouse); - EXPECT_EQ(mouse_records[1].timestamp, + fl_scrolling_manager_handle_scroll_event(manager, event, 1.0); + EXPECT_EQ(pointer_events.size(), 2u); + EXPECT_EQ(pointer_events[1].x, 4.0); + EXPECT_EQ(pointer_events[1].y, 8.0); + EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[1].timestamp, 1000lu); // Milliseconds -> Microseconds - EXPECT_EQ(mouse_records[1].scroll_delta_x, 0); - EXPECT_EQ(mouse_records[1].scroll_delta_y, 53 * 1.0); + EXPECT_EQ(pointer_events[1].scroll_delta_x, 0); + EXPECT_EQ(pointer_events[1].scroll_delta_y, 53 * 1.0); event->direction = GDK_SCROLL_LEFT; - fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0); - EXPECT_EQ(pan_zoom_records.size(), 0u); - EXPECT_EQ(mouse_records.size(), 3u); - EXPECT_EQ(mouse_records[2].x, 4.0); - EXPECT_EQ(mouse_records[2].y, 8.0); - EXPECT_EQ(mouse_records[2].device_kind, kFlutterPointerDeviceKindMouse); - EXPECT_EQ(mouse_records[2].timestamp, + fl_scrolling_manager_handle_scroll_event(manager, event, 1.0); + EXPECT_EQ(pointer_events.size(), 3u); + EXPECT_EQ(pointer_events[2].x, 4.0); + EXPECT_EQ(pointer_events[2].y, 8.0); + EXPECT_EQ(pointer_events[2].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[2].timestamp, 1000lu); // Milliseconds -> Microseconds - EXPECT_EQ(mouse_records[2].scroll_delta_x, 53 * -1.0); - EXPECT_EQ(mouse_records[2].scroll_delta_y, 0); + EXPECT_EQ(pointer_events[2].scroll_delta_x, 53 * -1.0); + EXPECT_EQ(pointer_events[2].scroll_delta_y, 0); event->direction = GDK_SCROLL_RIGHT; - fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0); - EXPECT_EQ(pan_zoom_records.size(), 0u); - EXPECT_EQ(mouse_records.size(), 4u); - EXPECT_EQ(mouse_records[3].x, 4.0); - EXPECT_EQ(mouse_records[3].y, 8.0); - EXPECT_EQ(mouse_records[3].device_kind, kFlutterPointerDeviceKindMouse); - EXPECT_EQ(mouse_records[3].timestamp, + fl_scrolling_manager_handle_scroll_event(manager, event, 1.0); + EXPECT_EQ(pointer_events.size(), 4u); + EXPECT_EQ(pointer_events[3].x, 4.0); + EXPECT_EQ(pointer_events[3].y, 8.0); + EXPECT_EQ(pointer_events[3].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[3].timestamp, 1000lu); // Milliseconds -> Microseconds - EXPECT_EQ(mouse_records[3].scroll_delta_x, 53 * 1.0); - EXPECT_EQ(mouse_records[3].scroll_delta_y, 0); + EXPECT_EQ(pointer_events[3].scroll_delta_x, 53 * 1.0); + EXPECT_EQ(pointer_events[3].scroll_delta_y, 0); } TEST(FlScrollingManagerTest, DiscreteScrolling) { - ScrollingTester tester; - std::vector mouse_records; - std::vector pan_zoom_records; - tester.recordMousePointerCallsTo(mouse_records); - tester.recordPointerPanZoomCallsTo(pan_zoom_records); + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + std::vector pointer_events; + embedder_api->SendPointerEvent = MOCK_ENGINE_PROC( + SendPointerEvent, + ([&pointer_events](auto engine, const FlutterPointerEvent* events, + size_t events_count) { + for (size_t i = 0; i < events_count; i++) { + pointer_events.push_back(events[i]); + } + + return kSuccess; + })); + + g_autoptr(FlScrollingManager) manager = fl_scrolling_manager_new(engine, 0); + GdkDevice* mouse = makeFakeDevice(GDK_SOURCE_MOUSE); GdkEventScroll* event = reinterpret_cast(gdk_event_new(GDK_SCROLL)); @@ -337,24 +122,34 @@ TEST(FlScrollingManagerTest, DiscreteScrolling) { event->delta_y = 2.0; event->device = mouse; event->direction = GDK_SCROLL_SMOOTH; - fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0); - EXPECT_EQ(pan_zoom_records.size(), 0u); - EXPECT_EQ(mouse_records.size(), 1u); - EXPECT_EQ(mouse_records[0].x, 4.0); - EXPECT_EQ(mouse_records[0].y, 8.0); - EXPECT_EQ(mouse_records[0].device_kind, kFlutterPointerDeviceKindMouse); - EXPECT_EQ(mouse_records[0].timestamp, + fl_scrolling_manager_handle_scroll_event(manager, event, 1.0); + EXPECT_EQ(pointer_events.size(), 1u); + EXPECT_EQ(pointer_events[0].x, 4.0); + EXPECT_EQ(pointer_events[0].y, 8.0); + EXPECT_EQ(pointer_events[0].device_kind, kFlutterPointerDeviceKindMouse); + EXPECT_EQ(pointer_events[0].timestamp, 1000lu); // Milliseconds -> Microseconds - EXPECT_EQ(mouse_records[0].scroll_delta_x, 53 * 1.0); - EXPECT_EQ(mouse_records[0].scroll_delta_y, 53 * 2.0); + EXPECT_EQ(pointer_events[0].scroll_delta_x, 53 * 1.0); + EXPECT_EQ(pointer_events[0].scroll_delta_y, 53 * 2.0); } TEST(FlScrollingManagerTest, Panning) { - ScrollingTester tester; - std::vector mouse_records; - std::vector pan_zoom_records; - tester.recordMousePointerCallsTo(mouse_records); - tester.recordPointerPanZoomCallsTo(pan_zoom_records); + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + std::vector pointer_events; + embedder_api->SendPointerEvent = MOCK_ENGINE_PROC( + SendPointerEvent, + ([&pointer_events](auto engine, const FlutterPointerEvent* events, + size_t events_count) { + for (size_t i = 0; i < events_count; i++) { + pointer_events.push_back(events[i]); + } + + return kSuccess; + })); + + g_autoptr(FlScrollingManager) manager = fl_scrolling_manager_new(engine, 0); + GdkDevice* touchpad = makeFakeDevice(GDK_SOURCE_TOUCHPAD); GdkEventScroll* event = reinterpret_cast(gdk_event_new(GDK_SCROLL)); @@ -365,224 +160,248 @@ TEST(FlScrollingManagerTest, Panning) { event->delta_y = 2.0; event->device = touchpad; event->direction = GDK_SCROLL_SMOOTH; - fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0); - EXPECT_EQ(pan_zoom_records.size(), 2u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[0].x, 4.0); - EXPECT_EQ(pan_zoom_records[0].y, 8.0); - EXPECT_EQ(pan_zoom_records[0].timestamp, + fl_scrolling_manager_handle_scroll_event(manager, event, 1.0); + EXPECT_EQ(pointer_events.size(), 2u); + EXPECT_EQ(pointer_events[0].x, 4.0); + EXPECT_EQ(pointer_events[0].y, 8.0); + EXPECT_EQ(pointer_events[0].timestamp, 1000lu); // Milliseconds -> Microseconds - EXPECT_EQ(pan_zoom_records[0].phase, kPanZoomStart); - EXPECT_EQ(pan_zoom_records[1].x, 4.0); - EXPECT_EQ(pan_zoom_records[1].y, 8.0); - EXPECT_EQ(pan_zoom_records[1].timestamp, + EXPECT_EQ(pointer_events[0].phase, kPanZoomStart); + EXPECT_EQ(pointer_events[1].x, 4.0); + EXPECT_EQ(pointer_events[1].y, 8.0); + EXPECT_EQ(pointer_events[1].timestamp, 1000lu); // Milliseconds -> Microseconds - EXPECT_EQ(pan_zoom_records[1].phase, kPanZoomUpdate); - EXPECT_EQ(pan_zoom_records[1].pan_x, 53 * -1.0); // directions get swapped - EXPECT_EQ(pan_zoom_records[1].pan_y, 53 * -2.0); - EXPECT_EQ(pan_zoom_records[1].scale, 1.0); - EXPECT_EQ(pan_zoom_records[1].rotation, 0.0); - fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0); - EXPECT_EQ(pan_zoom_records.size(), 3u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[2].x, 4.0); - EXPECT_EQ(pan_zoom_records[2].y, 8.0); - EXPECT_EQ(pan_zoom_records[2].timestamp, + EXPECT_EQ(pointer_events[1].phase, kPanZoomUpdate); + EXPECT_EQ(pointer_events[1].pan_x, 53 * -1.0); // directions get swapped + EXPECT_EQ(pointer_events[1].pan_y, 53 * -2.0); + EXPECT_EQ(pointer_events[1].scale, 1.0); + EXPECT_EQ(pointer_events[1].rotation, 0.0); + fl_scrolling_manager_handle_scroll_event(manager, event, 1.0); + EXPECT_EQ(pointer_events.size(), 3u); + EXPECT_EQ(pointer_events[2].x, 4.0); + EXPECT_EQ(pointer_events[2].y, 8.0); + EXPECT_EQ(pointer_events[2].timestamp, 1000lu); // Milliseconds -> Microseconds - EXPECT_EQ(pan_zoom_records[2].phase, kPanZoomUpdate); - EXPECT_EQ(pan_zoom_records[2].pan_x, 53 * -2.0); // directions get swapped - EXPECT_EQ(pan_zoom_records[2].pan_y, 53 * -4.0); - EXPECT_EQ(pan_zoom_records[2].scale, 1.0); - EXPECT_EQ(pan_zoom_records[2].rotation, 0.0); + EXPECT_EQ(pointer_events[2].phase, kPanZoomUpdate); + EXPECT_EQ(pointer_events[2].pan_x, 53 * -2.0); // directions get swapped + EXPECT_EQ(pointer_events[2].pan_y, 53 * -4.0); + EXPECT_EQ(pointer_events[2].scale, 1.0); + EXPECT_EQ(pointer_events[2].rotation, 0.0); event->is_stop = true; - fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0); - EXPECT_EQ(pan_zoom_records.size(), 4u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[3].x, 4.0); - EXPECT_EQ(pan_zoom_records[3].y, 8.0); - EXPECT_EQ(pan_zoom_records[3].timestamp, + fl_scrolling_manager_handle_scroll_event(manager, event, 1.0); + EXPECT_EQ(pointer_events.size(), 4u); + EXPECT_EQ(pointer_events[3].x, 4.0); + EXPECT_EQ(pointer_events[3].y, 8.0); + EXPECT_EQ(pointer_events[3].timestamp, 1000lu); // Milliseconds -> Microseconds - EXPECT_EQ(pan_zoom_records[3].phase, kPanZoomEnd); + EXPECT_EQ(pointer_events[3].phase, kPanZoomEnd); } TEST(FlScrollingManagerTest, Zooming) { - ScrollingTester tester; - std::vector mouse_records; - std::vector pan_zoom_records; - tester.recordMousePointerCallsTo(mouse_records); - tester.recordPointerPanZoomCallsTo(pan_zoom_records); + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + std::vector pointer_events; + embedder_api->SendPointerEvent = MOCK_ENGINE_PROC( + SendPointerEvent, + ([&pointer_events](auto engine, const FlutterPointerEvent* events, + size_t events_count) { + for (size_t i = 0; i < events_count; i++) { + pointer_events.push_back(events[i]); + } + + return kSuccess; + })); + + g_autoptr(FlScrollingManager) manager = fl_scrolling_manager_new(engine, 0); + size_t time_start = g_get_real_time(); - fl_scrolling_manager_handle_zoom_begin(tester.manager()); - EXPECT_EQ(pan_zoom_records.size(), 1u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[0].x, 0); - EXPECT_EQ(pan_zoom_records[0].y, 0); - EXPECT_EQ(pan_zoom_records[0].phase, kPanZoomStart); - EXPECT_GE(pan_zoom_records[0].timestamp, time_start); - fl_scrolling_manager_handle_zoom_update(tester.manager(), 1.1); - EXPECT_EQ(pan_zoom_records.size(), 2u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[1].x, 0); - EXPECT_EQ(pan_zoom_records[1].y, 0); - EXPECT_EQ(pan_zoom_records[1].phase, kPanZoomUpdate); - EXPECT_GE(pan_zoom_records[1].timestamp, pan_zoom_records[0].timestamp); - EXPECT_EQ(pan_zoom_records[1].pan_x, 0); - EXPECT_EQ(pan_zoom_records[1].pan_y, 0); - EXPECT_EQ(pan_zoom_records[1].scale, 1.1); - EXPECT_EQ(pan_zoom_records[1].rotation, 0); - fl_scrolling_manager_handle_zoom_end(tester.manager()); - EXPECT_EQ(pan_zoom_records.size(), 3u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[2].x, 0); - EXPECT_EQ(pan_zoom_records[2].y, 0); - EXPECT_EQ(pan_zoom_records[2].phase, kPanZoomEnd); - EXPECT_GE(pan_zoom_records[2].timestamp, pan_zoom_records[1].timestamp); + fl_scrolling_manager_handle_zoom_begin(manager); + EXPECT_EQ(pointer_events.size(), 1u); + EXPECT_EQ(pointer_events[0].x, 0); + EXPECT_EQ(pointer_events[0].y, 0); + EXPECT_EQ(pointer_events[0].phase, kPanZoomStart); + EXPECT_GE(pointer_events[0].timestamp, time_start); + fl_scrolling_manager_handle_zoom_update(manager, 1.1); + EXPECT_EQ(pointer_events.size(), 2u); + EXPECT_EQ(pointer_events[1].x, 0); + EXPECT_EQ(pointer_events[1].y, 0); + EXPECT_EQ(pointer_events[1].phase, kPanZoomUpdate); + EXPECT_GE(pointer_events[1].timestamp, pointer_events[0].timestamp); + EXPECT_EQ(pointer_events[1].pan_x, 0); + EXPECT_EQ(pointer_events[1].pan_y, 0); + EXPECT_EQ(pointer_events[1].scale, 1.1); + EXPECT_EQ(pointer_events[1].rotation, 0); + fl_scrolling_manager_handle_zoom_end(manager); + EXPECT_EQ(pointer_events.size(), 3u); + EXPECT_EQ(pointer_events[2].x, 0); + EXPECT_EQ(pointer_events[2].y, 0); + EXPECT_EQ(pointer_events[2].phase, kPanZoomEnd); + EXPECT_GE(pointer_events[2].timestamp, pointer_events[1].timestamp); } TEST(FlScrollingManagerTest, Rotating) { - ScrollingTester tester; - std::vector mouse_records; - std::vector pan_zoom_records; - tester.recordMousePointerCallsTo(mouse_records); - tester.recordPointerPanZoomCallsTo(pan_zoom_records); + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + std::vector pointer_events; + embedder_api->SendPointerEvent = MOCK_ENGINE_PROC( + SendPointerEvent, + ([&pointer_events](auto engine, const FlutterPointerEvent* events, + size_t events_count) { + for (size_t i = 0; i < events_count; i++) { + pointer_events.push_back(events[i]); + } + + return kSuccess; + })); + + g_autoptr(FlScrollingManager) manager = fl_scrolling_manager_new(engine, 0); + size_t time_start = g_get_real_time(); - fl_scrolling_manager_handle_rotation_begin(tester.manager()); - EXPECT_EQ(pan_zoom_records.size(), 1u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[0].x, 0); - EXPECT_EQ(pan_zoom_records[0].y, 0); - EXPECT_EQ(pan_zoom_records[0].phase, kPanZoomStart); - EXPECT_GE(pan_zoom_records[0].timestamp, time_start); - fl_scrolling_manager_handle_rotation_update(tester.manager(), 0.5); - EXPECT_EQ(pan_zoom_records.size(), 2u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[1].x, 0); - EXPECT_EQ(pan_zoom_records[1].y, 0); - EXPECT_EQ(pan_zoom_records[1].phase, kPanZoomUpdate); - EXPECT_GE(pan_zoom_records[1].timestamp, pan_zoom_records[0].timestamp); - EXPECT_EQ(pan_zoom_records[1].pan_x, 0); - EXPECT_EQ(pan_zoom_records[1].pan_y, 0); - EXPECT_EQ(pan_zoom_records[1].scale, 1.0); - EXPECT_EQ(pan_zoom_records[1].rotation, 0.5); - fl_scrolling_manager_handle_rotation_end(tester.manager()); - EXPECT_EQ(pan_zoom_records.size(), 3u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[2].x, 0); - EXPECT_EQ(pan_zoom_records[2].y, 0); - EXPECT_EQ(pan_zoom_records[2].phase, kPanZoomEnd); - EXPECT_GE(pan_zoom_records[2].timestamp, pan_zoom_records[1].timestamp); + fl_scrolling_manager_handle_rotation_begin(manager); + EXPECT_EQ(pointer_events.size(), 1u); + EXPECT_EQ(pointer_events[0].x, 0); + EXPECT_EQ(pointer_events[0].y, 0); + EXPECT_EQ(pointer_events[0].phase, kPanZoomStart); + EXPECT_GE(pointer_events[0].timestamp, time_start); + fl_scrolling_manager_handle_rotation_update(manager, 0.5); + EXPECT_EQ(pointer_events.size(), 2u); + EXPECT_EQ(pointer_events[1].x, 0); + EXPECT_EQ(pointer_events[1].y, 0); + EXPECT_EQ(pointer_events[1].phase, kPanZoomUpdate); + EXPECT_GE(pointer_events[1].timestamp, pointer_events[0].timestamp); + EXPECT_EQ(pointer_events[1].pan_x, 0); + EXPECT_EQ(pointer_events[1].pan_y, 0); + EXPECT_EQ(pointer_events[1].scale, 1.0); + EXPECT_EQ(pointer_events[1].rotation, 0.5); + fl_scrolling_manager_handle_rotation_end(manager); + EXPECT_EQ(pointer_events.size(), 3u); + EXPECT_EQ(pointer_events[2].x, 0); + EXPECT_EQ(pointer_events[2].y, 0); + EXPECT_EQ(pointer_events[2].phase, kPanZoomEnd); + EXPECT_GE(pointer_events[2].timestamp, pointer_events[1].timestamp); } TEST(FlScrollingManagerTest, SynchronizedZoomingAndRotating) { - ScrollingTester tester; - std::vector mouse_records; - std::vector pan_zoom_records; - tester.recordMousePointerCallsTo(mouse_records); - tester.recordPointerPanZoomCallsTo(pan_zoom_records); + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + std::vector pointer_events; + embedder_api->SendPointerEvent = MOCK_ENGINE_PROC( + SendPointerEvent, + ([&pointer_events](auto engine, const FlutterPointerEvent* events, + size_t events_count) { + for (size_t i = 0; i < events_count; i++) { + pointer_events.push_back(events[i]); + } + + return kSuccess; + })); + + g_autoptr(FlScrollingManager) manager = fl_scrolling_manager_new(engine, 0); + size_t time_start = g_get_real_time(); - fl_scrolling_manager_handle_zoom_begin(tester.manager()); - EXPECT_EQ(pan_zoom_records.size(), 1u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[0].x, 0); - EXPECT_EQ(pan_zoom_records[0].y, 0); - EXPECT_EQ(pan_zoom_records[0].phase, kPanZoomStart); - EXPECT_GE(pan_zoom_records[0].timestamp, time_start); - fl_scrolling_manager_handle_zoom_update(tester.manager(), 1.1); - EXPECT_EQ(pan_zoom_records.size(), 2u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[1].x, 0); - EXPECT_EQ(pan_zoom_records[1].y, 0); - EXPECT_EQ(pan_zoom_records[1].phase, kPanZoomUpdate); - EXPECT_GE(pan_zoom_records[1].timestamp, pan_zoom_records[0].timestamp); - EXPECT_EQ(pan_zoom_records[1].pan_x, 0); - EXPECT_EQ(pan_zoom_records[1].pan_y, 0); - EXPECT_EQ(pan_zoom_records[1].scale, 1.1); - EXPECT_EQ(pan_zoom_records[1].rotation, 0); - fl_scrolling_manager_handle_rotation_begin(tester.manager()); - EXPECT_EQ(pan_zoom_records.size(), 2u); - EXPECT_EQ(mouse_records.size(), 0u); - fl_scrolling_manager_handle_rotation_update(tester.manager(), 0.5); - EXPECT_EQ(pan_zoom_records.size(), 3u); - EXPECT_EQ(pan_zoom_records[2].x, 0); - EXPECT_EQ(pan_zoom_records[2].y, 0); - EXPECT_EQ(pan_zoom_records[2].phase, kPanZoomUpdate); - EXPECT_GE(pan_zoom_records[2].timestamp, pan_zoom_records[1].timestamp); - EXPECT_EQ(pan_zoom_records[2].pan_x, 0); - EXPECT_EQ(pan_zoom_records[2].pan_y, 0); - EXPECT_EQ(pan_zoom_records[2].scale, 1.1); - EXPECT_EQ(pan_zoom_records[2].rotation, 0.5); - fl_scrolling_manager_handle_zoom_end(tester.manager()); + fl_scrolling_manager_handle_zoom_begin(manager); + EXPECT_EQ(pointer_events.size(), 1u); + EXPECT_EQ(pointer_events[0].x, 0); + EXPECT_EQ(pointer_events[0].y, 0); + EXPECT_EQ(pointer_events[0].phase, kPanZoomStart); + EXPECT_GE(pointer_events[0].timestamp, time_start); + fl_scrolling_manager_handle_zoom_update(manager, 1.1); + EXPECT_EQ(pointer_events.size(), 2u); + EXPECT_EQ(pointer_events[1].x, 0); + EXPECT_EQ(pointer_events[1].y, 0); + EXPECT_EQ(pointer_events[1].phase, kPanZoomUpdate); + EXPECT_GE(pointer_events[1].timestamp, pointer_events[0].timestamp); + EXPECT_EQ(pointer_events[1].pan_x, 0); + EXPECT_EQ(pointer_events[1].pan_y, 0); + EXPECT_EQ(pointer_events[1].scale, 1.1); + EXPECT_EQ(pointer_events[1].rotation, 0); + fl_scrolling_manager_handle_rotation_begin(manager); + EXPECT_EQ(pointer_events.size(), 2u); + fl_scrolling_manager_handle_rotation_update(manager, 0.5); + EXPECT_EQ(pointer_events.size(), 3u); + EXPECT_EQ(pointer_events[2].x, 0); + EXPECT_EQ(pointer_events[2].y, 0); + EXPECT_EQ(pointer_events[2].phase, kPanZoomUpdate); + EXPECT_GE(pointer_events[2].timestamp, pointer_events[1].timestamp); + EXPECT_EQ(pointer_events[2].pan_x, 0); + EXPECT_EQ(pointer_events[2].pan_y, 0); + EXPECT_EQ(pointer_events[2].scale, 1.1); + EXPECT_EQ(pointer_events[2].rotation, 0.5); + fl_scrolling_manager_handle_zoom_end(manager); // End event should only be sent after both zoom and rotate complete. - EXPECT_EQ(pan_zoom_records.size(), 3u); - fl_scrolling_manager_handle_rotation_end(tester.manager()); - EXPECT_EQ(pan_zoom_records.size(), 4u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[3].x, 0); - EXPECT_EQ(pan_zoom_records[3].y, 0); - EXPECT_EQ(pan_zoom_records[3].phase, kPanZoomEnd); - EXPECT_GE(pan_zoom_records[3].timestamp, pan_zoom_records[2].timestamp); + EXPECT_EQ(pointer_events.size(), 3u); + fl_scrolling_manager_handle_rotation_end(manager); + EXPECT_EQ(pointer_events.size(), 4u); + EXPECT_EQ(pointer_events[3].x, 0); + EXPECT_EQ(pointer_events[3].y, 0); + EXPECT_EQ(pointer_events[3].phase, kPanZoomEnd); + EXPECT_GE(pointer_events[3].timestamp, pointer_events[2].timestamp); } // Make sure that zoom and rotate sequences which don't end at the same time // don't cause any problems. TEST(FlScrollingManagerTest, UnsynchronizedZoomingAndRotating) { - ScrollingTester tester; - std::vector mouse_records; - std::vector pan_zoom_records; - tester.recordMousePointerCallsTo(mouse_records); - tester.recordPointerPanZoomCallsTo(pan_zoom_records); + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + std::vector pointer_events; + embedder_api->SendPointerEvent = MOCK_ENGINE_PROC( + SendPointerEvent, + ([&pointer_events](auto engine, const FlutterPointerEvent* events, + size_t events_count) { + for (size_t i = 0; i < events_count; i++) { + pointer_events.push_back(events[i]); + } + + return kSuccess; + })); + + g_autoptr(FlScrollingManager) manager = fl_scrolling_manager_new(engine, 0); + size_t time_start = g_get_real_time(); - fl_scrolling_manager_handle_zoom_begin(tester.manager()); - EXPECT_EQ(pan_zoom_records.size(), 1u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[0].x, 0); - EXPECT_EQ(pan_zoom_records[0].y, 0); - EXPECT_EQ(pan_zoom_records[0].phase, kPanZoomStart); - EXPECT_GE(pan_zoom_records[0].timestamp, time_start); - fl_scrolling_manager_handle_zoom_update(tester.manager(), 1.1); - EXPECT_EQ(pan_zoom_records.size(), 2u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[1].x, 0); - EXPECT_EQ(pan_zoom_records[1].y, 0); - EXPECT_EQ(pan_zoom_records[1].phase, kPanZoomUpdate); - EXPECT_GE(pan_zoom_records[1].timestamp, pan_zoom_records[0].timestamp); - EXPECT_EQ(pan_zoom_records[1].pan_x, 0); - EXPECT_EQ(pan_zoom_records[1].pan_y, 0); - EXPECT_EQ(pan_zoom_records[1].scale, 1.1); - EXPECT_EQ(pan_zoom_records[1].rotation, 0); - fl_scrolling_manager_handle_rotation_begin(tester.manager()); - EXPECT_EQ(pan_zoom_records.size(), 2u); - EXPECT_EQ(mouse_records.size(), 0u); - fl_scrolling_manager_handle_rotation_update(tester.manager(), 0.5); - EXPECT_EQ(pan_zoom_records.size(), 3u); - EXPECT_EQ(pan_zoom_records[2].x, 0); - EXPECT_EQ(pan_zoom_records[2].y, 0); - EXPECT_EQ(pan_zoom_records[2].phase, kPanZoomUpdate); - EXPECT_GE(pan_zoom_records[2].timestamp, pan_zoom_records[1].timestamp); - EXPECT_EQ(pan_zoom_records[2].pan_x, 0); - EXPECT_EQ(pan_zoom_records[2].pan_y, 0); - EXPECT_EQ(pan_zoom_records[2].scale, 1.1); - EXPECT_EQ(pan_zoom_records[2].rotation, 0.5); - fl_scrolling_manager_handle_zoom_end(tester.manager()); - EXPECT_EQ(pan_zoom_records.size(), 3u); - fl_scrolling_manager_handle_rotation_update(tester.manager(), 1.0); - EXPECT_EQ(pan_zoom_records.size(), 4u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[3].x, 0); - EXPECT_EQ(pan_zoom_records[3].y, 0); - EXPECT_EQ(pan_zoom_records[3].phase, kPanZoomUpdate); - EXPECT_GE(pan_zoom_records[3].timestamp, pan_zoom_records[2].timestamp); - EXPECT_EQ(pan_zoom_records[3].pan_x, 0); - EXPECT_EQ(pan_zoom_records[3].pan_y, 0); - EXPECT_EQ(pan_zoom_records[3].scale, 1.1); - EXPECT_EQ(pan_zoom_records[3].rotation, 1.0); - fl_scrolling_manager_handle_rotation_end(tester.manager()); - EXPECT_EQ(pan_zoom_records.size(), 5u); - EXPECT_EQ(mouse_records.size(), 0u); - EXPECT_EQ(pan_zoom_records[4].x, 0); - EXPECT_EQ(pan_zoom_records[4].y, 0); - EXPECT_EQ(pan_zoom_records[4].phase, kPanZoomEnd); - EXPECT_GE(pan_zoom_records[4].timestamp, pan_zoom_records[3].timestamp); + fl_scrolling_manager_handle_zoom_begin(manager); + EXPECT_EQ(pointer_events.size(), 1u); + EXPECT_EQ(pointer_events[0].x, 0); + EXPECT_EQ(pointer_events[0].y, 0); + EXPECT_EQ(pointer_events[0].phase, kPanZoomStart); + EXPECT_GE(pointer_events[0].timestamp, time_start); + fl_scrolling_manager_handle_zoom_update(manager, 1.1); + EXPECT_EQ(pointer_events.size(), 2u); + EXPECT_EQ(pointer_events[1].x, 0); + EXPECT_EQ(pointer_events[1].y, 0); + EXPECT_EQ(pointer_events[1].phase, kPanZoomUpdate); + EXPECT_GE(pointer_events[1].timestamp, pointer_events[0].timestamp); + EXPECT_EQ(pointer_events[1].pan_x, 0); + EXPECT_EQ(pointer_events[1].pan_y, 0); + EXPECT_EQ(pointer_events[1].scale, 1.1); + EXPECT_EQ(pointer_events[1].rotation, 0); + fl_scrolling_manager_handle_rotation_begin(manager); + EXPECT_EQ(pointer_events.size(), 2u); + fl_scrolling_manager_handle_rotation_update(manager, 0.5); + EXPECT_EQ(pointer_events.size(), 3u); + EXPECT_EQ(pointer_events[2].x, 0); + EXPECT_EQ(pointer_events[2].y, 0); + EXPECT_EQ(pointer_events[2].phase, kPanZoomUpdate); + EXPECT_GE(pointer_events[2].timestamp, pointer_events[1].timestamp); + EXPECT_EQ(pointer_events[2].pan_x, 0); + EXPECT_EQ(pointer_events[2].pan_y, 0); + EXPECT_EQ(pointer_events[2].scale, 1.1); + EXPECT_EQ(pointer_events[2].rotation, 0.5); + fl_scrolling_manager_handle_zoom_end(manager); + EXPECT_EQ(pointer_events.size(), 3u); + fl_scrolling_manager_handle_rotation_update(manager, 1.0); + EXPECT_EQ(pointer_events.size(), 4u); + EXPECT_EQ(pointer_events[3].x, 0); + EXPECT_EQ(pointer_events[3].y, 0); + EXPECT_EQ(pointer_events[3].phase, kPanZoomUpdate); + EXPECT_GE(pointer_events[3].timestamp, pointer_events[2].timestamp); + EXPECT_EQ(pointer_events[3].pan_x, 0); + EXPECT_EQ(pointer_events[3].pan_y, 0); + EXPECT_EQ(pointer_events[3].scale, 1.1); + EXPECT_EQ(pointer_events[3].rotation, 1.0); + fl_scrolling_manager_handle_rotation_end(manager); + EXPECT_EQ(pointer_events.size(), 5u); + EXPECT_EQ(pointer_events[4].x, 0); + EXPECT_EQ(pointer_events[4].y, 0); + EXPECT_EQ(pointer_events[4].phase, kPanZoomEnd); + EXPECT_GE(pointer_events[4].timestamp, pointer_events[3].timestamp); } - -} // namespace diff --git a/shell/platform/linux/fl_scrolling_view_delegate.cc b/shell/platform/linux/fl_scrolling_view_delegate.cc deleted file mode 100644 index 2d8ce60aaae14..0000000000000 --- a/shell/platform/linux/fl_scrolling_view_delegate.cc +++ /dev/null @@ -1,44 +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. - -#include "flutter/shell/platform/linux/fl_scrolling_view_delegate.h" - -G_DEFINE_INTERFACE(FlScrollingViewDelegate, - fl_scrolling_view_delegate, - G_TYPE_OBJECT) - -static void fl_scrolling_view_delegate_default_init( - FlScrollingViewDelegateInterface* iface) {} - -void fl_scrolling_view_delegate_send_mouse_pointer_event( - FlScrollingViewDelegate* self, - FlutterPointerPhase phase, - size_t timestamp, - double x, - double y, - FlutterPointerDeviceKind device_kind, - double scroll_delta_x, - double scroll_delta_y, - int64_t buttons) { - g_return_if_fail(FL_IS_SCROLLING_VIEW_DELEGATE(self)); - - FL_SCROLLING_VIEW_DELEGATE_GET_IFACE(self)->send_mouse_pointer_event( - self, phase, timestamp, x, y, device_kind, scroll_delta_x, scroll_delta_y, - buttons); -} -void fl_scrolling_view_delegate_send_pointer_pan_zoom_event( - FlScrollingViewDelegate* self, - size_t timestamp, - double x, - double y, - FlutterPointerPhase phase, - double pan_x, - double pan_y, - double scale, - double rotation) { - g_return_if_fail(FL_IS_SCROLLING_VIEW_DELEGATE(self)); - - FL_SCROLLING_VIEW_DELEGATE_GET_IFACE(self)->send_pointer_pan_zoom_event( - self, timestamp, x, y, phase, pan_x, pan_y, scale, rotation); -} diff --git a/shell/platform/linux/fl_scrolling_view_delegate.h b/shell/platform/linux/fl_scrolling_view_delegate.h deleted file mode 100644 index e0def169824df..0000000000000 --- a/shell/platform/linux/fl_scrolling_view_delegate.h +++ /dev/null @@ -1,80 +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. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_SCROLLING_VIEW_DELEGATE_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SCROLLING_VIEW_DELEGATE_H_ - -#include -#include -#include - -#include "flutter/shell/platform/embedder/embedder.h" -#include "flutter/shell/platform/linux/fl_key_event.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" - -G_BEGIN_DECLS - -G_DECLARE_INTERFACE(FlScrollingViewDelegate, - fl_scrolling_view_delegate, - FL, - SCROLLING_VIEW_DELEGATE, - GObject); - -/** - * FlScrollingViewDelegate: - * - * An interface for a class that provides `FlScrollingManager` with - * platform-related features. - * - * This interface is typically implemented by `FlView`. - */ - -struct _FlScrollingViewDelegateInterface { - GTypeInterface g_iface; - - void (*send_mouse_pointer_event)(FlScrollingViewDelegate* delegate, - FlutterPointerPhase phase, - size_t timestamp, - double x, - double y, - FlutterPointerDeviceKind device_kind, - double scroll_delta_x, - double scroll_delta_y, - int64_t buttons); - - void (*send_pointer_pan_zoom_event)(FlScrollingViewDelegate* delegate, - size_t timestamp, - double x, - double y, - FlutterPointerPhase phase, - double pan_x, - double pan_y, - double scale, - double rotation); -}; - -void fl_scrolling_view_delegate_send_mouse_pointer_event( - FlScrollingViewDelegate* delegate, - FlutterPointerPhase phase, - size_t timestamp, - double x, - double y, - FlutterPointerDeviceKind device_kind, - double scroll_delta_x, - double scroll_delta_y, - int64_t buttons); -void fl_scrolling_view_delegate_send_pointer_pan_zoom_event( - FlScrollingViewDelegate* delegate, - size_t timestamp, - double x, - double y, - FlutterPointerPhase phase, - double pan_x, - double pan_y, - double scale, - double rotation); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_SCROLLING_VIEW_DELEGATE_H_ diff --git a/shell/platform/linux/fl_settings.cc b/shell/platform/linux/fl_settings.cc index 39821b779ecb0..84b06ee9267b2 100644 --- a/shell/platform/linux/fl_settings.cc +++ b/shell/platform/linux/fl_settings.cc @@ -9,11 +9,11 @@ G_DEFINE_INTERFACE(FlSettings, fl_settings, G_TYPE_OBJECT) enum { - kSignalChanged, - kSignalLastSignal, + SIGNAL_CHANGED, + LAST_SIGNAL, }; -static guint signals[kSignalLastSignal]; +static guint signals[LAST_SIGNAL]; static void fl_settings_default_init(FlSettingsInterface* iface) { /** @@ -22,7 +22,7 @@ static void fl_settings_default_init(FlSettingsInterface* iface) { * * This signal is emitted after the settings have been changed. */ - signals[kSignalChanged] = + signals[SIGNAL_CHANGED] = g_signal_new("changed", G_TYPE_FROM_INTERFACE(iface), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } @@ -49,7 +49,7 @@ gdouble fl_settings_get_text_scaling_factor(FlSettings* self) { void fl_settings_emit_changed(FlSettings* self) { g_return_if_fail(FL_IS_SETTINGS(self)); - g_signal_emit(self, signals[kSignalChanged], 0); + g_signal_emit(self, signals[SIGNAL_CHANGED], 0); } FlSettings* fl_settings_new() { diff --git a/shell/platform/linux/fl_settings.h b/shell/platform/linux/fl_settings.h index f7305f1876d51..4453fd86196ce 100644 --- a/shell/platform/linux/fl_settings.h +++ b/shell/platform/linux/fl_settings.h @@ -19,10 +19,8 @@ G_DECLARE_INTERFACE(FlSettings, fl_settings, FL, SETTINGS, GObject) * Available clock formats. */ typedef enum { - // NOLINTBEGIN(readability-identifier-naming) FL_CLOCK_FORMAT_12H, FL_CLOCK_FORMAT_24H, - // NOLINTEND(readability-identifier-naming) } FlClockFormat; /** @@ -33,10 +31,8 @@ typedef enum { * Available color schemes. */ typedef enum { - // NOLINTBEGIN(readability-identifier-naming) FL_COLOR_SCHEME_LIGHT, FL_COLOR_SCHEME_DARK, - // NOLINTEND(readability-identifier-naming) } FlColorScheme; /** diff --git a/shell/platform/linux/fl_settings_channel.cc b/shell/platform/linux/fl_settings_channel.cc new file mode 100644 index 0000000000000..2ddec19ea685b --- /dev/null +++ b/shell/platform/linux/fl_settings_channel.cc @@ -0,0 +1,77 @@ +// 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. + +#include "flutter/shell/platform/linux/fl_settings_channel.h" + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" + +static constexpr char kChannelName[] = "flutter/settings"; +static constexpr char kTextScaleFactorKey[] = "textScaleFactor"; +static constexpr char kAlwaysUse24HourFormatKey[] = "alwaysUse24HourFormat"; +static constexpr char kPlatformBrightnessKey[] = "platformBrightness"; +static constexpr char kPlatformBrightnessLight[] = "light"; +static constexpr char kPlatformBrightnessDark[] = "dark"; + +struct _FlSettingsChannel { + GObject parent_instance; + + FlBasicMessageChannel* channel; +}; + +G_DEFINE_TYPE(FlSettingsChannel, fl_settings_channel, G_TYPE_OBJECT) + +static void fl_settings_channel_dispose(GObject* object) { + FlSettingsChannel* self = FL_SETTINGS_CHANNEL(object); + + g_clear_object(&self->channel); + + G_OBJECT_CLASS(fl_settings_channel_parent_class)->dispose(object); +} + +static void fl_settings_channel_class_init(FlSettingsChannelClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_settings_channel_dispose; +} + +static void fl_settings_channel_init(FlSettingsChannel* self) {} + +FlSettingsChannel* fl_settings_channel_new(FlBinaryMessenger* messenger) { + FlSettingsChannel* self = FL_SETTINGS_CHANNEL( + g_object_new(fl_settings_channel_get_type(), nullptr)); + + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + self->channel = fl_basic_message_channel_new(messenger, kChannelName, + FL_MESSAGE_CODEC(codec)); + + return self; +} + +void fl_settings_channel_send( + FlSettingsChannel* self, + double text_scale_factor, + gboolean always_use_24_hour_format, + FlSettingsChannelPlatformBrightness platform_brightness) { + g_return_if_fail(FL_IS_SETTINGS_CHANNEL(self)); + + g_autoptr(FlValue) message = fl_value_new_map(); + fl_value_set_string_take(message, kTextScaleFactorKey, + fl_value_new_float(text_scale_factor)); + fl_value_set_string_take(message, kAlwaysUse24HourFormatKey, + fl_value_new_bool(always_use_24_hour_format)); + const gchar* platform_brightness_string; + switch (platform_brightness) { + case FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_LIGHT: + platform_brightness_string = kPlatformBrightnessLight; + break; + case FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_DARK: + platform_brightness_string = kPlatformBrightnessDark; + break; + default: + g_assert_not_reached(); + } + fl_value_set_string_take(message, kPlatformBrightnessKey, + fl_value_new_string(platform_brightness_string)); + fl_basic_message_channel_send(self->channel, message, nullptr, nullptr, + nullptr); +} diff --git a/shell/platform/linux/fl_settings_channel.h b/shell/platform/linux/fl_settings_channel.h new file mode 100644 index 0000000000000..3c3409a2f90e2 --- /dev/null +++ b/shell/platform/linux/fl_settings_channel.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_CHANNEL_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_CHANNEL_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" + +G_BEGIN_DECLS + +typedef enum { + FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_LIGHT, + FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_DARK +} FlSettingsChannelPlatformBrightness; + +G_DECLARE_FINAL_TYPE(FlSettingsChannel, + fl_settings_channel, + FL, + SETTINGS_CHANNEL, + GObject); + +/** + * FlSettingsChannel: + * + * #FlSettingsChannel is a channel that implements the Flutter user settings + * channel. + */ + +/** + * fl_settings_channel_new: + * @messenger: an #FlBinaryMessenger + * + * Creates a new channel that sends user settings to the platform. + * + * Returns: a new #FlSettingsChannel + */ +FlSettingsChannel* fl_settings_channel_new(FlBinaryMessenger* messenger); + +/* + * fl_settings_channel_send: + * @channel: an #FlSettingsChannel. + * @text_scale_factor: scale factor for text. + * @always_use_24_hour_format: TRUE if time should always be shown in 24 hour + * format. + * @platform_brightness: The brightness theme in use. + * + * Send a settings update to the platform. + */ +void fl_settings_channel_send( + FlSettingsChannel* channel, + double text_scale_factor, + gboolean always_use_24_hour_format, + FlSettingsChannelPlatformBrightness platform_brightness); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_CHANNEL_H_ diff --git a/shell/platform/linux/fl_settings_handler.cc b/shell/platform/linux/fl_settings_handler.cc index a62044b746874..11718a68fff46 100644 --- a/shell/platform/linux/fl_settings_handler.cc +++ b/shell/platform/linux/fl_settings_handler.cc @@ -8,35 +8,27 @@ #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/linux/fl_engine_private.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" - -static constexpr char kChannelName[] = "flutter/settings"; -static constexpr char kTextScaleFactorKey[] = "textScaleFactor"; -static constexpr char kAlwaysUse24HourFormatKey[] = "alwaysUse24HourFormat"; -static constexpr char kPlatformBrightnessKey[] = "platformBrightness"; -static constexpr char kPlatformBrightnessLight[] = "light"; -static constexpr char kPlatformBrightnessDark[] = "dark"; +#include "flutter/shell/platform/linux/fl_settings_channel.h" struct _FlSettingsHandler { GObject parent_instance; - FlBasicMessageChannel* channel; + FlSettingsChannel* channel; GWeakRef engine; FlSettings* settings; }; G_DEFINE_TYPE(FlSettingsHandler, fl_settings_handler, G_TYPE_OBJECT) -static const gchar* to_platform_brightness(FlColorScheme color_scheme) { +static FlSettingsChannelPlatformBrightness to_platform_brightness( + FlColorScheme color_scheme) { switch (color_scheme) { case FL_COLOR_SCHEME_LIGHT: - return kPlatformBrightnessLight; + return FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_LIGHT; case FL_COLOR_SCHEME_DARK: - return kPlatformBrightnessDark; + return FL_SETTINGS_CHANNEL_PLATFORM_BRIGHTNESS_DARK; default: - g_return_val_if_reached(nullptr); + g_assert_not_reached(); } } @@ -46,17 +38,9 @@ static void update_settings(FlSettingsHandler* self) { FlColorScheme color_scheme = fl_settings_get_color_scheme(self->settings); gdouble scaling_factor = fl_settings_get_text_scaling_factor(self->settings); - g_autoptr(FlValue) message = fl_value_new_map(); - fl_value_set_string_take(message, kTextScaleFactorKey, - fl_value_new_float(scaling_factor)); - fl_value_set_string_take( - message, kAlwaysUse24HourFormatKey, - fl_value_new_bool(clock_format == FL_CLOCK_FORMAT_24H)); - fl_value_set_string_take( - message, kPlatformBrightnessKey, - fl_value_new_string(to_platform_brightness(color_scheme))); - fl_basic_message_channel_send(self->channel, message, nullptr, nullptr, - nullptr); + fl_settings_channel_send(self->channel, scaling_factor, + clock_format == FL_CLOCK_FORMAT_24H, + to_platform_brightness(color_scheme)); g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); if (engine != nullptr) { @@ -96,9 +80,7 @@ FlSettingsHandler* fl_settings_handler_new(FlEngine* engine) { g_weak_ref_init(&self->engine, engine); FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(engine); - g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); - self->channel = fl_basic_message_channel_new(messenger, kChannelName, - FL_MESSAGE_CODEC(codec)); + self->channel = fl_settings_channel_new(messenger); return self; } diff --git a/shell/platform/linux/fl_settings_handler_test.cc b/shell/platform/linux/fl_settings_handler_test.cc index 3bffcc2f23313..e8ebd34934100 100644 --- a/shell/platform/linux/fl_settings_handler_test.cc +++ b/shell/platform/linux/fl_settings_handler_test.cc @@ -5,114 +5,187 @@ #include "flutter/shell/platform/linux/fl_settings_handler.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" +#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" #include "flutter/shell/platform/linux/fl_engine_private.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h" +#include "flutter/shell/platform/linux/testing/fl_mock_binary_messenger.h" #include "flutter/shell/platform/linux/testing/fl_test.h" -#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h" #include "flutter/shell/platform/linux/testing/mock_settings.h" #include "flutter/testing/testing.h" #include "gmock/gmock.h" #include "gtest/gtest.h" -MATCHER_P2(HasSetting, key, value, "") { - g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); - g_autoptr(FlValue) message = - fl_message_codec_decode_message(FL_MESSAGE_CODEC(codec), arg, nullptr); - if (fl_value_equal(fl_value_lookup_string(message, key), value)) { - return true; - } - *result_listener << ::testing::PrintToString(message); - return false; -} - -#define EXPECT_SETTING(messenger, key, value) \ - EXPECT_CALL( \ - messenger, \ - fl_binary_messenger_send_on_channel( \ - ::testing::Eq(messenger), \ - ::testing::StrEq("flutter/settings"), HasSetting(key, value), \ - ::testing::A(), ::testing::A(), \ - ::testing::A())) - TEST(FlSettingsHandlerTest, AlwaysUse24HourFormat) { ::testing::NiceMock settings; - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", FL_BINARY_MESSENGER(messenger), nullptr)); g_autoptr(FlSettingsHandler) handler = fl_settings_handler_new(engine); - g_autoptr(FlValue) use_12h = fl_value_new_bool(false); - g_autoptr(FlValue) use_24h = fl_value_new_bool(true); - EXPECT_CALL(settings, fl_settings_get_clock_format( ::testing::Eq(settings))) .WillOnce(::testing::Return(FL_CLOCK_FORMAT_12H)) .WillOnce(::testing::Return(FL_CLOCK_FORMAT_24H)); - EXPECT_SETTING(messenger, "alwaysUse24HourFormat", use_12h); - + gboolean called = FALSE; + fl_mock_binary_messenger_set_json_message_channel( + messenger, "flutter/settings", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_EQ(fl_value_get_type(message), FL_VALUE_TYPE_MAP); + FlValue* value = + fl_value_lookup_string(message, "alwaysUse24HourFormat"); + EXPECT_NE(value, nullptr); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_BOOL); + EXPECT_FALSE(fl_value_get_bool(value)); + + return fl_value_new_null(); + }, + &called); fl_settings_handler_start(handler, settings); - - EXPECT_SETTING(messenger, "alwaysUse24HourFormat", use_24h); - + EXPECT_TRUE(called); + + called = FALSE; + fl_mock_binary_messenger_set_json_message_channel( + messenger, "flutter/settings", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_EQ(fl_value_get_type(message), FL_VALUE_TYPE_MAP); + FlValue* value = + fl_value_lookup_string(message, "alwaysUse24HourFormat"); + EXPECT_NE(value, nullptr); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_BOOL); + EXPECT_TRUE(fl_value_get_bool(value)); + + return fl_value_new_null(); + }, + &called); fl_settings_emit_changed(settings); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlSettingsHandlerTest, PlatformBrightness) { ::testing::NiceMock settings; - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", FL_BINARY_MESSENGER(messenger), nullptr)); g_autoptr(FlSettingsHandler) handler = fl_settings_handler_new(engine); - g_autoptr(FlValue) light = fl_value_new_string("light"); - g_autoptr(FlValue) dark = fl_value_new_string("dark"); - EXPECT_CALL(settings, fl_settings_get_color_scheme( ::testing::Eq(settings))) .WillOnce(::testing::Return(FL_COLOR_SCHEME_LIGHT)) .WillOnce(::testing::Return(FL_COLOR_SCHEME_DARK)); - EXPECT_SETTING(messenger, "platformBrightness", light); - + gboolean called = FALSE; + fl_mock_binary_messenger_set_json_message_channel( + messenger, "flutter/settings", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_EQ(fl_value_get_type(message), FL_VALUE_TYPE_MAP); + FlValue* value = fl_value_lookup_string(message, "platformBrightness"); + EXPECT_NE(value, nullptr); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "light"); + + return fl_value_new_null(); + }, + &called); fl_settings_handler_start(handler, settings); - - EXPECT_SETTING(messenger, "platformBrightness", dark); - + EXPECT_TRUE(called); + + called = FALSE; + fl_mock_binary_messenger_set_json_message_channel( + messenger, "flutter/settings", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_EQ(fl_value_get_type(message), FL_VALUE_TYPE_MAP); + FlValue* value = fl_value_lookup_string(message, "platformBrightness"); + EXPECT_NE(value, nullptr); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "dark"); + + return fl_value_new_null(); + }, + &called); fl_settings_emit_changed(settings); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlSettingsHandlerTest, TextScaleFactor) { ::testing::NiceMock settings; - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", FL_BINARY_MESSENGER(messenger), nullptr)); g_autoptr(FlSettingsHandler) handler = fl_settings_handler_new(engine); - g_autoptr(FlValue) one = fl_value_new_float(1.0); - g_autoptr(FlValue) two = fl_value_new_float(2.0); - EXPECT_CALL(settings, fl_settings_get_text_scaling_factor( ::testing::Eq(settings))) .WillOnce(::testing::Return(1.0)) .WillOnce(::testing::Return(2.0)); - EXPECT_SETTING(messenger, "textScaleFactor", one); - + gboolean called = FALSE; + fl_mock_binary_messenger_set_json_message_channel( + messenger, "flutter/settings", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_EQ(fl_value_get_type(message), FL_VALUE_TYPE_MAP); + FlValue* value = fl_value_lookup_string(message, "textScaleFactor"); + EXPECT_NE(value, nullptr); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), 1.0); + + return fl_value_new_null(); + }, + &called); fl_settings_handler_start(handler, settings); - - EXPECT_SETTING(messenger, "textScaleFactor", two); - + EXPECT_TRUE(called); + + called = FALSE; + fl_mock_binary_messenger_set_json_message_channel( + messenger, "flutter/settings", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_EQ(fl_value_get_type(message), FL_VALUE_TYPE_MAP); + FlValue* value = fl_value_lookup_string(message, "textScaleFactor"); + EXPECT_NE(value, nullptr); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), 2.0); + + return fl_value_new_null(); + }, + &called); fl_settings_emit_changed(settings); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } // MOCK_ENGINE_PROC is leaky by design diff --git a/shell/platform/linux/fl_settings_portal.cc b/shell/platform/linux/fl_settings_portal.cc index a3eded35263d7..3df731bbf4b4b 100644 --- a/shell/platform/linux/fl_settings_portal.cc +++ b/shell/platform/linux/fl_settings_portal.cc @@ -62,7 +62,11 @@ static const FlSetting kAllSettings[] = { static constexpr char kClockFormat12Hour[] = "12h"; static constexpr char kGtkThemeDarkSuffix[] = "-dark"; -typedef enum { kDefault, kPreferDark, kPreferLight } ColorScheme; +typedef enum { + COLOR_SCHEME_DEFAULT, + COLOR_SCHEME_PREFER_DARK, + COLOR_SCHEME_PREFER_LIGHT +} ColorScheme; struct _FlSettingsPortal { GObject parent_instance; @@ -183,7 +187,7 @@ static FlColorScheme fl_settings_portal_get_color_scheme(FlSettings* settings) { g_autoptr(GVariant) value = nullptr; if (get_value(self, &kColorScheme, &value)) { - if (g_variant_get_uint32(value) == kPreferDark) { + if (g_variant_get_uint32(value) == COLOR_SCHEME_PREFER_DARK) { color_scheme = FL_COLOR_SCHEME_DARK; } } else if (get_value(self, &kGtkTheme, &value)) { diff --git a/shell/platform/linux/fl_standard_method_codec.cc b/shell/platform/linux/fl_standard_method_codec.cc index 46a803a041aaa..cd2b8b6a19c06 100644 --- a/shell/platform/linux/fl_standard_method_codec.cc +++ b/shell/platform/linux/fl_standard_method_codec.cc @@ -21,7 +21,7 @@ struct _FlStandardMethodCodec { FlStandardMessageCodec* message_codec; }; -enum { kPropMessageCodec = 1, kPropLast }; +enum { PROP_MESSAGE_CODEC = 1, PROP_LAST }; G_DEFINE_TYPE(FlStandardMethodCodec, fl_standard_method_codec, @@ -34,7 +34,7 @@ static void fl_standard_method_codec_set_property(GObject* object, FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(object); switch (prop_id) { - case kPropMessageCodec: + case PROP_MESSAGE_CODEC: g_set_object(&self->message_codec, FL_STANDARD_MESSAGE_CODEC(g_value_get_object(value))); break; @@ -51,7 +51,7 @@ static void fl_standard_method_codec_get_property(GObject* object, FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(object); switch (prop_id) { - case kPropMessageCodec: + case PROP_MESSAGE_CODEC: g_value_set_object(value, self->message_codec); break; default: @@ -278,7 +278,7 @@ static void fl_standard_method_codec_class_init( fl_standard_method_codec_decode_response; g_object_class_install_property( - G_OBJECT_CLASS(klass), kPropMessageCodec, + G_OBJECT_CLASS(klass), PROP_MESSAGE_CODEC, g_param_spec_object( "message-codec", "message-codec", "Message codec to use", fl_message_codec_get_type(), diff --git a/shell/platform/linux/fl_text_input_channel.cc b/shell/platform/linux/fl_text_input_channel.cc new file mode 100644 index 0000000000000..cc37e2aace26c --- /dev/null +++ b/shell/platform/linux/fl_text_input_channel.cc @@ -0,0 +1,409 @@ +// 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. + +#include "flutter/shell/platform/linux/fl_text_input_channel.h" + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" + +static constexpr char kChannelName[] = "flutter/textinput"; + +static constexpr char kBadArgumentsError[] = "Bad Arguments"; + +static constexpr char kSetClientMethod[] = "TextInput.setClient"; +static constexpr char kShowMethod[] = "TextInput.show"; +static constexpr char kSetEditingStateMethod[] = "TextInput.setEditingState"; +static constexpr char kClearClientMethod[] = "TextInput.clearClient"; +static constexpr char kHideMethod[] = "TextInput.hide"; +static constexpr char kUpdateEditingStateMethod[] = + "TextInputClient.updateEditingState"; +static constexpr char kUpdateEditingStateWithDeltasMethod[] = + "TextInputClient.updateEditingStateWithDeltas"; +static constexpr char kPerformActionMethod[] = "TextInputClient.performAction"; +static constexpr char kSetEditableSizeAndTransform[] = + "TextInput.setEditableSizeAndTransform"; +static constexpr char kSetMarkedTextRect[] = "TextInput.setMarkedTextRect"; + +static constexpr char kInputActionKey[] = "inputAction"; +static constexpr char kTextInputTypeKey[] = "inputType"; +static constexpr char kEnableDeltaModel[] = "enableDeltaModel"; +static constexpr char kTextInputTypeNameKey[] = "name"; +static constexpr char kTextKey[] = "text"; +static constexpr char kSelectionBaseKey[] = "selectionBase"; +static constexpr char kSelectionExtentKey[] = "selectionExtent"; +static constexpr char kSelectionAffinityKey[] = "selectionAffinity"; +static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional"; +static constexpr char kComposingBaseKey[] = "composingBase"; +static constexpr char kComposingExtentKey[] = "composingExtent"; + +static constexpr char kTransform[] = "transform"; + +static constexpr char kMultilineInputType[] = "TextInputType.multiline"; +static constexpr char kNoneInputType[] = "TextInputType.none"; + +static constexpr char kTextAffinityUpstream[] = "TextAffinity.upstream"; +static constexpr char kTextAffinityDownstream[] = "TextAffinity.downstream"; + +struct _FlTextInputChannel { + GObject parent_instance; + + FlMethodChannel* channel; + + FlTextInputChannelVTable* vtable; + + gpointer user_data; +}; + +G_DEFINE_TYPE(FlTextInputChannel, fl_text_input_channel, G_TYPE_OBJECT) + +static const gchar* text_affinity_to_string(FlTextAffinity affinity) { + switch (affinity) { + case FL_TEXT_AFFINITY_UPSTREAM: + return kTextAffinityUpstream; + case FL_TEXT_AFFINITY_DOWNSTREAM: + return kTextAffinityDownstream; + default: + g_assert_not_reached(); + } +} + +// Called when the input method client is set up. +static FlMethodResponse* set_client(FlTextInputChannel* self, FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || + fl_value_get_length(args) < 2) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Expected 2-element list", nullptr)); + } + + int64_t client_id = fl_value_get_int(fl_value_get_list_value(args, 0)); + FlValue* config_value = fl_value_get_list_value(args, 1); + const gchar* input_action = nullptr; + FlValue* input_action_value = + fl_value_lookup_string(config_value, kInputActionKey); + if (fl_value_get_type(input_action_value) == FL_VALUE_TYPE_STRING) { + input_action = fl_value_get_string(input_action_value); + } + + FlValue* enable_delta_model_value = + fl_value_lookup_string(config_value, kEnableDeltaModel); + gboolean enable_delta_model = fl_value_get_bool(enable_delta_model_value); + + // Reset the input type, then set only if appropriate. + FlTextInputType input_type = FL_TEXT_INPUT_TYPE_TEXT; + FlValue* input_type_value = + fl_value_lookup_string(config_value, kTextInputTypeKey); + if (fl_value_get_type(input_type_value) == FL_VALUE_TYPE_MAP) { + FlValue* input_type_name_value = + fl_value_lookup_string(input_type_value, kTextInputTypeNameKey); + if (fl_value_get_type(input_type_name_value) == FL_VALUE_TYPE_STRING) { + const gchar* input_type_name = fl_value_get_string(input_type_name_value); + if (g_strcmp0(input_type_name, kMultilineInputType) == 0) { + input_type = FL_TEXT_INPUT_TYPE_MULTILINE; + } else if (g_strcmp0(input_type_name, kNoneInputType) == 0) { + input_type = FL_TEXT_INPUT_TYPE_NONE; + } + } + } + + self->vtable->set_client(client_id, input_action, enable_delta_model, + input_type, self->user_data); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Hides the input method. +static FlMethodResponse* hide(FlTextInputChannel* self) { + self->vtable->hide(self->user_data); + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Shows the input method. +static FlMethodResponse* show(FlTextInputChannel* self) { + self->vtable->show(self->user_data); + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Updates the editing state from Flutter. +static FlMethodResponse* set_editing_state(FlTextInputChannel* self, + FlValue* args) { + const gchar* text = + fl_value_get_string(fl_value_lookup_string(args, kTextKey)); + int64_t selection_base = + fl_value_get_int(fl_value_lookup_string(args, kSelectionBaseKey)); + int64_t selection_extent = + fl_value_get_int(fl_value_lookup_string(args, kSelectionExtentKey)); + int64_t composing_base = + fl_value_get_int(fl_value_lookup_string(args, kComposingBaseKey)); + int64_t composing_extent = + fl_value_get_int(fl_value_lookup_string(args, kComposingExtentKey)); + + self->vtable->set_editing_state(text, selection_base, selection_extent, + composing_base, composing_extent, + self->user_data); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Called when the input method client is complete. +static FlMethodResponse* clear_client(FlTextInputChannel* self) { + self->vtable->clear_client(self->user_data); + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Handles updates to the EditableText size and position from the framework. +// +// On changes to the size or position of the RenderObject underlying the +// EditableText, this update may be triggered. It provides an updated size and +// transform from the local coordinate system of the EditableText to root +// Flutter coordinate system. +static FlMethodResponse* set_editable_size_and_transform( + FlTextInputChannel* self, + FlValue* args) { + FlValue* transform_value = fl_value_lookup_string(args, kTransform); + if (fl_value_get_length(transform_value) != 16) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Invalid transform", nullptr)); + } + + double transform[16]; + for (size_t i = 0; i < 16; i++) { + transform[i] = + fl_value_get_float(fl_value_get_list_value(transform_value, i)); + } + self->vtable->set_editable_size_and_transform(transform, self->user_data); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Handles updates to the composing rect from the framework. +// +// On changes to the state of the EditableText in the framework, this update +// may be triggered. It provides an updated rect for the composing region in +// local coordinates of the EditableText. In the case where there is no +// composing region, the cursor rect is sent. +static FlMethodResponse* set_marked_text_rect(FlTextInputChannel* self, + FlValue* args) { + double x = fl_value_get_float(fl_value_lookup_string(args, "x")); + double y = fl_value_get_float(fl_value_lookup_string(args, "y")); + double width = fl_value_get_float(fl_value_lookup_string(args, "width")); + double height = fl_value_get_float(fl_value_lookup_string(args, "height")); + + self->vtable->set_marked_text_rect(x, y, width, height, self->user_data); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Called when a method call is received from Flutter. +static void method_call_cb(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data) { + FlTextInputChannel* self = FL_TEXT_INPUT_CHANNEL(user_data); + + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + g_autoptr(FlMethodResponse) response = nullptr; + if (strcmp(method, kSetClientMethod) == 0) { + response = set_client(self, args); + } else if (strcmp(method, kShowMethod) == 0) { + response = show(self); + } else if (strcmp(method, kSetEditingStateMethod) == 0) { + response = set_editing_state(self, args); + } else if (strcmp(method, kClearClientMethod) == 0) { + response = clear_client(self); + } else if (strcmp(method, kHideMethod) == 0) { + response = hide(self); + } else if (strcmp(method, kSetEditableSizeAndTransform) == 0) { + response = set_editable_size_and_transform(self, args); + } else if (strcmp(method, kSetMarkedTextRect) == 0) { + response = set_marked_text_rect(self, args); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + g_autoptr(GError) error = nullptr; + if (!fl_method_call_respond(method_call, response, &error)) { + g_warning("Failed to send method call response: %s", error->message); + } +} + +static void fl_text_input_channel_dispose(GObject* object) { + FlTextInputChannel* self = FL_TEXT_INPUT_CHANNEL(object); + + g_clear_object(&self->channel); + + G_OBJECT_CLASS(fl_text_input_channel_parent_class)->dispose(object); +} + +static void fl_text_input_channel_class_init(FlTextInputChannelClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_text_input_channel_dispose; +} + +static void fl_text_input_channel_init(FlTextInputChannel* self) {} + +FlTextInputChannel* fl_text_input_channel_new(FlBinaryMessenger* messenger, + FlTextInputChannelVTable* vtable, + gpointer user_data) { + g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + g_return_val_if_fail(vtable != nullptr, nullptr); + + FlTextInputChannel* self = FL_TEXT_INPUT_CHANNEL( + g_object_new(fl_text_input_channel_get_type(), nullptr)); + + self->vtable = vtable; + self->user_data = user_data; + + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + self->channel = + fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self, + nullptr); + + return self; +} + +void fl_text_input_channel_update_editing_state( + FlTextInputChannel* self, + int64_t client_id, + const gchar* text, + int64_t selection_base, + int64_t selection_extent, + FlTextAffinity selection_affinity, + gboolean selection_is_directional, + int64_t composing_base, + int64_t composing_extent, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_TEXT_INPUT_CHANNEL(self)); + + g_autoptr(FlValue) args = fl_value_new_list(); + fl_value_append_take(args, fl_value_new_int(client_id)); + g_autoptr(FlValue) value = fl_value_new_map(); + + fl_value_set_string_take(value, kTextKey, fl_value_new_string(text)); + fl_value_set_string_take(value, kSelectionBaseKey, + fl_value_new_int(selection_base)); + fl_value_set_string_take(value, kSelectionExtentKey, + fl_value_new_int(selection_extent)); + fl_value_set_string_take( + value, kSelectionAffinityKey, + fl_value_new_string(text_affinity_to_string(selection_affinity))); + fl_value_set_string_take(value, kSelectionIsDirectionalKey, + fl_value_new_bool(selection_is_directional)); + fl_value_set_string_take(value, kComposingBaseKey, + fl_value_new_int(composing_base)); + fl_value_set_string_take(value, kComposingExtentKey, + fl_value_new_int(composing_extent)); + + fl_value_append(args, value); + + fl_method_channel_invoke_method(self->channel, kUpdateEditingStateMethod, + args, cancellable, callback, user_data); +} + +gboolean fl_text_input_channel_update_editing_state_finish(GObject* object, + GAsyncResult* result, + GError** error) { + g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish( + FL_METHOD_CHANNEL(object), result, error); + if (response == nullptr) { + return FALSE; + } + return fl_method_response_get_result(response, error) != nullptr; +} + +void fl_text_input_channel_update_editing_state_with_deltas( + FlTextInputChannel* self, + int64_t client_id, + const gchar* old_text, + const gchar* delta_text, + int64_t delta_start, + int64_t delta_end, + int64_t selection_base, + int64_t selection_extent, + FlTextAffinity selection_affinity, + gboolean selection_is_directional, + int64_t composing_base, + int64_t composing_extent, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_TEXT_INPUT_CHANNEL(self)); + + g_autoptr(FlValue) args = fl_value_new_list(); + fl_value_append_take(args, fl_value_new_int(client_id)); + + g_autoptr(FlValue) deltaValue = fl_value_new_map(); + fl_value_set_string_take(deltaValue, "oldText", + fl_value_new_string(old_text)); + fl_value_set_string_take(deltaValue, "deltaText", + fl_value_new_string(delta_text)); + fl_value_set_string_take(deltaValue, "deltaStart", + fl_value_new_int(delta_start)); + fl_value_set_string_take(deltaValue, "deltaEnd", fl_value_new_int(delta_end)); + fl_value_set_string_take(deltaValue, "selectionBase", + fl_value_new_int(selection_base)); + fl_value_set_string_take(deltaValue, "selectionExtent", + fl_value_new_int(selection_extent)); + fl_value_set_string_take( + deltaValue, "selectionAffinity", + fl_value_new_string(text_affinity_to_string(selection_affinity))); + fl_value_set_string_take(deltaValue, "selectionIsDirectional", + fl_value_new_bool(selection_is_directional)); + fl_value_set_string_take(deltaValue, "composingBase", + fl_value_new_int(composing_base)); + fl_value_set_string_take(deltaValue, "composingExtent", + fl_value_new_int(composing_extent)); + + g_autoptr(FlValue) deltas = fl_value_new_list(); + fl_value_append(deltas, deltaValue); + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_string(value, "deltas", deltas); + + fl_value_append(args, value); + + fl_method_channel_invoke_method(self->channel, + kUpdateEditingStateWithDeltasMethod, args, + cancellable, callback, user_data); +} + +gboolean fl_text_input_channel_update_editing_state_with_deltas_finish( + GObject* object, + GAsyncResult* result, + GError** error) { + g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish( + FL_METHOD_CHANNEL(object), result, error); + if (response == nullptr) { + return FALSE; + } + return fl_method_response_get_result(response, error) != nullptr; +} + +void fl_text_input_channel_perform_action(FlTextInputChannel* self, + int64_t client_id, + const gchar* input_action, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_TEXT_INPUT_CHANNEL(self)); + + g_autoptr(FlValue) args = fl_value_new_list(); + fl_value_append_take(args, fl_value_new_int(client_id)); + fl_value_append_take(args, fl_value_new_string(input_action)); + + fl_method_channel_invoke_method(self->channel, kPerformActionMethod, args, + cancellable, callback, user_data); +} + +gboolean fl_text_input_channel_perform_action_finish(GObject* object, + GAsyncResult* result, + GError** error) { + g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish( + FL_METHOD_CHANNEL(object), result, error); + if (response == nullptr) { + return FALSE; + } + return fl_method_response_get_result(response, error) != nullptr; +} diff --git a/shell/platform/linux/fl_text_input_channel.h b/shell/platform/linux/fl_text_input_channel.h new file mode 100644 index 0000000000000..566cd8a06549e --- /dev/null +++ b/shell/platform/linux/fl_text_input_channel.h @@ -0,0 +1,211 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_TEXT_INPUT_CHANNEL_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_TEXT_INPUT_CHANNEL_H_ + +#include + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" + +G_BEGIN_DECLS + +typedef enum { + FL_TEXT_INPUT_TYPE_TEXT, + // Send newline when multi-line and enter is pressed. + FL_TEXT_INPUT_TYPE_MULTILINE, + // The input method is not shown at all. + FL_TEXT_INPUT_TYPE_NONE, +} FlTextInputType; + +typedef enum { + FL_TEXT_AFFINITY_UPSTREAM, + FL_TEXT_AFFINITY_DOWNSTREAM, +} FlTextAffinity; + +G_DECLARE_FINAL_TYPE(FlTextInputChannel, + fl_text_input_channel, + FL, + TEXT_INPUT_CHANNEL, + GObject); + +/** + * FlTextInputChannel: + * + * #FlTextInputChannel is a channel that implements the shell side + * of SystemChannels.textInput from the Flutter services library. + */ + +typedef struct { + void (*set_client)(int64_t client_id, + const gchar* input_action, + gboolean enable_delta_model, + FlTextInputType input_type, + gpointer user_data); + void (*hide)(gpointer user_data); + void (*show)(gpointer user_data); + void (*set_editing_state)(const gchar* text, + int64_t selection_base, + int64_t selection_extent, + int64_t composing_base, + int64_t composing_extent, + gpointer user_data); + void (*clear_client)(gpointer user_data); + void (*set_editable_size_and_transform)(double* transform, + gpointer user_data); + void (*set_marked_text_rect)(double x, + double y, + double width, + double height, + gpointer user_data); +} FlTextInputChannelVTable; + +/** + * fl_text_input_channel_new: + * @messenger: an #FlBinaryMessenger. + * @vtable: callbacks for incoming method calls. + * @user_data: data to pass in callbacks. + * + * Creates a new channel that implements SystemChannels.textInput from the + * Flutter services library. + * + * Returns: a new #FlTextInputChannel. + */ +FlTextInputChannel* fl_text_input_channel_new(FlBinaryMessenger* messenger, + FlTextInputChannelVTable* vtable, + gpointer user_data); + +/** + * fl_text_input_channel_update_editing_state: + * @channel: an #FlTextInputChannel. + * @client_id: + * @text: + * @selection_base: + * @selection_extent: + * @selection_affinity: + * @selection_is_directional: + * @composing_base: + * @composing_extent: + * @cancellable: (allow-none): a #GCancellable or %NULL. + * @callback: (scope async): a #GAsyncReadyCallback to call when the method + * returns. + * @user_data: (closure): user data to pass to @callback. + */ +void fl_text_input_channel_update_editing_state( + FlTextInputChannel* channel, + int64_t client_id, + const gchar* text, + int64_t selection_base, + int64_t selection_extent, + FlTextAffinity selection_affinity, + gboolean selection_is_directional, + int64_t composing_base, + int64_t composing_extent, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +/** + * fl_text_input_channel_update_editing_state_finish: + * @object: + * @result: a #GAsyncResult. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Completes request started with fl_text_input_channel_update_editing_state(). + * + * Returns: %TRUE on success. + */ +gboolean fl_text_input_channel_update_editing_state_finish(GObject* object, + GAsyncResult* result, + GError** error); + +/** + * fl_text_input_channel_update_editing_state_with_deltas: + * @channel: an #FlTextInputChannel. + * @client_id: + * @old_text: + * @delta_text: + * @delta_start: + * @delta_end: + * @selection_base: + * @selection_extent: + * @selection_affinity: + * @selection_is_directional: + * @composing_base: + * @composing_extent: + * @cancellable: (allow-none): a #GCancellable or %NULL. + * @callback: (scope async): a #GAsyncReadyCallback to call when the method + * returns. + * @user_data: (closure): user data to pass to @callback. + */ +void fl_text_input_channel_update_editing_state_with_deltas( + FlTextInputChannel* channel, + int64_t client_id, + const gchar* old_text, + const gchar* delta_text, + int64_t delta_start, + int64_t delta_end, + int64_t selection_base, + int64_t selection_extent, + FlTextAffinity selection_affinity, + gboolean selection_is_directional, + int64_t composing_base, + int64_t composing_extent, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +/** + * fl_text_input_channel_update_editing_state_with_deltas_finish: + * @object: + * @result: a #GAsyncResult. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Completes request started with + * fl_text_input_channel_update_editing_state_with_deltas(). + * + * Returns: %TRUE on success. + */ +gboolean fl_text_input_channel_update_editing_state_with_deltas_finish( + GObject* object, + GAsyncResult* result, + GError** error); + +/** + * fl_text_input_channel_perform_action: + * @channel: an #FlTextInputChannel. + * @client_id: + * @input_action: action to perform. + * @cancellable: (allow-none): a #GCancellable or %NULL. + * @callback: (scope async): a #GAsyncReadyCallback to call when the method + * returns. + * @user_data: (closure): user data to pass to @callback. + */ +void fl_text_input_channel_perform_action(FlTextInputChannel* channel, + int64_t client_id, + const gchar* input_action, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +/** + * fl_text_input_channel_perform_action_finish: + * @object: + * @result: a #GAsyncResult. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Completes request started with fl_text_input_channel_perform_action(). + * + * Returns: %TRUE on success. + */ +gboolean fl_text_input_channel_perform_action_finish(GObject* object, + GAsyncResult* result, + GError** error); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_TEXT_INPUT_CHANNEL_H_ diff --git a/shell/platform/linux/fl_text_input_handler.cc b/shell/platform/linux/fl_text_input_handler.cc index d9a8c08facb7a..c3d1b6551359f 100644 --- a/shell/platform/linux/fl_text_input_handler.cc +++ b/shell/platform/linux/fl_text_input_handler.cc @@ -8,61 +8,16 @@ #include "flutter/shell/platform/common/text_editing_delta.h" #include "flutter/shell/platform/common/text_input_model.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" - -static constexpr char kChannelName[] = "flutter/textinput"; - -static constexpr char kBadArgumentsError[] = "Bad Arguments"; - -static constexpr char kSetClientMethod[] = "TextInput.setClient"; -static constexpr char kShowMethod[] = "TextInput.show"; -static constexpr char kSetEditingStateMethod[] = "TextInput.setEditingState"; -static constexpr char kClearClientMethod[] = "TextInput.clearClient"; -static constexpr char kHideMethod[] = "TextInput.hide"; -static constexpr char kUpdateEditingStateMethod[] = - "TextInputClient.updateEditingState"; -static constexpr char kUpdateEditingStateWithDeltasMethod[] = - "TextInputClient.updateEditingStateWithDeltas"; -static constexpr char kPerformActionMethod[] = "TextInputClient.performAction"; -static constexpr char kSetEditableSizeAndTransform[] = - "TextInput.setEditableSizeAndTransform"; -static constexpr char kSetMarkedTextRect[] = "TextInput.setMarkedTextRect"; - -static constexpr char kInputActionKey[] = "inputAction"; -static constexpr char kTextInputTypeKey[] = "inputType"; -static constexpr char kEnableDeltaModel[] = "enableDeltaModel"; -static constexpr char kTextInputTypeNameKey[] = "name"; -static constexpr char kTextKey[] = "text"; -static constexpr char kSelectionBaseKey[] = "selectionBase"; -static constexpr char kSelectionExtentKey[] = "selectionExtent"; -static constexpr char kSelectionAffinityKey[] = "selectionAffinity"; -static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional"; -static constexpr char kComposingBaseKey[] = "composingBase"; -static constexpr char kComposingExtentKey[] = "composingExtent"; - -static constexpr char kTransform[] = "transform"; - -static constexpr char kTextAffinityDownstream[] = "TextAffinity.downstream"; -static constexpr char kMultilineInputType[] = "TextInputType.multiline"; -static constexpr char kNoneInputType[] = "TextInputType.none"; +#include "flutter/shell/platform/linux/fl_text_input_channel.h" static constexpr char kNewlineInputAction[] = "TextInputAction.newline"; static constexpr int64_t kClientIdUnset = -1; -typedef enum { - kFlTextInputTypeText, - // Send newline when multi-line and enter is pressed. - kFlTextInputTypeMultiline, - // The input method is not shown at all. - kFlTextInputTypeNone, -} FlTextInputType; - -struct FlTextInputHandlerPrivate { +struct _FlTextInputHandler { GObject parent_instance; - FlMethodChannel* channel; + FlTextInputChannel* channel; // Client ID provided by Flutter to report events with. int64_t client_id; @@ -95,133 +50,72 @@ struct FlTextInputHandlerPrivate { // range. This value is updated via `TextInput.setMarkedTextRect` messages // over the text input channel. GdkRectangle composing_rect; + + GCancellable* cancellable; }; -G_DEFINE_TYPE_WITH_PRIVATE(FlTextInputHandler, - fl_text_input_handler, - G_TYPE_OBJECT) - -// Completes method call and returns TRUE if the call was successful. -static gboolean finish_method(GObject* object, - GAsyncResult* result, - GError** error) { - g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish( - FL_METHOD_CHANNEL(object), result, error); - if (response == nullptr) { - return FALSE; - } - return fl_method_response_get_result(response, error) != nullptr; -} +G_DEFINE_TYPE(FlTextInputHandler, fl_text_input_handler, G_TYPE_OBJECT) // Called when a response is received from TextInputClient.updateEditingState() static void update_editing_state_response_cb(GObject* object, GAsyncResult* result, gpointer user_data) { g_autoptr(GError) error = nullptr; - if (!finish_method(object, result, &error)) { - g_warning("Failed to call %s: %s", kUpdateEditingStateMethod, - error->message); + if (!fl_text_input_channel_update_editing_state_finish(object, result, + &error)) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning("Failed to update editing state: %s", error->message); + } + } +} + +// Called when a response is received from +// TextInputClient.updateEditingStateWithDeltas() +static void update_editing_state_with_deltas_response_cb(GObject* object, + GAsyncResult* result, + gpointer user_data) { + g_autoptr(GError) error = nullptr; + if (!fl_text_input_channel_update_editing_state_with_deltas_finish( + object, result, &error)) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning("Failed to update editing state with deltas: %s", + error->message); + } } } // Informs Flutter of text input changes. static void update_editing_state(FlTextInputHandler* self) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - - g_autoptr(FlValue) args = fl_value_new_list(); - fl_value_append_take(args, fl_value_new_int(priv->client_id)); - g_autoptr(FlValue) value = fl_value_new_map(); - - flutter::TextRange selection = priv->text_model->selection(); - fl_value_set_string_take( - value, kTextKey, - fl_value_new_string(priv->text_model->GetText().c_str())); - fl_value_set_string_take(value, kSelectionBaseKey, - fl_value_new_int(selection.base())); - fl_value_set_string_take(value, kSelectionExtentKey, - fl_value_new_int(selection.extent())); - int composing_base = -1; int composing_extent = -1; - if (!priv->text_model->composing_range().collapsed()) { - composing_base = priv->text_model->composing_range().base(); - composing_extent = priv->text_model->composing_range().extent(); + if (!self->text_model->composing_range().collapsed()) { + composing_base = self->text_model->composing_range().base(); + composing_extent = self->text_model->composing_range().extent(); } - fl_value_set_string_take(value, kComposingBaseKey, - fl_value_new_int(composing_base)); - fl_value_set_string_take(value, kComposingExtentKey, - fl_value_new_int(composing_extent)); - - // The following keys are not implemented and set to default values. - fl_value_set_string_take(value, kSelectionAffinityKey, - fl_value_new_string(kTextAffinityDownstream)); - fl_value_set_string_take(value, kSelectionIsDirectionalKey, - fl_value_new_bool(FALSE)); - - fl_value_append(args, value); - - fl_method_channel_invoke_method(priv->channel, kUpdateEditingStateMethod, - args, nullptr, - update_editing_state_response_cb, self); + flutter::TextRange selection = self->text_model->selection(); + fl_text_input_channel_update_editing_state( + self->channel, self->client_id, self->text_model->GetText().c_str(), + selection.base(), selection.extent(), FL_TEXT_AFFINITY_DOWNSTREAM, FALSE, + composing_base, composing_extent, self->cancellable, + update_editing_state_response_cb, self); } // Informs Flutter of text input changes by passing just the delta. static void update_editing_state_with_delta(FlTextInputHandler* self, flutter::TextEditingDelta* delta) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - - g_autoptr(FlValue) args = fl_value_new_list(); - fl_value_append_take(args, fl_value_new_int(priv->client_id)); - - g_autoptr(FlValue) deltaValue = fl_value_new_map(); - fl_value_set_string_take(deltaValue, "oldText", - fl_value_new_string(delta->old_text().c_str())); - - fl_value_set_string_take(deltaValue, "deltaText", - fl_value_new_string(delta->delta_text().c_str())); - - fl_value_set_string_take(deltaValue, "deltaStart", - fl_value_new_int(delta->delta_start())); - - fl_value_set_string_take(deltaValue, "deltaEnd", - fl_value_new_int(delta->delta_end())); - - flutter::TextRange selection = priv->text_model->selection(); - fl_value_set_string_take(deltaValue, "selectionBase", - fl_value_new_int(selection.base())); - - fl_value_set_string_take(deltaValue, "selectionExtent", - fl_value_new_int(selection.extent())); - - fl_value_set_string_take(deltaValue, "selectionAffinity", - fl_value_new_string(kTextAffinityDownstream)); - - fl_value_set_string_take(deltaValue, "selectionIsDirectional", - fl_value_new_bool(FALSE)); - + flutter::TextRange selection = self->text_model->selection(); int composing_base = -1; int composing_extent = -1; - if (!priv->text_model->composing_range().collapsed()) { - composing_base = priv->text_model->composing_range().base(); - composing_extent = priv->text_model->composing_range().extent(); + if (!self->text_model->composing_range().collapsed()) { + composing_base = self->text_model->composing_range().base(); + composing_extent = self->text_model->composing_range().extent(); } - fl_value_set_string_take(deltaValue, "composingBase", - fl_value_new_int(composing_base)); - fl_value_set_string_take(deltaValue, "composingExtent", - fl_value_new_int(composing_extent)); - - g_autoptr(FlValue) deltas = fl_value_new_list(); - fl_value_append(deltas, deltaValue); - g_autoptr(FlValue) value = fl_value_new_map(); - fl_value_set_string(value, "deltas", deltas); - - fl_value_append(args, value); - - fl_method_channel_invoke_method( - priv->channel, kUpdateEditingStateWithDeltasMethod, args, nullptr, - update_editing_state_response_cb, self); + fl_text_input_channel_update_editing_state_with_deltas( + self->channel, self->client_id, delta->old_text().c_str(), + delta->delta_text().c_str(), delta->delta_start(), delta->delta_end(), + selection.base(), selection.extent(), FL_TEXT_AFFINITY_DOWNSTREAM, FALSE, + composing_base, composing_extent, self->cancellable, + update_editing_state_with_deltas_response_cb, self); } // Called when a response is received from TextInputClient.performAction() @@ -229,55 +123,47 @@ static void perform_action_response_cb(GObject* object, GAsyncResult* result, gpointer user_data) { g_autoptr(GError) error = nullptr; - if (!finish_method(object, result, &error)) { - g_warning("Failed to call %s: %s", kPerformActionMethod, error->message); + if (!fl_text_input_channel_perform_action_finish(object, result, &error)) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning("Failed to perform action: %s", error->message); + } } } // Inform Flutter that the input has been activated. static void perform_action(FlTextInputHandler* self) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - g_return_if_fail(FL_IS_TEXT_INPUT_HANDLER(self)); - g_return_if_fail(priv->client_id != 0); - g_return_if_fail(priv->input_action != nullptr); - - g_autoptr(FlValue) args = fl_value_new_list(); - fl_value_append_take(args, fl_value_new_int(priv->client_id)); - fl_value_append_take(args, fl_value_new_string(priv->input_action)); + g_return_if_fail(self->client_id != 0); + g_return_if_fail(self->input_action != nullptr); - fl_method_channel_invoke_method(priv->channel, kPerformActionMethod, args, - nullptr, perform_action_response_cb, self); + fl_text_input_channel_perform_action(self->channel, self->client_id, + self->input_action, self->cancellable, + perform_action_response_cb, self); } // Signal handler for GtkIMContext::preedit-start static void im_preedit_start_cb(FlTextInputHandler* self) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - priv->text_model->BeginComposing(); + self->text_model->BeginComposing(); } // Signal handler for GtkIMContext::preedit-changed static void im_preedit_changed_cb(FlTextInputHandler* self) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - std::string text_before_change = priv->text_model->GetText(); + std::string text_before_change = self->text_model->GetText(); flutter::TextRange composing_before_change = - priv->text_model->composing_range(); + self->text_model->composing_range(); g_autofree gchar* buf = nullptr; gint cursor_offset = 0; - gtk_im_context_get_preedit_string(priv->im_context, &buf, nullptr, + gtk_im_context_get_preedit_string(self->im_context, &buf, nullptr, &cursor_offset); - if (priv->text_model->composing()) { - cursor_offset += priv->text_model->composing_range().start(); + if (self->text_model->composing()) { + cursor_offset += self->text_model->composing_range().start(); } else { - cursor_offset += priv->text_model->selection().start(); + cursor_offset += self->text_model->selection().start(); } - priv->text_model->UpdateComposingText(buf); - priv->text_model->SetSelection(flutter::TextRange(cursor_offset)); + self->text_model->UpdateComposingText(buf); + self->text_model->SetSelection(flutter::TextRange(cursor_offset)); - if (priv->enable_delta_model) { + if (self->enable_delta_model) { std::string text(buf); flutter::TextEditingDelta delta = flutter::TextEditingDelta( text_before_change, composing_before_change, text); @@ -289,20 +175,18 @@ static void im_preedit_changed_cb(FlTextInputHandler* self) { // Signal handler for GtkIMContext::commit static void im_commit_cb(FlTextInputHandler* self, const gchar* text) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - std::string text_before_change = priv->text_model->GetText(); + std::string text_before_change = self->text_model->GetText(); flutter::TextRange composing_before_change = - priv->text_model->composing_range(); - flutter::TextRange selection_before_change = priv->text_model->selection(); - gboolean was_composing = priv->text_model->composing(); + self->text_model->composing_range(); + flutter::TextRange selection_before_change = self->text_model->selection(); + gboolean was_composing = self->text_model->composing(); - priv->text_model->AddText(text); - if (priv->text_model->composing()) { - priv->text_model->CommitComposing(); + self->text_model->AddText(text); + if (self->text_model->composing()) { + self->text_model->CommitComposing(); } - if (priv->enable_delta_model) { + if (self->enable_delta_model) { flutter::TextRange replace_range = was_composing ? composing_before_change : selection_before_change; std::unique_ptr delta = @@ -316,12 +200,10 @@ static void im_commit_cb(FlTextInputHandler* self, const gchar* text) { // Signal handler for GtkIMContext::preedit-end static void im_preedit_end_cb(FlTextInputHandler* self) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - priv->text_model->EndComposing(); - if (priv->enable_delta_model) { + self->text_model->EndComposing(); + if (self->enable_delta_model) { flutter::TextEditingDelta delta = - flutter::TextEditingDelta(priv->text_model->GetText()); + flutter::TextEditingDelta(self->text_model->GetText()); update_editing_state_with_delta(self, &delta); } else { update_editing_state(self); @@ -330,11 +212,9 @@ static void im_preedit_end_cb(FlTextInputHandler* self) { // Signal handler for GtkIMContext::retrieve-surrounding static gboolean im_retrieve_surrounding_cb(FlTextInputHandler* self) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - auto text = priv->text_model->GetText(); - size_t cursor_offset = priv->text_model->GetCursorOffset(); - gtk_im_context_set_surrounding(priv->im_context, text.c_str(), -1, + auto text = self->text_model->GetText(); + size_t cursor_offset = self->text_model->GetCursorOffset(); + gtk_im_context_set_surrounding(self->im_context, text.c_str(), -1, cursor_offset); return TRUE; } @@ -343,15 +223,12 @@ static gboolean im_retrieve_surrounding_cb(FlTextInputHandler* self) { static gboolean im_delete_surrounding_cb(FlTextInputHandler* self, gint offset, gint n_chars) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - - std::string text_before_change = priv->text_model->GetText(); - if (priv->text_model->DeleteSurrounding(offset, n_chars)) { - if (priv->enable_delta_model) { + std::string text_before_change = self->text_model->GetText(); + if (self->text_model->DeleteSurrounding(offset, n_chars)) { + if (self->enable_delta_model) { flutter::TextEditingDelta delta = flutter::TextEditingDelta( - text_before_change, priv->text_model->composing_range(), - priv->text_model->GetText()); + text_before_change, self->text_model->composing_range(), + self->text_model->GetText()); update_editing_state_with_delta(self, &delta); } else { update_editing_state(self); @@ -361,158 +238,112 @@ static gboolean im_delete_surrounding_cb(FlTextInputHandler* self, } // Called when the input method client is set up. -static FlMethodResponse* set_client(FlTextInputHandler* self, FlValue* args) { - if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || - fl_value_get_length(args) < 2) { - return FL_METHOD_RESPONSE(fl_method_error_response_new( - kBadArgumentsError, "Expected 2-element list", nullptr)); - } - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - - priv->client_id = fl_value_get_int(fl_value_get_list_value(args, 0)); - FlValue* config_value = fl_value_get_list_value(args, 1); - g_free(priv->input_action); - FlValue* input_action_value = - fl_value_lookup_string(config_value, kInputActionKey); - if (fl_value_get_type(input_action_value) == FL_VALUE_TYPE_STRING) { - priv->input_action = g_strdup(fl_value_get_string(input_action_value)); - } - - FlValue* enable_delta_model_value = - fl_value_lookup_string(config_value, kEnableDeltaModel); - gboolean enable_delta_model = fl_value_get_bool(enable_delta_model_value); - priv->enable_delta_model = enable_delta_model; - - // Reset the input type, then set only if appropriate. - priv->input_type = kFlTextInputTypeText; - FlValue* input_type_value = - fl_value_lookup_string(config_value, kTextInputTypeKey); - if (fl_value_get_type(input_type_value) == FL_VALUE_TYPE_MAP) { - FlValue* input_type_name = - fl_value_lookup_string(input_type_value, kTextInputTypeNameKey); - if (fl_value_get_type(input_type_name) == FL_VALUE_TYPE_STRING) { - const gchar* input_type = fl_value_get_string(input_type_name); - if (g_strcmp0(input_type, kMultilineInputType) == 0) { - priv->input_type = kFlTextInputTypeMultiline; - } else if (g_strcmp0(input_type, kNoneInputType) == 0) { - priv->input_type = kFlTextInputTypeNone; - } - } - } +static void set_client(int64_t client_id, + const gchar* input_action, + gboolean enable_delta_model, + FlTextInputType input_type, + gpointer user_data) { + FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data); - return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + self->client_id = client_id; + g_free(self->input_action); + self->input_action = g_strdup(input_action); + self->enable_delta_model = enable_delta_model; + self->input_type = input_type; } // Hides the input method. -static FlMethodResponse* hide(FlTextInputHandler* self) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - gtk_im_context_focus_out(priv->im_context); +static void hide(gpointer user_data) { + FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data); - return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + gtk_im_context_focus_out(self->im_context); } // Shows the input method. -static FlMethodResponse* show(FlTextInputHandler* self) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - if (priv->input_type == kFlTextInputTypeNone) { - return hide(self); - } +static void show(gpointer user_data) { + FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data); - gtk_im_context_focus_in(priv->im_context); + if (self->input_type == FL_TEXT_INPUT_TYPE_NONE) { + hide(user_data); + return; + } - return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + gtk_im_context_focus_in(self->im_context); } // Updates the editing state from Flutter. -static FlMethodResponse* set_editing_state(FlTextInputHandler* self, - FlValue* args) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - const gchar* text = - fl_value_get_string(fl_value_lookup_string(args, kTextKey)); - priv->text_model->SetText(text); - - int64_t selection_base = - fl_value_get_int(fl_value_lookup_string(args, kSelectionBaseKey)); - int64_t selection_extent = - fl_value_get_int(fl_value_lookup_string(args, kSelectionExtentKey)); +static void set_editing_state(const gchar* text, + int64_t selection_base, + int64_t selection_extent, + int64_t composing_base, + int64_t composing_extent, + gpointer user_data) { + FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data); + + self->text_model->SetText(text); + // Flutter uses -1/-1 for invalid; translate that to 0/0 for the model. if (selection_base == -1 && selection_extent == -1) { selection_base = selection_extent = 0; } - priv->text_model->SetText(text); - priv->text_model->SetSelection( + self->text_model->SetText(text); + self->text_model->SetSelection( flutter::TextRange(selection_base, selection_extent)); - int64_t composing_base = - fl_value_get_int(fl_value_lookup_string(args, kComposingBaseKey)); - int64_t composing_extent = - fl_value_get_int(fl_value_lookup_string(args, kComposingExtentKey)); if (composing_base == -1 && composing_extent == -1) { - priv->text_model->EndComposing(); + self->text_model->EndComposing(); } else { size_t composing_start = std::min(composing_base, composing_extent); size_t cursor_offset = selection_base - composing_start; - priv->text_model->SetComposingRange( + self->text_model->SetComposingRange( flutter::TextRange(composing_base, composing_extent), cursor_offset); } - - return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } // Called when the input method client is complete. -static FlMethodResponse* clear_client(FlTextInputHandler* self) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - priv->client_id = kClientIdUnset; - - return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +static void clear_client(gpointer user_data) { + FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data); + self->client_id = kClientIdUnset; } // Update the IM cursor position. // // As text is input by the user, the framework sends two streams of updates -// over the text input channel: updates to the composing rect (cursor rect when -// not in IME composing mode) and updates to the matrix transform from local -// coordinates to Flutter root coordinates. This function is called after each -// of these updates. It transforms the composing rect to GDK window coordinates -// and notifies GTK of the updated cursor position. +// over the text input channel: updates to the composing rect (cursor rect +// when not in IME composing mode) and updates to the matrix transform from +// local coordinates to Flutter root coordinates. This function is called +// after each of these updates. It transforms the composing rect to GDK window +// coordinates and notifies GTK of the updated cursor position. static void update_im_cursor_position(FlTextInputHandler* self) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - g_autoptr(FlTextInputViewDelegate) view_delegate = - FL_TEXT_INPUT_VIEW_DELEGATE(g_weak_ref_get(&priv->view_delegate)); + FL_TEXT_INPUT_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); if (view_delegate == nullptr) { return; } // Skip update if not composing to avoid setting to position 0. - if (!priv->text_model->composing()) { + if (!self->text_model->composing()) { return; } // Transform the x, y positions of the cursor from local coordinates to // Flutter view coordinates. - gint x = priv->composing_rect.x * priv->editabletext_transform[0][0] + - priv->composing_rect.y * priv->editabletext_transform[1][0] + - priv->editabletext_transform[3][0] + priv->composing_rect.width; - gint y = priv->composing_rect.x * priv->editabletext_transform[0][1] + - priv->composing_rect.y * priv->editabletext_transform[1][1] + - priv->editabletext_transform[3][1] + priv->composing_rect.height; + gint x = self->composing_rect.x * self->editabletext_transform[0][0] + + self->composing_rect.y * self->editabletext_transform[1][0] + + self->editabletext_transform[3][0] + self->composing_rect.width; + gint y = self->composing_rect.x * self->editabletext_transform[0][1] + + self->composing_rect.y * self->editabletext_transform[1][1] + + self->editabletext_transform[3][1] + self->composing_rect.height; // Transform from Flutter view coordinates to GTK window coordinates. GdkRectangle preedit_rect = {}; fl_text_input_view_delegate_translate_coordinates( view_delegate, x, y, &preedit_rect.x, &preedit_rect.y); - // Set the cursor location in window coordinates so that GTK can position any - // system input method windows. - gtk_im_context_set_cursor_location(priv->im_context, &preedit_rect); + // Set the cursor location in window coordinates so that GTK can position + // any system input method windows. + gtk_im_context_set_cursor_location(self->im_context, &preedit_rect); } // Handles updates to the EditableText size and position from the framework. @@ -521,22 +352,14 @@ static void update_im_cursor_position(FlTextInputHandler* self) { // EditableText, this update may be triggered. It provides an updated size and // transform from the local coordinate system of the EditableText to root // Flutter coordinate system. -static FlMethodResponse* set_editable_size_and_transform( - FlTextInputHandler* self, - FlValue* args) { - FlValue* transform = fl_value_lookup_string(args, kTransform); - size_t transform_len = fl_value_get_length(transform); - g_warn_if_fail(transform_len == 16); - - for (size_t i = 0; i < transform_len; ++i) { - double val = fl_value_get_float(fl_value_get_list_value(transform, i)); - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - priv->editabletext_transform[i / 4][i % 4] = val; +static void set_editable_size_and_transform(double* transform, + gpointer user_data) { + FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data); + + for (size_t i = 0; i < 16; i++) { + self->editabletext_transform[i / 4][i % 4] = transform[i]; } update_im_cursor_position(self); - - return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } // Handles updates to the composing rect from the framework. @@ -545,97 +368,128 @@ static FlMethodResponse* set_editable_size_and_transform( // may be triggered. It provides an updated rect for the composing region in // local coordinates of the EditableText. In the case where there is no // composing region, the cursor rect is sent. -static FlMethodResponse* set_marked_text_rect(FlTextInputHandler* self, - FlValue* args) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - priv->composing_rect.x = - fl_value_get_float(fl_value_lookup_string(args, "x")); - priv->composing_rect.y = - fl_value_get_float(fl_value_lookup_string(args, "y")); - priv->composing_rect.width = - fl_value_get_float(fl_value_lookup_string(args, "width")); - priv->composing_rect.height = - fl_value_get_float(fl_value_lookup_string(args, "height")); - update_im_cursor_position(self); - - return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); -} - -// Called when a method call is received from Flutter. -static void method_call_cb(FlMethodChannel* channel, - FlMethodCall* method_call, - gpointer user_data) { +static void set_marked_text_rect(double x, + double y, + double width, + double height, + gpointer user_data) { FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(user_data); - const gchar* method = fl_method_call_get_name(method_call); - FlValue* args = fl_method_call_get_args(method_call); - - g_autoptr(FlMethodResponse) response = nullptr; - if (strcmp(method, kSetClientMethod) == 0) { - response = set_client(self, args); - } else if (strcmp(method, kShowMethod) == 0) { - response = show(self); - } else if (strcmp(method, kSetEditingStateMethod) == 0) { - response = set_editing_state(self, args); - } else if (strcmp(method, kClearClientMethod) == 0) { - response = clear_client(self); - } else if (strcmp(method, kHideMethod) == 0) { - response = hide(self); - } else if (strcmp(method, kSetEditableSizeAndTransform) == 0) { - response = set_editable_size_and_transform(self, args); - } else if (strcmp(method, kSetMarkedTextRect) == 0) { - response = set_marked_text_rect(self, args); - } else { - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - } - - g_autoptr(GError) error = nullptr; - if (!fl_method_call_respond(method_call, response, &error)) { - g_warning("Failed to send method call response: %s", error->message); - } + self->composing_rect.x = x; + self->composing_rect.y = y; + self->composing_rect.width = width; + self->composing_rect.height = height; + update_im_cursor_position(self); } // Disposes of an FlTextInputHandler. static void fl_text_input_handler_dispose(GObject* object) { FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(object); - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - - g_clear_object(&priv->channel); - g_clear_pointer(&priv->input_action, g_free); - g_clear_object(&priv->im_context); - if (priv->text_model != nullptr) { - delete priv->text_model; - priv->text_model = nullptr; + + g_cancellable_cancel(self->cancellable); + + g_clear_object(&self->channel); + g_clear_pointer(&self->input_action, g_free); + g_clear_object(&self->im_context); + if (self->text_model != nullptr) { + delete self->text_model; + self->text_model = nullptr; } - g_weak_ref_clear(&priv->view_delegate); + g_weak_ref_clear(&self->view_delegate); + g_clear_object(&self->cancellable); G_OBJECT_CLASS(fl_text_input_handler_parent_class)->dispose(object); } -// Implements FlTextInputHandler::filter_keypress. -static gboolean fl_text_input_handler_filter_keypress_default( - FlTextInputHandler* self, - FlKeyEvent* event) { - g_return_val_if_fail(FL_IS_TEXT_INPUT_HANDLER(self), false); +// Initializes the FlTextInputHandler class. +static void fl_text_input_handler_class_init(FlTextInputHandlerClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_text_input_handler_dispose; +} - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); +// Initializes an instance of the FlTextInputHandler class. +static void fl_text_input_handler_init(FlTextInputHandler* self) { + self->client_id = kClientIdUnset; + self->input_type = FL_TEXT_INPUT_TYPE_TEXT; + self->text_model = new flutter::TextInputModel(); + self->cancellable = g_cancellable_new(); +} - if (priv->client_id == kClientIdUnset) { +static void init_im_context(FlTextInputHandler* self, + GtkIMContext* im_context) { + self->im_context = GTK_IM_CONTEXT(g_object_ref(im_context)); + + // On Wayland, this call sets up the input method so it can be enabled + // immediately when required. Without it, on-screen keyboard's don't come up + // the first time a text field is focused. + gtk_im_context_focus_out(self->im_context); + + g_signal_connect_object(self->im_context, "preedit-start", + G_CALLBACK(im_preedit_start_cb), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(self->im_context, "preedit-end", + G_CALLBACK(im_preedit_end_cb), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(self->im_context, "preedit-changed", + G_CALLBACK(im_preedit_changed_cb), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(self->im_context, "commit", G_CALLBACK(im_commit_cb), + self, G_CONNECT_SWAPPED); + g_signal_connect_object(self->im_context, "retrieve-surrounding", + G_CALLBACK(im_retrieve_surrounding_cb), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(self->im_context, "delete-surrounding", + G_CALLBACK(im_delete_surrounding_cb), self, + G_CONNECT_SWAPPED); +} + +static FlTextInputChannelVTable text_input_vtable = { + .set_client = set_client, + .hide = hide, + .show = show, + .set_editing_state = set_editing_state, + .clear_client = clear_client, + .set_editable_size_and_transform = set_editable_size_and_transform, + .set_marked_text_rect = set_marked_text_rect, +}; + +FlTextInputHandler* fl_text_input_handler_new( + FlBinaryMessenger* messenger, + GtkIMContext* im_context, + FlTextInputViewDelegate* view_delegate) { + g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + g_return_val_if_fail(GTK_IS_IM_CONTEXT(im_context), nullptr); + g_return_val_if_fail(FL_IS_TEXT_INPUT_VIEW_DELEGATE(view_delegate), nullptr); + + FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER( + g_object_new(fl_text_input_handler_get_type(), nullptr)); + + self->channel = + fl_text_input_channel_new(messenger, &text_input_vtable, self); + + init_im_context(self, im_context); + + g_weak_ref_init(&self->view_delegate, view_delegate); + + return self; +} + +gboolean fl_text_input_handler_filter_keypress(FlTextInputHandler* self, + FlKeyEvent* event) { + g_return_val_if_fail(FL_IS_TEXT_INPUT_HANDLER(self), FALSE); + + if (self->client_id == kClientIdUnset) { return FALSE; } if (gtk_im_context_filter_keypress( - priv->im_context, + self->im_context, reinterpret_cast(fl_key_event_get_origin(event)))) { return TRUE; } - std::string text_before_change = priv->text_model->GetText(); - flutter::TextRange selection_before_change = priv->text_model->selection(); - std::string text = priv->text_model->GetText(); + std::string text_before_change = self->text_model->GetText(); + flutter::TextRange selection_before_change = self->text_model->selection(); + std::string text = self->text_model->GetText(); // Handle the enter/return key. gboolean do_action = FALSE; @@ -646,17 +500,17 @@ static gboolean fl_text_input_handler_filter_keypress_default( case GDK_KEY_End: case GDK_KEY_KP_End: if (fl_key_event_get_state(event) & GDK_SHIFT_MASK) { - changed = priv->text_model->SelectToEnd(); + changed = self->text_model->SelectToEnd(); } else { - changed = priv->text_model->MoveCursorToEnd(); + changed = self->text_model->MoveCursorToEnd(); } break; case GDK_KEY_Return: case GDK_KEY_KP_Enter: case GDK_KEY_ISO_Enter: - if (priv->input_type == kFlTextInputTypeMultiline && - strcmp(priv->input_action, kNewlineInputAction) == 0) { - priv->text_model->AddCodePoint('\n'); + if (self->input_type == FL_TEXT_INPUT_TYPE_MULTILINE && + strcmp(self->input_action, kNewlineInputAction) == 0) { + self->text_model->AddCodePoint('\n'); text = "\n"; changed = TRUE; } @@ -665,9 +519,9 @@ static gboolean fl_text_input_handler_filter_keypress_default( case GDK_KEY_Home: case GDK_KEY_KP_Home: if (fl_key_event_get_state(event) & GDK_SHIFT_MASK) { - changed = priv->text_model->SelectToBeginning(); + changed = self->text_model->SelectToBeginning(); } else { - changed = priv->text_model->MoveCursorToBeginning(); + changed = self->text_model->MoveCursorToBeginning(); } break; case GDK_KEY_BackSpace: @@ -683,7 +537,7 @@ static gboolean fl_text_input_handler_filter_keypress_default( } if (changed) { - if (priv->enable_delta_model) { + if (self->enable_delta_model) { flutter::TextEditingDelta delta = flutter::TextEditingDelta( text_before_change, selection_before_change, text); update_editing_state_with_delta(self, &delta); @@ -697,87 +551,3 @@ static gboolean fl_text_input_handler_filter_keypress_default( return changed; } - -// Initializes the FlTextInputHandler class. -static void fl_text_input_handler_class_init(FlTextInputHandlerClass* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_text_input_handler_dispose; - FL_TEXT_INPUT_HANDLER_CLASS(klass)->filter_keypress = - fl_text_input_handler_filter_keypress_default; -} - -// Initializes an instance of the FlTextInputHandler class. -static void fl_text_input_handler_init(FlTextInputHandler* self) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - - priv->client_id = kClientIdUnset; - priv->input_type = kFlTextInputTypeText; - priv->text_model = new flutter::TextInputModel(); -} - -static void init_im_context(FlTextInputHandler* self, - GtkIMContext* im_context) { - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - priv->im_context = GTK_IM_CONTEXT(g_object_ref(im_context)); - - // On Wayland, this call sets up the input method so it can be enabled - // immediately when required. Without it, on-screen keyboard's don't come up - // the first time a text field is focused. - gtk_im_context_focus_out(priv->im_context); - - g_signal_connect_object(priv->im_context, "preedit-start", - G_CALLBACK(im_preedit_start_cb), self, - G_CONNECT_SWAPPED); - g_signal_connect_object(priv->im_context, "preedit-end", - G_CALLBACK(im_preedit_end_cb), self, - G_CONNECT_SWAPPED); - g_signal_connect_object(priv->im_context, "preedit-changed", - G_CALLBACK(im_preedit_changed_cb), self, - G_CONNECT_SWAPPED); - g_signal_connect_object(priv->im_context, "commit", G_CALLBACK(im_commit_cb), - self, G_CONNECT_SWAPPED); - g_signal_connect_object(priv->im_context, "retrieve-surrounding", - G_CALLBACK(im_retrieve_surrounding_cb), self, - G_CONNECT_SWAPPED); - g_signal_connect_object(priv->im_context, "delete-surrounding", - G_CALLBACK(im_delete_surrounding_cb), self, - G_CONNECT_SWAPPED); -} - -FlTextInputHandler* fl_text_input_handler_new( - FlBinaryMessenger* messenger, - GtkIMContext* im_context, - FlTextInputViewDelegate* view_delegate) { - g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); - g_return_val_if_fail(GTK_IS_IM_CONTEXT(im_context), nullptr); - g_return_val_if_fail(FL_IS_TEXT_INPUT_VIEW_DELEGATE(view_delegate), nullptr); - - FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER( - g_object_new(fl_text_input_handler_get_type(), nullptr)); - - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - FlTextInputHandlerPrivate* priv = static_cast( - fl_text_input_handler_get_instance_private(self)); - priv->channel = - fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); - fl_method_channel_set_method_call_handler(priv->channel, method_call_cb, self, - nullptr); - - init_im_context(self, im_context); - - g_weak_ref_init(&priv->view_delegate, view_delegate); - - return self; -} - -// Filters the a keypress given to the handler through the handler's -// filter_keypress callback. -gboolean fl_text_input_handler_filter_keypress(FlTextInputHandler* self, - FlKeyEvent* event) { - g_return_val_if_fail(FL_IS_TEXT_INPUT_HANDLER(self), FALSE); - if (FL_TEXT_INPUT_HANDLER_GET_CLASS(self)->filter_keypress) { - return FL_TEXT_INPUT_HANDLER_GET_CLASS(self)->filter_keypress(self, event); - } - return FALSE; -} diff --git a/shell/platform/linux/fl_text_input_handler.h b/shell/platform/linux/fl_text_input_handler.h index 9fe4355c67729..f0095b2cde5f3 100644 --- a/shell/platform/linux/fl_text_input_handler.h +++ b/shell/platform/linux/fl_text_input_handler.h @@ -13,11 +13,11 @@ G_BEGIN_DECLS -G_DECLARE_DERIVABLE_TYPE(FlTextInputHandler, - fl_text_input_handler, - FL, - TEXT_INPUT_HANDLER, - GObject); +G_DECLARE_FINAL_TYPE(FlTextInputHandler, + fl_text_input_handler, + FL, + TEXT_INPUT_HANDLER, + GObject); /** * FlTextInputHandler: @@ -26,15 +26,6 @@ G_DECLARE_DERIVABLE_TYPE(FlTextInputHandler, * of SystemChannels.textInput from the Flutter services library. */ -struct _FlTextInputHandlerClass { - GObjectClass parent_class; - - /** - * Virtual method called to filter a keypress. - */ - gboolean (*filter_keypress)(FlTextInputHandler* self, FlKeyEvent* event); -}; - /** * fl_text_input_handler_new: * @messenger: an #FlBinaryMessenger. diff --git a/shell/platform/linux/fl_text_input_handler_test.cc b/shell/platform/linux/fl_text_input_handler_test.cc index 7787b422c3c6c..a124b236ff3c0 100644 --- a/shell/platform/linux/fl_text_input_handler_test.cc +++ b/shell/platform/linux/fl_text_input_handler_test.cc @@ -4,14 +4,11 @@ #include +#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" #include "flutter/shell/platform/linux/fl_method_codec_private.h" #include "flutter/shell/platform/linux/fl_text_input_handler.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h" +#include "flutter/shell/platform/linux/testing/fl_mock_binary_messenger.h" #include "flutter/shell/platform/linux/testing/fl_test.h" -#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h" -#include "flutter/shell/platform/linux/testing/mock_binary_messenger_response_handle.h" #include "flutter/shell/platform/linux/testing/mock_im_context.h" #include "flutter/shell/platform/linux/testing/mock_text_input_view_delegate.h" #include "flutter/testing/testing.h" @@ -19,83 +16,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -void printTo(FlMethodResponse* response, ::std::ostream* os) { - *os << ::testing::PrintToString( - fl_method_response_get_result(response, nullptr)); -} - -MATCHER_P(SuccessResponse, result, "") { - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(FlMethodResponse) response = - fl_method_codec_decode_response(FL_METHOD_CODEC(codec), arg, nullptr); - if (fl_value_equal(fl_method_response_get_result(response, nullptr), - result)) { - return true; - } - *result_listener << ::testing::PrintToString(response); - return false; -} - -MATCHER_P(FlValueEq, value, "equal to " + ::testing::PrintToString(value)) { - return fl_value_equal(arg, value); -} - -class MethodCallMatcher { - public: - using is_gtest_matcher = void; - - explicit MethodCallMatcher(::testing::Matcher name, - ::testing::Matcher args) - : name_(std::move(name)), args_(std::move(args)) {} - - bool MatchAndExplain(GBytes* method_call, - ::testing::MatchResultListener* result_listener) const { - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GError) error = nullptr; - g_autofree gchar* name = nullptr; - g_autoptr(FlValue) args = nullptr; - gboolean result = fl_method_codec_decode_method_call( - FL_METHOD_CODEC(codec), method_call, &name, &args, &error); - if (!result) { - *result_listener << ::testing::PrintToString(error->message); - return false; - } - if (!name_.MatchAndExplain(name, result_listener)) { - *result_listener << " where the name doesn't match: \"" << name << "\""; - return false; - } - if (!args_.MatchAndExplain(args, result_listener)) { - *result_listener << " where the args don't match: " - << ::testing::PrintToString(args); - return false; - } - return true; - } - - void DescribeTo(std::ostream* os) const { - *os << "method name "; - name_.DescribeTo(os); - *os << " and args "; - args_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const { - *os << "method name "; - name_.DescribeNegationTo(os); - *os << " or args "; - args_.DescribeNegationTo(os); - } - - private: - ::testing::Matcher name_; - ::testing::Matcher args_; -}; - -::testing::Matcher MethodCall(const std::string& name, - ::testing::Matcher args) { - return MethodCallMatcher(::testing::StrEq(name), std::move(args)); -} - static FlValue* build_map(std::map args) { FlValue* value = fl_value_new_map(); for (auto it = args.begin(); it != args.end(); ++it) { @@ -178,6 +98,49 @@ static FlValue* build_editing_delta(EditingDelta delta) { }); } +static void set_client(FlMockBinaryMessenger* messenger, InputConfig config) { + gboolean called = FALSE; + g_autoptr(FlValue) args = build_input_config(config); + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/textinput", "TextInput.setClient", args, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + + g_autoptr(FlValue) expected_result = fl_value_new_null(); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); +} + +static void set_editing_state(FlMockBinaryMessenger* messenger, + EditingState state) { + gboolean called = FALSE; + g_autoptr(FlValue) args = build_editing_state(state); + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/textinput", "TextInput.setEditingState", args, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + + g_autoptr(FlValue) expected_result = fl_value_new_null(); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); +} + static void send_key_event(FlTextInputHandler* handler, gint keyval, gint state = 0) { @@ -189,420 +152,391 @@ static void send_key_event(FlTextInputHandler* handler, } TEST(FlTextInputHandlerTest, MessageHandler) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); - EXPECT_TRUE(messenger.HasMessageHandler("flutter/textinput")); + EXPECT_TRUE( + fl_mock_binary_messenger_has_handler(messenger, "flutter/textinput")); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, SetClient) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); - g_autoptr(FlValue) args = build_input_config({.client_id = 1}); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) message = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setClient", args, nullptr); + set_client(messenger, {.client_id = 1}); - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", message); + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, Show) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); EXPECT_CALL(context, gtk_im_context_focus_in(::testing::Eq(context))); - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); + gboolean called = FALSE; + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/textinput", "TextInput.show", nullptr, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) message = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.show", nullptr, nullptr); + g_autoptr(FlValue) expected_result = fl_value_new_null(); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); - messenger.ReceiveMessage("flutter/textinput", message); + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, Hide) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); EXPECT_CALL(context, gtk_im_context_focus_out(::testing::Eq(context))); - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); + gboolean called = FALSE; + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/textinput", "TextInput.hide", nullptr, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) message = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.hide", nullptr, nullptr); + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); - messenger.ReceiveMessage("flutter/textinput", message); + g_autoptr(FlValue) expected_result = fl_value_new_null(); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, ClearClient) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); + gboolean called = FALSE; + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/textinput", "TextInput.clearClient", nullptr, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) message = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.clearClient", nullptr, nullptr); + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + + g_autoptr(FlValue) expected_result = fl_value_new_null(); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); - messenger.ReceiveMessage("flutter/textinput", message); + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, PerformAction) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); - // set input config - g_autoptr(FlValue) config = build_input_config({ - .client_id = 1, - .input_type = "TextInputType.multiline", - .input_action = "TextInputAction.newline", - }); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setClient", config, nullptr); - - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_client); - - // set editing state - g_autoptr(FlValue) state = build_editing_state({ - .text = "Flutter", - .selection_base = 7, - .selection_extent = 7, - }); - g_autoptr(GBytes) set_state = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setEditingState", state, nullptr); - - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_state); - - // update editing state - g_autoptr(FlValue) new_state = build_list({ - fl_value_new_int(1), // client_id - build_editing_state({ - .text = "Flutter\n", - .selection_base = 8, - .selection_extent = 8, - }), - }); - - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingState", - FlValueEq(new_state)), - ::testing::_, ::testing::_, ::testing::_)); - - // perform action - g_autoptr(FlValue) action = build_list({ - fl_value_new_int(1), // client_id - fl_value_new_string("TextInputAction.newline"), - }); - - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.performAction", - FlValueEq(action)), - ::testing::_, ::testing::_, ::testing::_)); + set_client(messenger, { + .client_id = 1, + .input_type = "TextInputType.multiline", + .input_action = "TextInputAction.newline", + }); + set_editing_state(messenger, { + .text = "Flutter", + .selection_base = 7, + .selection_extent = 7, + }); + + // Client will update editing state and perform action + int call_count = 0; + fl_mock_binary_messenger_set_json_method_channel( + messenger, "flutter/textinput", + [](FlMockBinaryMessenger* messenger, const gchar* name, FlValue* args, + gpointer user_data) { + int* call_count = static_cast(user_data); + + if (strcmp(name, "TextInputClient.updateEditingState") == 0) { + g_autoptr(FlValue) expected_args = build_list({ + fl_value_new_int(1), // client_id + build_editing_state({ + .text = "Flutter\n", + .selection_base = 8, + .selection_extent = 8, + }), + }); + EXPECT_TRUE(fl_value_equal(args, expected_args)); + EXPECT_EQ(*call_count, 0); + (*call_count)++; + } else if (strcmp(name, "TextInputClient.performAction") == 0) { + g_autoptr(FlValue) expected_args = build_list({ + fl_value_new_int(1), // client_id + fl_value_new_string("TextInputAction.newline"), + }); + EXPECT_TRUE(fl_value_equal(args, expected_args)); + EXPECT_EQ(*call_count, 1); + (*call_count)++; + } + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + }, + &call_count); send_key_event(handler, GDK_KEY_Return); + EXPECT_EQ(call_count, 2); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } // Regression test for https://github.com/flutter/flutter/issues/125879. TEST(FlTextInputHandlerTest, MultilineWithSendAction) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); - // Set input config. - g_autoptr(FlValue) config = build_input_config({ - .client_id = 1, - .input_type = "TextInputType.multiline", - .input_action = "TextInputAction.send", - }); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setClient", config, nullptr); - - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_client); - - // Set editing state. - g_autoptr(FlValue) state = build_editing_state({ - .text = "Flutter", - .selection_base = 7, - .selection_extent = 7, - }); - g_autoptr(GBytes) set_state = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setEditingState", state, nullptr); - - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_state); - - // Perform action. - g_autoptr(FlValue) action = build_list({ - fl_value_new_int(1), // client_id - fl_value_new_string("TextInputAction.send"), - }); + set_client(messenger, { + .client_id = 1, + .input_type = "TextInputType.multiline", + .input_action = "TextInputAction.send", + }); + set_editing_state(messenger, { + .text = "Flutter", + .selection_base = 7, + .selection_extent = 7, + }); // Because the input action is not set to TextInputAction.newline, the next // expected call is "TextInputClient.performAction". If the input action was // set to TextInputAction.newline the next call would be // "TextInputClient.updateEditingState" (this case is tested in the test named // 'PerformAction'). - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.performAction", - FlValueEq(action)), - ::testing::_, ::testing::_, ::testing::_)); + int call_count = 0; + fl_mock_binary_messenger_set_json_method_channel( + messenger, "flutter/textinput", + [](FlMockBinaryMessenger* messenger, const gchar* name, FlValue* args, + gpointer user_data) { + int* call_count = static_cast(user_data); + + EXPECT_STREQ(name, "TextInputClient.performAction"); + g_autoptr(FlValue) expected_args = nullptr; + switch (*call_count) { + case 0: + // Perform action. + expected_args = build_list({ + fl_value_new_int(1), // client_id + fl_value_new_string("TextInputAction.send"), + }); + break; + default: + g_assert_not_reached(); + break; + } + EXPECT_TRUE(fl_value_equal(args, expected_args)); + (*call_count)++; + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + }, + &call_count); send_key_event(handler, GDK_KEY_Return); + EXPECT_EQ(call_count, 1); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, MoveCursor) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); - // set input config - g_autoptr(FlValue) config = build_input_config({.client_id = 1}); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setClient", config, nullptr); - - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_client); - - // set editing state - g_autoptr(FlValue) state = build_editing_state({ - .text = "Flutter", - .selection_base = 4, - .selection_extent = 4, - }); - g_autoptr(GBytes) set_state = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setEditingState", state, nullptr); - - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_state); - - // move cursor to beginning - g_autoptr(FlValue) beginning = build_list({ - fl_value_new_int(1), // client_id - build_editing_state({ - .text = "Flutter", - .selection_base = 0, - .selection_extent = 0, - }), - }); - - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingState", - FlValueEq(beginning)), - ::testing::_, ::testing::_, ::testing::_)); + set_client(messenger, {.client_id = 1}); + set_editing_state(messenger, { + .text = "Flutter", + .selection_base = 4, + .selection_extent = 4, + }); + + int call_count = 0; + fl_mock_binary_messenger_set_json_method_channel( + messenger, "flutter/textinput", + [](FlMockBinaryMessenger* messenger, const gchar* name, FlValue* args, + gpointer user_data) { + int* call_count = static_cast(user_data); + + EXPECT_STREQ(name, "TextInputClient.updateEditingState"); + g_autoptr(FlValue) expected_args = nullptr; + switch (*call_count) { + case 0: + // move cursor to beginning + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_editing_state({ + .text = "Flutter", + .selection_base = 0, + .selection_extent = 0, + }), + }); + break; + case 1: + // move cursor to end + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_editing_state({ + .text = "Flutter", + .selection_base = 7, + .selection_extent = 7, + }), + }); + break; + default: + g_assert_not_reached(); + break; + } + EXPECT_TRUE(fl_value_equal(args, expected_args)); + (*call_count)++; + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + }, + &call_count); send_key_event(handler, GDK_KEY_Home); - - // move cursor to end - g_autoptr(FlValue) end = build_list({ - fl_value_new_int(1), // client_id - build_editing_state({ - .text = "Flutter", - .selection_base = 7, - .selection_extent = 7, - }), - }); - - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingState", - FlValueEq(end)), - ::testing::_, ::testing::_, ::testing::_)); - send_key_event(handler, GDK_KEY_End); + EXPECT_EQ(call_count, 2); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, Select) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); - // set input config - g_autoptr(FlValue) config = build_input_config({.client_id = 1}); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setClient", config, nullptr); - - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_client); - - // set editing state - g_autoptr(FlValue) state = build_editing_state({ - .text = "Flutter", - .selection_base = 4, - .selection_extent = 4, - }); - g_autoptr(GBytes) set_state = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setEditingState", state, nullptr); - - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_state); - - // select to end - g_autoptr(FlValue) select_to_end = build_list({ - fl_value_new_int(1), // client_id - build_editing_state({ - .text = "Flutter", - .selection_base = 4, - .selection_extent = 7, - }), - }); - - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingState", - FlValueEq(select_to_end)), - ::testing::_, ::testing::_, ::testing::_)); + set_client(messenger, {.client_id = 1}); + set_editing_state(messenger, { + .text = "Flutter", + .selection_base = 4, + .selection_extent = 4, + }); + + int call_count = 0; + fl_mock_binary_messenger_set_json_method_channel( + messenger, "flutter/textinput", + [](FlMockBinaryMessenger* messenger, const gchar* name, FlValue* args, + gpointer user_data) { + int* call_count = static_cast(user_data); + + EXPECT_STREQ(name, "TextInputClient.updateEditingState"); + g_autoptr(FlValue) expected_args = nullptr; + switch (*call_count) { + case 0: + // select to end + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_editing_state({ + .text = "Flutter", + .selection_base = 4, + .selection_extent = 7, + }), + }); + break; + case 1: + // select to beginning + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_editing_state({ + .text = "Flutter", + .selection_base = 4, + .selection_extent = 0, + }), + }); + break; + default: + g_assert_not_reached(); + break; + } + EXPECT_TRUE(fl_value_equal(args, expected_args)); + (*call_count)++; + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + }, + &call_count); send_key_event(handler, GDK_KEY_End, GDK_SHIFT_MASK); - - // select to beginning - g_autoptr(FlValue) select_to_beginning = build_list({ - fl_value_new_int(1), // client_id - build_editing_state({ - .text = "Flutter", - .selection_base = 4, - .selection_extent = 0, - }), - }); - - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingState", - FlValueEq(select_to_beginning)), - ::testing::_, ::testing::_, ::testing::_)); - send_key_event(handler, GDK_KEY_Home, GDK_SHIFT_MASK); + EXPECT_EQ(call_count, 2); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, Composing) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); - g_signal_emit_by_name(context, "preedit-start", nullptr); - // update EXPECT_CALL(context, gtk_im_context_get_preedit_string( @@ -612,94 +546,85 @@ TEST(FlTextInputHandlerTest, Composing) { ::testing::DoAll(::testing::SetArgPointee<1>(g_strdup("Flutter")), ::testing::SetArgPointee<3>(0))); - g_autoptr(FlValue) state = build_list({ - fl_value_new_int(-1), // client_id - build_editing_state({ - .text = "Flutter", - .selection_base = 0, - .selection_extent = 0, - .composing_base = 0, - .composing_extent = 7, - }), - }); - - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingState", - FlValueEq(state)), - ::testing::_, ::testing::_, ::testing::_)); + int call_count = 0; + fl_mock_binary_messenger_set_json_method_channel( + messenger, "flutter/textinput", + [](FlMockBinaryMessenger* messenger, const gchar* name, FlValue* args, + gpointer user_data) { + int* call_count = static_cast(user_data); + + EXPECT_STREQ(name, "TextInputClient.updateEditingState"); + g_autoptr(FlValue) expected_args = nullptr; + switch (*call_count) { + case 0: + expected_args = build_list({ + fl_value_new_int(-1), // client_id + build_editing_state({ + .text = "Flutter", + .selection_base = 0, + .selection_extent = 0, + .composing_base = 0, + .composing_extent = 7, + }), + }); + break; + case 1: + // commit + expected_args = build_list({ + fl_value_new_int(-1), // client_id + build_editing_state({ + .text = "engine", + .selection_base = 6, + .selection_extent = 6, + }), + }); + break; + case 2: + // end + expected_args = build_list({ + fl_value_new_int(-1), // client_id + build_editing_state({ + .text = "engine", + .selection_base = 6, + .selection_extent = 6, + }), + }); + break; + default: + g_assert_not_reached(); + break; + } + EXPECT_TRUE(fl_value_equal(args, expected_args)); + (*call_count)++; + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + }, + &call_count); + g_signal_emit_by_name(context, "preedit-start", nullptr); g_signal_emit_by_name(context, "preedit-changed", nullptr); - - // commit - g_autoptr(FlValue) commit = build_list({ - fl_value_new_int(-1), // client_id - build_editing_state({ - .text = "engine", - .selection_base = 6, - .selection_extent = 6, - }), - }); - - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingState", - FlValueEq(commit)), - ::testing::_, ::testing::_, ::testing::_)); - g_signal_emit_by_name(context, "commit", "engine", nullptr); - - // end - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingState", - ::testing::_), - ::testing::_, ::testing::_, ::testing::_)); - g_signal_emit_by_name(context, "preedit-end", nullptr); + EXPECT_EQ(call_count, 3); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, SurroundingText) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); - // set input config - g_autoptr(FlValue) config = build_input_config({.client_id = 1}); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setClient", config, nullptr); - - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_client); - - // set editing state - g_autoptr(FlValue) state = build_editing_state({ - .text = "Flutter", - .selection_base = 3, - .selection_extent = 3, - }); - g_autoptr(GBytes) set_state = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setEditingState", state, nullptr); - - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_state); + set_client(messenger, {.client_id = 1}); + set_editing_state(messenger, { + .text = "Flutter", + .selection_base = 3, + .selection_extent = 3, + }); // retrieve EXPECT_CALL(context, gtk_im_context_set_surrounding( @@ -710,35 +635,53 @@ TEST(FlTextInputHandlerTest, SurroundingText) { g_signal_emit_by_name(context, "retrieve-surrounding", &retrieved, nullptr); EXPECT_TRUE(retrieved); - // delete - g_autoptr(FlValue) update = build_list({ - fl_value_new_int(1), // client_id - build_editing_state({ - .text = "Flutr", - .selection_base = 3, - .selection_extent = 3, - }), - }); - - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingState", - FlValueEq(update)), - ::testing::_, ::testing::_, ::testing::_)); + int call_count = 0; + fl_mock_binary_messenger_set_json_method_channel( + messenger, "flutter/textinput", + [](FlMockBinaryMessenger* messenger, const gchar* name, FlValue* args, + gpointer user_data) { + int* call_count = static_cast(user_data); + + EXPECT_STREQ(name, "TextInputClient.updateEditingState"); + g_autoptr(FlValue) expected_args = nullptr; + switch (*call_count) { + case 0: + // delete + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_editing_state({ + .text = "Flutr", + .selection_base = 3, + .selection_extent = 3, + }), + }); + break; + default: + g_assert_not_reached(); + break; + } + EXPECT_TRUE(fl_value_equal(args, expected_args)); + (*call_count)++; + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + }, + &call_count); gboolean deleted = false; g_signal_emit_by_name(context, "delete-surrounding", 1, 2, &deleted, nullptr); EXPECT_TRUE(deleted); + EXPECT_EQ(call_count, 1); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, SetMarkedTextRect) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); g_signal_emit_by_name(context, "preedit-start", nullptr); @@ -767,35 +710,24 @@ TEST(FlTextInputHandlerTest, SetMarkedTextRect) { }), }, }); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) set_editable_size_and_transform = - fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setEditableSizeAndTransform", - size_and_transform, nullptr); - - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", - set_editable_size_and_transform); - - // set marked text rect - g_autoptr(FlValue) rect = build_map({ - {"x", fl_value_new_float(1)}, - {"y", fl_value_new_float(2)}, - {"width", fl_value_new_float(3)}, - {"height", fl_value_new_float(4)}, - }); - g_autoptr(GBytes) set_marked_text_rect = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setMarkedTextRect", rect, nullptr); - - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); + gboolean called = FALSE; + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/textinput", "TextInput.setEditableSizeAndTransform", + size_and_transform, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + + g_autoptr(FlValue) expected_result = fl_value_new_null(); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); EXPECT_CALL(delegate, fl_text_input_view_delegate_translate_coordinates( ::testing::Eq(delegate), @@ -812,34 +744,47 @@ TEST(FlTextInputHandlerTest, SetMarkedTextRect) { ::testing::Field(&GdkRectangle::width, 0), ::testing::Field(&GdkRectangle::height, 0))))); - messenger.ReceiveMessage("flutter/textinput", set_marked_text_rect); + // set marked text rect + g_autoptr(FlValue) rect = build_map({ + {"x", fl_value_new_float(1)}, + {"y", fl_value_new_float(2)}, + {"width", fl_value_new_float(3)}, + {"height", fl_value_new_float(4)}, + }); + called = FALSE; + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/textinput", "TextInput.setMarkedTextRect", rect, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + + g_autoptr(FlValue) expected_result = fl_value_new_null(); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, TextInputTypeNone) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); - g_autoptr(FlValue) args = build_input_config({ - .client_id = 1, - .input_type = "TextInputType.none", - }); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setClient", args, nullptr); - - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::A(), - SuccessResponse(null), ::testing::A())) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_client); + set_client(messenger, { + .client_id = 1, + .input_type = "TextInputType.none", + }); EXPECT_CALL(context, gtk_im_context_focus_in(::testing::Eq(context))) @@ -847,115 +792,106 @@ TEST(FlTextInputHandlerTest, TextInputTypeNone) { EXPECT_CALL(context, gtk_im_context_focus_out(::testing::Eq(context))); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); + gboolean called = FALSE; + fl_mock_binary_messenger_invoke_json_method( + messenger, "flutter/textinput", "TextInput.show", nullptr, + [](FlMockBinaryMessenger* messenger, FlMethodResponse* response, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); - g_autoptr(GBytes) show = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.show", nullptr, nullptr); + g_autoptr(FlValue) expected_result = fl_value_new_null(); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + expected_result)); + }, + &called); + EXPECT_TRUE(called); - messenger.ReceiveMessage("flutter/textinput", show); + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, TextEditingDelta) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); - // set config - g_autoptr(FlValue) args = build_input_config({ - .client_id = 1, - .enable_delta_model = true, - }); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setClient", args, nullptr); - - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::A(), - SuccessResponse(null), ::testing::A())) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_client); - - // set editing state - g_autoptr(FlValue) state = build_editing_state({ - .text = "Flutter", - .selection_base = 7, - .selection_extent = 7, - }); - g_autoptr(GBytes) set_state = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setEditingState", state, nullptr); - - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::_, SuccessResponse(null), ::testing::_)) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_state); + set_client(messenger, { + .client_id = 1, + .enable_delta_model = true, + }); + set_editing_state(messenger, { + .text = "Flutter", + .selection_base = 7, + .selection_extent = 7, + }); // update editing state with deltas - g_autoptr(FlValue) deltas = build_list({ - fl_value_new_int(1), // client_id - build_map({{ - "deltas", - build_list({ - build_editing_delta({ - .old_text = "Flutter", - .delta_text = "Flutter", - .delta_start = 7, - .delta_end = 7, - .selection_base = 0, - .selection_extent = 0, - }), - }), - }}), - }); - - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingStateWithDeltas", - FlValueEq(deltas)), - ::testing::_, ::testing::_, ::testing::_)); + int call_count = 0; + fl_mock_binary_messenger_set_json_method_channel( + messenger, "flutter/textinput", + [](FlMockBinaryMessenger* messenger, const gchar* name, FlValue* args, + gpointer user_data) { + int* call_count = static_cast(user_data); + + EXPECT_STREQ(name, "TextInputClient.updateEditingStateWithDeltas"); + g_autoptr(FlValue) expected_args = nullptr; + switch (*call_count) { + case 0: + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_map({{ + "deltas", + build_list({ + build_editing_delta({ + .old_text = "Flutter", + .delta_text = "Flutter", + .delta_start = 7, + .delta_end = 7, + .selection_base = 0, + .selection_extent = 0, + }), + }), + }}), + }); + break; + default: + g_assert_not_reached(); + break; + } + EXPECT_TRUE(fl_value_equal(args, expected_args)); + (*call_count)++; + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + }, + &call_count); send_key_event(handler, GDK_KEY_Home); + EXPECT_EQ(call_count, 1); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, ComposingDelta) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); // set config - g_autoptr(FlValue) args = build_input_config({ - .client_id = 1, - .enable_delta_model = true, - }); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setClient", args, nullptr); - - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::A(), - SuccessResponse(null), ::testing::A())) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_client); + set_client(messenger, { + .client_id = 1, + .enable_delta_model = true, + }); g_signal_emit_by_name(context, "preedit-start", nullptr); @@ -968,325 +904,283 @@ TEST(FlTextInputHandlerTest, ComposingDelta) { ::testing::DoAll(::testing::SetArgPointee<1>(g_strdup("Flutter ")), ::testing::SetArgPointee<3>(8))); - g_autoptr(FlValue) update = build_list({ - fl_value_new_int(1), // client_id - build_map({{ - "deltas", - build_list({ - build_editing_delta({ - .old_text = "", - .delta_text = "Flutter ", - .delta_start = 0, - .delta_end = 0, - .selection_base = 8, - .selection_extent = 8, - .composing_base = 0, - .composing_extent = 8, - }), - }), - }}), - }); - - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingStateWithDeltas", - FlValueEq(update)), - ::testing::_, ::testing::_, ::testing::_)); + int call_count = 0; + fl_mock_binary_messenger_set_json_method_channel( + messenger, "flutter/textinput", + [](FlMockBinaryMessenger* messenger, const gchar* name, FlValue* args, + gpointer user_data) { + int* call_count = static_cast(user_data); + + EXPECT_STREQ(name, "TextInputClient.updateEditingStateWithDeltas"); + g_autoptr(FlValue) expected_args = nullptr; + switch (*call_count) { + case 0: + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_map({{ + "deltas", + build_list({ + build_editing_delta({ + .old_text = "", + .delta_text = "Flutter ", + .delta_start = 0, + .delta_end = 0, + .selection_base = 8, + .selection_extent = 8, + .composing_base = 0, + .composing_extent = 8, + }), + }), + }}), + }); + break; + case 1: + // commit + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_map({{ + "deltas", + build_list({ + build_editing_delta({ + .old_text = "Flutter ", + .delta_text = "Flutter engine", + .delta_start = 0, + .delta_end = 8, + .selection_base = 14, + .selection_extent = 14, + .composing_base = -1, + .composing_extent = -1, + }), + }), + }}), + }); + break; + case 2: + // end + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_map({{ + "deltas", + build_list({ + build_editing_delta({ + .old_text = "Flutter engine", + .selection_base = 14, + .selection_extent = 14, + }), + }), + }}), + }); + break; + default: + g_assert_not_reached(); + break; + } + EXPECT_TRUE(fl_value_equal(args, expected_args)); + (*call_count)++; + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + }, + &call_count); g_signal_emit_by_name(context, "preedit-changed", nullptr); - - // commit - g_autoptr(FlValue) commit = build_list({ - fl_value_new_int(1), // client_id - build_map({{ - "deltas", - build_list({ - build_editing_delta({ - .old_text = "Flutter ", - .delta_text = "Flutter engine", - .delta_start = 0, - .delta_end = 8, - .selection_base = 14, - .selection_extent = 14, - .composing_base = -1, - .composing_extent = -1, - }), - }), - }}), - }); - - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingStateWithDeltas", - FlValueEq(commit)), - ::testing::_, ::testing::_, ::testing::_)); - g_signal_emit_by_name(context, "commit", "Flutter engine", nullptr); - - // end - g_autoptr(FlValue) end = build_list({ - fl_value_new_int(1), // client_id - build_map({{ - "deltas", - build_list({ - build_editing_delta({ - .old_text = "Flutter engine", - .selection_base = 14, - .selection_extent = 14, - }), - }), - }}), - }); - - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingStateWithDeltas", - FlValueEq(end)), - ::testing::_, ::testing::_, ::testing::_)); - g_signal_emit_by_name(context, "preedit-end", nullptr); + EXPECT_EQ(call_count, 3); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlTextInputHandlerTest, NonComposingDelta) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock context; ::testing::NiceMock delegate; - g_autoptr(FlTextInputHandler) handler = - fl_text_input_handler_new(messenger, context, delegate); + g_autoptr(FlTextInputHandler) handler = fl_text_input_handler_new( + FL_BINARY_MESSENGER(messenger), context, delegate); EXPECT_NE(handler, nullptr); // set config - g_autoptr(FlValue) args = build_input_config({ - .client_id = 1, - .enable_delta_model = true, - }); - g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "TextInput.setClient", args, nullptr); - - g_autoptr(FlValue) null = fl_value_new_null(); - EXPECT_CALL(messenger, fl_binary_messenger_send_response( - ::testing::Eq(messenger), - ::testing::A(), - SuccessResponse(null), ::testing::A())) - .WillOnce(::testing::Return(true)); - - messenger.ReceiveMessage("flutter/textinput", set_client); - - // commit F - g_autoptr(FlValue) commit = build_list({ - fl_value_new_int(1), // client_id - build_map({{ - "deltas", - build_list({ - build_editing_delta({ - .old_text = "", - .delta_text = "F", - .delta_start = 0, - .delta_end = 0, - .selection_base = 1, - .selection_extent = 1, - .composing_base = -1, - .composing_extent = -1, - }), - }), - }}), - }); - - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingStateWithDeltas", - FlValueEq(commit)), - ::testing::_, ::testing::_, ::testing::_)); + set_client(messenger, { + .client_id = 1, + .enable_delta_model = true, + }); + + int call_count = 0; + fl_mock_binary_messenger_set_json_method_channel( + messenger, "flutter/textinput", + [](FlMockBinaryMessenger* messenger, const gchar* name, FlValue* args, + gpointer user_data) { + int* call_count = static_cast(user_data); + + EXPECT_STREQ(name, "TextInputClient.updateEditingStateWithDeltas"); + g_autoptr(FlValue) expected_args = nullptr; + switch (*call_count) { + case 0: + // commit F + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_map({{ + "deltas", + build_list({ + build_editing_delta({ + .old_text = "", + .delta_text = "F", + .delta_start = 0, + .delta_end = 0, + .selection_base = 1, + .selection_extent = 1, + .composing_base = -1, + .composing_extent = -1, + }), + }), + }}), + }); + break; + case 1: + // commit l + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_map({{ + "deltas", + build_list({ + build_editing_delta({ + .old_text = "F", + .delta_text = "l", + .delta_start = 1, + .delta_end = 1, + .selection_base = 2, + .selection_extent = 2, + .composing_base = -1, + .composing_extent = -1, + }), + }), + }}), + }); + break; + case 2: + // commit u + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_map({{ + "deltas", + build_list({ + build_editing_delta({ + .old_text = "Fl", + .delta_text = "u", + .delta_start = 2, + .delta_end = 2, + .selection_base = 3, + .selection_extent = 3, + .composing_base = -1, + .composing_extent = -1, + }), + }), + }}), + }); + break; + case 3: + // commit t + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_map({{ + "deltas", + build_list({ + build_editing_delta({ + .old_text = "Flu", + .delta_text = "t", + .delta_start = 3, + .delta_end = 3, + .selection_base = 4, + .selection_extent = 4, + .composing_base = -1, + .composing_extent = -1, + }), + }), + }}), + }); + break; + case 4: + // commit t again + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_map({{ + "deltas", + build_list({ + build_editing_delta({ + .old_text = "Flut", + .delta_text = "t", + .delta_start = 4, + .delta_end = 4, + .selection_base = 5, + .selection_extent = 5, + .composing_base = -1, + .composing_extent = -1, + }), + }), + }}), + }); + break; + case 5: + // commit e + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_map({{ + "deltas", + build_list({ + build_editing_delta({ + .old_text = "Flutt", + .delta_text = "e", + .delta_start = 5, + .delta_end = 5, + .selection_base = 6, + .selection_extent = 6, + .composing_base = -1, + .composing_extent = -1, + }), + }), + }}), + }); + break; + case 6: + // commit r + expected_args = build_list({ + fl_value_new_int(1), // client_id + build_map({{ + "deltas", + build_list({ + build_editing_delta({ + .old_text = "Flutte", + .delta_text = "r", + .delta_start = 6, + .delta_end = 6, + .selection_base = 7, + .selection_extent = 7, + .composing_base = -1, + .composing_extent = -1, + }), + }), + }}), + }); + break; + default: + g_assert_not_reached(); + break; + } + EXPECT_TRUE(fl_value_equal(args, expected_args)); + (*call_count)++; + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + }, + &call_count); g_signal_emit_by_name(context, "commit", "F", nullptr); - - // commit l - g_autoptr(FlValue) commitL = build_list({ - fl_value_new_int(1), // client_id - build_map({{ - "deltas", - build_list({ - build_editing_delta({ - .old_text = "F", - .delta_text = "l", - .delta_start = 1, - .delta_end = 1, - .selection_base = 2, - .selection_extent = 2, - .composing_base = -1, - .composing_extent = -1, - }), - }), - }}), - }); - - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingStateWithDeltas", - FlValueEq(commitL)), - ::testing::_, ::testing::_, ::testing::_)); - g_signal_emit_by_name(context, "commit", "l", nullptr); - - // commit u - g_autoptr(FlValue) commitU = build_list({ - fl_value_new_int(1), // client_id - build_map({{ - "deltas", - build_list({ - build_editing_delta({ - .old_text = "Fl", - .delta_text = "u", - .delta_start = 2, - .delta_end = 2, - .selection_base = 3, - .selection_extent = 3, - .composing_base = -1, - .composing_extent = -1, - }), - }), - }}), - }); - - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingStateWithDeltas", - FlValueEq(commitU)), - ::testing::_, ::testing::_, ::testing::_)); - g_signal_emit_by_name(context, "commit", "u", nullptr); - - // commit t - g_autoptr(FlValue) commitTa = build_list({ - fl_value_new_int(1), // client_id - build_map({{ - "deltas", - build_list({ - build_editing_delta({ - .old_text = "Flu", - .delta_text = "t", - .delta_start = 3, - .delta_end = 3, - .selection_base = 4, - .selection_extent = 4, - .composing_base = -1, - .composing_extent = -1, - }), - }), - }}), - }); - - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingStateWithDeltas", - FlValueEq(commitTa)), - ::testing::_, ::testing::_, ::testing::_)); - g_signal_emit_by_name(context, "commit", "t", nullptr); - - // commit t again - g_autoptr(FlValue) commitTb = build_list({ - fl_value_new_int(1), // client_id - build_map({{ - "deltas", - build_list({ - build_editing_delta({ - .old_text = "Flut", - .delta_text = "t", - .delta_start = 4, - .delta_end = 4, - .selection_base = 5, - .selection_extent = 5, - .composing_base = -1, - .composing_extent = -1, - }), - }), - }}), - }); - - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingStateWithDeltas", - FlValueEq(commitTb)), - ::testing::_, ::testing::_, ::testing::_)); - g_signal_emit_by_name(context, "commit", "t", nullptr); - - // commit e - g_autoptr(FlValue) commitE = build_list({ - fl_value_new_int(1), // client_id - build_map({{ - "deltas", - build_list({ - build_editing_delta({ - .old_text = "Flutt", - .delta_text = "e", - .delta_start = 5, - .delta_end = 5, - .selection_base = 6, - .selection_extent = 6, - .composing_base = -1, - .composing_extent = -1, - }), - }), - }}), - }); - - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingStateWithDeltas", - FlValueEq(commitE)), - ::testing::_, ::testing::_, ::testing::_)); - g_signal_emit_by_name(context, "commit", "e", nullptr); - - // commit r - g_autoptr(FlValue) commitR = build_list({ - fl_value_new_int(1), // client_id - build_map({{ - "deltas", - build_list({ - build_editing_delta({ - .old_text = "Flutte", - .delta_text = "r", - .delta_start = 6, - .delta_end = 6, - .selection_base = 7, - .selection_extent = 7, - .composing_base = -1, - .composing_extent = -1, - }), - }), - }}), - }); - - EXPECT_CALL(messenger, - fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/textinput"), - MethodCall("TextInputClient.updateEditingStateWithDeltas", - FlValueEq(commitR)), - ::testing::_, ::testing::_, ::testing::_)); - g_signal_emit_by_name(context, "commit", "r", nullptr); + EXPECT_EQ(call_count, 7); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 58d234601f2be..be8cdd7f2051a 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -4,8 +4,6 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" -#include "flutter/shell/platform/linux/fl_view_private.h" - #include #include @@ -14,17 +12,14 @@ #include "flutter/common/constants.h" #include "flutter/shell/platform/linux/fl_accessible_node.h" #include "flutter/shell/platform/linux/fl_engine_private.h" -#include "flutter/shell/platform/linux/fl_framebuffer.h" #include "flutter/shell/platform/linux/fl_key_event.h" #include "flutter/shell/platform/linux/fl_keyboard_handler.h" #include "flutter/shell/platform/linux/fl_keyboard_manager.h" #include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h" -#include "flutter/shell/platform/linux/fl_mouse_cursor_handler.h" -#include "flutter/shell/platform/linux/fl_platform_handler.h" #include "flutter/shell/platform/linux/fl_plugin_registrar_private.h" +#include "flutter/shell/platform/linux/fl_pointer_manager.h" #include "flutter/shell/platform/linux/fl_renderer_gdk.h" #include "flutter/shell/platform/linux/fl_scrolling_manager.h" -#include "flutter/shell/platform/linux/fl_scrolling_view_delegate.h" #include "flutter/shell/platform/linux/fl_socket_accessible.h" #include "flutter/shell/platform/linux/fl_text_input_handler.h" #include "flutter/shell/platform/linux/fl_text_input_view_delegate.h" @@ -33,21 +28,22 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h" -static constexpr int kMicrosecondsPerMillisecond = 1000; - struct _FlView { GtkBox parent_instance; + // The widget rendering the Flutter view. + GtkGLArea* gl_area; + // Engine this view is showing. FlEngine* engine; - // Handler for engine restart signal. - guint on_pre_engine_restart_handler; + // Signal subscription for engine restarts. + guint on_pre_engine_restart_cb_id; // ID for this view. FlutterViewId view_id; - // Rendering output. + // Object that performs the view rendering. FlRendererGdk* renderer; // Background color. @@ -56,37 +52,34 @@ struct _FlView { // TRUE if have got the first frame to render. gboolean have_first_frame; - // Pointer button state recorded for sending status updates. - int64_t button_state; - // Monitor to track window state. FlWindowStateMonitor* window_state_monitor; + // Manages scrolling events. FlScrollingManager* scrolling_manager; + // Manages pointer events. + FlPointerManager* pointer_manager; + + // Manages keyboard events. FlKeyboardManager* keyboard_manager; // Flutter system channel handlers. FlKeyboardHandler* keyboard_handler; FlTextInputHandler* text_input_handler; - FlMouseCursorHandler* mouse_cursor_handler; - FlPlatformHandler* platform_handler; - - GtkWidget* event_box; - GtkGLArea* gl_area; - - // Tracks whether mouse pointer is inside the view. - gboolean pointer_inside; // Accessible tree from Flutter, exposed as an AtkPlug. FlViewAccessible* view_accessible; + // Signal subscripton for cursor changes. + guint cursor_changed_cb_id; + GCancellable* cancellable; }; -enum { kSignalFirstFrame, kSignalLastSignal }; +enum { SIGNAL_FIRST_FRAME, LAST_SIGNAL }; -static guint fl_view_signals[kSignalLastSignal]; +static guint fl_view_signals[LAST_SIGNAL]; static void fl_renderable_iface_init(FlRenderableInterface* iface); @@ -96,9 +89,6 @@ static void fl_view_plugin_registry_iface_init( static void fl_view_keyboard_delegate_iface_init( FlKeyboardViewDelegateInterface* iface); -static void fl_view_scrolling_delegate_iface_init( - FlScrollingViewDelegateInterface* iface); - static void fl_view_text_input_delegate_iface_init( FlTextInputViewDelegateInterface* iface); @@ -111,24 +101,21 @@ G_DEFINE_TYPE_WITH_CODE( fl_view_plugin_registry_iface_init) G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(), fl_view_keyboard_delegate_iface_init) - G_IMPLEMENT_INTERFACE(fl_scrolling_view_delegate_get_type(), - fl_view_scrolling_delegate_iface_init) - G_IMPLEMENT_INTERFACE( - fl_text_input_view_delegate_get_type(), - fl_view_text_input_delegate_iface_init)) + G_IMPLEMENT_INTERFACE(fl_text_input_view_delegate_get_type(), + fl_view_text_input_delegate_iface_init)) // Emit the first frame signal in the main thread. static gboolean first_frame_idle_cb(gpointer user_data) { FlView* self = FL_VIEW(user_data); - g_signal_emit(self, fl_view_signals[kSignalFirstFrame], 0); + g_signal_emit(self, fl_view_signals[SIGNAL_FIRST_FRAME], 0); return FALSE; } // Signal handler for GtkWidget::delete-event static gboolean window_delete_event_cb(FlView* self) { - fl_platform_handler_request_app_exit(self->platform_handler); + fl_engine_request_app_exit(self->engine); // Stop the event from propagating. return TRUE; } @@ -146,18 +133,18 @@ static void init_keyboard(FlView* self) { g_clear_object(&self->text_input_handler); self->text_input_handler = fl_text_input_handler_new( messenger, im_context, FL_TEXT_INPUT_VIEW_DELEGATE(self)); - g_clear_object(&self->keyboard_handler); - self->keyboard_handler = - fl_keyboard_handler_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(self)); g_clear_object(&self->keyboard_manager); self->keyboard_manager = - fl_keyboard_manager_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(self)); + fl_keyboard_manager_new(self->engine, FL_KEYBOARD_VIEW_DELEGATE(self)); + g_clear_object(&self->keyboard_handler); + self->keyboard_handler = + fl_keyboard_handler_new(messenger, self->keyboard_manager); } static void init_scrolling(FlView* self) { g_clear_object(&self->scrolling_manager); self->scrolling_manager = - fl_scrolling_manager_new(FL_SCROLLING_VIEW_DELEGATE(self)); + fl_scrolling_manager_new(self->engine, self->view_id); } static FlutterPointerDeviceKind get_device_kind(GdkEvent* event) { @@ -179,85 +166,45 @@ static FlutterPointerDeviceKind get_device_kind(GdkEvent* event) { } } -// Converts a GDK button event into a Flutter event and sends it to the engine. -static gboolean send_pointer_button_event(FlView* self, GdkEvent* event) { - guint event_time = gdk_event_get_time(event); - GdkEventType event_type = gdk_event_get_event_type(event); - GdkModifierType event_state = static_cast(0); - gdk_event_get_state(event, &event_state); +static gboolean get_mouse_button(GdkEvent* event, int64_t* button) { guint event_button = 0; gdk_event_get_button(event, &event_button); - gdouble event_x = 0.0, event_y = 0.0; - gdk_event_get_coords(event, &event_x, &event_y); - int64_t button; switch (event_button) { case 1: - button = kFlutterPointerButtonMousePrimary; - break; + *button = kFlutterPointerButtonMousePrimary; + return TRUE; case 2: - button = kFlutterPointerButtonMouseMiddle; - break; + *button = kFlutterPointerButtonMouseMiddle; + return TRUE; case 3: - button = kFlutterPointerButtonMouseSecondary; - break; + *button = kFlutterPointerButtonMouseSecondary; + return TRUE; default: return FALSE; } - int old_button_state = self->button_state; - FlutterPointerPhase phase = kMove; - if (event_type == GDK_BUTTON_PRESS) { - // Drop the event if Flutter already thinks the button is down. - if ((self->button_state & button) != 0) { - return FALSE; - } - self->button_state ^= button; - - phase = old_button_state == 0 ? kDown : kMove; - } else if (event_type == GDK_BUTTON_RELEASE) { - // Drop the event if Flutter already thinks the button is up. - if ((self->button_state & button) == 0) { - return FALSE; - } - self->button_state ^= button; - - phase = self->button_state == 0 ? kUp : kMove; - } - - if (self->engine == nullptr) { - return FALSE; - } - - gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); - fl_scrolling_manager_set_last_mouse_position( - self->scrolling_manager, event_x * scale_factor, event_y * scale_factor); - fl_keyboard_manager_sync_modifier_if_needed(self->keyboard_manager, - event_state, event_time); - fl_engine_send_mouse_pointer_event( - self->engine, self->view_id, phase, - event_time * kMicrosecondsPerMillisecond, event_x * scale_factor, - event_y * scale_factor, get_device_kind((GdkEvent*)event), 0, 0, - self->button_state); - - return TRUE; } -// Generates a mouse pointer event if the pointer appears inside the window. -static void check_pointer_inside(FlView* self, GdkEvent* event) { - if (!self->pointer_inside) { - self->pointer_inside = TRUE; +// Called when the mouse cursor changes. +static void cursor_changed_cb(FlView* self) { + FlMouseCursorHandler* handler = + fl_engine_get_mouse_cursor_handler(self->engine); + const gchar* cursor_name = fl_mouse_cursor_handler_get_cursor_name(handler); + GdkWindow* window = + gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self))); + g_autoptr(GdkCursor) cursor = + gdk_cursor_new_from_name(gdk_window_get_display(window), cursor_name); + gdk_window_set_cursor(window, cursor); +} - gdouble x, y; - if (gdk_event_get_coords(event, &x, &y)) { - gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); +// Set the mouse cursor. +static void setup_cursor(FlView* self) { + FlMouseCursorHandler* handler = + fl_engine_get_mouse_cursor_handler(self->engine); - fl_engine_send_mouse_pointer_event( - self->engine, self->view_id, kAdd, - gdk_event_get_time(event) * kMicrosecondsPerMillisecond, - x * scale_factor, y * scale_factor, get_device_kind(event), 0, 0, - self->button_state); - } - } + self->cursor_changed_cb_id = g_signal_connect_swapped( + handler, "cursor-changed", G_CALLBACK(cursor_changed_cb), self); + cursor_changed_cb(self); } // Updates the engine with the current window metrics. @@ -364,54 +311,12 @@ static void fl_view_plugin_registry_iface_init( static void fl_view_keyboard_delegate_iface_init( FlKeyboardViewDelegateInterface* iface) { - iface->send_key_event = - [](FlKeyboardViewDelegate* view_delegate, const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, void* user_data) { - FlView* self = FL_VIEW(view_delegate); - if (self->engine != nullptr) { - fl_engine_send_key_event(self->engine, event, callback, user_data); - }; - }; - iface->text_filter_key_press = [](FlKeyboardViewDelegate* view_delegate, FlKeyEvent* event) { FlView* self = FL_VIEW(view_delegate); return fl_text_input_handler_filter_keypress(self->text_input_handler, event); }; - - iface->get_keyboard_state = - [](FlKeyboardViewDelegate* view_delegate) -> GHashTable* { - FlView* self = FL_VIEW(view_delegate); - return fl_view_get_keyboard_state(self); - }; -} - -static void fl_view_scrolling_delegate_iface_init( - FlScrollingViewDelegateInterface* iface) { - iface->send_mouse_pointer_event = - [](FlScrollingViewDelegate* view_delegate, FlutterPointerPhase phase, - size_t timestamp, double x, double y, - FlutterPointerDeviceKind device_kind, double scroll_delta_x, - double scroll_delta_y, int64_t buttons) { - FlView* self = FL_VIEW(view_delegate); - if (self->engine != nullptr) { - fl_engine_send_mouse_pointer_event( - self->engine, self->view_id, phase, timestamp, x, y, device_kind, - scroll_delta_x, scroll_delta_y, buttons); - } - }; - iface->send_pointer_pan_zoom_event = - [](FlScrollingViewDelegate* view_delegate, size_t timestamp, double x, - double y, FlutterPointerPhase phase, double pan_x, double pan_y, - double scale, double rotation) { - FlView* self = FL_VIEW(view_delegate); - if (self->engine != nullptr) { - fl_engine_send_pointer_pan_zoom_event(self->engine, self->view_id, - timestamp, x, y, phase, pan_x, - pan_y, scale, rotation); - }; - }; } static void fl_view_text_input_delegate_iface_init( @@ -426,6 +331,20 @@ static void fl_view_text_input_delegate_iface_init( }; } +static void sync_modifier_if_needed(FlView* self, GdkEvent* event) { + guint event_time = gdk_event_get_time(event); + GdkModifierType event_state = static_cast(0); + gdk_event_get_state(event, &event_state); + fl_keyboard_manager_sync_modifier_if_needed(self->keyboard_manager, + event_state, event_time); +} + +static void set_scrolling_position(FlView* self, gdouble x, gdouble y) { + gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); + fl_scrolling_manager_set_last_mouse_position( + self->scrolling_manager, x * scale_factor, y * scale_factor); +} + // Signal handler for GtkWidget::button-press-event static gboolean button_press_event_cb(FlView* self, GdkEventButton* button_event) { @@ -438,16 +357,43 @@ static gboolean button_press_event_cb(FlView* self, return FALSE; } - check_pointer_inside(self, event); + int64_t button; + if (!get_mouse_button(event, &button)) { + return FALSE; + } + + gdouble x = 0.0, y = 0.0; + gdk_event_get_coords(event, &x, &y); + + set_scrolling_position(self, x, y); + sync_modifier_if_needed(self, event); - return send_pointer_button_event(self, event); + gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); + return fl_pointer_manager_handle_button_press( + self->pointer_manager, gdk_event_get_time(event), get_device_kind(event), + x * scale_factor, y * scale_factor, button); } // Signal handler for GtkWidget::button-release-event static gboolean button_release_event_cb(FlView* self, GdkEventButton* button_event) { GdkEvent* event = reinterpret_cast(button_event); - return send_pointer_button_event(self, event); + + int64_t button; + if (!get_mouse_button(event, &button)) { + return FALSE; + } + + gdouble x = 0.0, y = 0.0; + gdk_event_get_coords(event, &x, &y); + + set_scrolling_position(self, x, y); + sync_modifier_if_needed(self, event); + + gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); + return fl_pointer_manager_handle_button_release( + self->pointer_manager, gdk_event_get_time(event), get_device_kind(event), + x * scale_factor, y * scale_factor, button); } // Signal handler for GtkWidget::scroll-event @@ -465,77 +411,42 @@ static gboolean scroll_event_cb(FlView* self, GdkEventScroll* event) { static gboolean motion_notify_event_cb(FlView* self, GdkEventMotion* motion_event) { GdkEvent* event = reinterpret_cast(motion_event); + sync_modifier_if_needed(self, event); - if (self->engine == nullptr) { - return FALSE; - } - - guint event_time = gdk_event_get_time(event); - GdkModifierType event_state = static_cast(0); - gdk_event_get_state(event, &event_state); - gdouble event_x = 0.0, event_y = 0.0; - gdk_event_get_coords(event, &event_x, &event_y); - - check_pointer_inside(self, event); - + gdouble x = 0.0, y = 0.0; + gdk_event_get_coords(event, &x, &y); gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); - - fl_keyboard_manager_sync_modifier_if_needed(self->keyboard_manager, - event_state, event_time); - fl_engine_send_mouse_pointer_event( - self->engine, self->view_id, self->button_state != 0 ? kMove : kHover, - event_time * kMicrosecondsPerMillisecond, event_x * scale_factor, - event_y * scale_factor, get_device_kind((GdkEvent*)event), 0, 0, - self->button_state); - - return TRUE; + return fl_pointer_manager_handle_motion( + self->pointer_manager, gdk_event_get_time(event), get_device_kind(event), + x * scale_factor, y * scale_factor); } // Signal handler for GtkWidget::enter-notify-event static gboolean enter_notify_event_cb(FlView* self, GdkEventCrossing* crossing_event) { GdkEvent* event = reinterpret_cast(crossing_event); - - if (self->engine == nullptr) { - return FALSE; - } - - check_pointer_inside(self, event); - - return TRUE; + gdouble x = 0.0, y = 0.0; + gdk_event_get_coords(event, &x, &y); + gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); + return fl_pointer_manager_handle_enter( + self->pointer_manager, gdk_event_get_time(event), get_device_kind(event), + x * scale_factor, y * scale_factor); } // Signal handler for GtkWidget::leave-notify-event static gboolean leave_notify_event_cb(FlView* self, GdkEventCrossing* crossing_event) { - GdkEvent* event = reinterpret_cast(crossing_event); - - guint event_time = gdk_event_get_time(event); - gdouble event_x = 0.0, event_y = 0.0; - gdk_event_get_coords(event, &event_x, &event_y); - if (crossing_event->mode != GDK_CROSSING_NORMAL) { return FALSE; } - if (self->engine == nullptr) { - return FALSE; - } - - // Don't remove pointer while button is down; In case of dragging outside of - // window with mouse grab active Gtk will send another leave notify on - // release. - if (self->pointer_inside && self->button_state == 0) { - gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); - fl_engine_send_mouse_pointer_event( - self->engine, self->view_id, kRemove, - event_time * kMicrosecondsPerMillisecond, event_x * scale_factor, - event_y * scale_factor, get_device_kind((GdkEvent*)event), 0, 0, - self->button_state); - self->pointer_inside = FALSE; - } - - return TRUE; + GdkEvent* event = reinterpret_cast(crossing_event); + gdouble x = 0.0, y = 0.0; + gdk_event_get_coords(event, &x, &y); + gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); + return fl_pointer_manager_handle_leave( + self->pointer_manager, gdk_event_get_time(event), get_device_kind(event), + x * scale_factor, y * scale_factor); } static void gesture_rotation_begin_cb(FlView* self) { @@ -569,11 +480,7 @@ static GdkGLContext* create_context_cb(FlView* self) { fl_renderer_gdk_set_window(self->renderer, gtk_widget_get_parent_window(GTK_WIDGET(self))); - // Create system channel handlers. - FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(self->engine); init_scrolling(self); - self->mouse_cursor_handler = fl_mouse_cursor_handler_new(messenger, self); - self->platform_handler = fl_platform_handler_new(messenger); g_autoptr(GError) error = nullptr; if (!fl_renderer_gdk_create_contexts(self->renderer, &error)) { @@ -618,6 +525,8 @@ static void realize_cb(FlView* self) { return; } + setup_cursor(self); + handle_geometry_changed(self); self->view_accessible = fl_view_accessible_new(self->engine); @@ -680,6 +589,13 @@ static void fl_view_dispose(GObject* object) { fl_engine_set_update_semantics_handler(self->engine, nullptr, nullptr, nullptr); + FlMouseCursorHandler* handler = + fl_engine_get_mouse_cursor_handler(self->engine); + if (self->cursor_changed_cb_id != 0) { + g_signal_handler_disconnect(handler, self->cursor_changed_cb_id); + self->cursor_changed_cb_id = 0; + } + // Stop rendering. fl_renderer_remove_view(FL_RENDERER(self->renderer), self->view_id); @@ -688,10 +604,10 @@ static void fl_view_dispose(GObject* object) { nullptr); } - if (self->on_pre_engine_restart_handler != 0) { + if (self->on_pre_engine_restart_cb_id != 0) { g_signal_handler_disconnect(self->engine, - self->on_pre_engine_restart_handler); - self->on_pre_engine_restart_handler = 0; + self->on_pre_engine_restart_cb_id); + self->on_pre_engine_restart_cb_id = 0; } g_clear_object(&self->engine); @@ -699,10 +615,9 @@ static void fl_view_dispose(GObject* object) { g_clear_pointer(&self->background_color, gdk_rgba_free); g_clear_object(&self->window_state_monitor); g_clear_object(&self->scrolling_manager); + g_clear_object(&self->pointer_manager); g_clear_object(&self->keyboard_manager); g_clear_object(&self->keyboard_handler); - g_clear_object(&self->mouse_cursor_handler); - g_clear_object(&self->platform_handler); g_clear_object(&self->view_accessible); g_clear_object(&self->cancellable); @@ -720,21 +635,24 @@ static void fl_view_realize(GtkWidget* widget) { } // Implements GtkWidget::key_press_event. -static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) { +static gboolean fl_view_key_press_event(GtkWidget* widget, + GdkEventKey* key_event) { FlView* self = FL_VIEW(widget); + GdkEvent* event = reinterpret_cast(key_event); return fl_keyboard_manager_handle_event( - self->keyboard_manager, fl_key_event_new_from_gdk_event(gdk_event_copy( - reinterpret_cast(event)))); + self->keyboard_manager, + fl_key_event_new_from_gdk_event(gdk_event_copy(event))); } // Implements GtkWidget::key_release_event. static gboolean fl_view_key_release_event(GtkWidget* widget, - GdkEventKey* event) { + GdkEventKey* key_event) { FlView* self = FL_VIEW(widget); + GdkEvent* event = reinterpret_cast(key_event); return fl_keyboard_manager_handle_event( - self->keyboard_manager, fl_key_event_new_from_gdk_event(gdk_event_copy( - reinterpret_cast(event)))); + self->keyboard_manager, + fl_key_event_new_from_gdk_event(gdk_event_copy(event))); } static void fl_view_class_init(FlViewClass* klass) { @@ -747,7 +665,7 @@ static void fl_view_class_init(FlViewClass* klass) { widget_class->key_press_event = fl_view_key_press_event; widget_class->key_release_event = fl_view_key_release_event; - fl_view_signals[kSignalFirstFrame] = + fl_view_signals[SIGNAL_FIRST_FRAME] = g_signal_new("first-frame", fl_view_get_type(), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); @@ -766,35 +684,35 @@ static void fl_view_init(FlView* self) { .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0}; self->background_color = gdk_rgba_copy(&default_background); - self->event_box = gtk_event_box_new(); - gtk_widget_set_hexpand(self->event_box, TRUE); - gtk_widget_set_vexpand(self->event_box, TRUE); - gtk_container_add(GTK_CONTAINER(self), self->event_box); - gtk_widget_show(self->event_box); - gtk_widget_add_events(self->event_box, + GtkWidget* event_box = gtk_event_box_new(); + gtk_widget_set_hexpand(event_box, TRUE); + gtk_widget_set_vexpand(event_box, TRUE); + gtk_container_add(GTK_CONTAINER(self), event_box); + gtk_widget_show(event_box); + gtk_widget_add_events(event_box, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK); - g_signal_connect_swapped(self->event_box, "button-press-event", + g_signal_connect_swapped(event_box, "button-press-event", G_CALLBACK(button_press_event_cb), self); - g_signal_connect_swapped(self->event_box, "button-release-event", + g_signal_connect_swapped(event_box, "button-release-event", G_CALLBACK(button_release_event_cb), self); - g_signal_connect_swapped(self->event_box, "scroll-event", + g_signal_connect_swapped(event_box, "scroll-event", G_CALLBACK(scroll_event_cb), self); - g_signal_connect_swapped(self->event_box, "motion-notify-event", + g_signal_connect_swapped(event_box, "motion-notify-event", G_CALLBACK(motion_notify_event_cb), self); - g_signal_connect_swapped(self->event_box, "enter-notify-event", + g_signal_connect_swapped(event_box, "enter-notify-event", G_CALLBACK(enter_notify_event_cb), self); - g_signal_connect_swapped(self->event_box, "leave-notify-event", + g_signal_connect_swapped(event_box, "leave-notify-event", G_CALLBACK(leave_notify_event_cb), self); - GtkGesture* zoom = gtk_gesture_zoom_new(self->event_box); + GtkGesture* zoom = gtk_gesture_zoom_new(event_box); g_signal_connect_swapped(zoom, "begin", G_CALLBACK(gesture_zoom_begin_cb), self); g_signal_connect_swapped(zoom, "scale-changed", G_CALLBACK(gesture_zoom_update_cb), self); g_signal_connect_swapped(zoom, "end", G_CALLBACK(gesture_zoom_end_cb), self); - GtkGesture* rotate = gtk_gesture_rotate_new(self->event_box); + GtkGesture* rotate = gtk_gesture_rotate_new(event_box); g_signal_connect_swapped(rotate, "begin", G_CALLBACK(gesture_rotation_begin_cb), self); g_signal_connect_swapped(rotate, "angle-changed", @@ -805,7 +723,7 @@ static void fl_view_init(FlView* self) { self->gl_area = GTK_GL_AREA(gtk_gl_area_new()); gtk_gl_area_set_has_alpha(self->gl_area, TRUE); gtk_widget_show(GTK_WIDGET(self->gl_area)); - gtk_container_add(GTK_CONTAINER(self->event_box), GTK_WIDGET(self->gl_area)); + gtk_container_add(GTK_CONTAINER(event_box), GTK_WIDGET(self->gl_area)); g_signal_connect_swapped(self->gl_area, "render", G_CALLBACK(render_cb), self); @@ -823,9 +741,11 @@ G_MODULE_EXPORT FlView* fl_view_new(FlDartProject* project) { g_assert(FL_IS_RENDERER_GDK(renderer)); self->renderer = FL_RENDERER_GDK(g_object_ref(renderer)); + self->pointer_manager = fl_pointer_manager_new(self->view_id, engine); + fl_engine_set_update_semantics_handler(self->engine, update_semantics_cb, self, nullptr); - self->on_pre_engine_restart_handler = + self->on_pre_engine_restart_cb_id = g_signal_connect_swapped(engine, "on-pre-engine-restart", G_CALLBACK(on_pre_engine_restart_cb), self); @@ -847,7 +767,7 @@ G_MODULE_EXPORT FlView* fl_view_new_for_engine(FlEngine* engine) { g_assert(FL_IS_RENDERER_GDK(renderer)); self->renderer = FL_RENDERER_GDK(g_object_ref(renderer)); - self->on_pre_engine_restart_handler = + self->on_pre_engine_restart_cb_id = g_signal_connect_swapped(engine, "on-pre-engine-restart", G_CALLBACK(on_pre_engine_restart_cb), self); @@ -856,6 +776,10 @@ G_MODULE_EXPORT FlView* fl_view_new_for_engine(FlEngine* engine) { fl_renderer_add_renderable(FL_RENDERER(self->renderer), self->view_id, FL_RENDERABLE(self)); + self->pointer_manager = fl_pointer_manager_new(self->view_id, engine); + + setup_cursor(self); + return self; } @@ -876,8 +800,3 @@ G_MODULE_EXPORT void fl_view_set_background_color(FlView* self, gdk_rgba_free(self->background_color); self->background_color = gdk_rgba_copy(color); } - -GHashTable* fl_view_get_keyboard_state(FlView* self) { - g_return_val_if_fail(FL_IS_VIEW(self), nullptr); - return fl_keyboard_manager_get_pressed_state(self->keyboard_manager); -} diff --git a/shell/platform/linux/fl_view_accessible.cc b/shell/platform/linux/fl_view_accessible.cc index 09bb83930fb24..13012681eea32 100644 --- a/shell/platform/linux/fl_view_accessible.cc +++ b/shell/platform/linux/fl_view_accessible.cc @@ -22,8 +22,6 @@ struct _FlViewAccessible { gboolean root_node_created; }; -enum { kProp0, kPropEngine, kPropLast }; - G_DEFINE_TYPE(FlViewAccessible, fl_view_accessible, ATK_TYPE_PLUG) static FlAccessibleNode* create_node(FlViewAccessible* self, diff --git a/shell/platform/linux/fl_view_private.h b/shell/platform/linux/fl_view_private.h deleted file mode 100644 index e23b178cada72..0000000000000 --- a/shell/platform/linux/fl_view_private.h +++ /dev/null @@ -1,19 +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. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_PRIVATE_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_PRIVATE_H_ - -#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" - -/** - * fl_view_get_keyboard_state: - * @view: an #FlView. - * - * Returns the keyboard pressed state. The hash table contains one entry per - * pressed keys, mapping from the logical key to the physical key.* - */ -GHashTable* fl_view_get_keyboard_state(FlView* view); - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_PRIVATE_H_ diff --git a/shell/platform/linux/fl_view_test.cc b/shell/platform/linux/fl_view_test.cc index acf1bca7fd126..e6a51057c68c1 100644 --- a/shell/platform/linux/fl_view_test.cc +++ b/shell/platform/linux/fl_view_test.cc @@ -5,7 +5,6 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/shell/platform/linux/fl_engine_private.h" -#include "flutter/shell/platform/linux/fl_view_private.h" #include "flutter/shell/platform/linux/testing/fl_test.h" #include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h" @@ -83,6 +82,9 @@ TEST(FlViewTest, SecondaryView) { return kSuccess; })); + g_autoptr(GError) error = nullptr; + EXPECT_TRUE(fl_engine_start(engine, &error)); + FlView* secondary_view = fl_view_new_for_engine(engine); EXPECT_EQ(view_id, fl_view_get_id(secondary_view)); } @@ -104,6 +106,9 @@ TEST(FlViewTest, SecondaryViewError) { return kInvalidArguments; })); + g_autoptr(GError) error = nullptr; + EXPECT_TRUE(fl_engine_start(engine, &error)); + FlView* secondary_view = fl_view_new_for_engine(engine); EXPECT_EQ(view_id, fl_view_get_id(secondary_view)); } @@ -126,6 +131,9 @@ TEST(FlViewTest, ViewDestroy) { return kSuccess; })); + g_autoptr(GError) error = nullptr; + EXPECT_TRUE(fl_engine_start(engine, &error)); + FlView* secondary_view = fl_view_new_for_engine(engine); int64_t implicit_view_id = fl_view_get_id(implicit_view); @@ -156,6 +164,9 @@ TEST(FlViewTest, ViewDestroyError) { return kInvalidArguments; })); + g_autoptr(GError) error = nullptr; + EXPECT_TRUE(fl_engine_start(engine, &error)); + FlView* secondary_view = fl_view_new_for_engine(engine); gtk_widget_destroy(GTK_WIDGET(secondary_view)); diff --git a/shell/platform/linux/fl_window_state_monitor_test.cc b/shell/platform/linux/fl_window_state_monitor_test.cc index f88450741e10c..8aa6588d44ddb 100644 --- a/shell/platform/linux/fl_window_state_monitor_test.cc +++ b/shell/platform/linux/fl_window_state_monitor_test.cc @@ -3,254 +3,287 @@ // found in the LICENSE file. #include "flutter/shell/platform/linux/fl_window_state_monitor.h" +#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_string_codec.h" -#include "flutter/shell/platform/linux/testing/fl_test.h" -#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h" +#include "flutter/shell/platform/linux/testing/fl_mock_binary_messenger.h" #include "flutter/shell/platform/linux/testing/mock_window.h" #include "gtest/gtest.h" -// Matches if a FlValue is a the supplied string. -class FlValueStringMatcher { - public: - using is_gtest_matcher = void; - - explicit FlValueStringMatcher(::testing::Matcher value) - : value_(std::move(value)) {} - - bool MatchAndExplain(GBytes* data, - ::testing::MatchResultListener* result_listener) const { - g_autoptr(FlStringCodec) codec = fl_string_codec_new(); - g_autoptr(GError) error = nullptr; - g_autoptr(FlValue) value = - fl_message_codec_decode_message(FL_MESSAGE_CODEC(codec), data, &error); - if (value == nullptr) { - *result_listener << ::testing::PrintToString(error->message); - return false; - } - if (!value_.MatchAndExplain(fl_value_get_string(value), result_listener)) { - *result_listener << " where the value doesn't match: \"" << value << "\""; - return false; - } - return true; - } - - void DescribeTo(std::ostream* os) const { - *os << "value "; - value_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const { - *os << "value "; - value_.DescribeNegationTo(os); - } - - private: - ::testing::Matcher value_; -}; - -::testing::Matcher LifecycleString(const std::string& value) { - return FlValueStringMatcher(::testing::StrEq(value)); -} - TEST(FlWindowStateMonitorTest, GainFocus) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock mock_window; gtk_init(0, nullptr); EXPECT_CALL(mock_window, gdk_window_get_state) .WillOnce(::testing::Return(static_cast(0))); - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/lifecycle"), - LifecycleString("AppLifecycleState.resumed"), - ::testing::_, ::testing::_, ::testing::_)); + + gboolean called = TRUE; + fl_mock_binary_messenger_set_string_message_channel( + messenger, "flutter/lifecycle", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + EXPECT_STREQ(fl_value_get_string(message), "AppLifecycleState.resumed"); + return fl_value_new_string(""); + }, + &called); GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlWindowStateMonitor) monitor = - fl_window_state_monitor_new(messenger, window); + fl_window_state_monitor_new(FL_BINARY_MESSENGER(messenger), window); GdkEvent event = { .window_state = {.new_window_state = GDK_WINDOW_STATE_FOCUSED}}; gboolean handled; g_signal_emit_by_name(window, "window-state-event", &event, &handled); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlWindowStateMonitorTest, LoseFocus) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock mock_window; gtk_init(0, nullptr); EXPECT_CALL(mock_window, gdk_window_get_state) .WillOnce(::testing::Return(GDK_WINDOW_STATE_FOCUSED)); - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/lifecycle"), - LifecycleString("AppLifecycleState.inactive"), - ::testing::_, ::testing::_, ::testing::_)); + gboolean called = TRUE; + fl_mock_binary_messenger_set_string_message_channel( + messenger, "flutter/lifecycle", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + EXPECT_STREQ(fl_value_get_string(message), + "AppLifecycleState.inactive"); + return fl_value_new_string(""); + }, + &called); GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlWindowStateMonitor) monitor = - fl_window_state_monitor_new(messenger, window); + fl_window_state_monitor_new(FL_BINARY_MESSENGER(messenger), window); GdkEvent event = { .window_state = {.new_window_state = static_cast(0)}}; gboolean handled; g_signal_emit_by_name(window, "window-state-event", &event, &handled); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlWindowStateMonitorTest, EnterIconified) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock mock_window; gtk_init(0, nullptr); EXPECT_CALL(mock_window, gdk_window_get_state) .WillOnce(::testing::Return(static_cast(0))); - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/lifecycle"), - LifecycleString("AppLifecycleState.hidden"), - ::testing::_, ::testing::_, ::testing::_)); + gboolean called = TRUE; + fl_mock_binary_messenger_set_string_message_channel( + messenger, "flutter/lifecycle", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + EXPECT_STREQ(fl_value_get_string(message), "AppLifecycleState.hidden"); + return fl_value_new_string(""); + }, + &called); GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlWindowStateMonitor) monitor = - fl_window_state_monitor_new(messenger, window); + fl_window_state_monitor_new(FL_BINARY_MESSENGER(messenger), window); GdkEvent event = { .window_state = {.new_window_state = GDK_WINDOW_STATE_ICONIFIED}}; gboolean handled; g_signal_emit_by_name(window, "window-state-event", &event, &handled); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlWindowStateMonitorTest, LeaveIconified) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock mock_window; gtk_init(0, nullptr); EXPECT_CALL(mock_window, gdk_window_get_state) .WillOnce(::testing::Return(GDK_WINDOW_STATE_ICONIFIED)); - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/lifecycle"), - LifecycleString("AppLifecycleState.inactive"), - ::testing::_, ::testing::_, ::testing::_)); + gboolean called = TRUE; + fl_mock_binary_messenger_set_string_message_channel( + messenger, "flutter/lifecycle", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + EXPECT_STREQ(fl_value_get_string(message), + "AppLifecycleState.inactive"); + return fl_value_new_string(""); + }, + &called); GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlWindowStateMonitor) monitor = - fl_window_state_monitor_new(messenger, window); + fl_window_state_monitor_new(FL_BINARY_MESSENGER(messenger), window); GdkEvent event = { .window_state = {.new_window_state = static_cast(0)}}; gboolean handled; g_signal_emit_by_name(window, "window-state-event", &event, &handled); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlWindowStateMonitorTest, LeaveIconifiedFocused) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock mock_window; gtk_init(0, nullptr); EXPECT_CALL(mock_window, gdk_window_get_state) .WillOnce(::testing::Return(GDK_WINDOW_STATE_ICONIFIED)); - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/lifecycle"), - LifecycleString("AppLifecycleState.resumed"), - ::testing::_, ::testing::_, ::testing::_)); + gboolean called = TRUE; + fl_mock_binary_messenger_set_string_message_channel( + messenger, "flutter/lifecycle", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + EXPECT_STREQ(fl_value_get_string(message), "AppLifecycleState.resumed"); + return fl_value_new_string(""); + }, + &called); GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlWindowStateMonitor) monitor = - fl_window_state_monitor_new(messenger, window); + fl_window_state_monitor_new(FL_BINARY_MESSENGER(messenger), window); GdkEvent event = { .window_state = {.new_window_state = static_cast( GDK_WINDOW_STATE_FOCUSED)}}; gboolean handled; g_signal_emit_by_name(window, "window-state-event", &event, &handled); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlWindowStateMonitorTest, EnterWithdrawn) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock mock_window; gtk_init(0, nullptr); EXPECT_CALL(mock_window, gdk_window_get_state) .WillOnce(::testing::Return(static_cast(0))); - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/lifecycle"), - LifecycleString("AppLifecycleState.hidden"), - ::testing::_, ::testing::_, ::testing::_)); + gboolean called = TRUE; + fl_mock_binary_messenger_set_string_message_channel( + messenger, "flutter/lifecycle", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + EXPECT_STREQ(fl_value_get_string(message), "AppLifecycleState.hidden"); + return fl_value_new_string(""); + }, + &called); GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlWindowStateMonitor) monitor = - fl_window_state_monitor_new(messenger, window); + fl_window_state_monitor_new(FL_BINARY_MESSENGER(messenger), window); GdkEvent event = { .window_state = {.new_window_state = GDK_WINDOW_STATE_WITHDRAWN}}; gboolean handled; g_signal_emit_by_name(window, "window-state-event", &event, &handled); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlWindowStateMonitorTest, LeaveWithdrawn) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock mock_window; gtk_init(0, nullptr); EXPECT_CALL(mock_window, gdk_window_get_state) .WillOnce(::testing::Return(GDK_WINDOW_STATE_WITHDRAWN)); - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/lifecycle"), - LifecycleString("AppLifecycleState.inactive"), - ::testing::_, ::testing::_, ::testing::_)); + gboolean called = TRUE; + fl_mock_binary_messenger_set_string_message_channel( + messenger, "flutter/lifecycle", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + EXPECT_STREQ(fl_value_get_string(message), + "AppLifecycleState.inactive"); + return fl_value_new_string(""); + }, + &called); GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlWindowStateMonitor) monitor = - fl_window_state_monitor_new(messenger, window); + fl_window_state_monitor_new(FL_BINARY_MESSENGER(messenger), window); GdkEvent event = { .window_state = {.new_window_state = static_cast(0)}}; gboolean handled; g_signal_emit_by_name(window, "window-state-event", &event, &handled); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } TEST(FlWindowStateMonitorTest, LeaveWithdrawnFocused) { - ::testing::NiceMock messenger; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); ::testing::NiceMock mock_window; gtk_init(0, nullptr); EXPECT_CALL(mock_window, gdk_window_get_state) .WillOnce(::testing::Return(GDK_WINDOW_STATE_WITHDRAWN)); - EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( - ::testing::Eq(messenger), - ::testing::StrEq("flutter/lifecycle"), - LifecycleString("AppLifecycleState.resumed"), - ::testing::_, ::testing::_, ::testing::_)); + gboolean called = TRUE; + fl_mock_binary_messenger_set_string_message_channel( + messenger, "flutter/lifecycle", + [](FlMockBinaryMessenger* messenger, FlValue* message, + gpointer user_data) { + gboolean* called = static_cast(user_data); + *called = TRUE; + EXPECT_STREQ(fl_value_get_string(message), "AppLifecycleState.resumed"); + return fl_value_new_string(""); + }, + &called); GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlWindowStateMonitor) monitor = - fl_window_state_monitor_new(messenger, window); + fl_window_state_monitor_new(FL_BINARY_MESSENGER(messenger), window); GdkEvent event = { .window_state = {.new_window_state = static_cast( GDK_WINDOW_STATE_FOCUSED)}}; gboolean handled; g_signal_emit_by_name(window, "window-state-event", &event, &handled); + EXPECT_TRUE(called); + + fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); } diff --git a/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h b/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h index bdff02d40d471..adc11f71b8e04 100644 --- a/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h +++ b/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h @@ -28,7 +28,6 @@ G_BEGIN_DECLS typedef enum { // Part of the public API, so fixing the name is a breaking change. - // NOLINTNEXTLINE(readability-identifier-naming) FL_BINARY_MESSENGER_ERROR_ALREADY_RESPONDED, } FlBinaryMessengerError; diff --git a/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h b/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h index 9ffe7ea9526ce..0560511ecb291 100644 --- a/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h +++ b/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h @@ -27,11 +27,9 @@ G_BEGIN_DECLS #define FL_JSON_MESSAGE_CODEC_ERROR fl_json_message_codec_error_quark() typedef enum { - // NOLINTBEGIN(readability-identifier-naming) FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8, FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON, FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE, - // NOLINTEND(readability-identifier-naming) } FlJsonMessageCodecError; G_MODULE_EXPORT diff --git a/shell/platform/linux/public/flutter_linux/fl_message_codec.h b/shell/platform/linux/public/flutter_linux/fl_message_codec.h index fd93345e79e73..1ab5627952bdc 100644 --- a/shell/platform/linux/public/flutter_linux/fl_message_codec.h +++ b/shell/platform/linux/public/flutter_linux/fl_message_codec.h @@ -30,12 +30,10 @@ G_BEGIN_DECLS #define FL_MESSAGE_CODEC_ERROR fl_message_codec_error_quark() typedef enum { - // NOLINTBEGIN(readability-identifier-naming) FL_MESSAGE_CODEC_ERROR_FAILED, FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA, FL_MESSAGE_CODEC_ERROR_ADDITIONAL_DATA, FL_MESSAGE_CODEC_ERROR_UNSUPPORTED_TYPE, - // NOLINTEND(readability-identifier-naming) } FlMessageCodecError; G_MODULE_EXPORT diff --git a/shell/platform/linux/public/flutter_linux/fl_method_response.h b/shell/platform/linux/public/flutter_linux/fl_method_response.h index 5e595a2a6fa44..c7ceecec9aaf8 100644 --- a/shell/platform/linux/public/flutter_linux/fl_method_response.h +++ b/shell/platform/linux/public/flutter_linux/fl_method_response.h @@ -30,11 +30,9 @@ G_BEGIN_DECLS #define FL_METHOD_RESPONSE_ERROR fl_method_response_error_quark() typedef enum { - // NOLINTBEGIN(readability-identifier-naming) FL_METHOD_RESPONSE_ERROR_FAILED, FL_METHOD_RESPONSE_ERROR_REMOTE_ERROR, FL_METHOD_RESPONSE_ERROR_NOT_IMPLEMENTED, - // NOLINTEND(readability-identifier-naming) } FlMethodResponseError; G_MODULE_EXPORT diff --git a/shell/platform/linux/public/flutter_linux/fl_value.h b/shell/platform/linux/public/flutter_linux/fl_value.h index 21fe7074e8cea..86a2f9c25973c 100644 --- a/shell/platform/linux/public/flutter_linux/fl_value.h +++ b/shell/platform/linux/public/flutter_linux/fl_value.h @@ -61,7 +61,6 @@ typedef struct _FlValue FlValue; */ typedef enum { // Parts of the public API, so fixing the names is a breaking change. - // NOLINTBEGIN(readability-identifier-naming) FL_VALUE_TYPE_NULL, FL_VALUE_TYPE_BOOL, FL_VALUE_TYPE_INT, @@ -75,7 +74,6 @@ typedef enum { FL_VALUE_TYPE_MAP, FL_VALUE_TYPE_FLOAT32_LIST, FL_VALUE_TYPE_CUSTOM, - // NOLINTEND(readability-identifier-naming) } FlValueType; /** diff --git a/shell/platform/linux/testing/fl_mock_binary_messenger.cc b/shell/platform/linux/testing/fl_mock_binary_messenger.cc new file mode 100644 index 0000000000000..e26f9bcfcbb49 --- /dev/null +++ b/shell/platform/linux/testing/fl_mock_binary_messenger.cc @@ -0,0 +1,609 @@ +// 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. + +#include "flutter/shell/platform/linux/testing/fl_mock_binary_messenger.h" + +#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" +#include "flutter/shell/platform/linux/fl_method_codec_private.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_message_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_string_codec.h" + +G_DECLARE_FINAL_TYPE(FlMockBinaryMessengerResponseHandle, + fl_mock_binary_messenger_response_handle, + FL, + MOCK_BINARY_MESSENGER_RESPONSE_HANDLE, + FlBinaryMessengerResponseHandle) + +struct _FlMockBinaryMessengerResponseHandle { + FlBinaryMessengerResponseHandle parent_instance; + + FlMockBinaryMessengerCallback callback; + gpointer user_data; +}; + +G_DEFINE_TYPE(FlMockBinaryMessengerResponseHandle, + fl_mock_binary_messenger_response_handle, + fl_binary_messenger_response_handle_get_type()) + +static void fl_mock_binary_messenger_response_handle_class_init( + FlMockBinaryMessengerResponseHandleClass* klass) {} + +static void fl_mock_binary_messenger_response_handle_init( + FlMockBinaryMessengerResponseHandle* self) {} + +FlMockBinaryMessengerResponseHandle* +fl_mock_binary_messenger_response_handle_new( + FlMockBinaryMessengerCallback callback, + gpointer user_data) { + FlMockBinaryMessengerResponseHandle* self = + FL_MOCK_BINARY_MESSENGER_RESPONSE_HANDLE(g_object_new( + fl_mock_binary_messenger_response_handle_get_type(), nullptr)); + self->callback = callback; + self->user_data = user_data; + return self; +} + +struct _FlMockBinaryMessenger { + GObject parent_instance; + + // Handlers the embedder has registered. + GHashTable* handlers; + + // Mocked Dart channels. + GHashTable* mock_channels; + GHashTable* mock_message_channels; + GHashTable* mock_method_channels; +}; + +typedef struct { + FlMockBinaryMessengerChannelHandler callback; + gpointer user_data; +} MockChannel; + +static MockChannel* mock_channel_new( + FlMockBinaryMessengerChannelHandler callback, + gpointer user_data) { + MockChannel* channel = g_new0(MockChannel, 1); + channel->callback = callback; + channel->user_data = user_data; + return channel; +} + +static void mock_channel_free(MockChannel* channel) { + g_free(channel); +} + +typedef struct { + FlMessageCodec* codec; + FlMockBinaryMessengerMessageChannelHandler callback; + gpointer user_data; +} MockMessageChannel; + +static MockMessageChannel* mock_message_channel_new( + FlMockBinaryMessengerMessageChannelHandler callback, + FlMessageCodec* codec, + gpointer user_data) { + MockMessageChannel* channel = g_new0(MockMessageChannel, 1); + channel->codec = FL_MESSAGE_CODEC(g_object_ref(codec)); + channel->callback = callback; + channel->user_data = user_data; + return channel; +} + +static void mock_message_channel_free(MockMessageChannel* channel) { + g_object_unref(channel->codec); + g_free(channel); +} + +typedef struct { + FlMethodCodec* codec; + FlMockBinaryMessengerMethodChannelHandler callback; + gpointer user_data; +} MockMethodChannel; + +static MockMethodChannel* mock_method_channel_new( + FlMockBinaryMessengerMethodChannelHandler callback, + FlMethodCodec* codec, + gpointer user_data) { + MockMethodChannel* channel = g_new0(MockMethodChannel, 1); + channel->codec = FL_METHOD_CODEC(g_object_ref(codec)); + channel->callback = callback; + channel->user_data = user_data; + return channel; +} + +static void mock_method_channel_free(MockMethodChannel* channel) { + g_object_unref(channel->codec); + g_free(channel); +} + +typedef struct { + FlBinaryMessengerMessageHandler callback; + gpointer user_data; + GDestroyNotify destroy_notify; +} Handler; + +static Handler* handler_new(FlBinaryMessengerMessageHandler callback, + gpointer user_data, + GDestroyNotify destroy_notify) { + Handler* handler = g_new0(Handler, 1); + handler->callback = callback; + handler->user_data = user_data; + handler->destroy_notify = destroy_notify; + return handler; +} + +static void handler_free(Handler* handler) { + if (handler->destroy_notify) { + handler->destroy_notify(handler->user_data); + } + g_free(handler); +} + +static void fl_mock_binary_messenger_iface_init( + FlBinaryMessengerInterface* iface); + +G_DEFINE_TYPE_WITH_CODE( + FlMockBinaryMessenger, + fl_mock_binary_messenger, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(fl_binary_messenger_get_type(), + fl_mock_binary_messenger_iface_init)) + +static void fl_mock_binary_messenger_set_message_handler_on_channel( + FlBinaryMessenger* messenger, + const gchar* channel, + FlBinaryMessengerMessageHandler handler, + gpointer user_data, + GDestroyNotify destroy_notify) { + FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER(messenger); + g_hash_table_insert(self->handlers, g_strdup(channel), + handler_new(handler, user_data, destroy_notify)); +} + +static gboolean fl_mock_binary_messenger_send_response( + FlBinaryMessenger* messenger, + FlBinaryMessengerResponseHandle* response_handle, + GBytes* response, + GError** error) { + FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER(messenger); + + g_return_val_if_fail( + FL_IS_MOCK_BINARY_MESSENGER_RESPONSE_HANDLE(response_handle), FALSE); + FlMockBinaryMessengerResponseHandle* handle = + FL_MOCK_BINARY_MESSENGER_RESPONSE_HANDLE(response_handle); + + handle->callback(self, response, handle->user_data); + + return TRUE; +} + +static void fl_mock_binary_messenger_send_on_channel( + FlBinaryMessenger* messenger, + const gchar* channel, + GBytes* message, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER(messenger); + g_autoptr(GTask) task = g_task_new(self, cancellable, callback, user_data); + + MockChannel* mock_channel = static_cast( + g_hash_table_lookup(self->mock_channels, channel)); + MockMessageChannel* mock_message_channel = static_cast( + g_hash_table_lookup(self->mock_message_channels, channel)); + MockMethodChannel* mock_method_channel = static_cast( + g_hash_table_lookup(self->mock_method_channels, channel)); + g_autoptr(GBytes) response = nullptr; + if (mock_channel != nullptr) { + response = mock_channel->callback(self, message, mock_channel->user_data); + } else if (mock_message_channel != nullptr) { + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) message_value = fl_message_codec_decode_message( + mock_message_channel->codec, message, &error); + if (message_value == nullptr) { + g_warning("Failed to decode message: %s", error->message); + } else { + g_autoptr(FlValue) response_value = mock_message_channel->callback( + self, message_value, mock_message_channel->user_data); + response = fl_message_codec_encode_message(mock_message_channel->codec, + response_value, &error); + if (response == nullptr) { + g_warning("Failed to encode message: %s", error->message); + } + } + } else if (mock_method_channel != nullptr) { + g_autofree gchar* name = nullptr; + g_autoptr(FlValue) args = nullptr; + g_autoptr(GError) error = nullptr; + if (!fl_method_codec_decode_method_call(mock_method_channel->codec, message, + &name, &args, &error)) { + g_warning("Failed to decode method call: %s", error->message); + } else { + g_autoptr(FlMethodResponse) response_value = + mock_method_channel->callback(self, name, args, + mock_method_channel->user_data); + response = fl_method_codec_encode_response(mock_method_channel->codec, + response_value, &error); + if (response == nullptr) { + g_warning("Failed to encode method response: %s", error->message); + } + } + } + + if (response == nullptr) { + response = g_bytes_new(nullptr, 0); + } + + g_task_return_pointer(task, g_bytes_ref(response), + reinterpret_cast(g_bytes_unref)); +} + +static GBytes* fl_mock_binary_messenger_send_on_channel_finish( + FlBinaryMessenger* messenger, + GAsyncResult* result, + GError** error) { + return static_cast(g_task_propagate_pointer(G_TASK(result), error)); +} + +static void fl_mock_binary_messenger_resize_channel( + FlBinaryMessenger* messenger, + const gchar* channel, + int64_t new_size) {} + +static void fl_mock_binary_messenger_set_warns_on_channel_overflow( + FlBinaryMessenger* messenger, + const gchar* channel, + bool warns) {} + +static void fl_mock_binary_messenger_shutdown(FlBinaryMessenger* messenger) { + FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER(messenger); + g_hash_table_remove_all(self->handlers); +} + +static void fl_mock_binary_messenger_dispose(GObject* object) { + FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER(object); + + g_clear_pointer(&self->mock_channels, g_hash_table_unref); + g_clear_pointer(&self->mock_message_channels, g_hash_table_unref); + g_clear_pointer(&self->mock_method_channels, g_hash_table_unref); + + G_OBJECT_CLASS(fl_mock_binary_messenger_parent_class)->dispose(object); +} + +static void fl_mock_binary_messenger_class_init( + FlMockBinaryMessengerClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_mock_binary_messenger_dispose; +} + +static void fl_mock_binary_messenger_iface_init( + FlBinaryMessengerInterface* iface) { + iface->set_message_handler_on_channel = + fl_mock_binary_messenger_set_message_handler_on_channel; + iface->send_response = fl_mock_binary_messenger_send_response; + iface->send_on_channel = fl_mock_binary_messenger_send_on_channel; + iface->send_on_channel_finish = + fl_mock_binary_messenger_send_on_channel_finish; + iface->resize_channel = fl_mock_binary_messenger_resize_channel; + iface->set_warns_on_channel_overflow = + fl_mock_binary_messenger_set_warns_on_channel_overflow; + iface->shutdown = fl_mock_binary_messenger_shutdown; +} + +static void fl_mock_binary_messenger_init(FlMockBinaryMessenger* self) { + self->handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)handler_free); + + self->mock_channels = g_hash_table_new_full( + g_str_hash, g_str_equal, g_free, (GDestroyNotify)mock_channel_free); + self->mock_message_channels = + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)mock_message_channel_free); + self->mock_method_channels = + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)mock_method_channel_free); +} + +FlMockBinaryMessenger* fl_mock_binary_messenger_new() { + FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER( + g_object_new(fl_mock_binary_messenger_get_type(), nullptr)); + return self; +} + +gboolean fl_mock_binary_messenger_has_handler(FlMockBinaryMessenger* self, + const gchar* channel) { + g_return_val_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self), FALSE); + return g_hash_table_lookup(self->handlers, channel) != nullptr; +} + +void fl_mock_binary_messenger_set_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerChannelHandler handler, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + + g_hash_table_insert(self->mock_channels, g_strdup(channel), + mock_channel_new(handler, user_data)); +} + +void fl_mock_binary_messenger_set_message_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMessageCodec* codec, + FlMockBinaryMessengerMessageChannelHandler handler, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + + g_hash_table_insert(self->mock_message_channels, g_strdup(channel), + mock_message_channel_new(handler, codec, user_data)); +} + +void fl_mock_binary_messenger_set_standard_message_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerMessageChannelHandler handler, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + + g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new(); + return fl_mock_binary_messenger_set_message_channel( + self, channel, FL_MESSAGE_CODEC(codec), handler, user_data); +} + +void fl_mock_binary_messenger_set_string_message_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerMessageChannelHandler handler, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + + g_autoptr(FlStringCodec) codec = fl_string_codec_new(); + return fl_mock_binary_messenger_set_message_channel( + self, channel, FL_MESSAGE_CODEC(codec), handler, user_data); +} + +void fl_mock_binary_messenger_set_json_message_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerMessageChannelHandler handler, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + return fl_mock_binary_messenger_set_message_channel( + self, channel, FL_MESSAGE_CODEC(codec), handler, user_data); +} + +void fl_mock_binary_messenger_set_method_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMethodCodec* codec, + FlMockBinaryMessengerMethodChannelHandler handler, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + + g_hash_table_insert(self->mock_method_channels, g_strdup(channel), + mock_method_channel_new(handler, codec, user_data)); +} + +void fl_mock_binary_messenger_set_standard_method_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerMethodChannelHandler handler, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + fl_mock_binary_messenger_set_method_channel( + self, channel, FL_METHOD_CODEC(codec), handler, user_data); +} + +void fl_mock_binary_messenger_set_json_method_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerMethodChannelHandler handler, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + fl_mock_binary_messenger_set_method_channel( + self, channel, FL_METHOD_CODEC(codec), handler, user_data); +} + +void fl_mock_binary_messenger_send(FlMockBinaryMessenger* self, + const gchar* channel, + GBytes* message, + FlMockBinaryMessengerCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + + Handler* handler = + static_cast(g_hash_table_lookup(self->handlers, channel)); + if (handler == nullptr) { + return; + } + + handler->callback( + FL_BINARY_MESSENGER(self), channel, message, + FL_BINARY_MESSENGER_RESPONSE_HANDLE( + fl_mock_binary_messenger_response_handle_new(callback, user_data)), + handler->user_data); +} + +typedef struct { + FlMessageCodec* codec; + FlMockBinaryMessengerMessageCallback callback; + gpointer user_data; +} SendMessageData; + +static SendMessageData* send_message_data_new( + FlMessageCodec* codec, + FlMockBinaryMessengerMessageCallback callback, + gpointer user_data) { + SendMessageData* data = g_new0(SendMessageData, 1); + data->codec = FL_MESSAGE_CODEC(g_object_ref(codec)); + data->callback = callback; + data->user_data = user_data; + return data; +} + +static void send_message_data_free(SendMessageData* data) { + g_object_unref(data->codec); + free(data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(SendMessageData, send_message_data_free) + +static void send_message_cb(FlMockBinaryMessenger* self, + GBytes* response, + gpointer user_data) { + g_autoptr(SendMessageData) data = static_cast(user_data); + + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) response_value = + fl_message_codec_decode_message(data->codec, response, &error); + if (response_value == nullptr) { + g_warning("Failed to decode message response: %s", error->message); + return; + } + + data->callback(self, response_value, data->user_data); +} + +void fl_mock_binary_messenger_send_message( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMessageCodec* codec, + FlValue* message, + FlMockBinaryMessengerMessageCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) encoded_message = + fl_message_codec_encode_message(codec, message, &error); + if (encoded_message == nullptr) { + g_warning("Failed to encode message: %s", error->message); + return; + } + + fl_mock_binary_messenger_send( + self, channel, encoded_message, send_message_cb, + send_message_data_new(codec, callback, user_data)); +} + +void fl_mock_binary_messenger_send_standard_message( + FlMockBinaryMessenger* self, + const gchar* channel, + FlValue* message, + FlMockBinaryMessengerMessageCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new(); + fl_mock_binary_messenger_send_message(self, channel, FL_MESSAGE_CODEC(codec), + message, callback, user_data); +} + +void fl_mock_binary_messenger_send_json_message( + FlMockBinaryMessenger* self, + const gchar* channel, + FlValue* message, + FlMockBinaryMessengerMessageCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + fl_mock_binary_messenger_send_message(self, channel, FL_MESSAGE_CODEC(codec), + message, callback, user_data); +} + +typedef struct { + FlMethodCodec* codec; + FlMockBinaryMessengerMethodCallback callback; + gpointer user_data; +} InvokeMethodData; + +static InvokeMethodData* invoke_method_data_new( + FlMethodCodec* codec, + FlMockBinaryMessengerMethodCallback callback, + gpointer user_data) { + InvokeMethodData* data = g_new0(InvokeMethodData, 1); + data->codec = FL_METHOD_CODEC(g_object_ref(codec)); + data->callback = callback; + data->user_data = user_data; + return data; +} + +static void invoke_method_data_free(InvokeMethodData* data) { + g_object_unref(data->codec); + free(data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(InvokeMethodData, invoke_method_data_free) + +static void invoke_method_cb(FlMockBinaryMessenger* self, + GBytes* response, + gpointer user_data) { + g_autoptr(InvokeMethodData) data = static_cast(user_data); + + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) method_response = + fl_method_codec_decode_response(data->codec, response, &error); + if (method_response == nullptr) { + g_warning("Failed to decode method response: %s", error->message); + return; + } + + data->callback(self, method_response, data->user_data); +} + +void fl_mock_binary_messenger_invoke_method( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMethodCodec* codec, + const char* name, + FlValue* args, + FlMockBinaryMessengerMethodCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = + fl_method_codec_encode_method_call(codec, name, args, &error); + if (message == nullptr) { + g_warning("Failed to encode method call: %s", error->message); + return; + } + + fl_mock_binary_messenger_send( + self, channel, message, invoke_method_cb, + invoke_method_data_new(codec, callback, user_data)); +} + +void fl_mock_binary_messenger_invoke_standard_method( + FlMockBinaryMessenger* self, + const gchar* channel, + const char* name, + FlValue* args, + FlMockBinaryMessengerMethodCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + fl_mock_binary_messenger_invoke_method(self, channel, FL_METHOD_CODEC(codec), + name, args, callback, user_data); +} + +void fl_mock_binary_messenger_invoke_json_method( + FlMockBinaryMessenger* self, + const gchar* channel, + const char* name, + FlValue* args, + FlMockBinaryMessengerMethodCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(self)); + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + fl_mock_binary_messenger_invoke_method(self, channel, FL_METHOD_CODEC(codec), + name, args, callback, user_data); +} diff --git a/shell/platform/linux/testing/fl_mock_binary_messenger.h b/shell/platform/linux/testing/fl_mock_binary_messenger.h new file mode 100644 index 0000000000000..ab6276e2917e3 --- /dev/null +++ b/shell/platform/linux/testing/fl_mock_binary_messenger.h @@ -0,0 +1,160 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_TESTING_FL_MOCK_BINARY_MESSENGER_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_TESTING_FL_MOCK_BINARY_MESSENGER_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_message_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlMockBinaryMessenger, + fl_mock_binary_messenger, + FL, + MOCK_BINARY_MESSENGER, + GObject) + +typedef GBytes* (*FlMockBinaryMessengerChannelHandler)( + FlMockBinaryMessenger* messenger, + GBytes* message, + gpointer user_data); + +typedef FlValue* (*FlMockBinaryMessengerMessageChannelHandler)( + FlMockBinaryMessenger* messenger, + FlValue* message, + gpointer user_data); + +typedef FlMethodResponse* (*FlMockBinaryMessengerMethodChannelHandler)( + FlMockBinaryMessenger* messenger, + const gchar* name, + FlValue* args, + gpointer user_data); + +typedef void (*FlMockBinaryMessengerCallback)(FlMockBinaryMessenger* messenger, + GBytes* response, + gpointer user_data); + +typedef void (*FlMockBinaryMessengerMessageCallback)( + FlMockBinaryMessenger* messenger, + FlValue* response, + gpointer user_data); + +typedef void (*FlMockBinaryMessengerMethodCallback)( + FlMockBinaryMessenger* messenger, + FlMethodResponse* response, + gpointer user_data); + +FlMockBinaryMessenger* fl_mock_binary_messenger_new(); + +gboolean fl_mock_binary_messenger_has_handler(FlMockBinaryMessenger* self, + const gchar* channel); + +void fl_mock_binary_messenger_set_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerChannelHandler handler, + gpointer user_data); + +void fl_mock_binary_messenger_set_message_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMessageCodec* codec, + FlMockBinaryMessengerMessageChannelHandler handler, + gpointer user_data); + +void fl_mock_binary_messenger_set_standard_message_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerMessageChannelHandler handler, + gpointer user_data); + +void fl_mock_binary_messenger_set_string_message_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerMessageChannelHandler handler, + gpointer user_data); + +void fl_mock_binary_messenger_set_json_message_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerMessageChannelHandler handler, + gpointer user_data); + +void fl_mock_binary_messenger_set_method_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMethodCodec* codec, + FlMockBinaryMessengerMethodChannelHandler handler, + gpointer user_data); + +void fl_mock_binary_messenger_set_standard_method_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerMethodChannelHandler handler, + gpointer user_data); + +void fl_mock_binary_messenger_set_json_method_channel( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMockBinaryMessengerMethodChannelHandler handler, + gpointer user_data); + +void fl_mock_binary_messenger_send(FlMockBinaryMessenger* self, + const gchar* channel, + GBytes* message, + FlMockBinaryMessengerCallback callback, + gpointer user_data); + +void fl_mock_binary_messenger_send_message( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMessageCodec* codec, + FlValue* message, + FlMockBinaryMessengerMessageCallback callback, + gpointer user_data); + +void fl_mock_binary_messenger_send_standard_message( + FlMockBinaryMessenger* self, + const gchar* channel, + FlValue* message, + FlMockBinaryMessengerMessageCallback callback, + gpointer user_data); + +void fl_mock_binary_messenger_send_json_message( + FlMockBinaryMessenger* self, + const gchar* channel, + FlValue* message, + FlMockBinaryMessengerMessageCallback callback, + gpointer user_data); + +void fl_mock_binary_messenger_invoke_method( + FlMockBinaryMessenger* self, + const gchar* channel, + FlMethodCodec* codec, + const char* name, + FlValue* args, + FlMockBinaryMessengerMethodCallback callback, + gpointer user_data); + +void fl_mock_binary_messenger_invoke_standard_method( + FlMockBinaryMessenger* self, + const gchar* channel, + const char* name, + FlValue* args, + FlMockBinaryMessengerMethodCallback callback, + gpointer user_data); + +void fl_mock_binary_messenger_invoke_json_method( + FlMockBinaryMessenger* self, + const gchar* channel, + const char* name, + FlValue* args, + FlMockBinaryMessengerMethodCallback callback, + gpointer user_data); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_FL_MOCK_BINARY_MESSENGER_H_ diff --git a/shell/platform/linux/testing/mock_binary_messenger.cc b/shell/platform/linux/testing/mock_binary_messenger.cc deleted file mode 100644 index 8b22f2291bd75..0000000000000 --- a/shell/platform/linux/testing/mock_binary_messenger.cc +++ /dev/null @@ -1,160 +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. - -#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h" -#include "flutter/shell/platform/linux/testing/mock_binary_messenger_response_handle.h" - -using namespace flutter::testing; - -G_DECLARE_FINAL_TYPE(FlMockBinaryMessenger, - fl_mock_binary_messenger, - FL, - MOCK_BINARY_MESSENGER, - GObject) - -struct _FlMockBinaryMessenger { - GObject parent_instance; - MockBinaryMessenger* mock; -}; - -static FlBinaryMessenger* fl_mock_binary_messenger_new( - MockBinaryMessenger* mock) { - FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER( - g_object_new(fl_mock_binary_messenger_get_type(), nullptr)); - self->mock = mock; - return FL_BINARY_MESSENGER(self); -} - -MockBinaryMessenger::MockBinaryMessenger() - : instance_(fl_mock_binary_messenger_new(this)) {} - -MockBinaryMessenger::~MockBinaryMessenger() { - if (FL_IS_BINARY_MESSENGER(instance_)) { - g_clear_object(&instance_); - } -} - -MockBinaryMessenger::operator FlBinaryMessenger*() { - return instance_; -} - -bool MockBinaryMessenger::HasMessageHandler(const gchar* channel) const { - return message_handlers_.at(channel) != nullptr; -} - -void MockBinaryMessenger::SetMessageHandler( - const gchar* channel, - FlBinaryMessengerMessageHandler handler, - gpointer user_data) { - message_handlers_[channel] = handler; - user_datas_[channel] = user_data; -} - -void MockBinaryMessenger::ReceiveMessage(const gchar* channel, - GBytes* message) { - FlBinaryMessengerMessageHandler handler = message_handlers_[channel]; - if (response_handles_[channel] == nullptr) { - response_handles_[channel] = FL_BINARY_MESSENGER_RESPONSE_HANDLE( - fl_mock_binary_messenger_response_handle_new()); - } - handler(instance_, channel, message, response_handles_[channel], - user_datas_[channel]); -} - -static void fl_mock_binary_messenger_iface_init( - FlBinaryMessengerInterface* iface); - -G_DEFINE_TYPE_WITH_CODE( - FlMockBinaryMessenger, - fl_mock_binary_messenger, - G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE(fl_binary_messenger_get_type(), - fl_mock_binary_messenger_iface_init)) - -static void fl_mock_binary_messenger_class_init( - FlMockBinaryMessengerClass* klass) {} - -static void fl_mock_binary_messenger_set_message_handler_on_channel( - FlBinaryMessenger* messenger, - const gchar* channel, - FlBinaryMessengerMessageHandler handler, - gpointer user_data, - GDestroyNotify destroy_notify) { - g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(messenger)); - FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER(messenger); - self->mock->SetMessageHandler(channel, handler, user_data); - self->mock->fl_binary_messenger_set_message_handler_on_channel( - messenger, channel, handler, user_data, destroy_notify); -} - -static gboolean fl_mock_binary_messenger_send_response( - FlBinaryMessenger* messenger, - FlBinaryMessengerResponseHandle* response_handle, - GBytes* response, - GError** error) { - g_return_val_if_fail(FL_IS_MOCK_BINARY_MESSENGER(messenger), false); - FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER(messenger); - return self->mock->fl_binary_messenger_send_response( - messenger, response_handle, response, error); -} - -static void fl_mock_binary_messenger_send_on_channel( - FlBinaryMessenger* messenger, - const gchar* channel, - GBytes* message, - GCancellable* cancellable, - GAsyncReadyCallback callback, - gpointer user_data) { - g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(messenger)); - FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER(messenger); - self->mock->fl_binary_messenger_send_on_channel( - messenger, channel, message, cancellable, callback, user_data); -} - -static GBytes* fl_mock_binary_messenger_send_on_channel_finish( - FlBinaryMessenger* messenger, - GAsyncResult* result, - GError** error) { - g_return_val_if_fail(FL_IS_MOCK_BINARY_MESSENGER(messenger), nullptr); - FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER(messenger); - return self->mock->fl_binary_messenger_send_on_channel_finish(messenger, - result, error); -} - -static void fl_mock_binary_messenger_resize_channel( - FlBinaryMessenger* messenger, - const gchar* channel, - int64_t new_size) { - g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(messenger)); - FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER(messenger); - self->mock->fl_binary_messenger_resize_channel(messenger, channel, new_size); -} - -static void fl_mock_binary_messenger_set_warns_on_channel_overflow( - FlBinaryMessenger* messenger, - const gchar* channel, - bool warns) { - g_return_if_fail(FL_IS_MOCK_BINARY_MESSENGER(messenger)); - FlMockBinaryMessenger* self = FL_MOCK_BINARY_MESSENGER(messenger); - self->mock->fl_binary_messenger_set_warns_on_channel_overflow(messenger, - channel, warns); -} - -static void fl_mock_binary_messenger_shutdown(FlBinaryMessenger* messenger) {} - -static void fl_mock_binary_messenger_iface_init( - FlBinaryMessengerInterface* iface) { - iface->set_message_handler_on_channel = - fl_mock_binary_messenger_set_message_handler_on_channel; - iface->send_response = fl_mock_binary_messenger_send_response; - iface->send_on_channel = fl_mock_binary_messenger_send_on_channel; - iface->send_on_channel_finish = - fl_mock_binary_messenger_send_on_channel_finish; - iface->resize_channel = fl_mock_binary_messenger_resize_channel; - iface->set_warns_on_channel_overflow = - fl_mock_binary_messenger_set_warns_on_channel_overflow; - iface->shutdown = fl_mock_binary_messenger_shutdown; -} - -static void fl_mock_binary_messenger_init(FlMockBinaryMessenger* self) {} diff --git a/shell/platform/linux/testing/mock_binary_messenger.h b/shell/platform/linux/testing/mock_binary_messenger.h deleted file mode 100644 index 394e55882ed2d..0000000000000 --- a/shell/platform/linux/testing/mock_binary_messenger.h +++ /dev/null @@ -1,91 +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. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_BINARY_MESSENGER_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_BINARY_MESSENGER_H_ - -#include - -#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" - -#include "gmock/gmock.h" - -namespace flutter { -namespace testing { - -// Mock for FlBinaryMessenger. -class MockBinaryMessenger { - public: - MockBinaryMessenger(); - ~MockBinaryMessenger(); - - // This was an existing use of operator overloading. It's against our style - // guide but enabling clang tidy on header files is a higher priority than - // fixing this. - // NOLINTNEXTLINE(google-explicit-constructor) - operator FlBinaryMessenger*(); - - MOCK_METHOD(void, - fl_binary_messenger_set_message_handler_on_channel, - (FlBinaryMessenger * messenger, - const gchar* channel, - FlBinaryMessengerMessageHandler handler, - gpointer user_data, - GDestroyNotify destroy_notify)); - - MOCK_METHOD(gboolean, - fl_binary_messenger_send_response, - (FlBinaryMessenger * messenger, - FlBinaryMessengerResponseHandle* response_handle, - GBytes* response, - GError** error)); - - MOCK_METHOD(void, - fl_binary_messenger_send_on_channel, - (FlBinaryMessenger * messenger, - const gchar* channel, - GBytes* message, - GCancellable* cancellable, - GAsyncReadyCallback callback, - gpointer user_data)); - - MOCK_METHOD(GBytes*, - fl_binary_messenger_send_on_channel_finish, - (FlBinaryMessenger * messenger, - GAsyncResult* result, - GError** error)); - - MOCK_METHOD(void, - fl_binary_messenger_resize_channel, - (FlBinaryMessenger * messenger, - const gchar* channel, - int64_t new_size)); - - MOCK_METHOD(void, - fl_binary_messenger_set_warns_on_channel_overflow, - (FlBinaryMessenger * messenger, - const gchar* channel, - bool warns)); - - bool HasMessageHandler(const gchar* channel) const; - - void SetMessageHandler(const gchar* channel, - FlBinaryMessengerMessageHandler handler, - gpointer user_data); - - void ReceiveMessage(const gchar* channel, GBytes* message); - - private: - FlBinaryMessenger* instance_ = nullptr; - std::unordered_map - message_handlers_; - std::unordered_map - response_handles_; - std::unordered_map user_datas_; -}; - -} // namespace testing -} // namespace flutter - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_BINARY_MESSENGER_H_ diff --git a/shell/platform/linux/testing/mock_binary_messenger_response_handle.cc b/shell/platform/linux/testing/mock_binary_messenger_response_handle.cc deleted file mode 100644 index 4bd7e419d2071..0000000000000 --- a/shell/platform/linux/testing/mock_binary_messenger_response_handle.cc +++ /dev/null @@ -1,25 +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. - -#include "flutter/shell/platform/linux/testing/mock_binary_messenger_response_handle.h" - -struct _FlMockBinaryMessengerResponseHandle { - FlBinaryMessengerResponseHandle parent_instance; -}; - -G_DEFINE_TYPE(FlMockBinaryMessengerResponseHandle, - fl_mock_binary_messenger_response_handle, - fl_binary_messenger_response_handle_get_type()); - -static void fl_mock_binary_messenger_response_handle_class_init( - FlMockBinaryMessengerResponseHandleClass* klass) {} - -static void fl_mock_binary_messenger_response_handle_init( - FlMockBinaryMessengerResponseHandle* self) {} - -FlMockBinaryMessengerResponseHandle* -fl_mock_binary_messenger_response_handle_new() { - return FL_MOCK_BINARY_MESSENGER_RESPONSE_HANDLE( - g_object_new(fl_mock_binary_messenger_response_handle_get_type(), NULL)); -} diff --git a/shell/platform/linux/testing/mock_binary_messenger_response_handle.h b/shell/platform/linux/testing/mock_binary_messenger_response_handle.h deleted file mode 100644 index 42d86cc80fa9c..0000000000000 --- a/shell/platform/linux/testing/mock_binary_messenger_response_handle.h +++ /dev/null @@ -1,23 +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. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_BINARY_MESSENGER_RESPONSE_HANDLE_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_BINARY_MESSENGER_RESPONSE_HANDLE_H_ - -#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlMockBinaryMessengerResponseHandle, - fl_mock_binary_messenger_response_handle, - FL, - MOCK_BINARY_MESSENGER_RESPONSE_HANDLE, - FlBinaryMessengerResponseHandle) - -FlMockBinaryMessengerResponseHandle* -fl_mock_binary_messenger_response_handle_new(); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_BINARY_MESSENGER_RESPONSE_HANDLE_H_ diff --git a/shell/platform/linux/testing/mock_epoxy.cc b/shell/platform/linux/testing/mock_epoxy.cc index b6a2cc38ea76f..b03207079c1e2 100644 --- a/shell/platform/linux/testing/mock_epoxy.cc +++ b/shell/platform/linux/testing/mock_epoxy.cc @@ -4,6 +4,7 @@ // found in the LICENSE file. #include "flutter/shell/platform/linux/testing/mock_epoxy.h" +#include "flutter/fml/logging.h" using namespace flutter::testing; @@ -352,10 +353,14 @@ EGLBoolean _eglSwapBuffers(EGLDisplay dpy, EGLSurface surface) { static GLuint bound_texture_2d; +static std::map framebuffer_renderbuffers; + void _glAttachShader(GLuint program, GLuint shader) {} static void _glBindFramebuffer(GLenum target, GLuint framebuffer) {} +static void _glBindRenderbuffer(GLenum target, GLuint framebuffer) {} + static void _glBindTexture(GLenum target, GLuint texture) { if (target == GL_TEXTURE_2D) { bound_texture_2d = texture; @@ -396,6 +401,13 @@ void _glDeleteShader(GLuint shader) {} void _glDeleteTextures(GLsizei n, const GLuint* textures) {} +static void _glFramebufferRenderbuffer(GLenum target, + GLenum attachment, + GLenum renderbuffertarget, + GLuint renderbuffer) { + framebuffer_renderbuffers[attachment] = renderbuffer; +} + static void _glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, @@ -414,6 +426,26 @@ static void _glGenFramebuffers(GLsizei n, GLuint* framebuffers) { } } +static void _glGenRenderbuffers(GLsizei n, GLuint* renderbuffers) { + for (GLsizei i = 0; i < n; i++) { + renderbuffers[i] = 0; + } +} + +static void _glGetFramebufferAttachmentParameteriv(GLenum target, + GLenum attachment, + GLenum pname, + GLint* params) { + if (pname == GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) { + auto it = framebuffer_renderbuffers.find(attachment); + *params = + (it != framebuffer_renderbuffers.end()) ? GL_RENDERBUFFER : GL_NONE; + } else if (pname == GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) { + auto it = framebuffer_renderbuffers.find(attachment); + *params = (it != framebuffer_renderbuffers.end()) ? it->second : 0; + } +} + static void _glGetIntegerv(GLenum pname, GLint* data) { if (pname == GL_TEXTURE_BINDING_2D) { *data = bound_texture_2d; @@ -466,6 +498,11 @@ static GLenum _glGetError() { void _glLinkProgram(GLuint program) {} +void _glRenderbufferStorage(GLenum target, + GLenum internalformat, + GLsizei width, + GLsizei height) {} + void _glShaderSource(GLuint shader, GLsizei count, const GLchar* const* string, @@ -536,6 +573,7 @@ EGLBoolean (*epoxy_eglSwapBuffers)(EGLDisplay dpy, EGLSurface surface); void (*epoxy_glAttachShader)(GLuint program, GLuint shader); void (*epoxy_glBindFramebuffer)(GLenum target, GLuint framebuffer); +void (*epoxy_glBindRenderbuffer)(GLenum target, GLuint renderbuffer); void (*epoxy_glBindTexture)(GLenum target, GLuint texture); void (*epoxy_glBlitFramebuffer)(GLint srcX0, GLint srcY0, @@ -553,14 +591,26 @@ GLuint (*epoxy_glCreateShader)(GLenum shaderType); void (*epoxy_glDeleteFramebuffers)(GLsizei n, const GLuint* framebuffers); void (*expoxy_glDeleteShader)(GLuint shader); void (*epoxy_glDeleteTextures)(GLsizei n, const GLuint* textures); +void (*epoxy_glFramebufferRenderbuffer)(GLenum target, + GLenum attachment, + GLenum renderbuffertarget, + GLuint renderbuffer); void (*epoxy_glFramebufferTexture2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +void (*epoxy_glGetFramebufferAttachmentParameteriv)(GLenum target, + GLenum attachment, + GLenum pname, + GLint* params); void (*epoxy_glGenFramebuffers)(GLsizei n, GLuint* framebuffers); void (*epoxy_glGenTextures)(GLsizei n, GLuint* textures); void (*epoxy_glLinkProgram)(GLuint program); +void (*epoxy_glRenderbufferStorage)(GLenum target, + GLenum internalformat, + GLsizei width, + GLsizei height); void (*epoxy_glShaderSource)(GLuint shader, GLsizei count, const GLchar* const* string, @@ -595,6 +645,7 @@ static void library_init() { epoxy_glAttachShader = _glAttachShader; epoxy_glBindFramebuffer = _glBindFramebuffer; + epoxy_glBindRenderbuffer = _glBindRenderbuffer; epoxy_glBindTexture = _glBindTexture; epoxy_glBlitFramebuffer = _glBlitFramebuffer; epoxy_glCompileShader = _glCompileShader; @@ -604,9 +655,13 @@ static void library_init() { epoxy_glDeleteFramebuffers = _glDeleteFramebuffers; epoxy_glDeleteShader = _glDeleteShader; epoxy_glDeleteTextures = _glDeleteTextures; + epoxy_glFramebufferRenderbuffer = _glFramebufferRenderbuffer; epoxy_glFramebufferTexture2D = _glFramebufferTexture2D; epoxy_glGenFramebuffers = _glGenFramebuffers; + epoxy_glGenRenderbuffers = _glGenRenderbuffers; epoxy_glGenTextures = _glGenTextures; + epoxy_glGetFramebufferAttachmentParameteriv = + _glGetFramebufferAttachmentParameteriv; epoxy_glGetIntegerv = _glGetIntegerv; epoxy_glGetProgramiv = _glGetProgramiv; epoxy_glGetProgramInfoLog = _glGetProgramInfoLog; @@ -614,6 +669,7 @@ static void library_init() { epoxy_glGetShaderInfoLog = _glGetShaderInfoLog; epoxy_glGetString = _glGetString; epoxy_glLinkProgram = _glLinkProgram; + epoxy_glRenderbufferStorage = _glRenderbufferStorage; epoxy_glShaderSource = _glShaderSource; epoxy_glTexParameterf = _glTexParameterf; epoxy_glTexParameteri = _glTexParameteri; diff --git a/shell/platform/linux/testing/mock_text_input_handler.cc b/shell/platform/linux/testing/mock_text_input_handler.cc deleted file mode 100644 index 049e36926a9ad..0000000000000 --- a/shell/platform/linux/testing/mock_text_input_handler.cc +++ /dev/null @@ -1,42 +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. - -#include "flutter/shell/platform/linux/testing/mock_text_input_handler.h" - -struct _FlMockTextInputHandler { - FlTextInputHandler parent_instance; - - gboolean (*filter_keypress)(FlTextInputHandler* self, FlKeyEvent* event); -}; - -G_DEFINE_TYPE(FlMockTextInputHandler, - fl_mock_text_input_handler, - fl_text_input_handler_get_type()) - -static gboolean mock_text_input_handler_filter_keypress( - FlTextInputHandler* self, - FlKeyEvent* event) { - FlMockTextInputHandler* mock_self = FL_MOCK_TEXT_INPUT_HANDLER(self); - if (mock_self->filter_keypress) { - return mock_self->filter_keypress(self, event); - } - return FALSE; -} - -static void fl_mock_text_input_handler_class_init( - FlMockTextInputHandlerClass* klass) { - FL_TEXT_INPUT_HANDLER_CLASS(klass)->filter_keypress = - mock_text_input_handler_filter_keypress; -} - -static void fl_mock_text_input_handler_init(FlMockTextInputHandler* self) {} - -// Creates a mock text_input_handler -FlMockTextInputHandler* fl_mock_text_input_handler_new( - gboolean (*filter_keypress)(FlTextInputHandler* self, FlKeyEvent* event)) { - FlMockTextInputHandler* self = FL_MOCK_TEXT_INPUT_HANDLER( - g_object_new(fl_mock_text_input_handler_get_type(), nullptr)); - self->filter_keypress = filter_keypress; - return self; -} diff --git a/shell/platform/linux/testing/mock_text_input_handler.h b/shell/platform/linux/testing/mock_text_input_handler.h deleted file mode 100644 index 7bb736dd3d14b..0000000000000 --- a/shell/platform/linux/testing/mock_text_input_handler.h +++ /dev/null @@ -1,25 +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. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_TEXT_INPUT_HANDLER_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_TEXT_INPUT_HANDLER_H_ - -#include - -#include "flutter/shell/platform/linux/fl_text_input_handler.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlMockTextInputHandler, - fl_mock_text_input_handler, - FL, - MOCK_TEXT_INPUT_HANDLER, - FlTextInputHandler) - -FlMockTextInputHandler* fl_mock_text_input_handler_new( - gboolean (*filter_keypress)(FlTextInputHandler* self, FlKeyEvent* event)); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_TEXT_INPUT_HANDLER_H_ diff --git a/shell/platform/linux/testing/mock_window.cc b/shell/platform/linux/testing/mock_window.cc index 9e86bc79cf45f..89902307ed38c 100644 --- a/shell/platform/linux/testing/mock_window.cc +++ b/shell/platform/linux/testing/mock_window.cc @@ -15,3 +15,18 @@ MockWindow::MockWindow() { GdkWindowState gdk_window_get_state(GdkWindow* window) { return mock->gdk_window_get_state(window); } + +GdkDisplay* gdk_window_get_display(GdkWindow* window) { + return nullptr; +} + +GdkMonitor* gdk_display_get_monitor_at_window(GdkDisplay* display, + GdkWindow* window) { + return nullptr; +} + +GdkCursor* gdk_cursor_new_from_name(GdkDisplay* display, const gchar* name) { + return nullptr; +} + +void gdk_window_set_cursor(GdkWindow* window, GdkCursor* cursor) {} diff --git a/shell/testing/tester_main.cc b/shell/testing/tester_main.cc index 1d7358f0d50cf..c29fe2e6097fd 100644 --- a/shell/testing/tester_main.cc +++ b/shell/testing/tester_main.cc @@ -211,7 +211,7 @@ class TesterPlatformView : public PlatformView, if (delegate_.OnPlatformViewGetSettings().enable_impeller) { FML_DCHECK(impeller_context_holder_.context); auto surface = std::make_unique( - impeller_context_holder_.surface_context); + nullptr, impeller_context_holder_.surface_context); FML_DCHECK(surface->IsValid()); return surface; } @@ -267,9 +267,11 @@ class ScriptCompletionTaskObserver { public: ScriptCompletionTaskObserver(Shell& shell, fml::RefPtr main_task_runner, + fml::RefPtr ui_task_runner, bool run_forever) : shell_(shell), main_task_runner_(std::move(main_task_runner)), + ui_task_runner_(std::move(ui_task_runner)), run_forever_(run_forever) {} int GetExitCodeForLastError() const { @@ -283,6 +285,12 @@ class ScriptCompletionTaskObserver { // just yet. return; } + if (shell_.EngineHasPendingMicrotasks()) { + // Post an empty task to force a run of the engine task observer that + // drains the microtask queue. + ui_task_runner_->PostTask([] {}); + return; + } if (run_forever_) { // We need this script to run forever. We have already recorded the last @@ -302,6 +310,7 @@ class ScriptCompletionTaskObserver { private: Shell& shell_; fml::RefPtr main_task_runner_; + fml::RefPtr ui_task_runner_; bool run_forever_ = false; std::optional last_error_; bool has_terminated_ = false; @@ -456,6 +465,7 @@ int RunTester(const flutter::Settings& settings, *shell, // a valid shell fml::MessageLoop::GetCurrent() .GetTaskRunner(), // the message loop to terminate + ui_task_runner, // runner for Dart microtasks run_forever // should the exit be ignored ); diff --git a/shell/vmservice/pubspec.yaml b/shell/vmservice/pubspec.yaml index 0580a7281b6f6..9d8b480b10df7 100644 --- a/shell/vmservice/pubspec.yaml +++ b/shell/vmservice/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/skia/BUILD.gn b/skia/BUILD.gn index 2b3f81f3859c4..617b8c4513781 100644 --- a/skia/BUILD.gn +++ b/skia/BUILD.gn @@ -221,6 +221,7 @@ optional("fontmgr_android") { deps = [ ":typeface_freetype", + ":typeface_proxy", "//flutter/third_party/expat", ] public = skia_ports_fontmgr_android_public @@ -270,6 +271,11 @@ optional("fontmgr_custom_empty") { sources = skia_ports_fontmgr_empty_sources } +skia_source_set("typeface_proxy") { + configs = [ ":skia_public" ] + sources = skia_ports_typeface_proxy_sources +} + optional("fontmgr_fontconfig") { enabled = skia_enable_fontmgr_fontconfig public_defines = [ "SK_FONTMGR_FONTCONFIG_AVAILABLE" ] @@ -277,7 +283,10 @@ optional("fontmgr_fontconfig") { # The public header includes fontconfig.h and uses FcConfig* public_deps = [ "//third_party:fontconfig" ] public = skia_ports_fontmgr_fontconfig_public - deps = [ ":typeface_freetype" ] + deps = [ + ":typeface_freetype", + ":typeface_proxy", + ] sources = skia_ports_fontmgr_fontconfig_sources } @@ -505,6 +514,7 @@ optional("jpeg_decode") { optional("jpeg_encode") { enabled = skia_use_libjpeg_turbo_encode && !skia_use_ndk_images + public_defines = [ "SK_CODEC_ENCODES_JPEG" ] deps = [ "//flutter/third_party/libjpeg-turbo:libjpeg" ] public = skia_encode_jpeg_public @@ -529,22 +539,27 @@ optional("xps") { sources = skia_xps_sources } -optional("png_decode") { +optional("png_decode_libpng") { enabled = skia_use_libpng_decode public_defines = [ "SK_CODEC_DECODES_PNG", "SK_CODEC_DECODES_ICO", + "SK_CODEC_DECODES_PNG_WITH_LIBPNG", ] deps = [ "//flutter/third_party/libpng" ] - sources = [ "$_skia_root/src/codec/SkIcoCodec.cpp" ] - sources += skia_codec_png + sources = [ "$_skia_root/src/codec/SkIcoCodec.cpp" ] + skia_codec_png_base + + skia_codec_libpng_srcs } optional("png_encode") { enabled = skia_use_libpng_encode && !skia_use_ndk_images - public = skia_encode_png_public + public_defines = [ + "SK_CODEC_ENCODES_PNG", + "SK_CODEC_ENCODES_PNG_WITH_LIBPNG", + ] + public = skia_encode_png_public deps = [ "//flutter/third_party/libpng" ] sources = skia_encode_png_srcs } @@ -567,6 +582,7 @@ optional("webp_decode") { optional("webp_encode") { enabled = skia_use_libwebp_encode && !skia_use_ndk_images + public_defines = [ "SK_CODEC_ENCODES_WEBP" ] public = skia_encode_webp_public deps = [ "//flutter/third_party/libwebp" ] @@ -625,7 +641,7 @@ skia_component("skia") { ":hsw", ":jpeg_decode", ":ndk_images", - ":png_decode", + ":png_decode_libpng", ":webp_decode", ":wuffs", ":xml", @@ -649,7 +665,7 @@ skia_component("skia") { sources += skia_codec_decode_bmp sources += skia_encode_srcs sources += skia_sksl_core_sources - sources += skia_sksl_default_module_sources + sources += skia_sksl_core_module_sources sources += skia_ports_sources sources += [ "$_skia_root/src/android/SkAndroidFrameworkUtils.cpp", diff --git a/skia/flutter_defines.gni b/skia/flutter_defines.gni index 0b40a0f7c0eed..83e6433ab8043 100644 --- a/skia/flutter_defines.gni +++ b/skia/flutter_defines.gni @@ -13,7 +13,6 @@ flutter_defines = [ "SK_LEGACY_IGNORE_DRAW_VERTICES_BLEND_WITH_NO_SHADER", "SK_DISABLE_LEGACY_METAL_BACKEND_SURFACE", "SK_DISABLE_LEGACY_PARAGRAPH_UNICODE", - "SK_USE_LEGACY_BLUR_RASTER", # Fast low-precision software rendering isn't a priority for Flutter. "SK_DISABLE_LEGACY_SHADERCONTEXT", diff --git a/skia/modules/canvaskit/BUILD.gn b/skia/modules/canvaskit/BUILD.gn index 30f40c8f86045..1a24c8ff2bc87 100644 --- a/skia/modules/canvaskit/BUILD.gn +++ b/skia/modules/canvaskit/BUILD.gn @@ -164,11 +164,16 @@ canvaskit_wasm_lib("canvaskit") { "-sDYNAMIC_EXECUTION=0", "-sEXPORT_NAME=CanvasKitInit", "-sEXPORTED_FUNCTIONS=[_malloc,_free]", + "-sEXPORTED_RUNTIME_METHODS=HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPF32,HEAPU32,HEAPF64", + "-sINCOMING_MODULE_JS_API=onRuntimeInitialized", "-sFORCE_FILESYSTEM=0", "-sFILESYSTEM=0", "-sMODULARIZE", "-sNO_EXIT_RUNTIME=1", - "-sINITIAL_MEMORY=128MB", + "-sINITIAL_MEMORY=32MB", + + # Grow memory by +100% i.e. double the size each time we run out of memory. + "-sMEMORY_GROWTH_GEOMETRIC_STEP=1.0", "-sWASM", "-sSTRICT=1", ] diff --git a/sky/packages/sky_engine/LICENSE b/sky/packages/sky_engine/LICENSE index a8fab3bb5bf39..a97cfa1218fcf 100644 --- a/sky/packages/sky_engine/LICENSE +++ b/sky/packages/sky_engine/LICENSE @@ -24248,22 +24248,6 @@ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --------------------------------------------------------------------------------- -boringssl - -Copyright (c) 2023, Google LLC - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY @@ -24340,6 +24324,22 @@ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-------------------------------------------------------------------------------- +boringssl + +Copyright (c) 2024, Google LLC + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY @@ -32253,7 +32253,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. -You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/86ac62245577068c94e6f88b1595c33ff314fad6 +You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/1a28e6c86b09f1c83365f54388c32ed97c9e9b31 /third_party/fallback_root_certificates/ -------------------------------------------------------------------------------- diff --git a/sky/packages/sky_engine/pubspec.yaml b/sky/packages/sky_engine/pubspec.yaml index e0648ded011cd..40ef5d948eaeb 100644 --- a/sky/packages/sky_engine/pubspec.yaml +++ b/sky/packages/sky_engine/pubspec.yaml @@ -1,3 +1,3 @@ name: sky_engine environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 diff --git a/sky/tools/cp.py b/sky/tools/cp.py new file mode 100755 index 0000000000000..1319735968909 --- /dev/null +++ b/sky/tools/cp.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Copy a file. + +This module works much like the cp posix command - it takes 2 arguments: +(src, dst) and copies the file with path |src| to |dst|. +""" + +import os +import shutil +import sys + + +def main(src, dst): + # Use copy instead of copyfile to ensure the executable bit is copied. + dstpath = os.path.normpath(dst) + try: + shutil.copy(src, dstpath) + except shutil.SameFileError: + if not (os.path.islink(dstpath) or os.stat(dstpath).st_nlink > 1): + raise + # Copy will fail if the destination is the link to the source. + # If that's the case, then delete the destination link first, + # then repeat the copy. + os.remove(dstpath) + shutil.copy(src, dstpath) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1], sys.argv[2])) diff --git a/testing/BUILD.gn b/testing/BUILD.gn index 22723892732ee..1a21ac33669be 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -190,19 +190,22 @@ if (enable_unittests) { # On iOS, this is enabled to allow for Metal tests to run within a test app if (is_mac || is_ios) { source_set("metal") { + testonly = true + if (shell_enable_metal) { + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + sources = [ "test_metal_context.h", "test_metal_context.mm", - "test_metal_surface.cc", "test_metal_surface.h", + "test_metal_surface.mm", "test_metal_surface_impl.h", "test_metal_surface_impl.mm", ] - deps = [ - ":skia", - "//flutter/fml", - ] + deps = [ "//flutter/fml" ] + public_deps = [ ":skia" ] # Skia's Vulkan support is enabled for all platforms (except iOS), and so parts of # Skia's graphics context reference Vulkan symbols. @@ -210,8 +213,6 @@ if (is_mac || is_ios) { deps += [ "//flutter/vulkan" ] } } - - testonly = true } } @@ -222,8 +223,12 @@ if (use_swiftshader) { testonly = true sources = [ + "test_gl_context.cc", + "test_gl_context.h", "test_gl_surface.cc", "test_gl_surface.h", + "test_gl_utils.cc", + "test_gl_utils.h", ] deps = [ @@ -243,3 +248,24 @@ if (use_swiftshader) { ] } } + +if (enable_unittests) { + executable("testing_unittests") { + testonly = true + + sources = [] + deps = [ + ":testing_fixtures", + "//flutter/third_party/googletest:gtest", + "//flutter/third_party/googletest:gtest_main", + ] + + if (test_enable_metal) { + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + + sources += [ "test_metal_surface_unittests.mm" ] + deps += [ ":metal" ] + } + } +} diff --git a/testing/assertions.h b/testing/assertions.h index a6e7b22fbc24e..68cea9675e712 100644 --- a/testing/assertions.h +++ b/testing/assertions.h @@ -7,15 +7,13 @@ #include -namespace flutter { -namespace testing { +namespace flutter::testing { inline bool NumberNear(double a, double b) { static const double epsilon = 1e-3; return (a > (b - epsilon)) && (a < (b + epsilon)); } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_ASSERTIONS_H_ diff --git a/testing/benchmark/pubspec.yaml b/testing/benchmark/pubspec.yaml index c69c058628f43..3009b77b4a06a 100644 --- a/testing/benchmark/pubspec.yaml +++ b/testing/benchmark/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/testing/canvas_test.h b/testing/canvas_test.h index 8333257f81df0..843af5adec380 100644 --- a/testing/canvas_test.h +++ b/testing/canvas_test.h @@ -9,8 +9,7 @@ #include "gtest/gtest.h" #include "third_party/skia/include/core/SkColorSpace.h" -namespace flutter { -namespace testing { +namespace flutter::testing { // This fixture allows creating tests that make use of a mock |SkCanvas|. template @@ -27,7 +26,6 @@ class CanvasTestBase : public BaseT { }; using CanvasTest = CanvasTestBase<::testing::Test>; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_CANVAS_TEST_H_ diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart index dc62b9c77e223..604a6ce483285 100644 --- a/testing/dart/canvas_test.dart +++ b/testing/dart/canvas_test.dart @@ -1394,6 +1394,236 @@ void main() async { final ByteData? data = await resultImage.toByteData(); expect(data, isNotNull); }); + + Image makeCheckerBoard(int width, int height) { + final recorder = PictureRecorder(); + final canvas = Canvas(recorder); + + const double left = 0; + final double centerX = width * 0.5; + final double right = width.toDouble(); + + const double top = 0; + final double centerY = height * 0.5; + final double bottom = height.toDouble(); + + canvas.drawRect(Rect.fromLTRB(left, top, centerX, centerY), + Paint()..color = const Color.fromARGB(255, 0, 255, 0)); + canvas.drawRect(Rect.fromLTRB(centerX, top, right, centerY), + Paint()..color = const Color.fromARGB(255, 255, 255, 0)); + canvas.drawRect(Rect.fromLTRB(left, centerY, centerX, bottom), + Paint()..color = const Color.fromARGB(255, 0, 0, 255)); + canvas.drawRect(Rect.fromLTRB(centerX, centerY, right, bottom), + Paint()..color = const Color.fromARGB(255, 255, 0, 0)); + + final picture = recorder.endRecording(); + return picture.toImageSync(width, height); + } + + Image renderingOpsWithTileMode(TileMode? tileMode) { + final recorder = PictureRecorder(); + final canvas = Canvas(recorder); + canvas.drawColor(const Color.fromARGB(255, 224, 224, 224), BlendMode.src); + + const Rect zone = Rect.fromLTWH(15, 15, 20, 20); + final Rect arena = zone.inflate(15); + const Rect ovalZone = Rect.fromLTWH(20, 15, 10, 20); + + final gradient = Gradient.linear( + zone.topLeft, + zone.bottomRight, + [ + const Color.fromARGB(255, 0, 255, 0), + const Color.fromARGB(255, 0, 0, 255), + ], + [0, 1], + ); + final filter = ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0, tileMode: tileMode); + final Paint white = Paint()..color = const Color.fromARGB(255, 255, 255, 255); + final Paint grey = Paint()..color = const Color.fromARGB(255, 127, 127, 127); + final Paint unblurredFill = Paint()..shader = gradient; + final Paint blurredFill = Paint.from(unblurredFill) + ..imageFilter = filter; + final Paint unblurredStroke = Paint.from(unblurredFill) + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round + ..strokeWidth = 10; + final Paint blurredStroke = Paint.from(unblurredStroke) + ..imageFilter = filter; + final Image image = makeCheckerBoard(20, 20); + const Rect imageBounds = Rect.fromLTRB(0, 0, 20, 20); + const Rect imageCenter = Rect.fromLTRB(5, 5, 9, 9); + final points = [ + zone.topLeft, + zone.topCenter, + zone.topRight, + zone.centerLeft, + zone.center, + zone.centerRight, + zone.bottomLeft, + zone.bottomCenter, + zone.bottomRight, + ]; + final vertices = Vertices( + VertexMode.triangles, + [ + zone.topLeft, + zone.bottomRight, + zone.topRight, + zone.topLeft, + zone.bottomRight, + zone.bottomLeft, + ], + colors: [ + const Color.fromARGB(255, 0, 255, 0), + const Color.fromARGB(255, 255, 0, 0), + const Color.fromARGB(255, 255, 255, 0), + const Color.fromARGB(255, 0, 255, 0), + const Color.fromARGB(255, 255, 0, 0), + const Color.fromARGB(255, 0, 0, 255), + ], + ); + final atlasXforms = [ + RSTransform.fromComponents( + rotation: 0.0, + scale: 1.0, + anchorX: 0, + anchorY: 0, + translateX: zone.topLeft.dx, + translateY: zone.topLeft.dy, + ), + RSTransform.fromComponents( + rotation: pi / 2, + scale: 1.0, + anchorX: 0, + anchorY: 0, + translateX: zone.topRight.dx, + translateY: zone.topRight.dy, + ), + RSTransform.fromComponents( + rotation: pi, + scale: 1.0, + anchorX: 0, + anchorY: 0, + translateX: zone.bottomRight.dx, + translateY: zone.bottomRight.dy, + ), + RSTransform.fromComponents( + rotation: pi * 3 / 2, + scale: 1.0, + anchorX: 0, + anchorY: 0, + translateX: zone.bottomLeft.dx, + translateY: zone.bottomLeft.dy, + ), + RSTransform.fromComponents( + rotation: pi / 4, + scale: 1.0, + anchorX: 4, + anchorY: 4, + translateX: zone.center.dx, + translateY: zone.center.dy, + ), + ]; + const atlasRects = [ + Rect.fromLTRB(6, 6, 14, 14), + Rect.fromLTRB(6, 6, 14, 14), + Rect.fromLTRB(6, 6, 14, 14), + Rect.fromLTRB(6, 6, 14, 14), + Rect.fromLTRB(6, 6, 14, 14), + ]; + + const double pad = 10; + final double offset = arena.width + pad; + const int columns = 5; + final Rect pairArena = Rect.fromLTRB(arena.left - 3, arena.top - 3, + arena.right + 3, arena.bottom + offset + 3); + + final List renderers = [ + (canvas, fill, stroke) { + canvas.saveLayer(zone.inflate(5), fill); + canvas.drawLine(zone.topLeft, zone.bottomRight, unblurredStroke); + canvas.drawLine(zone.topRight, zone.bottomLeft, unblurredStroke); + canvas.restore(); + }, + (canvas, fill, stroke) => canvas.drawLine(zone.topLeft, zone.bottomRight, stroke), + (canvas, fill, stroke) => canvas.drawRect(zone, fill), + (canvas, fill, stroke) => canvas.drawOval(ovalZone, fill), + (canvas, fill, stroke) => canvas.drawCircle(zone.center, zone.width * 0.5, fill), + (canvas, fill, stroke) => canvas.drawRRect(RRect.fromRectXY(zone, 4.0, 4.0), fill), + (canvas, fill, stroke) => canvas.drawDRRect(RRect.fromRectXY(zone, 4.0, 4.0), + RRect.fromRectXY(zone.deflate(4), 4.0, 4.0), + fill), + (canvas, fill, stroke) => canvas.drawArc(zone, pi / 4, pi * 3 / 2, true, fill), + (canvas, fill, stroke) => canvas.drawPath(Path() + ..moveTo(zone.left, zone.top) + ..lineTo(zone.right, zone.top) + ..lineTo(zone.left, zone.bottom) + ..lineTo(zone.right, zone.bottom), + stroke), + (canvas, fill, stroke) => canvas.drawImage(image, zone.topLeft, fill), + (canvas, fill, stroke) => canvas.drawImageRect(image, imageBounds, zone.inflate(2), fill), + (canvas, fill, stroke) => canvas.drawImageNine(image, imageCenter, zone.inflate(2), fill), + (canvas, fill, stroke) => canvas.drawPoints(PointMode.points, points, stroke), + (canvas, fill, stroke) => canvas.drawVertices(vertices, BlendMode.dstOver, fill), + (canvas, fill, stroke) => canvas.drawAtlas(image, atlasXforms, atlasRects, + null, null, null, fill), + ]; + + canvas.save(); + canvas.translate(pad, pad); + int renderIndex = 0; + int rows = 0; + while (renderIndex < renderers.length) { + rows += 2; + canvas.save(); + for (int col = 0; col < columns && renderIndex < renderers.length; col++) { + final renderer = renderers[renderIndex++]; + canvas.drawRect(pairArena, grey); + canvas.drawRect(arena, white); + renderer(canvas, unblurredFill, unblurredStroke); + canvas.save(); + canvas.translate(0, offset); + canvas.drawRect(arena, white); + renderer(canvas, blurredFill, blurredStroke); + canvas.restore(); + canvas.translate(offset, 0); + } + canvas.restore(); + canvas.translate(0, offset * 2); + } + canvas.restore(); + + final picture = recorder.endRecording(); + return picture.toImageSync((offset * columns + pad).round(), + (offset * rows + pad).round()); + } + + test('Rendering ops with ImageFilter blur with default tile mode', () async { + final image = renderingOpsWithTileMode(null); + await comparer.addGoldenImage(image, 'canvas_test_blurred_rendering_with_default_tile_mode.png'); + }); + + test('Rendering ops with ImageFilter blur with clamp tile mode', () async { + final image = renderingOpsWithTileMode(TileMode.clamp); + await comparer.addGoldenImage(image, 'canvas_test_blurred_rendering_with_clamp_tile_mode.png'); + }); + + test('Rendering ops with ImageFilter blur with mirror tile mode', () async { + final image = renderingOpsWithTileMode(TileMode.mirror); + await comparer.addGoldenImage(image, 'canvas_test_blurred_rendering_with_mirror_tile_mode.png'); + }); + + test('Rendering ops with ImageFilter blur with repeated tile mode', () async { + final image = renderingOpsWithTileMode(TileMode.repeated); + await comparer.addGoldenImage(image, 'canvas_test_blurred_rendering_with_repeated_tile_mode.png'); + }); + + test('Rendering ops with ImageFilter blur with decal tile mode', () async { + final image = renderingOpsWithTileMode(TileMode.decal); + await comparer.addGoldenImage(image, 'canvas_test_blurred_rendering_with_decal_tile_mode.png'); + }); } Future createTestImage() async { diff --git a/testing/dart/codec_test.dart b/testing/dart/codec_test.dart index e21b099a8cee0..7d0a2a23d2758 100644 --- a/testing/dart/codec_test.dart +++ b/testing/dart/codec_test.dart @@ -252,6 +252,46 @@ void main() { imageData = (await image.toByteData())!; expect(imageData.getUint32(imageData.lengthInBytes - 4), 0x00000000); }); + + test( + 'Animated apng frame decode does not crash with invalid destination region', + () async { + final Uint8List data = File( + path.join('flutter', 'lib', 'ui', 'fixtures', 'out_of_bounds.apng'), + ).readAsBytesSync(); + + final ui.Codec codec = await ui.instantiateImageCodec(data); + try { + await codec.getNextFrame(); + fail('exception not thrown'); + } on Exception catch (e) { + if (impellerEnabled) { + expect(e.toString(), contains('Could not decompress image.')); + } else { + expect(e.toString(), contains('Codec failed')); + } + } + }); + + test( + 'Animated apng frame decode does not crash with invalid destination region and bounds wrapping', + () async { + final Uint8List data = File( + path.join('flutter', 'lib', 'ui', 'fixtures', 'out_of_bounds_wrapping.apng'), + ).readAsBytesSync(); + + final ui.Codec codec = await ui.instantiateImageCodec(data); + try { + await codec.getNextFrame(); + fail('exception not thrown'); + } on Exception catch (e) { + if (impellerEnabled) { + expect(e.toString(), contains('Could not decompress image.')); + } else { + expect(e.toString(), contains('Codec failed')); + } + } + }); } /// Returns a File handle to a file in the skia/resources directory. diff --git a/testing/dart/color_test.dart b/testing/dart/color_test.dart index 2b45535369e42..a23f485aadeca 100644 --- a/testing/dart/color_test.dart +++ b/testing/dart/color_test.dart @@ -339,6 +339,11 @@ void main() { // Call base class member, make sure it uses overridden value. expect(color.red, 0xE0); }); + + test('toARGB32 converts to a 32-bit integer', () { + const Color color = Color.from(alpha: 0.1, red: 0.2, green: 0.3, blue: 0.4); + expect(color.toARGB32(), equals(0x1a334d66)); + }); } class DynamicColorClass extends Color { diff --git a/testing/dart/compositing_test.dart b/testing/dart/compositing_test.dart index b363981ebbc6e..44377b042980a 100644 --- a/testing/dart/compositing_test.dart +++ b/testing/dart/compositing_test.dart @@ -336,7 +336,7 @@ void main() { return builder.pushOpacity(100, oldLayer: oldLayer as OpacityEngineLayer?); }); testNoSharing((SceneBuilder builder, EngineLayer? oldLayer) { - return builder.pushBackdropFilter(ImageFilter.blur(), oldLayer: oldLayer as BackdropFilterEngineLayer?); + return builder.pushBackdropFilter(ImageFilter.blur(sigmaX: 1.0), oldLayer: oldLayer as BackdropFilterEngineLayer?); }); testNoSharing((SceneBuilder builder, EngineLayer? oldLayer) { return builder.pushShaderMask( diff --git a/testing/dart/fragment_shader_test.dart b/testing/dart/fragment_shader_test.dart index fbe9f1a01b009..e065a2eb7d186 100644 --- a/testing/dart/fragment_shader_test.dart +++ b/testing/dart/fragment_shader_test.dart @@ -345,6 +345,68 @@ void main() async { shader.dispose(); }); + test('ImageFilter.shader errors if shader does not have correct uniform layout', () async { + if (!impellerEnabled) { + print('Skipped for Skia'); + return; + } + const List shaders = [ + 'no_uniforms.frag.iplr', + 'missing_size.frag.iplr', + 'missing_texture.frag.iplr' + ]; + const List<(bool, bool)> errors = [ + (true, true), + (true, false), + (false, false) + ]; + for (int i = 0; i < 3; i++) { + final String fileName = shaders[i]; + final FragmentProgram program = await FragmentProgram.fromAsset( + fileName + ); + final FragmentShader shader = program.fragmentShader(); + + Object? error; + try { + ImageFilter.shader(shader); + } catch (err) { + error = err; + } + expect(error is StateError, true); + final (floatError, samplerError) = errors[i]; + if (floatError) { + expect(error.toString(), contains('shader has fewer than two float')); + } + if (samplerError) { + expect(error.toString(), contains('shader is missing a sampler uniform')); + } + } + }); + + test('ImageFilter.shader can be applied to canvas operations', () async { + if (!impellerEnabled) { + print('Skipped for Skia'); + return; + } + final FragmentProgram program = await FragmentProgram.fromAsset( + 'filter_shader.frag.iplr', + ); + final FragmentShader shader = program.fragmentShader(); + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawPaint( + Paint() + ..color = const Color(0xFFFF0000) + ..imageFilter = ImageFilter.shader(shader) + ); + final Image image = await recorder.endRecording().toImage(1, 1); + final ByteData data = (await image.toByteData())!; + final Color color = Color(data.buffer.asUint32List()[0]); + + expect(color, const Color(0xFF00FF00)); + }); + if (impellerEnabled) { print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823'); return; diff --git a/testing/dart/gpu_test.dart b/testing/dart/gpu_test.dart index 508e7e08d9e6f..1317a5fbb4f6e 100644 --- a/testing/dart/gpu_test.dart +++ b/testing/dart/gpu_test.dart @@ -60,6 +60,7 @@ RenderPassState createSimpleRenderPass({Vector4? clearColor}) { final gpu.Texture? depthStencilTexture = gpu.gpuContext.createTexture( gpu.StorageMode.deviceTransient, 100, 100, format: gpu.gpuContext.defaultDepthStencilFormat); + assert(depthStencilTexture != null); final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer(); @@ -74,6 +75,66 @@ RenderPassState createSimpleRenderPass({Vector4? clearColor}) { return RenderPassState(renderTexture, commandBuffer, renderPass); } +RenderPassState createSimpleRenderPassWithMSAA() { + // Create transient MSAA attachments, which will live entirely in tile memory + // for most GPUs. + + final gpu.Texture? renderTexture = gpu.gpuContext.createTexture( + gpu.StorageMode.deviceTransient, 100, 100, + format: gpu.gpuContext.defaultColorFormat, sampleCount: 4); + assert(renderTexture != null); + + final gpu.Texture? depthStencilTexture = gpu.gpuContext.createTexture( + gpu.StorageMode.deviceTransient, 100, 100, + format: gpu.gpuContext.defaultDepthStencilFormat, sampleCount: 4); + assert(depthStencilTexture != null); + + // Create the single-sample resolve texture that live in DRAM and will be + // drawn to the screen. + + final gpu.Texture? resolveTexture = gpu.gpuContext.createTexture( + gpu.StorageMode.devicePrivate, 100, 100, + format: gpu.gpuContext.defaultColorFormat); + assert(resolveTexture != null); + + final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer(); + + final gpu.RenderTarget renderTarget = gpu.RenderTarget.singleColor( + gpu.ColorAttachment( + texture: renderTexture!, + resolveTexture: resolveTexture, + storeAction: gpu.StoreAction.multisampleResolve), + depthStencilAttachment: + gpu.DepthStencilAttachment(texture: depthStencilTexture!)); + + final gpu.RenderPass renderPass = + commandBuffer.createRenderPass(renderTarget); + + return RenderPassState(resolveTexture!, commandBuffer, renderPass); +} + +void drawTriangle(RenderPassState state, Vector4 color) { + final gpu.RenderPipeline pipeline = createUnlitRenderPipeline(); + + state.renderPass.bindPipeline(pipeline); + + final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer(); + final gpu.BufferView vertices = transients.emplace(float32([ + -0.5, 0.5, // + 0.0, -0.5, // + 0.5, 0.5, // + ])); + final gpu.BufferView vertInfoData = + transients.emplace(unlitUBO(Matrix4.identity(), color)); + state.renderPass.bindVertexBuffer(vertices, 3); + + final gpu.UniformSlot vertInfo = + pipeline.vertexShader.getUniformSlot('VertInfo'); + state.renderPass.bindUniform(vertInfo, vertInfoData); + + state.renderPass.draw(); +} + void main() async { final ImageComparer comparer = await ImageComparer.create(); @@ -110,10 +171,42 @@ void main() async { final gpu.BufferView view1 = hostBuffer .emplace(Int8List.fromList([0, 1, 2, 3]).buffer.asByteData()); - expect(view1.offsetInBytes >= 4, true); + expect(view1.offsetInBytes, + equals(gpu.gpuContext.minimumUniformByteAlignment)); + expect(view1.lengthInBytes, 4); + }, skip: !impellerEnabled); + + test('HostBuffer.reset', () async { + final gpu.HostBuffer hostBuffer = gpu.gpuContext.createHostBuffer(); + + final gpu.BufferView view0 = hostBuffer + .emplace(Int8List.fromList([0, 1, 2, 3]).buffer.asByteData()); + expect(view0.offsetInBytes, 0); + expect(view0.lengthInBytes, 4); + + hostBuffer.reset(); + + final gpu.BufferView view1 = hostBuffer + .emplace(Int8List.fromList([0, 1, 2, 3]).buffer.asByteData()); + expect(view1.offsetInBytes, 0); expect(view1.lengthInBytes, 4); }, skip: !impellerEnabled); + test('HostBuffer reuses DeviceBuffers after N frames', () async { + final gpu.HostBuffer hostBuffer = gpu.gpuContext.createHostBuffer(); + + final gpu.BufferView view0 = hostBuffer + .emplace(Int8List.fromList([0, 1, 2, 3]).buffer.asByteData()); + + for (int i = 0; i < hostBuffer.frameCount; i++) { + hostBuffer.reset(); + } + final gpu.BufferView view1 = hostBuffer + .emplace(Int8List.fromList([0, 1, 2, 3]).buffer.asByteData()); + + expect(view0.buffer, equals(view1.buffer)); + }, skip: !impellerEnabled); + test('GpuContext.createDeviceBuffer', () async { final gpu.DeviceBuffer? deviceBuffer = gpu.gpuContext.createDeviceBuffer(gpu.StorageMode.hostVisible, 4); @@ -324,6 +417,29 @@ void main() async { } }, skip: !impellerEnabled); + test('RenderPass.bindTexture throws for deviceTransient Textures', () async { + final state = createSimpleRenderPass(); + + final gpu.RenderPipeline pipeline = createUnlitRenderPipeline(); + // Although this is a non-texture uniform slot, it'll work fine for the + // purposes of testing this error. + final gpu.UniformSlot vertInfo = + pipeline.vertexShader.getUniformSlot('VertInfo'); + + final gpu.Texture texture = gpu.gpuContext + .createTexture(gpu.StorageMode.deviceTransient, 100, 100)!; + + try { + state.renderPass.bindTexture(vertInfo, texture); + fail('Exception not thrown when binding a transient texture.'); + } catch (e) { + expect( + e.toString(), + contains( + 'Textures with StorageMode.deviceTransient cannot be bound to a RenderPass')); + } + }, skip: !impellerEnabled); + // Performs no draw calls. Just clears the render target to a solid green color. test('Can render clear color', () async { final state = createSimpleRenderPass(clearColor: Colors.lime); @@ -334,37 +450,47 @@ void main() async { await comparer.addGoldenImage(image, 'flutter_gpu_test_clear_color.png'); }, skip: !impellerEnabled); - // Renders a green triangle pointing downwards. - test('Can render triangle', () async { + // Regression test for https://github.com/flutter/flutter/issues/157324 + test('Can bind uniforms in range', () async { final state = createSimpleRenderPass(); final gpu.RenderPipeline pipeline = createUnlitRenderPipeline(); - state.renderPass.bindPipeline(pipeline); - - // Configure blending with defaults (just to test the bindings). - state.renderPass.setColorBlendEnable(true); - state.renderPass.setColorBlendEquation(gpu.ColorBlendEquation()); + final gpu.UniformSlot vertInfo = + pipeline.vertexShader.getUniformSlot('VertInfo'); - final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer(); - final gpu.BufferView vertices = transients.emplace(float32([ - -0.5, 0.5, // - 0.0, -0.5, // - 0.5, 0.5, // - ])); - final gpu.BufferView vertInfoData = transients.emplace(float32([ + final ByteData vertInfoData = float32([ 1, 0, 0, 0, // mvp 0, 1, 0, 0, // mvp 0, 0, 1, 0, // mvp 0, 0, 0, 1, // mvp 0, 1, 0, 1, // color - ])); - state.renderPass.bindVertexBuffer(vertices, 3); - - final gpu.UniformSlot vertInfo = - pipeline.vertexShader.getUniformSlot('VertInfo'); - state.renderPass.bindUniform(vertInfo, vertInfoData); - state.renderPass.draw(); + ]); + final uniformBuffer = + gpu.gpuContext.createDeviceBufferWithCopy(vertInfoData)!; + final gooduniformBufferView = gpu.BufferView( + uniformBuffer, + offsetInBytes: 0, + lengthInBytes: uniformBuffer.sizeInBytes, + ); + state.renderPass.bindUniform(vertInfo, gooduniformBufferView); + + final badUniformBufferView = gpu.BufferView( + uniformBuffer, + offsetInBytes: 0, + lengthInBytes: uniformBuffer.sizeInBytes + 1, + ); + try { + state.renderPass.bindUniform(vertInfo, badUniformBufferView); + fail('Exception not thrown for bad buffer view range.'); + } catch (e) { + expect(e.toString(), contains('Failed to bind uniform')); + } + }, skip: !impellerEnabled); + // Renders a green triangle pointing downwards. + test('Can render triangle', () async { + final state = createSimpleRenderPass(); + drawTriangle(state, Colors.lime); state.commandBuffer.submit(); final ui.Image image = state.renderTexture.asImage(); @@ -408,9 +534,36 @@ void main() async { state.commandBuffer.submit(); final ui.Image image = state.renderTexture.asImage(); - await comparer.addGoldenImage(image, 'flutter_gpu_test_triangle_polygon_mode.png'); + await comparer.addGoldenImage( + image, 'flutter_gpu_test_triangle_polygon_mode.png'); }, skip: !impellerEnabled); + // Renders a green triangle pointing downwards, with 4xMSAA. + test('Can render triangle with MSAA', () async { + final state = createSimpleRenderPassWithMSAA(); + drawTriangle(state, Colors.lime); + state.commandBuffer.submit(); + + final ui.Image image = state.renderTexture.asImage(); + await comparer.addGoldenImage(image, 'flutter_gpu_test_triangle_msaa.png'); + }, skip: !(impellerEnabled && gpu.gpuContext.doesSupportOffscreenMSAA)); + + test( + 'Rendering with MSAA throws exception when offscreen MSAA is not supported', + () async { + try { + final state = createSimpleRenderPassWithMSAA(); + drawTriangle(state, Colors.lime); + state.commandBuffer.submit(); + fail('Exception not thrown when offscreen MSAA is not supported.'); + } catch (e) { + expect( + e.toString(), + contains( + 'The backend does not support multisample anti-aliasing for offscreen color and stencil attachments')); + } + }, skip: !(impellerEnabled && !gpu.gpuContext.doesSupportOffscreenMSAA)); + // Renders a hollow green triangle pointing downwards. test('Can render hollowed out triangle using stencil ops', () async { final state = createSimpleRenderPass(); @@ -548,13 +701,20 @@ void main() async { final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer(); final gpu.BufferView vertices = transients.emplace(float32([ - 1.0, 0.0, - 0.5, 0.8, - -0.5, 0.8, - -1.0, 0.0, - -0.5, -0.8, - 0.5, -0.8, - 1.0, 0.0 + 1.0, + 0.0, + 0.5, + 0.8, + -0.5, + 0.8, + -1.0, + 0.0, + -0.5, + -0.8, + 0.5, + -0.8, + 1.0, + 0.0 ])); final gpu.BufferView vertInfoData = transients.emplace(float32([ 1, 0, 0, 0, // mvp @@ -573,6 +733,81 @@ void main() async { state.commandBuffer.submit(); final ui.Image image = state.renderTexture.asImage(); - await comparer.addGoldenImage(image, 'flutter_gpu_test_hexgon_line_strip.png'); + await comparer.addGoldenImage( + image, 'flutter_gpu_test_hexgon_line_strip.png'); + }, skip: !impellerEnabled); + + // Renders the middle part triangle using scissor. + test('Can render portion of the triangle using scissor', () async { + final state = createSimpleRenderPass(); + + final gpu.RenderPipeline pipeline = createUnlitRenderPipeline(); + state.renderPass.bindPipeline(pipeline); + + // Configure blending with defaults (just to test the bindings). + state.renderPass.setColorBlendEnable(true); + state.renderPass.setColorBlendEquation(gpu.ColorBlendEquation()); + + // Set primitive type. + state.renderPass.setPrimitiveType(gpu.PrimitiveType.triangle); + + // Set scissor. + state.renderPass.setScissor(gpu.Scissor(x: 25, width: 50, height: 100)); + + final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer(); + final gpu.BufferView vertices = transients.emplace(float32([ + -1.0, + -1.0, + 0.0, + 1.0, + 1.0, + -1.0])); + final gpu.BufferView vertInfoData = transients.emplace(float32([ + 1, 0, 0, 0, // mvp + 0, 1, 0, 0, // mvp + 0, 0, 1, 0, // mvp + 0, 0, 0, 1, // mvp + 0, 1, 0, 1, // color + ])); + state.renderPass.bindVertexBuffer(vertices, 3); + + final gpu.UniformSlot vertInfo = + pipeline.vertexShader.getUniformSlot('VertInfo'); + state.renderPass.bindUniform(vertInfo, vertInfoData); + state.renderPass.draw(); + + state.commandBuffer.submit(); + + final ui.Image image = state.renderTexture.asImage(); + await comparer.addGoldenImage( + image, 'flutter_gpu_test_scissor.png'); + }, skip: !impellerEnabled); + + test('RenderPass.setScissor doesnt throw for valid values', + () async { + final state = createSimpleRenderPass(); + + state.renderPass.setScissor(gpu.Scissor(x: 25, width: 50, height: 100)); + state.renderPass.setScissor(gpu.Scissor(width: 50, height: 100)); + }, skip: !impellerEnabled); + + test('RenderPass.setScissor throws for invalid values', () async { + final state = createSimpleRenderPass(); + + try { + state.renderPass.setScissor(gpu.Scissor(x: -1, width: 50, height: 100)); + fail('Exception not thrown for invalid scissor.'); + } catch (e) { + expect(e.toString(), + contains('Invalid values for scissor. All values should be positive.')); + } + + try { + state.renderPass.setScissor(gpu.Scissor(width: 50, height: -100)); + fail('Exception not thrown for invalid scissor.'); + } catch (e) { + expect(e.toString(), + contains('Invalid values for scissor. All values should be positive.')); + } }, skip: !impellerEnabled); } diff --git a/testing/dart/image_filter_test.dart b/testing/dart/image_filter_test.dart index dab241627158d..65e555fcb70c8 100644 --- a/testing/dart/image_filter_test.dart +++ b/testing/dart/image_filter_test.dart @@ -14,9 +14,9 @@ import 'impeller_enabled.dart'; const Color red = Color(0xFFAA0000); const Color green = Color(0xFF00AA00); -const int greenCenterBlurred = 0x1C001300; -const int greenSideBlurred = 0x15000E00; -const int greenCornerBlurred = 0x10000A00; +const int greenCenterBlurred = 0x29001B00; +const int greenSideBlurred = 0x19001000; +const int greenCornerBlurred = 0x0F000A00; const int greenCenterScaled = 0xFF00AA00; const int greenSideScaled = 0x80005500; @@ -68,7 +68,7 @@ void main() async { return bytes.buffer.asUint32List(); } - ImageFilter makeBlur(double sigmaX, double sigmaY, [TileMode tileMode = TileMode.clamp]) => + ImageFilter makeBlur(double sigmaX, double sigmaY, [TileMode? tileMode]) => ImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); ImageFilter makeDilate(double radiusX, double radiusY) => @@ -106,6 +106,9 @@ void main() async { return [ makeBlur(10.0, 10.0), makeBlur(10.0, 10.0, TileMode.decal), + makeBlur(10.0, 10.0, TileMode.clamp), + makeBlur(10.0, 10.0, TileMode.mirror), + makeBlur(10.0, 10.0, TileMode.repeated), makeBlur(10.0, 20.0), makeBlur(20.0, 20.0), makeDilate(10.0, 20.0), @@ -183,6 +186,24 @@ void main() async { checkBytes(bytes, greenCenterBlurred, greenSideBlurred, greenCornerBlurred); }); + test('ImageFilter - blur toString', () async { + + var filter = makeBlur(1.9, 2.1); + expect(filter.toString(), 'ImageFilter.blur(1.9, 2.1, unspecified)'); + + filter = makeBlur(1.9, 2.1, TileMode.decal); + expect(filter.toString(), 'ImageFilter.blur(1.9, 2.1, decal)'); + + filter = makeBlur(1.9, 2.1, TileMode.clamp); + expect(filter.toString(), 'ImageFilter.blur(1.9, 2.1, clamp)'); + + filter = makeBlur(1.9, 2.1, TileMode.mirror); + expect(filter.toString(), 'ImageFilter.blur(1.9, 2.1, mirror)'); + + filter = makeBlur(1.9, 2.1, TileMode.repeated); + expect(filter.toString(), 'ImageFilter.blur(1.9, 2.1, repeated)'); + }); + test('ImageFilter - dilate', () async { final Paint paint = Paint() ..color = green @@ -279,7 +300,7 @@ void main() async { test('Composite ImageFilter toString', () { expect( ImageFilter.compose(outer: makeBlur(20.0, 20.0, TileMode.decal), inner: makeBlur(10.0, 10.0)).toString(), - contains('blur(10.0, 10.0, clamp) -> blur(20.0, 20.0, decal)'), + contains('blur(10.0, 10.0, unspecified) -> blur(20.0, 20.0, decal)'), ); // Produces a flat list of filters diff --git a/testing/dart/painting_test.dart b/testing/dart/painting_test.dart index 3fe451784bcf1..58ea2a5dd0fb5 100644 --- a/testing/dart/painting_test.dart +++ b/testing/dart/painting_test.dart @@ -109,7 +109,7 @@ void main() { redClippedPicture.dispose(); }); - Image backdropBlurWithTileMode(TileMode tileMode) { + Image backdropBlurWithTileMode(TileMode? tileMode) { Picture makePicture(CanvasCallback callback) { final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -194,6 +194,20 @@ void main() { image.dispose(); }); + test('BackdropFilter with Blur default TileMode acts as TileMode.mirror', () async { + final Image image = backdropBlurWithTileMode(null); + + final ImageComparer comparer = await ImageComparer.create(); + // It would be nice to compare the output here to the "mirror" golden + // image generated above, but this file name is where the results of + // this test will be written and the comparison will be done independently + // in a separate step. If we repeated the name of the "mirror" golden, + // we would just overwrite the results of the mirror test above. + await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_default_tile_mode.png'); + + image.dispose(); + }); + test('ImageFilter.matrix defaults to FilterQuality.medium', () { final Float64List data = Matrix4.identity().storage; expect( diff --git a/testing/dart/pubspec.yaml b/testing/dart/pubspec.yaml index 00920cf6c4be0..51b88d4c72cd1 100644 --- a/testing/dart/pubspec.yaml +++ b/testing/dart/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/testing/dart/semantics_test.dart b/testing/dart/semantics_test.dart index a5841e7d1d2fe..8b2f88921054c 100644 --- a/testing/dart/semantics_test.dart +++ b/testing/dart/semantics_test.dart @@ -22,7 +22,7 @@ void main() { }); // This must match the number of actions in lib/ui/semantics.dart - const int numSemanticsActions = 23; + const int numSemanticsActions = 24; test('SemanticsAction.values refers to all actions.', () async { expect(SemanticsAction.values.length, equals(numSemanticsActions)); for (int index = 0; index < numSemanticsActions; ++index) { diff --git a/testing/dart_isolate_runner.cc b/testing/dart_isolate_runner.cc index c0b42a34430bf..d94975117fab9 100644 --- a/testing/dart_isolate_runner.cc +++ b/testing/dart_isolate_runner.cc @@ -8,8 +8,8 @@ #include "flutter/runtime/isolate_configuration.h" -namespace flutter { -namespace testing { +namespace flutter::testing { + AutoIsolateShutdown::AutoIsolateShutdown(std::shared_ptr isolate, fml::RefPtr runner) : isolate_(std::move(isolate)), runner_(std::move(runner)) {} @@ -173,5 +173,4 @@ std::unique_ptr RunDartCodeInIsolate( return result; } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/dart_isolate_runner.h b/testing/dart_isolate_runner.h index a6dac4c316dd3..c2459c73b361b 100644 --- a/testing/dart_isolate_runner.h +++ b/testing/dart_isolate_runner.h @@ -14,8 +14,7 @@ #include "flutter/runtime/dart_vm.h" #include "flutter/runtime/dart_vm_lifecycle.h" -namespace flutter { -namespace testing { +namespace flutter::testing { class AutoIsolateShutdown { public: @@ -66,7 +65,6 @@ std::unique_ptr RunDartCodeInIsolate( fml::WeakPtr io_manager = {}, std::unique_ptr platform_configuration = nullptr); -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_DART_ISOLATE_RUNNER_H_ diff --git a/testing/debugger_detection.cc b/testing/debugger_detection.cc index daea74636d20f..2a9dadeb50b53 100644 --- a/testing/debugger_detection.cc +++ b/testing/debugger_detection.cc @@ -19,8 +19,7 @@ #include #endif // FML_OS_WIN -namespace flutter { -namespace testing { +namespace flutter::testing { DebuggerStatus GetDebuggerStatus() { #if FML_OS_MACOSX @@ -61,7 +60,6 @@ DebuggerStatus GetDebuggerStatus() { #else return DebuggerStatus::kDontKnow; #endif -} // namespace testing +} -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/debugger_detection.h b/testing/debugger_detection.h index 7745cfb319aa5..48e0e578c400a 100644 --- a/testing/debugger_detection.h +++ b/testing/debugger_detection.h @@ -7,8 +7,7 @@ #include "flutter/fml/macros.h" -namespace flutter { -namespace testing { +namespace flutter::testing { enum class DebuggerStatus { kDontKnow, @@ -17,7 +16,6 @@ enum class DebuggerStatus { DebuggerStatus GetDebuggerStatus(); -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_DEBUGGER_DETECTION_H_ diff --git a/testing/display_list_testing.cc b/testing/display_list_testing.cc index c1815e741af11..b3c6fe65ba34e 100644 --- a/testing/display_list_testing.cc +++ b/testing/display_list_testing.cc @@ -8,9 +8,11 @@ #include #include "flutter/display_list/display_list.h" +#include "flutter/display_list/effects/dl_color_filters.h" +#include "flutter/display_list/effects/dl_color_sources.h" +#include "flutter/display_list/effects/dl_image_filters.h" -namespace flutter { -namespace testing { +namespace flutter::testing { // clang-format off bool DisplayListsEQ_Verbose(const DisplayList* a, const DisplayList* b) { @@ -36,8 +38,7 @@ bool DisplayListsNE_Verbose(const DisplayList* a, const DisplayList* b) { return true; } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing namespace std { @@ -208,19 +209,6 @@ extern std::ostream& operator<<(std::ostream& os, const DlPath& path) { << ")"; } -static std::ostream& operator<<(std::ostream& os, const SkMatrix& matrix) { - return os << "SkMatrix(" - << "[" << matrix[0] << ", " << matrix[1] << ", " << matrix[2] << "], " - << "[" << matrix[3] << ", " << matrix[4] << ", " << matrix[5] << "], " - << "[" << matrix[6] << ", " << matrix[7] << ", " << matrix[8] << "]" - << ")"; -} - -static std::ostream& operator<<(std::ostream& os, const SkMatrix* matrix) { - if (matrix) return os << "&" << *matrix; - return os << "no matrix"; -} - static std::ostream& operator<<(std::ostream& os, const SkRSXform& xform) { return os << "SkRSXform(" << "scos: " << xform.fSCos << ", " @@ -386,10 +374,21 @@ std::ostream& operator<<(std::ostream& os, const DlImage* image) { return os << "isTextureBacked: " << image->isTextureBacked() << ")"; } +std::ostream& operator<<(std::ostream& os, + const flutter::DlImageFilter& filter) { + DisplayListStreamDispatcher(os, 0).out(filter); + return os; +} + +std::ostream& operator<<(std::ostream& os, + const flutter::DlColorFilter& filter) { + DisplayListStreamDispatcher(os, 0).out(filter); + return os; +} + } // namespace std -namespace flutter { -namespace testing { +namespace flutter::testing { std::ostream& DisplayListStreamDispatcher::startl() { for (int i = 0; i < cur_indent_; i++) { @@ -445,12 +444,6 @@ void DisplayListStreamDispatcher::setColorSource(const DlColorSource* source) { } startl() << "setColorSource("; switch (source->type()) { - case DlColorSourceType::kColor: { - const DlColorColorSource* color_src = source->asColor(); - FML_DCHECK(color_src); - os_ << "DlColorColorSource(" << color_src->color() << ")"; - break; - } case DlColorSourceType::kImage: { const DlImageColorSource* image_src = source->asImage(); FML_DCHECK(image_src); @@ -613,7 +606,7 @@ void DisplayListStreamDispatcher::out(const DlImageFilter& filter) { case DlImageFilterType::kErode: { const DlErodeImageFilter* erode = filter.asErode(); FML_DCHECK(erode); - os_ << "DlDilateImageFilter(" << erode->radius_x() << ", " << erode->radius_y() << ")"; + os_ << "DlErodeImageFilter(" << erode->radius_x() << ", " << erode->radius_y() << ")"; break; } case DlImageFilterType::kMatrix: { @@ -662,6 +655,14 @@ void DisplayListStreamDispatcher::out(const DlImageFilter& filter) { startl() << ")"; break; } + case flutter::DlImageFilterType::kRuntimeEffect: { + [[maybe_unused]] const DlRuntimeEffectImageFilter* runtime_effect = filter.asRuntimeEffectFilter(); + FML_DCHECK(runtime_effect); + os_ << "DlRuntimeEffectImageFilter("; + os_ << runtime_effect->samplers().size() << " samplers, "; + os_ << runtime_effect->uniform_data()->size() << " uniform bytes)"; + break; + } } } void DisplayListStreamDispatcher::out(const DlImageFilter* filter) { @@ -695,9 +696,9 @@ void DisplayListStreamDispatcher::saveLayer(const DlRect& bounds, os_ << "," << std::endl; indent(10); if (backdrop_id.has_value()) { - startl() << "backdrop: " << backdrop_id.value(); + startl() << "backdrop: " << backdrop_id.value() << ", "; } else { - startl() << "backdrop: (no id)"; + startl() << "backdrop: (no id), "; } out(backdrop); outdent(10); @@ -971,5 +972,4 @@ void DisplayListStreamDispatcher::drawShadow(const DlPath& path, } // clang-format on -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/display_list_testing.h b/testing/display_list_testing.h index 96b15e67ccd4d..17cf01d0239ea 100644 --- a/testing/display_list_testing.h +++ b/testing/display_list_testing.h @@ -7,11 +7,11 @@ #include +#include "display_list/effects/dl_image_filter.h" #include "flutter/display_list/display_list.h" #include "flutter/display_list/dl_op_receiver.h" -namespace flutter { -namespace testing { +namespace flutter::testing { [[nodiscard]] bool DisplayListsEQ_Verbose(const DisplayList* a, const DisplayList* b); @@ -36,8 +36,7 @@ namespace testing { return DisplayListsNE_Verbose(a.get(), b.get()); } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing namespace std { @@ -80,11 +79,14 @@ extern std::ostream& operator<<(std::ostream& os, extern std::ostream& operator<<(std::ostream& os, const flutter::DisplayListOpCategory& category); extern std::ostream& operator<<(std::ostream& os, const flutter::DlPath& path); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlImageFilter& type); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlColorFilter& type); } // namespace std -namespace flutter { -namespace testing { +namespace flutter::testing { class DisplayListStreamDispatcher final : public DlOpReceiver { public: @@ -197,6 +199,11 @@ class DisplayListStreamDispatcher final : public DlOpReceiver { bool transparent_occluder, DlScalar dpr) override; + void out(const DlColorFilter& filter); + void out(const DlColorFilter* filter); + void out(const DlImageFilter& filter); + void out(const DlImageFilter* filter); + private: std::ostream& os_; int cur_indent_; @@ -211,11 +218,6 @@ class DisplayListStreamDispatcher final : public DlOpReceiver { std::ostream& out_array(std::string name, int count, const T array[]); std::ostream& startl(); - - void out(const DlColorFilter& filter); - void out(const DlColorFilter* filter); - void out(const DlImageFilter& filter); - void out(const DlImageFilter* filter); }; class DisplayListGeneralReceiver : public DlOpReceiver { @@ -261,7 +263,6 @@ class DisplayListGeneralReceiver : public DlOpReceiver { case DlColorSourceType::kRuntimeEffect: RecordByType(DisplayListOpType::kSetRuntimeEffectColorSource); break; - case DlColorSourceType::kColor: case DlColorSourceType::kLinearGradient: case DlColorSourceType::kRadialGradient: case DlColorSourceType::kConicalGradient: @@ -285,6 +286,7 @@ class DisplayListGeneralReceiver : public DlOpReceiver { case DlImageFilterType::kCompose: case DlImageFilterType::kLocalMatrix: case DlImageFilterType::kColorFilter: + case DlImageFilterType::kRuntimeEffect: RecordByType(DisplayListOpType::kSetSharedImageFilter); break; } @@ -607,7 +609,6 @@ class DisplayListGeneralReceiver : public DlOpReceiver { uint32_t op_count_ = 0u; }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_DISPLAY_LIST_TESTING_H_ diff --git a/testing/elf_loader.cc b/testing/elf_loader.cc index d420d841d94b8..d7cff00b060fa 100644 --- a/testing/elf_loader.cc +++ b/testing/elf_loader.cc @@ -11,8 +11,7 @@ #include "flutter/runtime/dart_vm.h" #include "flutter/testing/testing.h" -namespace flutter { -namespace testing { +namespace flutter::testing { ELFAOTSymbols LoadELFSymbolFromFixturesIfNeccessary(std::string elf_filename) { if (!DartVM::IsRunningPrecompiledCode()) { @@ -135,5 +134,4 @@ bool PrepareSettingsForAOTWithSymbols(Settings& settings, return true; } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/elf_loader.h b/testing/elf_loader.h index fb9ff47b08182..6547ae77c216c 100644 --- a/testing/elf_loader.h +++ b/testing/elf_loader.h @@ -11,8 +11,7 @@ #include "flutter/fml/macros.h" #include "third_party/dart/runtime/bin/elf_loader.h" -namespace flutter { -namespace testing { +namespace flutter::testing { inline constexpr const char* kDefaultAOTAppELFFileName = "app_elf_snapshot.so"; @@ -81,7 +80,6 @@ ELFAOTSymbols LoadELFSplitSymbolFromFixturesIfNeccessary( bool PrepareSettingsForAOTWithSymbols(Settings& settings, const ELFAOTSymbols& symbols); -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_ELF_LOADER_H_ diff --git a/testing/fixture_test.cc b/testing/fixture_test.cc index dee760990cc7e..7d54779ff321a 100644 --- a/testing/fixture_test.cc +++ b/testing/fixture_test.cc @@ -8,8 +8,7 @@ #include "flutter/testing/dart_fixture.h" -namespace flutter { -namespace testing { +namespace flutter::testing { FixtureTest::FixtureTest() : DartFixture() {} @@ -20,5 +19,4 @@ FixtureTest::FixtureTest(std::string kernel_filename, std::move(elf_filename), std::move(elf_split_filename)) {} -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/fixture_test.h b/testing/fixture_test.h index f54bc80d28a2f..df424f0356d82 100644 --- a/testing/fixture_test.h +++ b/testing/fixture_test.h @@ -7,8 +7,7 @@ #include "flutter/testing/dart_fixture.h" -namespace flutter { -namespace testing { +namespace flutter::testing { class FixtureTest : public DartFixture, public ThreadTest { public: @@ -24,7 +23,6 @@ class FixtureTest : public DartFixture, public ThreadTest { FML_DISALLOW_COPY_AND_ASSIGN(FixtureTest); }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_FIXTURE_TEST_H_ diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt index e2fd3037a2ff0..7a7372d3fb720 100644 --- a/testing/impeller_golden_tests_output.txt +++ b/testing/impeller_golden_tests_output.txt @@ -1,5 +1,8 @@ digest.json impeller_GoldenTests_ConicalGradient.png +impeller_Play_AiksTest_AdvancedBlendColorFilterWithDestinationOpacity_Metal.png +impeller_Play_AiksTest_AdvancedBlendColorFilterWithDestinationOpacity_OpenGLES.png +impeller_Play_AiksTest_AdvancedBlendColorFilterWithDestinationOpacity_Vulkan.png impeller_Play_AiksTest_BackdropRestoreUsesCorrectCoverageForFirstRestoredClip_Metal.png impeller_Play_AiksTest_BackdropRestoreUsesCorrectCoverageForFirstRestoredClip_OpenGLES.png impeller_Play_AiksTest_BackdropRestoreUsesCorrectCoverageForFirstRestoredClip_Vulkan.png @@ -286,6 +289,9 @@ impeller_Play_AiksTest_CanRenderColoredRect_Vulkan.png impeller_Play_AiksTest_CanRenderConicalGradientWithDitheringEnabled_Metal.png impeller_Play_AiksTest_CanRenderConicalGradientWithDitheringEnabled_OpenGLES.png impeller_Play_AiksTest_CanRenderConicalGradientWithDitheringEnabled_Vulkan.png +impeller_Play_AiksTest_CanRenderConicalGradientWithIncompleteStops_Metal.png +impeller_Play_AiksTest_CanRenderConicalGradientWithIncompleteStops_OpenGLES.png +impeller_Play_AiksTest_CanRenderConicalGradientWithIncompleteStops_Vulkan.png impeller_Play_AiksTest_CanRenderConicalGradient_Metal.png impeller_Play_AiksTest_CanRenderConicalGradient_OpenGLES.png impeller_Play_AiksTest_CanRenderConicalGradient_Vulkan.png @@ -379,6 +385,9 @@ impeller_Play_AiksTest_CanRenderLinearGradientWayManyColorsClamp_Vulkan.png impeller_Play_AiksTest_CanRenderLinearGradientWithDitheringEnabled_Metal.png impeller_Play_AiksTest_CanRenderLinearGradientWithDitheringEnabled_OpenGLES.png impeller_Play_AiksTest_CanRenderLinearGradientWithDitheringEnabled_Vulkan.png +impeller_Play_AiksTest_CanRenderLinearGradientWithIncompleteStops_Metal.png +impeller_Play_AiksTest_CanRenderLinearGradientWithIncompleteStops_OpenGLES.png +impeller_Play_AiksTest_CanRenderLinearGradientWithIncompleteStops_Vulkan.png impeller_Play_AiksTest_CanRenderLinearGradientWithOverlappingStopsClamp_Metal.png impeller_Play_AiksTest_CanRenderLinearGradientWithOverlappingStopsClamp_OpenGLES.png impeller_Play_AiksTest_CanRenderLinearGradientWithOverlappingStopsClamp_Vulkan.png @@ -409,12 +418,18 @@ impeller_Play_AiksTest_CanRenderRadialGradientManyColors_Vulkan.png impeller_Play_AiksTest_CanRenderRadialGradientWithDitheringEnabled_Metal.png impeller_Play_AiksTest_CanRenderRadialGradientWithDitheringEnabled_OpenGLES.png impeller_Play_AiksTest_CanRenderRadialGradientWithDitheringEnabled_Vulkan.png +impeller_Play_AiksTest_CanRenderRadialGradientWithIncompleteStops_Metal.png +impeller_Play_AiksTest_CanRenderRadialGradientWithIncompleteStops_OpenGLES.png +impeller_Play_AiksTest_CanRenderRadialGradientWithIncompleteStops_Vulkan.png impeller_Play_AiksTest_CanRenderRadialGradient_Metal.png impeller_Play_AiksTest_CanRenderRadialGradient_OpenGLES.png impeller_Play_AiksTest_CanRenderRadialGradient_Vulkan.png impeller_Play_AiksTest_CanRenderRoundedRectWithNonUniformRadii_Metal.png impeller_Play_AiksTest_CanRenderRoundedRectWithNonUniformRadii_OpenGLES.png impeller_Play_AiksTest_CanRenderRoundedRectWithNonUniformRadii_Vulkan.png +impeller_Play_AiksTest_CanRenderRuntimeEffectFilter_Metal.png +impeller_Play_AiksTest_CanRenderRuntimeEffectFilter_OpenGLES.png +impeller_Play_AiksTest_CanRenderRuntimeEffectFilter_Vulkan.png impeller_Play_AiksTest_CanRenderSimpleClips_Metal.png impeller_Play_AiksTest_CanRenderSimpleClips_OpenGLES.png impeller_Play_AiksTest_CanRenderSimpleClips_Vulkan.png @@ -457,6 +472,9 @@ impeller_Play_AiksTest_CanRenderSweepGradientRepeat_Vulkan.png impeller_Play_AiksTest_CanRenderSweepGradientWithDitheringEnabled_Metal.png impeller_Play_AiksTest_CanRenderSweepGradientWithDitheringEnabled_OpenGLES.png impeller_Play_AiksTest_CanRenderSweepGradientWithDitheringEnabled_Vulkan.png +impeller_Play_AiksTest_CanRenderSweepGradientWithIncompleteStops_Metal.png +impeller_Play_AiksTest_CanRenderSweepGradientWithIncompleteStops_OpenGLES.png +impeller_Play_AiksTest_CanRenderSweepGradientWithIncompleteStops_Vulkan.png impeller_Play_AiksTest_CanRenderTextFrameWithFractionScaling_Metal.png impeller_Play_AiksTest_CanRenderTextFrameWithFractionScaling_OpenGLES.png impeller_Play_AiksTest_CanRenderTextFrameWithFractionScaling_Vulkan.png @@ -557,9 +575,18 @@ impeller_Play_AiksTest_CoordinateConversionsAreCorrect_Vulkan.png impeller_Play_AiksTest_CoverageOriginShouldBeAccountedForInSubpasses_Metal.png impeller_Play_AiksTest_CoverageOriginShouldBeAccountedForInSubpasses_OpenGLES.png impeller_Play_AiksTest_CoverageOriginShouldBeAccountedForInSubpasses_Vulkan.png +impeller_Play_AiksTest_DepthValuesForLineMode_Metal.png +impeller_Play_AiksTest_DepthValuesForLineMode_OpenGLES.png +impeller_Play_AiksTest_DepthValuesForLineMode_Vulkan.png +impeller_Play_AiksTest_DepthValuesForPolygonMode_Metal.png +impeller_Play_AiksTest_DepthValuesForPolygonMode_OpenGLES.png +impeller_Play_AiksTest_DepthValuesForPolygonMode_Vulkan.png impeller_Play_AiksTest_DestructiveBlendColorFilterFloodsClip_Metal.png impeller_Play_AiksTest_DestructiveBlendColorFilterFloodsClip_OpenGLES.png impeller_Play_AiksTest_DestructiveBlendColorFilterFloodsClip_Vulkan.png +impeller_Play_AiksTest_DifferenceClipsMustRenderIdenticallyAcrossBackends_Metal.png +impeller_Play_AiksTest_DifferenceClipsMustRenderIdenticallyAcrossBackends_OpenGLES.png +impeller_Play_AiksTest_DifferenceClipsMustRenderIdenticallyAcrossBackends_Vulkan.png impeller_Play_AiksTest_DispatcherDoesNotCullPerspectiveTransformedChildDisplayLists_Metal.png impeller_Play_AiksTest_DispatcherDoesNotCullPerspectiveTransformedChildDisplayLists_OpenGLES.png impeller_Play_AiksTest_DispatcherDoesNotCullPerspectiveTransformedChildDisplayLists_Vulkan.png @@ -588,6 +615,9 @@ impeller_Play_AiksTest_DrawAtlasWithColorSimple_Vulkan.png impeller_Play_AiksTest_DrawAtlasWithOpacity_Metal.png impeller_Play_AiksTest_DrawAtlasWithOpacity_OpenGLES.png impeller_Play_AiksTest_DrawAtlasWithOpacity_Vulkan.png +impeller_Play_AiksTest_DrawImageRectSrcOutsideBounds_Metal.png +impeller_Play_AiksTest_DrawImageRectSrcOutsideBounds_OpenGLES.png +impeller_Play_AiksTest_DrawImageRectSrcOutsideBounds_Vulkan.png impeller_Play_AiksTest_DrawLinesRenderCorrectly_Metal.png impeller_Play_AiksTest_DrawLinesRenderCorrectly_OpenGLES.png impeller_Play_AiksTest_DrawLinesRenderCorrectly_Vulkan.png @@ -921,6 +951,9 @@ impeller_Play_AiksTest_VerticesGeometryUVPositionDataWithTranslate_Vulkan.png impeller_Play_AiksTest_VerticesGeometryUVPositionData_Metal.png impeller_Play_AiksTest_VerticesGeometryUVPositionData_OpenGLES.png impeller_Play_AiksTest_VerticesGeometryUVPositionData_Vulkan.png +impeller_Play_BlitPassTest_CanResizeTexturesPlayground_Metal.png +impeller_Play_BlitPassTest_CanResizeTexturesPlayground_OpenGLES.png +impeller_Play_BlitPassTest_CanResizeTexturesPlayground_Vulkan.png impeller_Play_DlGoldenTest_Bug147807_Metal.png impeller_Play_DlGoldenTest_Bug147807_OpenGLES.png impeller_Play_DlGoldenTest_Bug147807_Vulkan.png @@ -945,6 +978,9 @@ impeller_Play_DlGoldenTest_GaussianVsRRectBlurScaled_Vulkan.png impeller_Play_DlGoldenTest_GaussianVsRRectBlur_Metal.png impeller_Play_DlGoldenTest_GaussianVsRRectBlur_OpenGLES.png impeller_Play_DlGoldenTest_GaussianVsRRectBlur_Vulkan.png +impeller_Play_DlGoldenTest_StrokedRRectFastBlur_Metal.png +impeller_Play_DlGoldenTest_StrokedRRectFastBlur_OpenGLES.png +impeller_Play_DlGoldenTest_StrokedRRectFastBlur_Vulkan.png impeller_Play_DlGoldenTest_TextBlurMaskFilterDisrespectCTM_Metal.png impeller_Play_DlGoldenTest_TextBlurMaskFilterDisrespectCTM_OpenGLES.png impeller_Play_DlGoldenTest_TextBlurMaskFilterDisrespectCTM_Vulkan.png diff --git a/testing/ios/IosUnitTests/App/Info.plist b/testing/ios/IosUnitTests/App/Info.plist index 885a3b1804edf..e87b8d51863b9 100644 --- a/testing/ios/IosUnitTests/App/Info.plist +++ b/testing/ios/IosUnitTests/App/Info.plist @@ -46,8 +46,6 @@ FLTLeakDartVM - FLTEnableImpeller - FLTTraceSystrace FLTEnableDartProfiling diff --git a/testing/lsan_suppressions.txt b/testing/lsan_suppressions.txt index 25f507f67d610..52ad4c64ee8e8 100644 --- a/testing/lsan_suppressions.txt +++ b/testing/lsan_suppressions.txt @@ -50,12 +50,6 @@ leak:RefCountedTest_DebugChecks_Test::TestBody # and allow some tests to inspect contents. leak:*flutter/shell/platform/linux/testing/mock_engine.cc -# TODO(bdero): Fix FL leaks: https://github.com/flutter/flutter/issues/90155 -leak:*flutter/shell/platform/linux/fl_keyboard_handler_test.cc* -leak:*flutter/shell/platform/linux/fl_key_channel_responder_test.cc* -leak:*flutter/shell/platform/linux/fl_basic_message_channel_test.cc* -leak:fl_message_codec_decode_message - # TODO(bdero): https://github.com/flutter/flutter/issues/90156 # Unfortunately, realloc calls originating from g_realloc effectively obscure # the trace of the original allocation. Deeper investigation is required to diff --git a/testing/post_task_sync.cc b/testing/post_task_sync.cc index 1a4b2a1f0671f..0861828fa6f64 100644 --- a/testing/post_task_sync.cc +++ b/testing/post_task_sync.cc @@ -6,8 +6,7 @@ #include "flutter/fml/synchronization/waitable_event.h" -namespace flutter { -namespace testing { +namespace flutter::testing { void PostTaskSync(const fml::RefPtr& task_runner, const std::function& function) { @@ -19,5 +18,4 @@ void PostTaskSync(const fml::RefPtr& task_runner, latch.Wait(); } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/post_task_sync.h b/testing/post_task_sync.h index ea9b64d0f0109..a551a485dd27f 100644 --- a/testing/post_task_sync.h +++ b/testing/post_task_sync.h @@ -7,13 +7,11 @@ #include "flutter/fml/task_runner.h" -namespace flutter { -namespace testing { +namespace flutter::testing { void PostTaskSync(const fml::RefPtr& task_runner, const std::function& function); -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_POST_TASK_SYNC_H_ diff --git a/testing/run_tests.py b/testing/run_tests.py index 3b71480f9dba9..d2cf7b0267859 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -23,7 +23,7 @@ # Explicitly import the parts of sys that are needed. This is to avoid using # sys.stdout and sys.stderr directly. Instead, only the logger defined below # should be used for output. -from sys import exit as sys_exit, platform as sys_platform, path as sys_path +from sys import exit as sys_exit, platform as sys_platform, path as sys_path, stdout as sys_stdout import tempfile import time import typing @@ -45,7 +45,8 @@ LOG_FILE = os.path.join(OUT_DIR, 'run_tests.log') logger = logging.getLogger(__name__) -console_logger_handler = logging.StreamHandler() +# Write console logs to stdout (by default StreamHandler uses stderr) +console_logger_handler = logging.StreamHandler(sys_stdout) file_logger_handler = logging.FileHandler(LOG_FILE) @@ -130,8 +131,9 @@ def run_cmd( # pylint: disable=too-many-arguments for forbidden_string in forbidden_output: if forbidden_string in output: + matches = [x.group(0) for x in re.findall(f'^.*{forbidden_string}.*$', output)] raise RuntimeError( - 'command "%s" contained forbidden string "%s"' % (command_string, forbidden_string) + f'command "{command_string}" contained forbidden string "{forbidden_string}": {matches}' ) print_divider('<') @@ -416,6 +418,7 @@ def make_test(name, flags=None, extra_env=None): return (name, flags, extra_env) unittests = [ + make_test('assets_unittests'), make_test('client_wrapper_glfw_unittests'), make_test('client_wrapper_unittests'), make_test('common_cpp_core_unittests'), @@ -427,9 +430,9 @@ def make_test(name, flags=None, extra_env=None): make_test('embedder_proctable_unittests'), make_test('embedder_unittests'), make_test('fml_unittests'), - make_test('fml_arc_unittests'), make_test('no_dart_plugin_registrant_unittests'), make_test('runtime_unittests'), + make_test('testing_unittests'), make_test('tonic_unittests'), # The image release unit test can take a while on slow machines. make_test('ui_unittests', flags=repeat_flags + ['--timeout=90']), @@ -552,7 +555,7 @@ def make_test(name, flags=None, extra_env=None): '--enable_vulkan_validation', '--enable_playground', '--playground_timeout_ms=4000', - '--gtest_filter="*ColorWheel/Vulkan"', + '--gtest_filter="*ColorWheel*"', ], coverage=coverage, extra_env=extra_env, @@ -1349,9 +1352,9 @@ def main(): assert not is_windows(), "Android engine files can't be compiled on Windows." java_filter = args.java_filter if ',' in java_filter or '*' in java_filter: - logger.wraning( + logger.warning( 'Can only filter JUnit4 tests by single entire class name, ' - 'eg "io.flutter.SmokeTest". Ignoring filter=' + java_filter + 'eg "io.flutter.SmokeTest". Ignoring filter=%s', java_filter ) java_filter = None run_java_tests(java_filter, args.android_variant) diff --git a/testing/scenario_app/android/app/src/main/AndroidManifest.xml b/testing/scenario_app/android/app/src/main/AndroidManifest.xml index 3c194a37bf9c7..20e937e294c46 100644 --- a/testing/scenario_app/android/app/src/main/AndroidManifest.xml +++ b/testing/scenario_app/android/app/src/main/AndroidManifest.xml @@ -3,7 +3,6 @@ xmlns:tools="http://schemas.android.com/tools"> args) async { osRuntime: results.option('os-runtime')!, osVersion: results.option('os-version')!, withImpeller: results.flag('with-impeller'), - withSkia: results.flag('with-skia'), dumpXcresultOnFailure: dumpXcresultOnFailurePath, ); completer.complete(); @@ -110,7 +109,6 @@ Future _run( required String osRuntime, required String osVersion, required bool withImpeller, - required bool withSkia, required String? dumpXcresultOnFailure, }) async { // Terminate early on SIGINT. @@ -135,45 +133,6 @@ Future _run( cleanup.add(() => _deleteIfPresent(resultBundle)); - if (withSkia) { - io.stderr.writeln('Running simulator tests with Skia'); - io.stderr.writeln(); - final process = await _runTests( - outScenariosPath: scenarioPath, - resultBundlePath: resultBundle.path, - osVersion: osVersion, - deviceName: deviceName, - iosEngineVariant: iosEngineVariant, - xcodeBuildExtraArgs: [ - // Plist with `FTEEnableImpeller=NO`; all projects in the workspace require this file. - // For example, `FlutterAppExtensionTestHost` has a dummy file under the below directory. - r'INFOPLIST_FILE=$(TARGET_NAME)/Info_Skia.plist', - ], - ); - cleanup.add(process.kill); - - // Create a temporary directory, if needed. - var storePath = dumpXcresultOnFailure; - if (storePath == null) { - final dumpDir = io.Directory.systemTemp.createTempSync(); - storePath = dumpDir.path; - cleanup.add(() => dumpDir.delete(recursive: true)); - } - - if (await process.exitCode != 0) { - final String outputPath = _zipAndStoreFailedTestResults( - iosEngineVariant: iosEngineVariant, - resultBundle: resultBundle, - storePath: storePath, - ); - io.stderr.writeln('Failed test results are stored at $outputPath'); - throw _ToolFailure('test failed.'); - } else { - io.stderr.writeln('test succcess.'); - } - _deleteIfPresent(resultBundle); - } - if (withImpeller) { final process = await _runTests( outScenariosPath: scenarioPath, @@ -247,15 +206,7 @@ final _args = ArgParser() ) ..addFlag( 'with-impeller', - help: 'Whether to use the Impeller backend to run the tests.\n\nCan be ' - 'combined with --with-skia to run the test suite with both backends.', - defaultsTo: true, - ) - ..addFlag( - 'with-skia', - help: - 'Whether to use the Skia backend to run the tests.\n\nCan be combined ' - 'with --with-impeller to run the test suite with both backends.', + help: 'Whether to use the Impeller backend to run the tests.', defaultsTo: true, ) ..addOption( diff --git a/testing/scenario_app/ios/Scenarios/ScenariosTests/FlutterViewControllerTest.m b/testing/scenario_app/ios/Scenarios/ScenariosTests/FlutterViewControllerTest.m index 533b4153af061..339e0d7c002b8 100644 --- a/testing/scenario_app/ios/Scenarios/ScenariosTests/FlutterViewControllerTest.m +++ b/testing/scenario_app/ios/Scenarios/ScenariosTests/FlutterViewControllerTest.m @@ -91,9 +91,7 @@ - (void)testDrawLayer { [rootVC presentViewController:self.flutterViewController animated:NO completion:nil]; CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); - - __block dispatch_block_t callback; - callback = ^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{ size_t width = 300u; CGContextRef context = CGBitmapContextCreate(nil, width, width, 8, 4 * width, color_space, @@ -104,14 +102,8 @@ - (void)testDrawLayer { [imageRendered fulfill]; return; } - CGContextRelease(context); - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), - callback); - }; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), - callback); + }); [self waitForExpectationsWithTimeout:30.0 handler:nil]; diff --git a/testing/scenario_app/lib/src/platform_view.dart b/testing/scenario_app/lib/src/platform_view.dart index 241317f3213af..ce712b64385ad 100644 --- a/testing/scenario_app/lib/src/platform_view.dart +++ b/testing/scenario_app/lib/src/platform_view.dart @@ -2089,7 +2089,7 @@ class PlatformViewWithOtherBackDropFilter extends PlatformViewScenario { final Picture picture = recorder.endRecording(); builder.addPicture(Offset.zero, picture); - final ImageFilter filter = ImageFilter.blur(sigmaX: 8, sigmaY: 8); + final ImageFilter filter = ImageFilter.blur(sigmaX: 8, sigmaY: 8, tileMode: TileMode.clamp); builder.pushBackdropFilter(filter); final PictureRecorder recorder2 = PictureRecorder(); @@ -2190,7 +2190,7 @@ class TwoPlatformViewsWithOtherBackDropFilter extends Scenario final Picture picture2 = recorder2.endRecording(); builder.addPicture(const Offset(100, 100), picture2); - final ImageFilter filter = ImageFilter.blur(sigmaX: 8, sigmaY: 8); + final ImageFilter filter = ImageFilter.blur(sigmaX: 8, sigmaY: 8, tileMode: TileMode.clamp); builder.pushBackdropFilter(filter); builder.pushOffset(0, 600); @@ -2277,7 +2277,7 @@ class PlatformViewWithNegativeBackDropFilter extends Scenario final Picture picture2 = recorder2.endRecording(); builder.addPicture(const Offset(100, 100), picture2); - final ImageFilter filter = ImageFilter.blur(sigmaX: -8, sigmaY: 8); + final ImageFilter filter = ImageFilter.blur(sigmaX: -8, sigmaY: 8, tileMode: TileMode.clamp); builder.pushBackdropFilter(filter); final Scene scene = builder.build(); diff --git a/testing/scenario_app/pubspec.yaml b/testing/scenario_app/pubspec.yaml index f729a481226f7..b720088b87369 100644 --- a/testing/scenario_app/pubspec.yaml +++ b/testing/scenario_app/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/testing/skia_gold_client/pubspec.yaml b/testing/skia_gold_client/pubspec.yaml index d0e133633eb53..6efc55b8c9180 100644 --- a/testing/skia_gold_client/pubspec.yaml +++ b/testing/skia_gold_client/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/testing/smoke_test_failure/pubspec.yaml b/testing/smoke_test_failure/pubspec.yaml index bfb5ab78aaaf5..d82f9e2cb56c5 100644 --- a/testing/smoke_test_failure/pubspec.yaml +++ b/testing/smoke_test_failure/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/testing/stream_capture.cc b/testing/stream_capture.cc index ed02282f4dc19..a249e44cc7107 100644 --- a/testing/stream_capture.cc +++ b/testing/stream_capture.cc @@ -4,8 +4,7 @@ #include "flutter/testing/stream_capture.h" -namespace flutter { -namespace testing { +namespace flutter::testing { StreamCapture::StreamCapture(std::ostream* ostream) : ostream_(ostream), old_buffer_(ostream_->rdbuf()) { @@ -27,5 +26,4 @@ std::string StreamCapture::GetOutput() const { return buffer_.str(); } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/stream_capture.h b/testing/stream_capture.h index 0b0db4863814a..d19daab80dbf4 100644 --- a/testing/stream_capture.h +++ b/testing/stream_capture.h @@ -9,8 +9,7 @@ #include #include -namespace flutter { -namespace testing { +namespace flutter::testing { // Temporarily replaces the specified stream's output buffer to capture output. // @@ -41,7 +40,6 @@ class StreamCapture { std::streambuf* old_buffer_; }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_STREAM_CAPTURE_H_ diff --git a/testing/symbols/pubspec.yaml b/testing/symbols/pubspec.yaml index cd7e2c196baf9..ee16ae2772154 100644 --- a/testing/symbols/pubspec.yaml +++ b/testing/symbols/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/testing/test_args.cc b/testing/test_args.cc index ace858cd64e7f..60c077aa091fb 100644 --- a/testing/test_args.cc +++ b/testing/test_args.cc @@ -4,8 +4,7 @@ #include "flutter/testing/test_args.h" -namespace flutter { -namespace testing { +namespace flutter::testing { static fml::CommandLine gProcessArgs; @@ -17,5 +16,4 @@ void SetArgsForProcess(int argc, char** argv) { gProcessArgs = fml::CommandLineFromArgcArgv(argc, argv); } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/test_args.h b/testing/test_args.h index 494359b060ada..9b3ea250c800b 100644 --- a/testing/test_args.h +++ b/testing/test_args.h @@ -7,14 +7,12 @@ #include "flutter/fml/command_line.h" -namespace flutter { -namespace testing { +namespace flutter::testing { const fml::CommandLine& GetArgsForProcess(); void SetArgsForProcess(int argc, char** argv); -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_TEST_ARGS_H_ diff --git a/testing/test_dart_native_resolver.cc b/testing/test_dart_native_resolver.cc index b96371f514e52..9c56a90c00272 100644 --- a/testing/test_dart_native_resolver.cc +++ b/testing/test_dart_native_resolver.cc @@ -11,8 +11,7 @@ #include "third_party/tonic/logging/dart_error.h" #include "tonic/converter/dart_converter.h" -namespace flutter { -namespace testing { +namespace flutter::testing { TestDartNativeResolver::TestDartNativeResolver() = default; @@ -124,5 +123,4 @@ void TestDartNativeResolver::SetNativeResolverForIsolate() { } } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/test_dart_native_resolver.h b/testing/test_dart_native_resolver.h index bd0a3fe0882dd..87174379f2309 100644 --- a/testing/test_dart_native_resolver.h +++ b/testing/test_dart_native_resolver.h @@ -23,8 +23,7 @@ return entrypoint; \ })() -namespace flutter { -namespace testing { +namespace flutter::testing { using NativeEntry = std::function; @@ -56,7 +55,6 @@ class TestDartNativeResolver FML_DISALLOW_COPY_AND_ASSIGN(TestDartNativeResolver); }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_TEST_DART_NATIVE_RESOLVER_H_ diff --git a/testing/test_gl_context.cc b/testing/test_gl_context.cc new file mode 100644 index 0000000000000..3e3b329d933ba --- /dev/null +++ b/testing/test_gl_context.cc @@ -0,0 +1,128 @@ +// 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. + +#include "flutter/testing/test_gl_context.h" + +#include +#include +#include + +#include + +#include "flutter/fml/logging.h" +#include "flutter/testing/test_gl_utils.h" + +namespace flutter::testing { + +namespace { +bool HasExtension(const char* extensions, const char* name) { + const char* r = strstr(extensions, name); + auto len = strlen(name); + // check that the extension name is terminated by space or null terminator + return r != nullptr && (r[len] == ' ' || r[len] == 0); +} + +void CheckSwanglekExtensions() { + const char* extensions = ::eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + FML_CHECK(HasExtension(extensions, "EGL_EXT_platform_base")) << extensions; + FML_CHECK(HasExtension(extensions, "EGL_ANGLE_platform_angle_vulkan")) + << extensions; + FML_CHECK(HasExtension(extensions, + "EGL_ANGLE_platform_angle_device_type_swiftshader")) + << extensions; +} + +EGLDisplay CreateSwangleDisplay() { + CheckSwanglekExtensions(); + + PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_EXT = + reinterpret_cast( + eglGetProcAddress("eglGetPlatformDisplayEXT")); + FML_CHECK(egl_get_platform_display_EXT) + << "eglGetPlatformDisplayEXT not available."; + + const EGLint display_config[] = { + EGL_PLATFORM_ANGLE_TYPE_ANGLE, + EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE, + EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, + EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE, + EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, + EGL_PLATFORM_VULKAN_DISPLAY_MODE_HEADLESS_ANGLE, + EGL_NONE, + }; + + return egl_get_platform_display_EXT( + EGL_PLATFORM_ANGLE_ANGLE, + reinterpret_cast(EGL_DEFAULT_DISPLAY), + display_config); +} +} // namespace + +TestEGLContext::TestEGLContext() { + display = CreateSwangleDisplay(); + FML_CHECK(display != EGL_NO_DISPLAY); + + auto result = ::eglInitialize(display, nullptr, nullptr); + FML_CHECK(result == EGL_TRUE) << GetEGLError(); + + config = {0}; + + EGLint num_config = 0; + const EGLint attribute_list[] = {EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, + EGL_SURFACE_TYPE, + EGL_PBUFFER_BIT, + EGL_CONFORMANT, + EGL_OPENGL_ES2_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_NONE}; + + result = ::eglChooseConfig(display, attribute_list, &config, 1, &num_config); + FML_CHECK(result == EGL_TRUE) << GetEGLError(); + FML_CHECK(num_config == 1) << GetEGLError(); + + { + const EGLint context_attributes[] = { + EGL_CONTEXT_CLIENT_VERSION, // + 2, // + EGL_NONE // + }; + + onscreen_context = + ::eglCreateContext(display, // display connection + config, // config + EGL_NO_CONTEXT, // sharegroup + context_attributes // context attributes + ); + FML_CHECK(onscreen_context != EGL_NO_CONTEXT) << GetEGLError(); + + offscreen_context = + ::eglCreateContext(display, // display connection + config, // config + onscreen_context, // sharegroup + context_attributes // context attributes + ); + FML_CHECK(offscreen_context != EGL_NO_CONTEXT) << GetEGLError(); + } +} + +TestEGLContext::~TestEGLContext() { + auto result = ::eglDestroyContext(display, onscreen_context); + FML_CHECK(result == EGL_TRUE) << GetEGLError(); + + result = ::eglDestroyContext(display, offscreen_context); + FML_CHECK(result == EGL_TRUE) << GetEGLError(); + + result = ::eglTerminate(display); + FML_CHECK(result == EGL_TRUE); +} + +} // namespace flutter::testing diff --git a/testing/test_gl_context.h b/testing/test_gl_context.h new file mode 100644 index 0000000000000..3cced3554ecc8 --- /dev/null +++ b/testing/test_gl_context.h @@ -0,0 +1,31 @@ +// 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. + +#ifndef FLUTTER_TESTING_TEST_GL_CONTEXT_H_ +#define FLUTTER_TESTING_TEST_GL_CONTEXT_H_ + +namespace flutter::testing { + +struct TestEGLContext { + explicit TestEGLContext(); + + ~TestEGLContext(); + + using EGLDisplay = void*; + using EGLContext = void*; + using EGLConfig = void*; + + EGLDisplay display; + EGLContext onscreen_context; + EGLContext offscreen_context; + + // EGLConfig is technically a property of the surfaces, no the context, + // but it's not that well separated in EGL (e.g. when + // EGL_KHR_no_config_context is not supported), so we just store it here. + EGLConfig config; +}; + +} // namespace flutter::testing + +#endif // FLUTTER_TESTING_TEST_GL_CONTEXT_H_ diff --git a/testing/test_gl_surface.cc b/testing/test_gl_surface.cc index 198cdc026dbee..06af8ac172684 100644 --- a/testing/test_gl_surface.cc +++ b/testing/test_gl_surface.cc @@ -5,15 +5,13 @@ #include "flutter/testing/test_gl_surface.h" #include -#include -#include #include -#include +#include #include -#include "flutter/fml/build_config.h" #include "flutter/fml/logging.h" +#include "flutter/testing/test_gl_utils.h" #include "third_party/skia/include/core/SkColorSpace.h" #include "third_party/skia/include/core/SkColorType.h" #include "third_party/skia/include/core/SkSurface.h" @@ -24,177 +22,7 @@ #include "third_party/skia/include/gpu/ganesh/gl/GrGLDirectContext.h" #include "third_party/skia/include/gpu/ganesh/gl/GrGLTypes.h" -namespace flutter { -namespace testing { - -static std::string GetEGLError() { - std::stringstream stream; - - auto error = ::eglGetError(); - - stream << "EGL Result: '"; - - switch (error) { - case EGL_SUCCESS: - stream << "EGL_SUCCESS"; - break; - case EGL_NOT_INITIALIZED: - stream << "EGL_NOT_INITIALIZED"; - break; - case EGL_BAD_ACCESS: - stream << "EGL_BAD_ACCESS"; - break; - case EGL_BAD_ALLOC: - stream << "EGL_BAD_ALLOC"; - break; - case EGL_BAD_ATTRIBUTE: - stream << "EGL_BAD_ATTRIBUTE"; - break; - case EGL_BAD_CONTEXT: - stream << "EGL_BAD_CONTEXT"; - break; - case EGL_BAD_CONFIG: - stream << "EGL_BAD_CONFIG"; - break; - case EGL_BAD_CURRENT_SURFACE: - stream << "EGL_BAD_CURRENT_SURFACE"; - break; - case EGL_BAD_DISPLAY: - stream << "EGL_BAD_DISPLAY"; - break; - case EGL_BAD_SURFACE: - stream << "EGL_BAD_SURFACE"; - break; - case EGL_BAD_MATCH: - stream << "EGL_BAD_MATCH"; - break; - case EGL_BAD_PARAMETER: - stream << "EGL_BAD_PARAMETER"; - break; - case EGL_BAD_NATIVE_PIXMAP: - stream << "EGL_BAD_NATIVE_PIXMAP"; - break; - case EGL_BAD_NATIVE_WINDOW: - stream << "EGL_BAD_NATIVE_WINDOW"; - break; - case EGL_CONTEXT_LOST: - stream << "EGL_CONTEXT_LOST"; - break; - default: - stream << "Unknown"; - } - - stream << "' (0x" << std::hex << error << std::dec << ")."; - return stream.str(); -} - -static bool HasExtension(const char* extensions, const char* name) { - const char* r = strstr(extensions, name); - auto len = strlen(name); - // check that the extension name is terminated by space or null terminator - return r != nullptr && (r[len] == ' ' || r[len] == 0); -} - -static void CheckSwanglekExtensions() { - const char* extensions = ::eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - FML_CHECK(HasExtension(extensions, "EGL_EXT_platform_base")) << extensions; - FML_CHECK(HasExtension(extensions, "EGL_ANGLE_platform_angle_vulkan")) - << extensions; - FML_CHECK(HasExtension(extensions, - "EGL_ANGLE_platform_angle_device_type_swiftshader")) - << extensions; -} - -static EGLDisplay CreateSwangleDisplay() { - CheckSwanglekExtensions(); - - PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_EXT = - reinterpret_cast( - eglGetProcAddress("eglGetPlatformDisplayEXT")); - FML_CHECK(egl_get_platform_display_EXT) - << "eglGetPlatformDisplayEXT not available."; - - const EGLint display_config[] = { - EGL_PLATFORM_ANGLE_TYPE_ANGLE, - EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE, - EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, - EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE, - EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, - EGL_PLATFORM_VULKAN_DISPLAY_MODE_HEADLESS_ANGLE, - EGL_NONE, - }; - - return egl_get_platform_display_EXT( - EGL_PLATFORM_ANGLE_ANGLE, - reinterpret_cast(EGL_DEFAULT_DISPLAY), - display_config); -} - -TestEGLContext::TestEGLContext() { - display = CreateSwangleDisplay(); - FML_CHECK(display != EGL_NO_DISPLAY); - - auto result = ::eglInitialize(display, nullptr, nullptr); - FML_CHECK(result == EGL_TRUE) << GetEGLError(); - - config = {0}; - - EGLint num_config = 0; - const EGLint attribute_list[] = {EGL_RED_SIZE, - 8, - EGL_GREEN_SIZE, - 8, - EGL_BLUE_SIZE, - 8, - EGL_ALPHA_SIZE, - 8, - EGL_SURFACE_TYPE, - EGL_PBUFFER_BIT, - EGL_CONFORMANT, - EGL_OPENGL_ES2_BIT, - EGL_RENDERABLE_TYPE, - EGL_OPENGL_ES2_BIT, - EGL_NONE}; - - result = ::eglChooseConfig(display, attribute_list, &config, 1, &num_config); - FML_CHECK(result == EGL_TRUE) << GetEGLError(); - FML_CHECK(num_config == 1) << GetEGLError(); - - { - const EGLint context_attributes[] = { - EGL_CONTEXT_CLIENT_VERSION, // - 2, // - EGL_NONE // - }; - - onscreen_context = - ::eglCreateContext(display, // display connection - config, // config - EGL_NO_CONTEXT, // sharegroup - context_attributes // context attributes - ); - FML_CHECK(onscreen_context != EGL_NO_CONTEXT) << GetEGLError(); - - offscreen_context = - ::eglCreateContext(display, // display connection - config, // config - onscreen_context, // sharegroup - context_attributes // context attributes - ); - FML_CHECK(offscreen_context != EGL_NO_CONTEXT) << GetEGLError(); - } -} - -TestEGLContext::~TestEGLContext() { - auto result = ::eglDestroyContext(display, onscreen_context); - FML_CHECK(result == EGL_TRUE) << GetEGLError(); - - result = ::eglDestroyContext(display, offscreen_context); - FML_CHECK(result == EGL_TRUE) << GetEGLError(); - - result = ::eglTerminate(display); - FML_CHECK(result == EGL_TRUE); -} +namespace flutter::testing { TestGLOnscreenOnlySurface::TestGLOnscreenOnlySurface( std::shared_ptr context, @@ -433,5 +261,4 @@ bool TestGLSurface::MakeResourceCurrent() { return result == EGL_TRUE; } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/test_gl_surface.h b/testing/test_gl_surface.h index 437c7188e6b6c..bcb4c476bfeed 100644 --- a/testing/test_gl_surface.h +++ b/testing/test_gl_surface.h @@ -8,31 +8,11 @@ #include #include "flutter/fml/macros.h" - +#include "flutter/testing/test_gl_context.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" -namespace flutter { -namespace testing { - -struct TestEGLContext { - explicit TestEGLContext(); - - ~TestEGLContext(); - - using EGLDisplay = void*; - using EGLContext = void*; - using EGLConfig = void*; - - EGLDisplay display; - EGLContext onscreen_context; - EGLContext offscreen_context; - - // EGLConfig is technically a property of the surfaces, no the context, - // but it's not that well separated in EGL (e.g. when - // EGL_KHR_no_config_context is not supported), so we just store it here. - EGLConfig config; -}; +namespace flutter::testing { class TestGLOnscreenOnlySurface { public: @@ -96,7 +76,6 @@ class TestGLSurface : public TestGLOnscreenOnlySurface { FML_DISALLOW_COPY_AND_ASSIGN(TestGLSurface); }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_TEST_GL_SURFACE_H_ diff --git a/testing/test_gl_utils.cc b/testing/test_gl_utils.cc new file mode 100644 index 0000000000000..7bc319f7aa884 --- /dev/null +++ b/testing/test_gl_utils.cc @@ -0,0 +1,74 @@ +// 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. + +#include "flutter/testing/test_gl_utils.h" + +#include + +#include + +namespace flutter::testing { + +std::string GetEGLError() { + std::stringstream stream; + + auto error = ::eglGetError(); + + stream << "EGL Result: '"; + + switch (error) { + case EGL_SUCCESS: + stream << "EGL_SUCCESS"; + break; + case EGL_NOT_INITIALIZED: + stream << "EGL_NOT_INITIALIZED"; + break; + case EGL_BAD_ACCESS: + stream << "EGL_BAD_ACCESS"; + break; + case EGL_BAD_ALLOC: + stream << "EGL_BAD_ALLOC"; + break; + case EGL_BAD_ATTRIBUTE: + stream << "EGL_BAD_ATTRIBUTE"; + break; + case EGL_BAD_CONTEXT: + stream << "EGL_BAD_CONTEXT"; + break; + case EGL_BAD_CONFIG: + stream << "EGL_BAD_CONFIG"; + break; + case EGL_BAD_CURRENT_SURFACE: + stream << "EGL_BAD_CURRENT_SURFACE"; + break; + case EGL_BAD_DISPLAY: + stream << "EGL_BAD_DISPLAY"; + break; + case EGL_BAD_SURFACE: + stream << "EGL_BAD_SURFACE"; + break; + case EGL_BAD_MATCH: + stream << "EGL_BAD_MATCH"; + break; + case EGL_BAD_PARAMETER: + stream << "EGL_BAD_PARAMETER"; + break; + case EGL_BAD_NATIVE_PIXMAP: + stream << "EGL_BAD_NATIVE_PIXMAP"; + break; + case EGL_BAD_NATIVE_WINDOW: + stream << "EGL_BAD_NATIVE_WINDOW"; + break; + case EGL_CONTEXT_LOST: + stream << "EGL_CONTEXT_LOST"; + break; + default: + stream << "Unknown"; + } + + stream << "' (0x" << std::hex << error << std::dec << ")."; + return stream.str(); +} + +} // namespace flutter::testing diff --git a/testing/test_gl_utils.h b/testing/test_gl_utils.h new file mode 100644 index 0000000000000..66f7948f75412 --- /dev/null +++ b/testing/test_gl_utils.h @@ -0,0 +1,16 @@ +// 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. + +#ifndef FLUTTER_TESTING_TEST_GL_UTILS_H_ +#define FLUTTER_TESTING_TEST_GL_UTILS_H_ + +#include + +namespace flutter::testing { + +std::string GetEGLError(); + +} // namespace flutter::testing + +#endif // FLUTTER_TESTING_TEST_GL_UTILS_H_ diff --git a/testing/test_metal_context.h b/testing/test_metal_context.h index 19e6196332a3e..5d3fb60aa628f 100644 --- a/testing/test_metal_context.h +++ b/testing/test_metal_context.h @@ -6,12 +6,17 @@ #define FLUTTER_TESTING_TEST_METAL_CONTEXT_H_ #include +#include #include +#include + #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" #include "third_party/skia/include/ports/SkCFObject.h" -namespace flutter { +namespace flutter::testing { + +struct MetalObjCFields; class TestMetalContext { public: @@ -24,9 +29,9 @@ class TestMetalContext { ~TestMetalContext(); - void* GetMetalDevice() const; + id GetMetalDevice() const; - void* GetMetalCommandQueue() const; + id GetMetalCommandQueue() const; sk_sp GetSkiaContext() const; @@ -38,14 +43,14 @@ class TestMetalContext { TextureInfo GetTextureInfo(int64_t texture_id); private: - void* device_; - void* command_queue_; + id device_; + id command_queue_; sk_sp skia_context_; std::mutex textures_mutex_; int64_t texture_id_ctr_ = 1; // guarded by textures_mutex std::map> textures_; // guarded by textures_mutex }; -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_TEST_METAL_CONTEXT_H_ diff --git a/testing/test_metal_context.mm b/testing/test_metal_context.mm index 9b5d59c07051a..7fcebbf3e84cb 100644 --- a/testing/test_metal_context.mm +++ b/testing/test_metal_context.mm @@ -8,60 +8,54 @@ #include #include "flutter/fml/logging.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" #include "third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendContext.h" #include "third_party/skia/include/gpu/ganesh/mtl/GrMtlDirectContext.h" -namespace flutter { +static_assert(__has_feature(objc_arc), "ARC must be enabled."); + +namespace flutter::testing { TestMetalContext::TestMetalContext() { - auto device = fml::scoped_nsprotocol{MTLCreateSystemDefaultDevice()}; + id device = MTLCreateSystemDefaultDevice(); if (!device) { FML_LOG(ERROR) << "Could not acquire Metal device."; return; } - auto command_queue = fml::scoped_nsobject{[device.get() newCommandQueue]}; + id command_queue = [device newCommandQueue]; if (!command_queue) { FML_LOG(ERROR) << "Could not create the default command queue."; return; } - [command_queue.get() setLabel:@"Flutter Test Queue"]; + [command_queue setLabel:@"Flutter Test Queue"]; GrMtlBackendContext backendContext = {}; // Skia expect arguments to `MakeMetal` transfer ownership of the reference in for release later // when the GrDirectContext is collected. - backendContext.fDevice.reset([device.get() retain]); - backendContext.fQueue.reset([command_queue.get() retain]); + backendContext.fDevice.retain((__bridge GrMTLHandle)device); + backendContext.fQueue.retain((__bridge GrMTLHandle)command_queue); skia_context_ = GrDirectContexts::MakeMetal(backendContext); if (!skia_context_) { FML_LOG(ERROR) << "Could not create the GrDirectContext from the Metal Device " "and command queue."; } - - device_ = [device.get() retain]; - command_queue_ = [command_queue.get() retain]; + device_ = device; + command_queue_ = command_queue; } TestMetalContext::~TestMetalContext() { std::scoped_lock lock(textures_mutex_); textures_.clear(); - if (device_) { - [(__bridge id)device_ release]; - } - if (command_queue_) { - [(__bridge id)command_queue_ release]; - } } -void* TestMetalContext::GetMetalDevice() const { +id TestMetalContext::GetMetalDevice() const { return device_; } -void* TestMetalContext::GetMetalCommandQueue() const { +id TestMetalContext::GetMetalCommandQueue() const { return command_queue_; } @@ -71,36 +65,41 @@ TestMetalContext::TextureInfo TestMetalContext::CreateMetalTexture(const SkISize& size) { std::scoped_lock lock(textures_mutex_); - auto texture_descriptor = fml::scoped_nsobject{ - [[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm - width:size.width() - height:size.height() - mipmapped:NO] retain]}; + MTLTextureDescriptor* texture_descriptor = + [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm + width:size.width() + height:size.height() + mipmapped:NO]; // The most pessimistic option and disables all optimizations but allows tests // the most flexible access to the surface. They may read and write to the // surface from shaders or use as a pixel view. - texture_descriptor.get().usage = MTLTextureUsageUnknown; + texture_descriptor.usage = MTLTextureUsageUnknown; if (!texture_descriptor) { FML_CHECK(false) << "Invalid texture descriptor."; return {.texture_id = -1, .texture = nullptr}; } - id device = (__bridge id)GetMetalDevice(); - sk_cfp texture = sk_cfp{[device newTextureWithDescriptor:texture_descriptor.get()]}; + if (!device_) { + FML_CHECK(false) << "Invalid Metal device."; + return {.texture_id = -1, .texture = nullptr}; + } + id texture = [device_ newTextureWithDescriptor:texture_descriptor]; if (!texture) { FML_CHECK(false) << "Could not create texture from texture descriptor."; return {.texture_id = -1, .texture = nullptr}; } const int64_t texture_id = texture_id_ctr_++; - textures_[texture_id] = texture; + sk_cfp texture_ptr; + texture_ptr.retain((__bridge void*)texture); + textures_[texture_id] = texture_ptr; return { .texture_id = texture_id, - .texture = texture.get(), + .texture = (__bridge void*)texture, }; } @@ -127,4 +126,4 @@ } } -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/test_metal_surface.h b/testing/test_metal_surface.h index 9d1698cef87ab..44441d815b044 100644 --- a/testing/test_metal_surface.h +++ b/testing/test_metal_surface.h @@ -11,7 +11,7 @@ #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" -namespace flutter { +namespace flutter::testing { //------------------------------------------------------------------------------ /// @brief Creates a MTLTexture backed SkSurface and context that can be @@ -53,6 +53,6 @@ class TestMetalSurface { FML_DISALLOW_COPY_AND_ASSIGN(TestMetalSurface); }; -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_TEST_METAL_SURFACE_H_ diff --git a/testing/test_metal_surface.cc b/testing/test_metal_surface.mm similarity index 88% rename from testing/test_metal_surface.cc rename to testing/test_metal_surface.mm index 15578b819133f..43f67f87569b7 100644 --- a/testing/test_metal_surface.cc +++ b/testing/test_metal_surface.mm @@ -9,7 +9,7 @@ #include "third_party/skia/include/core/SkSurface.h" -namespace flutter { +namespace flutter::testing { bool TestMetalSurface::PlatformSupportsMetal() { return true; @@ -18,16 +18,14 @@ bool TestMetalSurface::PlatformSupportsMetal() { std::unique_ptr TestMetalSurface::Create( const TestMetalContext& test_metal_context, SkISize surface_size) { - return std::make_unique(test_metal_context, - surface_size); + return std::make_unique(test_metal_context, surface_size); } std::unique_ptr TestMetalSurface::Create( const TestMetalContext& test_metal_context, int64_t texture_id, SkISize surface_size) { - return std::make_unique(test_metal_context, texture_id, - surface_size); + return std::make_unique(test_metal_context, texture_id, surface_size); } TestMetalSurface::TestMetalSurface() = default; @@ -54,4 +52,4 @@ TestMetalContext::TextureInfo TestMetalSurface::GetTextureInfo() { return impl_ ? impl_->GetTextureInfo() : TestMetalContext::TextureInfo(); } -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/test_metal_surface_impl.h b/testing/test_metal_surface_impl.h index fb96891e930ae..c3fdea24906dc 100644 --- a/testing/test_metal_surface_impl.h +++ b/testing/test_metal_surface_impl.h @@ -11,7 +11,7 @@ #include "third_party/skia/include/core/SkSurface.h" -namespace flutter { +namespace flutter::testing { class TestMetalSurfaceImpl : public TestMetalSurface { public: @@ -52,6 +52,6 @@ class TestMetalSurfaceImpl : public TestMetalSurface { FML_DISALLOW_COPY_AND_ASSIGN(TestMetalSurfaceImpl); }; -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_TEST_METAL_SURFACE_IMPL_H_ diff --git a/testing/test_metal_surface_impl.mm b/testing/test_metal_surface_impl.mm index 23c870bd0e68e..85e745020b6ca 100644 --- a/testing/test_metal_surface_impl.mm +++ b/testing/test_metal_surface_impl.mm @@ -7,7 +7,6 @@ #include #include "flutter/fml/logging.h" -#include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/testing/test_metal_context.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColorSpace.h" @@ -21,14 +20,16 @@ #include "third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendSurface.h" #include "third_party/skia/include/gpu/ganesh/mtl/GrMtlTypes.h" -namespace flutter { +static_assert(__has_feature(objc_arc), "ARC must be enabled."); + +namespace flutter::testing { void TestMetalSurfaceImpl::Init(const TestMetalContext::TextureInfo& texture_info, const SkISize& surface_size) { id texture = (__bridge id)texture_info.texture; GrMtlTextureInfo skia_texture_info; - skia_texture_info.fTexture.reset([texture retain]); + skia_texture_info.fTexture.retain((__bridge GrMTLHandle)texture); GrBackendTexture backend_texture = GrBackendTextures::MakeMtl( surface_size.width(), surface_size.height(), skgpu::Mipmapped::kNo, skia_texture_info); @@ -120,4 +121,4 @@ return IsValid() ? texture_info_ : TestMetalContext::TextureInfo(); } -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/test_metal_surface_unittests.cc b/testing/test_metal_surface_unittests.mm similarity index 84% rename from testing/test_metal_surface_unittests.cc rename to testing/test_metal_surface_unittests.mm index daf53170dc9fd..975e4b03071e0 100644 --- a/testing/test_metal_surface_unittests.cc +++ b/testing/test_metal_surface_unittests.mm @@ -6,8 +6,7 @@ #include "flutter/testing/test_metal_surface.h" #include "flutter/testing/testing.h" -namespace flutter { -namespace testing { +namespace flutter::testing { #ifdef SHELL_ENABLE_METAL @@ -28,8 +27,7 @@ TEST(TestMetalSurface, CanCreateValidTestMetalSurface) { } TestMetalContext metal_context = TestMetalContext(); - auto surface = - TestMetalSurface::Create(metal_context, SkISize::Make(100, 100)); + auto surface = TestMetalSurface::Create(metal_context, SkISize::Make(100, 100)); ASSERT_NE(surface, nullptr); ASSERT_TRUE(surface->IsValid()); ASSERT_NE(surface->GetSurface(), nullptr); @@ -38,5 +36,4 @@ TEST(TestMetalSurface, CanCreateValidTestMetalSurface) { #endif -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/test_timeout_listener.cc b/testing/test_timeout_listener.cc index 1163c921e78e4..afe442508f5fb 100644 --- a/testing/test_timeout_listener.cc +++ b/testing/test_timeout_listener.cc @@ -7,8 +7,7 @@ #include #include -namespace flutter { -namespace testing { +namespace flutter::testing { class PendingTests : public std::enable_shared_from_this { public: @@ -120,5 +119,4 @@ void TestTimeoutListener::OnTestEnd(const ::testing::TestInfo& test_info) { }); } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/test_timeout_listener.h b/testing/test_timeout_listener.h index e6f466d92df2a..3a3872a952cfd 100644 --- a/testing/test_timeout_listener.h +++ b/testing/test_timeout_listener.h @@ -12,8 +12,7 @@ #include "flutter/fml/thread.h" #include "flutter/testing/testing.h" -namespace flutter { -namespace testing { +namespace flutter::testing { class PendingTests; @@ -38,7 +37,6 @@ class TestTimeoutListener : public ::testing::EmptyTestEventListener { FML_DISALLOW_COPY_AND_ASSIGN(TestTimeoutListener); }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_TEST_TIMEOUT_LISTENER_H_ diff --git a/testing/test_vulkan_context.cc b/testing/test_vulkan_context.cc index 084ac16deee10..b600707791d2f 100644 --- a/testing/test_vulkan_context.cc +++ b/testing/test_vulkan_context.cc @@ -22,8 +22,7 @@ #include "third_party/skia/include/gpu/vk/VulkanExtensions.h" #include "vulkan/vulkan_core.h" -namespace flutter { -namespace testing { +namespace flutter::testing { TestVulkanContext::TestVulkanContext() { // --------------------------------------------------------------------------- @@ -185,5 +184,4 @@ sk_sp TestVulkanContext::GetGrDirectContext() const { return context_; } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/test_vulkan_context.h b/testing/test_vulkan_context.h index ff82a858596ab..25a9d03a203aa 100644 --- a/testing/test_vulkan_context.h +++ b/testing/test_vulkan_context.h @@ -15,8 +15,7 @@ #include "third_party/skia/include/core/SkSize.h" #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" -namespace flutter { -namespace testing { +namespace flutter::testing { class TestVulkanContext : public fml::RefCountedThreadSafe { public: @@ -42,7 +41,6 @@ class TestVulkanContext : public fml::RefCountedThreadSafe { FML_DISALLOW_COPY_AND_ASSIGN(TestVulkanContext); }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_TEST_VULKAN_CONTEXT_H_ diff --git a/testing/test_vulkan_image.cc b/testing/test_vulkan_image.cc index 4c365412ae6ec..b1c04d50473bb 100644 --- a/testing/test_vulkan_image.cc +++ b/testing/test_vulkan_image.cc @@ -6,8 +6,7 @@ #include "flutter/testing/test_vulkan_context.h" -namespace flutter { -namespace testing { +namespace flutter::testing { TestVulkanImage::TestVulkanImage() = default; @@ -20,5 +19,4 @@ VkImage TestVulkanImage::GetImage() { return image_; } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/test_vulkan_image.h b/testing/test_vulkan_image.h index e0a030c8169df..53ba6871664ea 100644 --- a/testing/test_vulkan_image.h +++ b/testing/test_vulkan_image.h @@ -11,8 +11,7 @@ #include "flutter/vulkan/procs/vulkan_handle.h" #include "third_party/skia/include/core/SkSize.h" -namespace flutter { -namespace testing { +namespace flutter::testing { class TestVulkanContext; @@ -40,7 +39,6 @@ class TestVulkanImage { friend TestVulkanContext; }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_TEST_VULKAN_IMAGE_H_ diff --git a/testing/test_vulkan_surface.cc b/testing/test_vulkan_surface.cc index b8c8cd1fba395..1c68f7a7bc3ae 100644 --- a/testing/test_vulkan_surface.cc +++ b/testing/test_vulkan_surface.cc @@ -16,8 +16,7 @@ #include "third_party/skia/include/gpu/ganesh/vk/GrVkBackendSurface.h" #include "third_party/skia/include/gpu/ganesh/vk/GrVkTypes.h" -namespace flutter { -namespace testing { +namespace flutter::testing { TestVulkanSurface::TestVulkanSurface(TestVulkanImage&& image) : image_(std::move(image)){}; @@ -110,5 +109,4 @@ VkImage TestVulkanSurface::GetImage() { return image_.GetImage(); } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/test_vulkan_surface.h b/testing/test_vulkan_surface.h index 51bed5bed07a0..f889f9f0df07a 100644 --- a/testing/test_vulkan_surface.h +++ b/testing/test_vulkan_surface.h @@ -13,9 +13,7 @@ #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" -namespace flutter { - -namespace testing { +namespace flutter::testing { class TestVulkanSurface { public: @@ -36,7 +34,6 @@ class TestVulkanSurface { sk_sp surface_; }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_TEST_VULKAN_SURFACE_H_ diff --git a/testing/testing.cc b/testing/testing.cc index a12f6159cb29d..6fb2f4e33ffdf 100644 --- a/testing/testing.cc +++ b/testing/testing.cc @@ -9,8 +9,7 @@ #include "flutter/fml/file.h" #include "flutter/fml/paths.h" -namespace flutter { -namespace testing { +namespace flutter::testing { std::string GetCurrentTestName() { return ::testing::UnitTest::GetInstance()->current_test_info()->name(); @@ -121,5 +120,4 @@ bool MemsetPatternSetOrCheck(std::vector& buffer, MemsetPatternOp op) { return MemsetPatternSetOrCheck(buffer.data(), buffer.size(), op); } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/testing.h b/testing/testing.h index 369f66fbe5214..7a14ef2638a11 100644 --- a/testing/testing.h +++ b/testing/testing.h @@ -14,8 +14,7 @@ #include "gtest/gtest.h" #include "third_party/skia/include/core/SkData.h" -namespace flutter { -namespace testing { +namespace flutter::testing { const char* GetSourcePath(); @@ -115,7 +114,6 @@ bool MemsetPatternSetOrCheck(uint8_t* buffer, size_t size, MemsetPatternOp op); bool MemsetPatternSetOrCheck(std::vector& buffer, MemsetPatternOp op); -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_TESTING_H_ diff --git a/testing/thread_test.cc b/testing/thread_test.cc index 64b082f1805c7..1cfcfbb48cf95 100644 --- a/testing/thread_test.cc +++ b/testing/thread_test.cc @@ -6,8 +6,8 @@ #include "flutter/testing/thread_test.h" -namespace flutter { -namespace testing { +namespace flutter::testing { + namespace { fml::RefPtr GetDefaultTaskRunner() { @@ -31,5 +31,4 @@ fml::RefPtr ThreadTest::CreateNewThread( return runner; } -} // namespace testing -} // namespace flutter +} // namespace flutter::testing diff --git a/testing/thread_test.h b/testing/thread_test.h index 995bd45112a05..1ba0431ca9f97 100644 --- a/testing/thread_test.h +++ b/testing/thread_test.h @@ -14,8 +14,7 @@ #include "flutter/fml/thread.h" #include "gtest/gtest.h" -namespace flutter { -namespace testing { +namespace flutter::testing { //------------------------------------------------------------------------------ /// @brief A fixture that creates threads with running message loops that @@ -65,7 +64,6 @@ class ThreadTest : public ::testing::Test { FML_DISALLOW_COPY_AND_ASSIGN(ThreadTest); }; -} // namespace testing -} // namespace flutter +} // namespace flutter::testing #endif // FLUTTER_TESTING_THREAD_TEST_H_ diff --git a/third_party/.gitignore b/third_party/.gitignore index 5bd42c1696fca..bf3e9d89a4d9d 100644 --- a/third_party/.gitignore +++ b/third_party/.gitignore @@ -1,6 +1,7 @@ # Ignore everything by default, as these come from gclient/DEPS. # We'll explicitly include the folders we want to track. /* +/boringssl/src # Include the .gitignore file itself and .clang-tidy. !.gitignore @@ -12,6 +13,7 @@ # Include folders that have hand-written code (not DEPS). !accessibility/ +!boringssl/ !canvaskit/ !spring_animation/ !test_shaders/ diff --git a/third_party/accessibility/BUILD.gn b/third_party/accessibility/BUILD.gn index 19ba407c38220..0ef1c499c2669 100644 --- a/third_party/accessibility/BUILD.gn +++ b/third_party/accessibility/BUILD.gn @@ -76,6 +76,10 @@ if (enable_unittests) { ] if (is_mac) { + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + ldflags = [ "-ObjC" ] + sources += [ "ax/platform/ax_platform_node_mac_unittest.mm" ] frameworks = [ "AppKit.framework", @@ -84,9 +88,6 @@ if (enable_unittests) { "CoreText.framework", "IOSurface.framework", ] - - cflags_objcc = flutter_cflags_objcc - ldflags = [ "-ObjC" ] } if (is_win) { sources += [ diff --git a/third_party/accessibility/ax/BUILD.gn b/third_party/accessibility/ax/BUILD.gn index 7de6ae2223ef6..b28d7bbcb7402 100644 --- a/third_party/accessibility/ax/BUILD.gn +++ b/third_party/accessibility/ax/BUILD.gn @@ -82,6 +82,9 @@ source_set("ax") { ] if (is_mac) { + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + sources += [ "platform/ax_platform_node_mac.h", "platform/ax_platform_node_mac.mm", diff --git a/third_party/accessibility/ax/platform/ax_platform_node_mac.h b/third_party/accessibility/ax/platform/ax_platform_node_mac.h index 1de7f4bc04cb2..6719056ee324c 100644 --- a/third_party/accessibility/ax/platform/ax_platform_node_mac.h +++ b/third_party/accessibility/ax/platform/ax_platform_node_mac.h @@ -8,7 +8,6 @@ #import #include "base/macros.h" -#include "base/platform/darwin/scoped_nsobject.h" #include "ax/ax_export.h" @@ -39,7 +38,7 @@ class AXPlatformNodeMac : public AXPlatformNodeBase { private: ~AXPlatformNodeMac() override; - base::scoped_nsobject native_node_; + AXPlatformNodeCocoa* native_node_; BASE_DISALLOW_COPY_AND_ASSIGN(AXPlatformNodeMac); }; diff --git a/third_party/accessibility/ax/platform/ax_platform_node_mac.mm b/third_party/accessibility/ax/platform/ax_platform_node_mac.mm index 572b2ca741266..e16b08acd8270 100644 --- a/third_party/accessibility/ax/platform/ax_platform_node_mac.mm +++ b/third_party/accessibility/ax/platform/ax_platform_node_mac.mm @@ -28,8 +28,8 @@ using ActionList = std::vector>; struct AnnouncementSpec { - base::scoped_nsobject announcement; - base::scoped_nsobject window; + NSString* announcement; + NSWindow* window; bool is_polite; }; @@ -434,8 +434,8 @@ - (NSString*)getName { return nullptr; auto announcement = std::make_unique(); - announcement->announcement = base::scoped_nsobject([announcementText retain]); - announcement->window = base::scoped_nsobject([[self AXWindowInternal] retain]); + announcement->announcement = announcementText; + announcement->window = [self AXWindowInternal]; announcement->is_polite = liveStatus != "assertive"; return announcement; } @@ -498,7 +498,7 @@ - (NSArray*)accessibilityActionNames { if (!_node) return @[]; - base::scoped_nsobject axActions([[NSMutableArray alloc] init]); + NSMutableArray* axActions = [[NSMutableArray alloc] init]; const ui::AXNodeData& data = _node->GetData(); const ActionList& action_list = GetActionList(); @@ -514,7 +514,7 @@ - (NSArray*)accessibilityActionNames { if (AlsoUseShowMenuActionForDefaultAction(data)) [axActions addObject:NSAccessibilityShowMenuAction]; - return axActions.autorelease(); + return axActions; } - (void)accessibilityPerformAction:(NSString*)action { @@ -834,8 +834,7 @@ - (BOOL)isAccessibilitySelectorAllowed:(SEL)selector { // accessibility clients from trying to set the selection range, which won't // work because of 692362. if (selector == @selector(setAccessibilitySelectedText:) || - selector == @selector(setAccessibilitySelectedTextRange:) || - selector == @selector(setAccessibilitySelectedTextMarkerRange:)) { + selector == @selector(setAccessibilitySelectedTextRange:)) { return restriction != ax::mojom::Restriction::kReadOnly; } @@ -973,9 +972,7 @@ - (NSAttributedString*)accessibilityAttributedStringForRange:(NSRange)range { if (!_node) return nil; // TODO(https://crbug.com/958811): Implement this for real. - base::scoped_nsobject attributedString( - [[NSAttributedString alloc] initWithString:[self accessibilityStringForRange:range]]); - return attributedString.autorelease(); + return [[NSAttributedString alloc] initWithString:[self accessibilityStringForRange:range]]; } - (NSData*)accessibilityRTFForRange:(NSRange)range { @@ -1085,8 +1082,8 @@ - (BOOL)isAccessibilityFocused { gfx::NativeViewAccessible AXPlatformNodeMac::GetNativeViewAccessible() { if (!native_node_) - native_node_.reset([[AXPlatformNodeCocoa alloc] initWithNode:this]); - return native_node_.get(); + native_node_ = [[AXPlatformNodeCocoa alloc] initWithNode:this]; + return native_node_; } void AXPlatformNodeMac::NotifyAccessibilityEvent(ax::mojom::Event event_type) { diff --git a/third_party/accessibility/base/BUILD.gn b/third_party/accessibility/base/BUILD.gn index f495debd1ab84..9dccdb47e3e01 100644 --- a/third_party/accessibility/base/BUILD.gn +++ b/third_party/accessibility/base/BUILD.gn @@ -42,12 +42,6 @@ source_set("base") { ] libs = [ "propsys.lib" ] } - if (is_mac) { - sources += [ - "platform/darwin/scoped_nsobject.h", - "platform/darwin/scoped_nsobject.mm", - ] - } public_deps = [ "$dart_src/third_party/double-conversion/src:libdouble_conversion", diff --git a/third_party/accessibility/base/platform/darwin/scoped_nsobject.h b/third_party/accessibility/base/platform/darwin/scoped_nsobject.h deleted file mode 100644 index 410904e71bd68..0000000000000 --- a/third_party/accessibility/base/platform/darwin/scoped_nsobject.h +++ /dev/null @@ -1,156 +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. - -#ifndef ACCESSIBILITY_BASE_PLATFORM_DARWIN_SCOPED_NSOBJECT_H_ -#define ACCESSIBILITY_BASE_PLATFORM_DARWIN_SCOPED_NSOBJECT_H_ - -// Include NSObject.h directly because Foundation.h pulls in many dependencies. -// (Approx 100k lines of code versus 1.5k for NSObject.h). scoped_nsobject gets -// singled out because it is most typically included from other header files. -#import - -#include "base/compiler_specific.h" -#include "base/macros.h" - -@class NSAutoreleasePool; - -namespace base { - -// scoped_nsobject<> is patterned after scoped_ptr<>, but maintains ownership -// of an NSObject subclass object. Style deviations here are solely for -// compatibility with scoped_ptr<>'s interface, with which everyone is already -// familiar. -// -// scoped_nsobject<> takes ownership of an object (in the constructor or in -// reset()) by taking over the caller's existing ownership claim. The caller -// must own the object it gives to scoped_nsobject<>, and relinquishes an -// ownership claim to that object. scoped_nsobject<> does not call -retain, -// callers have to call this manually if appropriate. -// -// scoped_nsprotocol<> has the same behavior as scoped_nsobject, but can be used -// with protocols. -// -// scoped_nsobject<> is not to be used for NSAutoreleasePools. For -// NSAutoreleasePools use ScopedNSAutoreleasePool from -// scoped_nsautorelease_pool.h instead. -// We check for bad uses of scoped_nsobject and NSAutoreleasePool at compile -// time with a template specialization (see below). - -template -class scoped_nsprotocol { - public: - explicit scoped_nsprotocol(NST object = nil) : object_(object) {} - - scoped_nsprotocol(const scoped_nsprotocol& that) : object_([that.object_ retain]) {} - - template - scoped_nsprotocol(const scoped_nsprotocol& that) : object_([that.get() retain]) {} - - ~scoped_nsprotocol() { [object_ release]; } - - scoped_nsprotocol& operator=(const scoped_nsprotocol& that) { - reset([that.get() retain]); - return *this; - } - - void reset(NST object = nil) { - // We intentionally do not check that object != object_ as the caller must - // either already have an ownership claim over whatever it passes to this - // method, or call it with the |RETAIN| policy which will have ensured that - // the object is retained once more when reaching this point. - [object_ release]; - object_ = object; - } - - bool operator==(NST that) const { return object_ == that; } - bool operator!=(NST that) const { return object_ != that; } - - operator NST() const { return object_; } - - NST get() const { return object_; } - - void swap(scoped_nsprotocol& that) { - NST temp = that.object_; - that.object_ = object_; - object_ = temp; - } - - // Shift reference to the autorelease pool to be released later. - NST autorelease() { return [release() autorelease]; } - - private: - NST object_; - - // scoped_nsprotocol<>::release() is like scoped_ptr<>::release. It is NOT a - // wrapper for [object_ release]. To force a scoped_nsprotocol<> to call - // [object_ release], use scoped_nsprotocol<>::reset(). - [[nodiscard]] NST release() { - NST temp = object_; - object_ = nil; - return temp; - } -}; - -// Free functions -template -void swap(scoped_nsprotocol& p1, scoped_nsprotocol& p2) { - p1.swap(p2); -} - -template -bool operator==(C p1, const scoped_nsprotocol& p2) { - return p1 == p2.get(); -} - -template -bool operator!=(C p1, const scoped_nsprotocol& p2) { - return p1 != p2.get(); -} - -template -class scoped_nsobject : public scoped_nsprotocol { - public: - explicit scoped_nsobject(NST* object = nil) : scoped_nsprotocol(object) {} - - scoped_nsobject(const scoped_nsobject& that) : scoped_nsprotocol(that) {} - - template - scoped_nsobject(const scoped_nsobject& that) : scoped_nsprotocol(that) {} - - scoped_nsobject& operator=(const scoped_nsobject& that) { - scoped_nsprotocol::operator=(that); - return *this; - } -}; - -// Specialization to make scoped_nsobject work. -template <> -class scoped_nsobject : public scoped_nsprotocol { - public: - explicit scoped_nsobject(id object = nil) : scoped_nsprotocol(object) {} - - scoped_nsobject(const scoped_nsobject& that) : scoped_nsprotocol(that) {} - - template - scoped_nsobject(const scoped_nsobject& that) : scoped_nsprotocol(that) {} - - scoped_nsobject& operator=(const scoped_nsobject& that) { - scoped_nsprotocol::operator=(that); - return *this; - } -}; - -// Do not use scoped_nsobject for NSAutoreleasePools, use -// ScopedNSAutoreleasePool instead. This is a compile time check. See details -// at top of header. -template <> -class scoped_nsobject { - private: - explicit scoped_nsobject(NSAutoreleasePool* object = nil); - BASE_DISALLOW_COPY_AND_ASSIGN(scoped_nsobject); -}; - -} // namespace base - -#endif // FLUTTER_BASE_PLATFORM_DARWIN_SCOPED_NSOBJECT_H_ diff --git a/third_party/accessibility/gfx/BUILD.gn b/third_party/accessibility/gfx/BUILD.gn index cf50601c3cf41..cd88f7050662e 100644 --- a/third_party/accessibility/gfx/BUILD.gn +++ b/third_party/accessibility/gfx/BUILD.gn @@ -51,6 +51,8 @@ source_set("gfx") { "mac/coordinate_conversion.h", "mac/coordinate_conversion.mm", ] + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc } deps = [ diff --git a/third_party/boringssl/BUILD.gn b/third_party/boringssl/BUILD.gn new file mode 100644 index 0000000000000..acf5cacfdee00 --- /dev/null +++ b/third_party/boringssl/BUILD.gn @@ -0,0 +1,93 @@ +# Copyright 2014 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/android/config.gni") +import("//build/config/arm.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build_overrides/build.gni") +import("src/gen/sources.gni") + +# Config for us and everybody else depending on BoringSSL. +config("external_config") { + include_dirs = [ "src/include" ] + if (is_component_build) { + defines = [ "BORINGSSL_SHARED_LIBRARY" ] + } +} + +# The config used by the :boringssl component itself, and the fuzzer copies. +config("component_config") { + visibility = [ ":*" ] + configs = [ ":internal_config" ] + defines = [ "BORINGSSL_IMPLEMENTATION" ] + + cflags_c = [ "-std=c17" ] +} + +# This config is used by anything that consumes internal headers. Tests consume +# this rather than :component_config. +config("internal_config") { + visibility = [ ":*" ] + defines = [ + "OPENSSL_SMALL", + "OPENSSL_STATIC_ARMCAP", + ] + if (is_posix) { + defines += [ "_XOPEN_SOURCE=700" ] + } +} + +config("no_asm_config") { + visibility = [ ":*" ] + defines = [ "OPENSSL_NO_ASM" ] +} + +# TODO(crbug.com/42290535): Move Chromium's use of libpki to the public API and +# unexport pki_internal_headers. +all_sources = bcm_internal_headers + bcm_sources + crypto_internal_headers + + crypto_sources + ssl_internal_headers + ssl_sources + pki_sources +all_headers = crypto_headers + ssl_headers + pki_headers + pki_internal_headers + +if (is_msan) { + # MSan instrumentation is incompatible with assembly optimizations. + # BoringSSL's GAS-compatible assembly knows how to detect MSan, but the NASM + # assembly does not, so we check for MSan explicitly. + source_set("boringssl_asm") { + visibility = [ ":*" ] + public_configs = [ ":no_asm_config" ] + } +} else if (is_win && (current_cpu == "x86" || current_cpu == "x64")) { + # Windows' x86 and x86_64 assembly is built with NASM. + source_set("boringssl_asm") { + visibility = [ ":*" ] + public_configs = [ ":no_asm_config" ] + } +} else { + # All other targets use GAS-compatible assembler. BoringSSL's assembly files + # are all wrapped in processor checks for the corresponding target, so there + # is no need to add target conditions in the build. + source_set("boringssl_asm") { + visibility = [ ":*" ] + sources = rebase_path(bcm_sources_asm + crypto_sources_asm, ".", "src") + include_dirs = [ "src/include" ] + } +} + +source_set("boringssl") { + sources = rebase_path(all_sources, ".", "src") + public = rebase_path(all_headers, ".", "src") + + if (is_win) { + configs += [ ":no_asm_config" ] + } else { + deps = [ ":boringssl_asm" ] + } + + public_configs = [ ":external_config" ] + configs += [ ":component_config" ] + + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] +} diff --git a/third_party/canvaskit/BUILD.gn b/third_party/canvaskit/BUILD.gn index 648dfc7fabc0f..5af031dec8386 100644 --- a/third_party/canvaskit/BUILD.gn +++ b/third_party/canvaskit/BUILD.gn @@ -93,6 +93,28 @@ wasm_toolchain("skwasm") { } } +wasm_toolchain("skwasm_st") { + extra_toolchain_args = { + # In Chromium browsers, we can use the browser's APIs to get the necessary + # ICU data. + skia_use_icu = false + skia_use_client_icu = true + skia_icu_bidi_third_party_dir = "//flutter/third_party/canvaskit/icu_bidi" + + skia_use_libjpeg_turbo_decode = false + skia_use_libpng_decode = false + skia_use_libwebp_decode = false + + # We use OffscreenCanvas to produce PNG data instead of skia + skia_use_no_png_encode = true + skia_use_libpng_encode = false + + # skwasm_st is singlethreaded + wasm_use_pthreads = false + wasm_prioritize_size = true + } +} + copy("skwasm_group") { visibility = [ "//flutter/web_sdk:*" ] public_deps = [ "//flutter/lib/web_ui/skwasm(:skwasm)" ] @@ -101,7 +123,6 @@ copy("skwasm_group") { "$root_out_dir/skwasm/skwasm.js", "$root_out_dir/skwasm/skwasm.js.symbols", "$root_out_dir/skwasm/skwasm.wasm", - "$root_out_dir/skwasm/skwasm.worker.js", ] if (is_debug) { if (!wasm_use_dwarf) { @@ -110,3 +131,20 @@ copy("skwasm_group") { } outputs = [ "$root_out_dir/flutter_web_sdk/canvaskit/{{source_file_part}}" ] } + +copy("skwasm_st_group") { + visibility = [ "//flutter/web_sdk:*" ] + public_deps = [ "//flutter/lib/web_ui/skwasm:skwasm_st(:skwasm_st)" ] + + sources = [ + "$root_out_dir/skwasm_st/skwasm_st.js", + "$root_out_dir/skwasm_st/skwasm_st.js.symbols", + "$root_out_dir/skwasm_st/skwasm_st.wasm", + ] + if (is_debug) { + if (!wasm_use_dwarf) { + sources += [ "$root_out_dir/skwasm_st/skwasm.wasm.map" ] + } + } + outputs = [ "$root_out_dir/flutter_web_sdk/canvaskit/{{source_file_part}}" ] +} diff --git a/third_party/tonic/filesystem/filesystem/file.cc b/third_party/tonic/filesystem/filesystem/file.cc index c8436c89054a3..29bd77fd37041 100644 --- a/third_party/tonic/filesystem/filesystem/file.cc +++ b/third_party/tonic/filesystem/filesystem/file.cc @@ -5,6 +5,7 @@ #include "tonic/filesystem/filesystem/file.h" #include +#include #include #include diff --git a/third_party/tonic/filesystem/filesystem/file.h b/third_party/tonic/filesystem/filesystem/file.h index be83b0f952544..0e1aece1fe664 100644 --- a/third_party/tonic/filesystem/filesystem/file.h +++ b/third_party/tonic/filesystem/filesystem/file.h @@ -5,6 +5,7 @@ #ifndef FILESYSTEM_FILE_H_ #define FILESYSTEM_FILE_H_ +#include #include #include diff --git a/third_party/txt/src/skia/paragraph_skia.cc b/third_party/txt/src/skia/paragraph_skia.cc index b3521dd529515..38065e9c595b5 100644 --- a/third_party/txt/src/skia/paragraph_skia.cc +++ b/third_party/txt/src/skia/paragraph_skia.cc @@ -191,7 +191,7 @@ class DisplayListParagraphPainter : public skt::ParagraphPainter { // rendering will be faster as it avoids software rasterization. A stroke // width of four was chosen by eyeballing the point at which the path // text looks good enough, with some room for error. - return (paint.getColorSource() && !paint.getColorSource()->asColor()) || + return paint.getColorSource() || (paint.getDrawStyle() == DlDrawStyle::kStroke && paint.getStrokeWidth() > 4); } diff --git a/third_party/txt/tests/paragraph_unittests.cc b/third_party/txt/tests/paragraph_unittests.cc index 66ce7f298a8ea..7a7385db1eefc 100644 --- a/third_party/txt/tests/paragraph_unittests.cc +++ b/third_party/txt/tests/paragraph_unittests.cc @@ -285,7 +285,7 @@ TEST_F(PainterTest, DrawTextWithGradientImpeller) { std::vector colors = {DlColor::kRed(), DlColor::kCyan()}; std::vector stops = {0.0, 1.0}; foreground.setColorSource(DlColorSource::MakeLinear( - SkPoint::Make(0, 0), SkPoint::Make(100, 100), 2, colors.data(), + DlPoint(0, 0), DlPoint(100, 100), 2, colors.data(), stops.data(), DlTileMode::kClamp)); style.foreground = foreground; @@ -306,7 +306,7 @@ TEST_F(PainterTest, DrawEmojiTextWithGradientImpeller) { std::vector colors = {DlColor::kRed(), DlColor::kCyan()}; std::vector stops = {0.0, 1.0}; foreground.setColorSource(DlColorSource::MakeLinear( - SkPoint::Make(0, 0), SkPoint::Make(100, 100), 2, colors.data(), + DlPoint(0, 0), DlPoint(100, 100), 2, colors.data(), stops.data(), DlTileMode::kClamp)); style.foreground = foreground; diff --git a/third_party/web_locale_keymap/pubspec.yaml b/third_party/web_locale_keymap/pubspec.yaml index 646fbb3b96997..2c107b6f432c8 100644 --- a/third_party/web_locale_keymap/pubspec.yaml +++ b/third_party/web_locale_keymap/pubspec.yaml @@ -3,7 +3,7 @@ name: web_locale_keymap publish_to: none environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 dev_dependencies: test: ^1.21.7 diff --git a/third_party/web_test_fonts/pubspec.yaml b/third_party/web_test_fonts/pubspec.yaml index 2042ad239ca21..511f6d635435b 100644 --- a/third_party/web_test_fonts/pubspec.yaml +++ b/third_party/web_test_fonts/pubspec.yaml @@ -3,7 +3,7 @@ name: web_test_fonts publish_to: none environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 dev_dependencies: args: any diff --git a/third_party/web_unicode/pubspec.yaml b/third_party/web_unicode/pubspec.yaml index fd57811f90f19..f003c2e14bf83 100644 --- a/third_party/web_unicode/pubspec.yaml +++ b/third_party/web_unicode/pubspec.yaml @@ -3,7 +3,7 @@ name: web_unicode publish_to: none environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 dev_dependencies: args: any diff --git a/tools/activate_emsdk.py b/tools/activate_emsdk.py index 64219942a4a97..58609986ea9fb 100644 --- a/tools/activate_emsdk.py +++ b/tools/activate_emsdk.py @@ -14,7 +14,7 @@ EMSDK_PATH = os.path.join(EMSDK_ROOT, 'emsdk.py') # See lib/web_ui/README.md for instructions on updating the EMSDK version. -EMSDK_VERSION = '3.1.44' +EMSDK_VERSION = '3.1.70' def main(): diff --git a/tools/android_lint/pubspec.yaml b/tools/android_lint/pubspec.yaml index ff929f14a07b6..8b07f881d01f5 100644 --- a/tools/android_lint/pubspec.yaml +++ b/tools/android_lint/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/api_check/pubspec.yaml b/tools/api_check/pubspec.yaml index dc35621348c89..e0818fbbf641e 100644 --- a/tools/api_check/pubspec.yaml +++ b/tools/api_check/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/build_bucket_golden_scraper/pubspec.yaml b/tools/build_bucket_golden_scraper/pubspec.yaml index 667717300ac55..27b77c79ae458 100644 --- a/tools/build_bucket_golden_scraper/pubspec.yaml +++ b/tools/build_bucket_golden_scraper/pubspec.yaml @@ -6,7 +6,7 @@ name: build_bucket_golden_scraper publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/clang_tidy/pubspec.yaml b/tools/clang_tidy/pubspec.yaml index bf9fa508f0f32..954f4c3a26fd4 100644 --- a/tools/clang_tidy/pubspec.yaml +++ b/tools/clang_tidy/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/clangd_check/pubspec.yaml b/tools/clangd_check/pubspec.yaml index 54fe7e7db938c..2225fe689919a 100644 --- a/tools/clangd_check/pubspec.yaml +++ b/tools/clangd_check/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/compare_goldens/pubspec.yaml b/tools/compare_goldens/pubspec.yaml index d606f86817571..34c6134958d92 100644 --- a/tools/compare_goldens/pubspec.yaml +++ b/tools/compare_goldens/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/const_finder/pubspec.yaml b/tools/const_finder/pubspec.yaml index 6d27db30abf72..8f825732b99ec 100644 --- a/tools/const_finder/pubspec.yaml +++ b/tools/const_finder/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/dir_contents_diff/pubspec.yaml b/tools/dir_contents_diff/pubspec.yaml index 2e51ec4e0f894..a1b32abe95ad6 100644 --- a/tools/dir_contents_diff/pubspec.yaml +++ b/tools/dir_contents_diff/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/engine_tool/BUILD.gn b/tools/engine_tool/BUILD.gn index 0171d080b2fae..23cb8c07283fc 100644 --- a/tools/engine_tool/BUILD.gn +++ b/tools/engine_tool/BUILD.gn @@ -8,6 +8,8 @@ group("tests") { testonly = true public_deps = [ ":build_command_test", + ":build_plan_test", + ":cleanup_command_test", ":entry_point_test", ":fetch_command_test", ":flutter_tools_test", @@ -40,6 +42,14 @@ dart_test("build_command_test") { main_dart = "test/commands/build_command_test.dart" } +dart_test("cleanup_command_test") { + main_dart = "test/commands/cleanup_command_test.dart" +} + +dart_test("build_plan_test") { + main_dart = "test/commands/build_plan_test.dart" +} + dart_test("entry_point_test") { main_dart = "test/entry_point_test.dart" } diff --git a/tools/engine_tool/README.md b/tools/engine_tool/README.md index 0ab53e048f2ea..582bbf5005e41 100644 --- a/tools/engine_tool/README.md +++ b/tools/engine_tool/README.md @@ -1,66 +1,385 @@ -# The Engine Tool +# Engine Tool + +🔗 Permalink: [flutter.dev/to/et](https://flutter.dev/to/et) + +--- + +`et`, or _engine tool_, is a command-line tool that intends to provide a +unified interface for building and working in the flutter engine. [![Open `e: engine-tool` issues](https://img.shields.io/github/issues/flutter/flutter/e%3A%20engine-tool)](https://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3A%22e%3A+engine-tool%22) -This is a command line Dart program that automates workflows in the -`flutter/engine` repository. + + +**Table of Contents**: + +- [Getting Started](#getting-started) +- [Understanding the concept of a build configuration](#understanding-the-concept-of-a-build-configuration) +- [Common Tasks](#common-tasks) + - [Building a host engine](#building-a-host-engine) + - [Building a target engine](#building-a-target-engine) + - [Building specific targets](#building-specific-targets) + - [Running C++ tests](#running-c-tests) + - [Running formatters](#running-formatters) + - [Running linters](#running-linters) + - [Running a Flutter app with a local engine build](#running-a-flutter-app-with-a-local-engine-build) +- [Advanced Features](#advanced-features) + - [Enabling remote build execution](#enabling-remote-build-execution) + - [Running Dart tests](#running-dart-tests) + - [Using a custom engine configuration](#using-a-custom-engine-configuration) + - [Reclaiming older output directories](#reclaiming-older-output-directories) +- [Contributing](#contributing) + +## Getting Started + +`et` assumes that you have a working knowledge of the Flutter framework and +engine (see: [architectural layers][]), and have a valid checkout of the engine +source on a supported platform (see: +[setting up the engine development environment][]); in fact, the tool will not +run at all outside of a valid repository setup. + +[architectural layers]: https://docs.flutter.dev/resources/architectural-overview#architectural-layers +[setting up the engine development environment]: https://github.com/flutter/engine/blob/main/docs/contributing/Setting-up-the-Engine-development-environment.md + +It is recommended to add `et` to your `PATH` by adding the [`bin` folder][]: + +```sh +PATH=$PATH:/path/to/engine/flutter/bin +``` + +[`bin` folder]: ../../bin + +To verify you have a working installation, try `et help`: + +```sh +$ et help +A command line tool for working on the Flutter Engine. + +This is a community supported project, file a bug or feature request: +https://flutter.dev/to/engine-tool-bug. + +Usage: et [arguments] + +Global options: +-h, --help Print this usage information. +-v, --verbose Prints verbose output + +Available commands: + build Builds the engine + fetch Download the Flutter engine's dependencies + format Formats files using standard formatters and styles. + lint Lint the engine repository. + query Provides information about build configurations and tests. + run Run a Flutter app with a local engine build. + test Runs a test target + +Run "et help " for more information about a command. +``` + +## Understanding the concept of a build configuration + +Many commands in `et` use or reference a _build configuration_, often explicitly +specified using `--config` (or the short-hand `-c`). + +A build configuration has at _least_: + +- A known platform the build can run on (`drone_dimensions`); +- Compile-time flags used to configure the build (`gn`); +- A human-readable name and description (`name`, `description`); + +Build configurations are _typically_ defined in +[`ci/builders`](../../ci/builders/), where they either live in a task-specific +configuration file (i.e. to build and run tests on CI), such as +[`mac_unopt.json`](../../ci/builders/mac_unopt.json), or one of many +configurations that are used only for local development and iteration in +[`local_engine.json`](../../ci/builders/local_engine.json). The builds specified +in these files are referenced _by name_ as `--config`: + +```sh +# Implicitly references ci/builders/mac_unopt.json +et build --config ci/host_debug_unopt_arm64 + +# Implicitly references ci/builders/local_engine.json +et build --config host_debug_unopt_arm64 +``` + +See [building a host engine](#building-a-host-engine) and +[building a target engine](#building-a-target-engine) for more details. + +> [!CAUTION] +> Each build configuration (sometimes called a _variant_) produces a different +> set of output files in `$ENGINE/src/out`, e.g. ``$ENGINE/src/out/host_debug`; +> these outputs can be multiple GBs, and add up quickly. Consider using +> [`et cleanup`](#reclaiming-older-output-directories) to delete older output +> directories automatically. + +## Common Tasks + +Tasks we expect the majority of contributors and users of the engine to need. + +### Building a host engine + +The default and most common operation is to build a _host_ variant of the +engine, or an engine that runs on the local (desktop) operating system currently +being used. For example, when running on an ARM64 macOS laptop, an ARM64 macOS +desktop engine build, or when running an x64 Linux desktop, a x64 Linux desktop +engine build. + +```sh +# Builds the current platform's (host) debug build. +et build + +# Equivalent to the above. +et build --config host_debug +``` + +> [!TIP] +> To understand where the names come from, see +> [understanding the concept of a build configuration](#understanding-the-concept-of-a-build-configuration). + +A host engine is useful when: + +- You want to test, debug, or iterate functionality independent of a specific device or platform; +- You are working on functionality specific to the (desktop) platform you currently have; +- You want to use a combination of a host engine and target engine to [run a Flutter app](#running-a-flutter-app-with-a-local-engine-build). + +### Building a target engine + +The Flutter engine supports multiple _target_ engines, or engines not native to +the _current_ desktop-class operating system, such as Android or iOS. For +example, when running on a MacOS laptop or desktop, you can build an iOS +simulator or application engine: + +```sh +# Builds an iOS device (non-simulator) engine. +et build --config ios_debug + +# Builds an iOS simulator engine. +et build --config ios_debug_sim +``` + +By convention, target engines are not prefixed with `host`. + +A target engine is useful when: + +- You are working on functionality specific to the (target) platform; +- You want to use a combination of a host engine and target engine to [run a Flutter app](#running-a-flutter-app-with-a-local-engine-build). + +### Building specific targets + +By default, the _entire engine_ is built. + +For example these commands are equivalent: + +```sh +et build --config host_debug + +et build --config host_debug //flutter/... +``` + +While caching often avoids rebuilding parts of the engine that have not changed, +sometimes you as the developer will have more information than the dependency +tree on what exactly needs to be rebuilt between changes. To build _specific_ +targets, provide the fully qualified path to the `GN` target: + +```sh +# Builds only the "flutter.jar" artifact. +et build --config android_debug_unopt_arm64 //flutter/shell/platform/android:android_jar +``` + +To build all targets in particular directory, _recursively_, use `/dirname/...`: + +```sh +# Builds all targets, recursively, in //flutter/shell/platform. +et build --config android_debug_unopt_arm64 //flutter/shell/platform/... +``` + +To build all targets in particular directory, _non-recursively_, use `:all`: + +```sh +# Builds all targets, non-recursively, in //flutter/shell/platform. +et build --config android_debug_unopt_arm64 //flutter/shell/platform:all +``` + +### Running C++ tests + +C++ unit tests can be simulatenously rebuilt and run using `et test`: + +```sh +et test //flutter/impeller:impeller_unittests +``` + +Both `/...` and `:all` are supported as well. + +> [!NOTE] +> Support for non-C++ tests is limited. See [running Dart tests](#running-dart-tests). + +### Running formatters + +To run all formatters on _changed_ files, use `et format`: + +```sh +et format +``` + +Sometimes a dependency change (or change in a tool) might invalidate the format +checks of a file you did not change (dirty): + +```sh +# Checks *all* files, which is *much* slower! +et format --all +``` + +### Running linters + +Similar to formatters, global linters can be run with `et lint`: + +```sh +et lint +``` + +At the time of this writing, the linters always operate on the entire +repository. + +### Running a Flutter app with a local engine build + +Normally to run a Flutter application with a prebuilt engine, you'd run: + +```sh +cd to/project/dir +flutter run +``` + +While iterating on the engine source, you may want to use the engine outputs +(both host and target) built above. `et run` can help: + +```sh +cd to/project/dir +et run +``` + +> [!NOTE] > `et run` will rebuild (if necessary) host and target builds, which can take +> a significant amount of time. + +## Advanced Features + +Tasks that might be restricted to a subset of the engine team, or for upstream +users (such as developers that work on the Dart VM or SDK teams). Features may +be in a somewhat incomplete state compared to common tasks, or might be a bit +less intuitive or easy to use. + +### Enabling remote build execution + +Google employees have the option of using remote-build execution, or RBE, to +greatly speed up many builds by reusing previously built (and cached) artifacts +as well as delegating the compiler to a high-powered (remote) virtual machine. + +To enable RBE, follow [flutter.dev/to/engine-rbe](https://flutter.dev/to/engine-rbe). + +Once enabled, by default, `et` builds will use RBE where possible, which also +(implicitly) requires an active internet connection. It is possible to +temporarily (for one command) change whether to prefer remote builds or +exclusively use local builds by using `--build-strategy`: + +```sh +# Exclusively builds locally, which can be faster for some incremental builds. +# Does not require an internet connection. +et build --build-strategy=local + +# Exclusively builds remotely, which is less taxing on your local machine. +# Requires a fast internet connection. +et build --build-strategy=remote +``` + +To _disable_ RBE once it is enabled, a build can use `--no-rbe`: + +```sh +et build --no-rbe +``` + +> [!CAUTION] +> Disabling RBE invalidates the build context, which means that previously built +> artifacts (when the flag was enabled) are _not_ re-used. It is recommended to +> use `--build-strategy=local` instead unless you are debugging the tool or the +> RBE configuration itself. + +### Running Dart tests + +There is limited support for running _Dart_ unittests using `et`: + +```sh +et test //flutter/tools/engine_tool/... +``` > [!NOTE] -> This tool is under development and is not yet ready for general use. Consider -> filing a [feature request](https://github.com/flutter/flutter/issues/new?labels=e:%20engine-tool,team-engine). - -## Prerequisites - -The tool requires an initial `gclient sync -D` as described in the [repo setup -steps](https://github.com/flutter/flutter/wiki/Setting-up-the-Engine-development-environment#getting-the-source) -before it will work. - -## Status - -The tool has the following commands. - -- `help` - Prints helpful information about commands and usage. -- `build` - Builds the Flutter engine. -- `fetch` - Downloads Flutter engine dependencies. -- `format` - Formats files in the engine tree using various off-the-shelf - formatters. -- `run` - Runs a flutter application with a local build of the engine. -- `query builds` - Lists the CI builds described under `ci/builders` that the - host platform is capable of executing. - -### Missing features - -There are currently many missing features. Some overall goals are listed in the -GitHub issue [here](https://github.com/flutter/flutter/issues/132807). Some -desirable new features would do the following: - -- Add a `doctor` command. -- Update the engine checkout so that engine developers no longer have to remember - to run `gclient sync -D`. -- Build and test the engine using CI configurations locally, with the - possibility to override or add new build options and targets. -- Build engines using options coming only from the command line. -- List tests and run them locally, automatically building their dependencies - first. Automatically start emulators or simulators if they're needed to run a - test. -- Spawn individual builders remotely using `led` from `depot_tools`. -- Encapsulate all code formatters, checkers, linters, etc. for all languages. -- Find a compatible version of the flutter/flutter repo, check it out, and spawn - tests from that repo with a locally built engine to run on an emulator, - simulator or device. -- Use a real logging package for prettier terminal output. -- Wire the tool up to a package providing autocomplete like - [cli_completion](https://pub.dev/packages/cli_completion.). - -The way the current tooling in the engine repo works may need to be rewritten, -especially tests spawned by `run_tests.py`, in order to provide this interface. +> Unlike C++, it is not currently required to have `BUILD.gn` targets declared +> for Dart tests, and the vast majority of packages do not have them. As we add +> and adopt GN more broadly this command will become more generally useful. + +### Using a custom engine configuration + +Most of the time developers will use a pre-configured engine configuration +(see [understanding the concept of a build configuration](#understanding-the-concept-of-a-build-configuration)) +as these configurations are already generally supported, and often tested on CI. +If you need to build a configuration _not-specified_, consider the following: + +1. Does my configuration represent a combination of flags that should be + tested on CI or re-used by others? + + If so, the best option may be adding the build to + [ci/builders](../../ci/builders/), either as a CI build or merely as a + [local engine build](../../ci/builders/local_engine.json). By adding your + build here it will be reproducible for other developers, documented, and + automatically usable within the `et` command-line tool. + +2. Is my configuration for 1-off testing or validation only? + + If so, any combination of _additional_ GN arguments (i.e. arguments that + otherwise would be parsed by [tools/gn](../gn)) can be provided by passing + `--gn-args`, often in conjunction with an existing configuration template. + + For example, using link-time optimization (LTO): + + ```sh + et build --config host_release --lto + ``` + + Or, using a from-source Dart SDK (often used by Dart SDK and VM developers): + + ```sh + et build --config host_debug --gn-args="--no-prebuilt-dart-sdk" + ``` + +> [!TIP] +> For more information on [build configurations, see the README](../../ci/builders/README.md). + +### Reclaiming older output directories + +The `et cleanup` command removes older output directories that have not been +accessed, by default in the last 30 days, but customizable with the command-line +argument `--untouched-since`. + +Consider using `dry-run` to preview what _would_ be deleted: + +```sh +# Deletes all output directories older than 30 days. +et cleanup + +# Shows what output directories would be deleted by the above command. +et cleanup --dry-run + +# Deletes all output directories accessed last in 2023. +et cleanup --untouched-since=2024-01-01 +``` ## Contributing +We welcome contributions to improve `et` for our all developers. + - Follow the [Flutter style guide](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo) for Dart code that are relevant outside of the framework repo. It contains - conventions that go beyond code formatting, which - we'll follow even if using `dart format` in the future. + conventions that go beyond code formatting, which we'll follow even if using + `dart format` in the future. - Do not call directly into `dart:io` except from `main.dart`. Instead access the system only through the `Enviroment` object. - All commands must have unit tests. If some functionality needs a fake @@ -70,8 +389,12 @@ especially tests spawned by `run_tests.py`, in order to provide this interface. by this tool _should_ be, then modify underlying scripts and tools to provide APIs to support that. -Run tests using `//flutter/testing/run_tests.py`: +Run tests using `et`: ```shell -testing/run_tests.py --type dart-host --dart-host-filter flutter/tools/engine_tool +et test //flutter/tools/engine_tool/... ``` + +If you're not sure what to work on, consider our existing label of `e: engine-tool`: + +[![Open `e: engine-tool` issues](https://img.shields.io/github/issues/flutter/flutter/e%3A%20engine-tool)](https://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3A%22e%3A+engine-tool%22) diff --git a/tools/engine_tool/lib/src/build_plan.dart b/tools/engine_tool/lib/src/build_plan.dart index ccfbfeb20e766..3aab6ec0f02b6 100644 --- a/tools/engine_tool/lib/src/build_plan.dart +++ b/tools/engine_tool/lib/src/build_plan.dart @@ -6,6 +6,7 @@ import 'package:args/args.dart'; import 'package:collection/collection.dart'; import 'package:engine_build_configs/engine_build_configs.dart'; import 'package:meta/meta.dart'; +import 'package:platform/platform.dart'; import 'build_utils.dart'; import 'environment.dart'; @@ -16,6 +17,7 @@ const _flagConcurrency = 'concurrency'; const _flagStrategy = 'build-strategy'; const _flagRbe = 'rbe'; const _flagLto = 'lto'; +const _flagExtraGnArgs = 'gn-args'; /// Describes what (platform, targets) and how (strategy, options) to build. /// @@ -75,6 +77,11 @@ final class BuildPlan { } throw FatalError('Invalid value for --$_flagConcurrency: $value'); }(), + extraGnArgs: () { + final value = args.multiOption(_flagExtraGnArgs); + _checkExtraGnArgs(value); + return value; + }(), ); } @@ -84,7 +91,8 @@ final class BuildPlan { required this.useRbe, required this.useLto, required this.concurrency, - }) { + required Iterable extraGnArgs, + }) : extraGnArgs = List.unmodifiable(extraGnArgs) { if (!useRbe && strategy == BuildStrategy.remote) { throw FatalError( 'Cannot use remote builds without RBE enabled.\n\n$_rbeInstructions', @@ -92,6 +100,54 @@ final class BuildPlan { } } + /// Arguments that cannot be provided to [BuildPlan.extraGnArgs]. + /// + /// Instead, provide them explicitly as other [BuildPlan] arguments. + @visibleForTesting + static const reservedGnArgs = { + _flagRbe, + _flagLto, + 'no-$_flagRbe', + 'no-$_flagLto', + // If we are to expand this list to include flags that are not a 1:1 mapping + // - for example we want to reserve "--foo-bar" but it's called "--use-baz" + // in "et", let's (a) re-think having these arguments named differently and + // (b) if necessary, consider changing this set to a map instead so a clear + // error can be presented below. + }; + + /// Error thrown when [reservedGnArgs] are used as [extraGnArgs]. + @visibleForTesting + static final reservedGnArgsError = FatalError( + 'Flags such as ${reservedGnArgs.join(', ')} should be specified as ' + 'direct arguments to "et" and not using "--gn-args". For example, ' + '`et build --no-lto` instead of `et build --gn-args="--no-lto"`.', + ); + + /// Error thrown when a non-flag argument is provided as [extraGnArgs]. + @visibleForTesting + static final argumentsMustBeFlagsError = FatalError( + 'Arguments provided to --gn-args must be flags (booleans) and be ' + 'specified as either in the format "--flag" or "--no-flag". Options ' + 'that are not flags or are abberviated ("-F") are not currently ' + 'supported; consider filing a request: ' + 'https://fluter.dev/to/engine-tool-bug.', + ); + + static void _checkExtraGnArgs(Iterable gnArgs) { + for (final arg in gnArgs) { + if (!arg.startsWith('--') || arg.contains('=') || arg.contains(' ')) { + throw argumentsMustBeFlagsError; + } + + // Strip off the prefix and compare it to reserved flags. + final withoutPrefix = arg.replaceFirst('--', ''); + if (reservedGnArgs.contains(withoutPrefix)) { + throw reservedGnArgsError; + } + } + } + static String _defaultHostDebug() { return 'host_debug'; } @@ -107,28 +163,40 @@ final class BuildPlan { required Map configs, }) { // Add --config. - final builds = runnableBuilds( - environment, - configs, - environment.verbose || !help, + final builds = _extractBuilds( + environment.platform, + runnableConfigs: _runnableBuildConfigs( + environment.platform, + configsByName: configs, + ), + hideCiSpecificBuilds: help && !environment.verbose, ); debugCheckBuilds(builds); parser.addOption( _flagConfig, abbr: 'c', - defaultsTo: () { - if (builds.any((b) => b.name == 'host_debug')) { - return 'host_debug'; - } - return null; - }(), + help: '' + 'Selects a build configuration for the current platform.\n' + '\n' + 'If omitted, et attempts ' + 'to default to a suitable target platform. This is typically a ' + '"host_debug" build when building on a supported desktop OS, or a ' + 'suitable build when targeting (via "et run") a flutter app.\n' + '\n' + '${environment.verbose ? '' + 'Since verbose mode was selected, both local development ' + 'configurations and configurations that are typically only ' + 'used on CI will be visible, including possible duplicates.' : '' + 'Configurations include (use --verbose for more details):'}', allowed: [ for (final config in builds) mangleConfigName(environment, config.name), - ], - allowedHelp: { - for (final config in builds) - mangleConfigName(environment, config.name): config.description, - }, + ]..sort(), + allowedHelp: environment.verbose + ? { + for (final config in builds) + mangleConfigName(environment, config.name): config.description, + } + : null, ); // Add --lto. @@ -175,6 +243,18 @@ final class BuildPlan { help: 'How many jobs to run in parallel.', ); + // Add --gn-args. + parser.addMultiOption( + _flagExtraGnArgs, + help: '' + 'Additional arguments to provide to "gn".\n' + 'GN arguments change the parameters of the compiler and invalidate ' + 'the current build, and should be used sparingly. If there is an ' + 'engine build that should be reused and tested on CI prefer adding ' + 'the arguments to "//flutter/ci/builders/local_engine.json".', + hide: !environment.verbose, + ); + return builds; } @@ -201,6 +281,13 @@ final class BuildPlan { /// Whether to build with LTO (link-time optimization). final bool useLto; + /// Additional GN arguments to use for a build. + /// + /// By contract, these arguments are always strictly _flags_ (not options), + /// and specified as either `--flag`, `-F`, or as the negative variant (such + /// as `--no-flag`). + final List extraGnArgs; + @override bool operator ==(Object other) { return other is BuildPlan && @@ -208,12 +295,20 @@ final class BuildPlan { strategy == other.strategy && useRbe == other.useRbe && useLto == other.useLto && - concurrency == other.concurrency; + concurrency == other.concurrency && + const ListEquality().equals(extraGnArgs, other.extraGnArgs); } @override int get hashCode { - return Object.hash(build.name, strategy, useRbe, useLto, concurrency); + return Object.hash( + build.name, + strategy, + useRbe, + useLto, + concurrency, + Object.hashAll(extraGnArgs), + ); } /// Converts this build plan to its equivalent [RbeConfig]. @@ -236,6 +331,7 @@ final class BuildPlan { return [ if (!useRbe) '--no-rbe', if (useLto) '--lto' else '--no-lto', + ...extraGnArgs, ]; } @@ -248,6 +344,7 @@ final class BuildPlan { buffer.writeln(' useRbe: $useRbe'); buffer.writeln(' strategy: $strategy'); buffer.writeln(' concurrency: $concurrency'); + buffer.writeln(' extraGnArgs: $extraGnArgs'); buffer.write('>'); return buffer.toString(); } @@ -271,9 +368,47 @@ enum BuildStrategy { remote( 'Use remote builds.' '\n' - 'If --$_flagStrategy is not specified, the build will fail.', + 'If --$_flagRbe is not specified, the build will fail.', ); const BuildStrategy(this._help); final String _help; } + +typedef _ConfigsByName = Iterable>; + +/// Computes a list of build configs that can can execute on [environment]. +_ConfigsByName _runnableBuildConfigs( + Platform platform, { + required Map configsByName, +}) { + return configsByName.entries.where((entry) { + return entry.value.canRunOn(platform); + }); +} + +/// Extracts [Build]s from [runnableConfigs] that can execute on [platform]. +/// +/// If [hideCiSpecificBuilds], builds that are unlikely to be picked for local +/// development (i.e. start with the prefix `ci/` by convention) are not +/// returned in order to make command-line _help_ text shorter. +List _extractBuilds( + Platform platform, { + required _ConfigsByName runnableConfigs, + required bool hideCiSpecificBuilds, +}) { + return [ + for (final buildConfig in runnableConfigs) + ...buildConfig.value.builds.where( + (build) { + if (!build.canRunOn(platform)) { + return false; + } + if (!hideCiSpecificBuilds) { + return true; + } + return build.name.startsWith(platform.operatingSystem); + }, + ), + ]; +} diff --git a/tools/engine_tool/lib/src/build_utils.dart b/tools/engine_tool/lib/src/build_utils.dart index 3a25b56df5fb5..5a99a0f3578c0 100644 --- a/tools/engine_tool/lib/src/build_utils.dart +++ b/tools/engine_tool/lib/src/build_utils.dart @@ -11,51 +11,6 @@ import 'environment.dart'; import 'label.dart'; import 'logger.dart'; -/// A function that returns true or false when given a [BuilderConfig] and its -/// name. -typedef ConfigFilter = bool Function(String name, BuilderConfig config); - -/// A function that returns true or false when given a [BuilderConfig] name -/// and a [Build]. -typedef BuildFilter = bool Function(String configName, Build build); - -/// Returns a filtered copy of [input] filtering out configs where test -/// returns false. -Map filterBuilderConfigs( - Map input, ConfigFilter test) { - return { - for (final MapEntry entry in input.entries) - if (test(entry.key, entry.value)) entry.key: entry.value, - }; -} - -/// Returns a copy of [input] filtering out configs that are not runnable -/// on the current platform. -Map runnableBuilderConfigs( - Environment env, Map input) { - return filterBuilderConfigs(input, (String name, BuilderConfig config) { - return config.canRunOn(env.platform); - }); -} - -/// Returns a List of [Build] that match test. -List filterBuilds(Map input, BuildFilter test) { - return [ - for (final MapEntry entry in input.entries) - for (final Build build in entry.value.builds) - if (test(entry.key, build)) build, - ]; -} - -/// Returns a list of runnable builds. -List runnableBuilds( - Environment env, Map input, bool verbose) { - return filterBuilds(input, (String configName, Build build) { - return build.canRunOn(env.platform) && - (verbose || build.name.startsWith(env.platform.operatingSystem)); - }); -} - /// Validates the list of builds. /// Calls assert. void debugCheckBuilds(List builds) { @@ -99,8 +54,10 @@ String mangleConfigName(Environment env, String name) { if (_doNotMangle(env, name)) { return name; } - throw ArgumentError( - 'name argument "$name" must start with a valid platform name or "ci"', + throw ArgumentError.value( + name, + 'name', + 'Expected to start with a valid platform name (i.e. $osPrefix) or "ci/"', ); } diff --git a/tools/engine_tool/lib/src/commands/cleanup_command.dart b/tools/engine_tool/lib/src/commands/cleanup_command.dart new file mode 100644 index 0000000000000..894c2f14959b7 --- /dev/null +++ b/tools/engine_tool/lib/src/commands/cleanup_command.dart @@ -0,0 +1,164 @@ +// 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:async'; +import 'dart:io'; + +import 'package:path/path.dart' as p; + +import '../logger.dart'; +import 'command.dart'; +import 'flags.dart'; + +/// The `cleanup` command for removing unnecessary on-disk artifacts. +final class CleanupCommand extends CommandBase { + /// Constructs the `cleanup` command. + CleanupCommand({ + required super.environment, + super.help = false, + super.usageLineLength, + }) { + argParser.addFlag( + dryRunFlag, + abbr: 'd', + help: 'Write changes to stdout without modifying the file system.', + negatable: false, + ); + + argParser.addOption( + 'untouched-since', + defaultsTo: () { + const thirtyDays = Duration(days: 30); + final dateTime = environment.now().subtract(thirtyDays); + return _toDateString(dateTime); + }(), + help: 'What date to consider artifacts old enough to safely remove.', + valueHelp: 'YYYY-MM-DD', + ); + } + + @override + String get name => 'cleanup'; + + @override + String get description => 'Removes stale or unnecessary on-disk artifacts.'; + + @override + List get aliases => const ['gc']; + + static final _dateString = RegExp(r'^(\d{4})-(\d{2})-(\d{2})$'); + + @override + Future run() async { + final dryRun = argResults!.flag('dry-run'); + final since = () { + final yyyyMmDd = argResults!.option('untouched-since')!; + final dateMatch = _dateString.matchAsPrefix(yyyyMmDd); + if (dateMatch == null) { + throw FatalError('Invalid --untouched-since: $yyyyMmDd'); + } + return DateTime( + int.parse(dateMatch.group(1)!), + int.parse(dateMatch.group(2)!), + int.parse(dateMatch.group(3)!), + ); + }(); + + // Look at the directories in "out" for ones older than "since". + environment.logger.status('Checking ${environment.engine.outDir.path}...'); + final toDelete = [ + await for (final entity in environment.engine.outDir.list()) + if (entity is Directory && + _shouldDelete(entity, ifAccessedLaterThan: since)) + entity + ]..sort((a, b) => a.path.compareTo(b.path)); + + if (toDelete.isEmpty) { + environment.logger.status( + 'No directories were accessed later than ${_toDateString(since)}.', + ); + return 0; + } + + final totalSize = toDelete.fold(0, (p, n) => p + _getSizeRecursive(n)); + + if (dryRun) { + environment.logger.status( + 'The following directories were accessed later than ${_toDateString(since)}:', + ); + for (final e in toDelete) { + environment.logger.status(' ${p.basename(e.path)}'); + } + environment.logger.status( + 'Run without --dry-run to reclaim ' + '${_toReadableBytes(totalSize.toDouble())}.', + ); + return 0; + } + + final spinner = environment.logger.startSpinner(); + for (final e in toDelete) { + try { + await e.delete(recursive: true); + } on FileSystemException catch (_) { + environment.logger.warning('Failed to delete ${p.basename(e.path)}'); + } + } + spinner.finish(); + + environment.logger.status( + 'Deleted ${toDelete.length} output directories and reclaimed ' + '${_toReadableBytes(totalSize.toDouble())}.', + ); + + return 0; + } + + static bool _shouldDelete( + Directory entity, { + required DateTime ifAccessedLaterThan, + }) { + final accessed = entity.statSync().accessed; + return accessed.isBefore(ifAccessedLaterThan); + } +} + +int _getSizeRecursive(Directory dir) { + return dir.listSync().fold(0, (p, n) => p + n.statSync().size); +} + +String _toDateString(DateTime dateTime) { + final output = StringBuffer('${dateTime.year}-'); + output.write(dateTime.month.toString().padLeft(2, '0')); + output.write('-'); + output.write(dateTime.day.toString().padLeft(2, '0')); + return output.toString(); +} + +String _toReadableBytes(double bytes) { + var type = _FileSize.bytes; + if (bytes >= 1024) { + type = _FileSize.kilobytes; + bytes = bytes / 1024; + } + if (bytes >= 1024) { + type = _FileSize.megabytes; + bytes = bytes / 1024; + } + if (bytes >= 1024) { + type = _FileSize.gigabytes; + bytes = bytes / 1024; + } + return '${bytes.toStringAsFixed(2)}${type.suffix}'; +} + +enum _FileSize { + bytes('bytes'), + kilobytes('KB'), + megabytes('MB'), + gigabytes('GB'); + + const _FileSize(this.suffix); + final String suffix; +} diff --git a/tools/engine_tool/lib/src/commands/command_runner.dart b/tools/engine_tool/lib/src/commands/command_runner.dart index 454cfa2edaed3..04fe0ba849766 100644 --- a/tools/engine_tool/lib/src/commands/command_runner.dart +++ b/tools/engine_tool/lib/src/commands/command_runner.dart @@ -7,6 +7,7 @@ import 'package:engine_build_configs/engine_build_configs.dart'; import '../environment.dart'; import 'build_command.dart'; +import 'cleanup_command.dart'; import 'fetch_command.dart'; import 'flags.dart'; import 'format_command.dart'; @@ -25,8 +26,19 @@ final class ToolCommandRunner extends CommandRunner { required this.environment, required this.configs, this.help = false, - }) : super(toolName, toolDescription, usageLineLength: _usageLineLength) { + }) : super( + 'et', + '' + 'A command line tool for working on ' + 'the Flutter Engine.\n\nThis is a community supported project, ' + 'for more information see https://flutter.dev/to/et.', + usageLineLength: _usageLineLength, + ) { final List> commands = >[ + CleanupCommand( + environment: environment, + usageLineLength: _usageLineLength, + ), FetchCommand( environment: environment, usageLineLength: _usageLineLength, @@ -73,16 +85,6 @@ final class ToolCommandRunner extends CommandRunner { ); } - /// The name of the tool as reported in the tool's usage and help - /// messages. - static const String toolName = 'et'; - - /// The description of the tool reported in the tool's usage and help - /// messages. - static const String toolDescription = 'A command line tool for working on ' - 'the Flutter Engine.\n\nThis is a community supported project, file ' - 'a bug or feature request: https://flutter.dev/to/engine-tool-bug.'; - /// The host system environment. final Environment environment; diff --git a/tools/engine_tool/lib/src/environment.dart b/tools/engine_tool/lib/src/environment.dart index 94bedd1e14f5f..0a1b13a5e350b 100644 --- a/tools/engine_tool/lib/src/environment.dart +++ b/tools/engine_tool/lib/src/environment.dart @@ -26,6 +26,7 @@ final class Environment { required this.logger, required this.platform, required this.processRunner, + this.now = DateTime.now, this.verbose = false, }); @@ -47,6 +48,9 @@ final class Environment { /// Facility for commands to run subprocesses. final ProcessRunner processRunner; + /// Returns the current [Datetime]. + final DateTime Function() now; + /// Whether it appears that the current environment supports remote builds. /// /// This is a heuristic based on the presence of certain directories in the diff --git a/tools/engine_tool/lib/src/logger.dart b/tools/engine_tool/lib/src/logger.dart index 0892487197317..d642769aa5388 100644 --- a/tools/engine_tool/lib/src/logger.dart +++ b/tools/engine_tool/lib/src/logger.dart @@ -31,8 +31,7 @@ class Logger { /// Constructs a logger for use in the tool. Logger({ log.Level level = statusLevel, - }) - : _logger = log.Logger.detached('et'), + }) : _logger = log.Logger.detached('et'), _test = false { _logger.level = level; _logger.onRecord.listen(_handler); @@ -45,8 +44,7 @@ class Logger { Logger.test( void Function(log.LogRecord) onLog, { log.Level level = statusLevel, - }) - : _logger = log.Logger.detached('et'), + }) : _logger = log.Logger.detached('et'), _test = true { _logger.level = level; _logger.onRecord.listen(onLog); @@ -187,6 +185,7 @@ class Logger { } /// Starts printing a progress spinner. + @useResult Spinner startSpinner({ void Function()? onFinish, }) { @@ -367,7 +366,7 @@ class FlutterSpinner extends Spinner { } /// FatalErrors are thrown when a fatal error has occurred. -class FatalError extends Error { +final class FatalError extends Error { /// Constructs a FatalError with a message. FatalError(this._message); diff --git a/tools/engine_tool/pubspec.yaml b/tools/engine_tool/pubspec.yaml index fa42d1596614d..c413e5d9a9a70 100644 --- a/tools/engine_tool/pubspec.yaml +++ b/tools/engine_tool/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/engine_tool/test/build_plan_test.dart b/tools/engine_tool/test/build_plan_test.dart index 529641fa2d8e4..20d8a36d63d0a 100644 --- a/tools/engine_tool/test/build_plan_test.dart +++ b/tools/engine_tool/test/build_plan_test.dart @@ -5,6 +5,7 @@ import 'package:args/args.dart'; import 'package:engine_build_configs/engine_build_configs.dart'; import 'package:engine_tool/src/build_plan.dart'; +import 'package:engine_tool/src/environment.dart'; import 'package:engine_tool/src/logger.dart'; import 'package:test/test.dart'; @@ -12,34 +13,37 @@ import 'src/test_build_configs.dart'; import 'src/utils.dart'; void main() { - test('rbe defaults to true if detected', () { - final testEnv = TestEnvironment.withTestEngine( - withRbe: true, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, + /// Configures and parses [BuildPlan] from [args]. + /// + /// By default a non-verbose, non-RBE, Linux x64 configured [BuildPlan] + /// is created and used to configure the available options and instructions + /// for the returned [ArgParser]. + /// + /// - To use a different [TestEnvironment], provide [environment]. + /// - To use a different set of builds, provide [builds]. + BuildPlan configureAndParse( + List args, { + TestEnvironment? environment, + void Function(TestBuilderConfig)? builds, + }) { + final (builders, parser, env) = _createTestFixture( + environment: environment, + builds: builds, ); - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, + return BuildPlan.fromArgResults( + parser.parse(args), + env, + builds: builders, ); + } - final plan = BuildPlan.fromArgResults( - parser.parse([]), - testEnv.environment, - builds: builds, + test('rbe defaults to true if detected', () { + final plan = configureAndParse( + [], + environment: TestEnvironment.withTestEngine( + withRbe: true, + ), ); expect(plan.useRbe, isTrue); @@ -47,172 +51,35 @@ void main() { }); test('rbe defaults to false if not detected', () { - final testEnv = TestEnvironment.withTestEngine( - // This is the default, but make it explicit for the test. - // ignore: avoid_redundant_argument_values - withRbe: false, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - final plan = BuildPlan.fromArgResults( - parser.parse([]), - testEnv.environment, - builds: builds, + final plan = configureAndParse( + [], + environment: TestEnvironment.withTestEngine( + // This is the default, but make it explicit for the test. + // ignore: avoid_redundant_argument_values + withRbe: false, + ), ); expect(plan.useRbe, isFalse); expect(plan.toGnArgs(), contains('--no-rbe')); }); - test('rbe forced to true if not detected is an error', () { - final testEnv = TestEnvironment.withTestEngine( - // This is the default, but make it explicit for the test. - // ignore: avoid_redundant_argument_values - withRbe: false, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - expect( - () => BuildPlan.fromArgResults( - parser.parse(['--rbe']), - testEnv.environment, - builds: builds, - ), - throwsA(isA().having( - (e) => e.toString(), - 'toString()', - contains('RBE requested but configuration not found'), - )), - ); - }); - test('lto is true if explicitly enabled', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - final plan = BuildPlan.fromArgResults( - parser.parse(['--lto']), - testEnv.environment, - builds: builds, - ); + final plan = configureAndParse(['--lto']); expect(plan.useLto, isTrue); expect(plan.toGnArgs(), contains('--lto')); }); test('lto is false if explicitly disabled', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - final plan = BuildPlan.fromArgResults( - parser.parse(['--no-lto']), - testEnv.environment, - builds: builds, - ); + final plan = configureAndParse(['--no-lto']); expect(plan.useLto, isFalse); expect(plan.toGnArgs(), contains('--no-lto')); }); test('lto is true if the config omits --no-lto', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - final plan = BuildPlan.fromArgResults( - parser.parse([]), - testEnv.environment, - builds: builds, - ); + final plan = configureAndParse([]); expect( plan.useLto, @@ -223,33 +90,13 @@ void main() { }); test('lto is false if the config uses --no-lto', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - enableLto: false, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - final plan = BuildPlan.fromArgResults( - parser.parse([]), - testEnv.environment, - builds: builds, - ); + final plan = configureAndParse([], builds: (testConfig) { + testConfig.addBuild( + name: 'linux/host_debug', + dimension: TestDroneDimension.linux, + enableLto: false, + ); + }); expect( plan.useLto, @@ -260,43 +107,88 @@ void main() { }); test('concurrency defaults to null if not specified', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); + final plan = configureAndParse([]); - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, + expect(plan.concurrency, isNull); + }); + + test('concurrency parses the number provided', () { + final plan = configureAndParse(['--concurrency=1024']); + + expect(plan.concurrency, 1024); + }); + + test('strategy defaults to auto', () { + final plan = configureAndParse([]); + + expect(plan.strategy, BuildStrategy.auto); + expect( + plan.toRbeConfig(), + same(const RbeConfig()), + reason: 'Auto should use the default RbeConfig instance.', ); + }); - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, + test('strategy can be set to --local', () { + final plan = configureAndParse(['--build-strategy=local']); + + expect(plan.strategy, BuildStrategy.local); + expect( + plan.toRbeConfig(), + same(const RbeConfig( + execStrategy: RbeExecStrategy.local, + remoteDisabled: true, + )), + reason: 'Local should use RbeExecStrategy.local with RBE disabled', + ); + }); + + test('strategy can be set to --remote', () { + final plan = configureAndParse( + ['--build-strategy=remote'], + environment: TestEnvironment.withTestEngine( + withRbe: true, + ), ); - final plan = BuildPlan.fromArgResults( - parser.parse([]), - testEnv.environment, - builds: builds, + expect(plan.strategy, BuildStrategy.remote); + expect( + plan.toRbeConfig(), + same(const RbeConfig(execStrategy: RbeExecStrategy.remote)), + reason: 'Local should use RbeExecStrategy.remote', ); + }); - expect(plan.concurrency, isNull); + test('can provide a single extra "--gn-args"', () { + final result = configureAndParse(['--gn-args', '--foo']); + expect(result.extraGnArgs, ['--foo']); }); - test('concurrency parses the number provided', () { - final testEnv = TestEnvironment.withTestEngine(); + test('can provide multiple extra "--gn-arg"s', () { + final result = configureAndParse([ + '--gn-args', + '--foo', + '--gn-args', + '--bar', + ]); + expect(result.extraGnArgs, ['--foo', '--bar']); + }); + + test('build defaults to host_debug', () { + final plan = configureAndParse([]); + + expect(plan.build.name, 'linux/host_debug'); + }); + + test('build defaults to the provided default', () { + final testEnv = TestEnvironment.withTestEngine( + withRbe: true, + ); addTearDown(testEnv.cleanup); final testConfig = TestBuilderConfig(); testConfig.addBuild( - name: 'linux/host_debug', + name: 'linux/host_debug_unopt_arm64', dimension: TestDroneDimension.linux, ); @@ -313,700 +205,448 @@ void main() { ); final plan = BuildPlan.fromArgResults( - parser.parse(['--concurrency=1024']), + parser.parse([]), testEnv.environment, builds: builds, - ); - - expect(plan.concurrency, 1024); - }); - - test('concurrency fails on a non-integer', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - expect( - () => BuildPlan.fromArgResults( - parser.parse(['--concurrency=ABCD']), - testEnv.environment, - builds: builds, - ), - throwsA(isA().having( - (e) => e.toString(), - 'toString()', - contains('Invalid value for --concurrency: ABCD'), - )), - ); - }); - - test('concurrency fails on a negative integer', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - expect( - () => BuildPlan.fromArgResults( - parser.parse(['--concurrency=-1024']), - testEnv.environment, - builds: builds, - ), - throwsA(isA().having( - (e) => e.toString(), - 'toString()', - contains('Invalid value for --concurrency: -1024'), - )), - ); - }); - - test('strategy defaults to auto', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - final plan = BuildPlan.fromArgResults( - parser.parse(['--concurrency=1024']), - testEnv.environment, - builds: builds, - ); - - expect(plan.strategy, BuildStrategy.auto); - expect( - plan.toRbeConfig(), - same(const RbeConfig()), - reason: 'Auto should use the default RbeConfig instance.', - ); - }); - - test('strategy can be set to --local', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - final plan = BuildPlan.fromArgResults( - parser.parse(['--build-strategy=local']), - testEnv.environment, - builds: builds, - ); - - expect(plan.strategy, BuildStrategy.local); - expect( - plan.toRbeConfig(), - same(const RbeConfig( - execStrategy: RbeExecStrategy.local, - remoteDisabled: true, - )), - reason: 'Local should use RbeExecStrategy.local with RBE disabled', - ); - }); - - test('strategy can be set to --remote', () { - final testEnv = TestEnvironment.withTestEngine( - withRbe: true, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - final plan = BuildPlan.fromArgResults( - parser.parse(['--build-strategy=remote']), - testEnv.environment, - builds: builds, - ); - - expect(plan.strategy, BuildStrategy.remote); - expect( - plan.toRbeConfig(), - same(const RbeConfig(execStrategy: RbeExecStrategy.remote)), - reason: 'Local should use RbeExecStrategy.remote', - ); - }); - - test('strategy of --remote with RBE disabled fails', () { - final testEnv = TestEnvironment.withTestEngine( - // This is the default, but make it explicit for the test. - // ignore: avoid_redundant_argument_values - withRbe: false, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - expect( - () => BuildPlan.fromArgResults( - parser.parse(['--build-strategy=remote']), - testEnv.environment, - builds: builds, - ), - throwsA(isA().having( - (e) => e.toString(), - 'toString()', - contains('Cannot use remote builds without RBE enabled'), - )), - ); - }); - - test('build defaults to host_debug', () { - final testEnv = TestEnvironment.withTestEngine( - withRbe: true, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - final plan = BuildPlan.fromArgResults( - parser.parse([]), - testEnv.environment, - builds: builds, - ); - - expect(plan.build.name, 'linux/host_debug'); - }); - - test('build defaults to the provided default', () { - final testEnv = TestEnvironment.withTestEngine( - withRbe: true, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug_unopt_arm64', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - final plan = BuildPlan.fromArgResults( - parser.parse([]), - testEnv.environment, - builds: builds, - defaultBuild: () => 'host_debug_unopt_arm64', + defaultBuild: () => 'host_debug_unopt_arm64', ); expect(plan.build.name, 'linux/host_debug_unopt_arm64'); }); - - test('build fails if host_debug not specified and no config set', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug_unopt_arm64', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - expect( - () => BuildPlan.fromArgResults( - parser.parse([]), - testEnv.environment, - builds: builds, - ), - throwsA(isA().having( - (e) => e.toString(), - 'toString()', - contains('Unknown build configuration: host_debug'), - )), - ); - }); - - test('build fails if a config not available is requested', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final builds = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: false, - ); - - expect( - () => BuildPlan.fromArgResults( - parser.parse(['--config=host_debug_unopt_arm64']), - testEnv.environment, - builds: builds, - ), - throwsA( - isA().having( + + // These tests should strictly check for build failures as a result of + // invalid combination of flags or flags with invalid values; i.e. a valid + // BuildPlan is not returned. + group('prevents invalid flags', () { + test('can provide only long-form "--gn-arg"s', () { + expect( + () => configureAndParse(['--gn-args', '-F']), + throwsA(BuildPlan.argumentsMustBeFlagsError), + ); + }); + + test('strategy of --remote with RBE disabled fails', () { + expect( + () => configureAndParse( + ['--build-strategy=remote'], + environment: TestEnvironment.withTestEngine( + // This is the default, but make it explicit for the test. + // ignore: avoid_redundant_argument_values + withRbe: false, + ), + ), + throwsA(isA().having( (e) => e.toString(), 'toString()', - contains( - '"host_debug_unopt_arm64" is not an allowed value for option', + contains('Cannot use remote builds without RBE enabled'), + )), + ); + }); + + test('rbe forced to true if not detected is an error', () { + expect( + () => configureAndParse( + ['--rbe'], + environment: TestEnvironment.withTestEngine( + // This is the default, but make it explicit for the test. + // ignore: avoid_redundant_argument_values + withRbe: false, ), ), - ), - ); - }); - - test('show builds in help message as long as not a [ci/...] build', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'ci/host_debug', - dimension: TestDroneDimension.linux, - ); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final _ = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', + throwsA(isA().having( + (e) => e.toString(), + 'toString()', + contains('RBE requested but configuration not found'), + )), + ); + }); + + test('concurrency fails on a non-integer', () { + expect( + () => configureAndParse(['--concurrency=ABCD']), + throwsA(isA().having( + (e) => e.toString(), + 'toString()', + contains('Invalid value for --concurrency: ABCD'), + )), + ); + }); + + test('concurrency fails on a negative integer', () { + expect( + () => configureAndParse(['--concurrency=-1024']), + throwsA(isA().having( + (e) => e.toString(), + 'toString()', + contains('Invalid value for --concurrency: -1024'), + )), + ); + }); + + test('build fails if host_debug not specified and no config set', () { + expect( + () => configureAndParse([], builds: (testConfig) { + testConfig.addBuild( + name: 'linux/host_debug_unopt_arm64', + dimension: TestDroneDimension.linux, + ); + }), + throwsA(isA().having( + (e) => e.toString(), + 'toString()', + contains('Unknown build configuration: host_debug'), + )), + ); + }); + + group('build fails if an extra "--gn-args" contains a reserved flag', () { + for (final reserved in BuildPlan.reservedGnArgs) { + test(reserved, () { + expect( + () => configureAndParse(['--gn-args', '--$reserved']), + throwsA(isA().having( + (e) => e.toString(), + 'toString()', + contains(BuildPlan.reservedGnArgsError.toString()), + )), + ); + }); + } + }); + + test('builds fails if a non-flag (space) is provided to "--gn-args"', () { + expect( + () => configureAndParse(['--gn-args', '--foo name']), + throwsA(isA().having( + (e) => e.toString(), + 'toString()', + contains(BuildPlan.argumentsMustBeFlagsError.toString()), + )), + ); + }); + + test('builds fails if a non-flag (with =) is provided to "--gn-args"', () { + expect( + () => configureAndParse(['--gn-args', '--foo=name']), + throwsA(isA().having( + (e) => e.toString(), + 'toString()', + contains('Arguments provided to --gn-args must be flags'), + )), + ); + }); + + test('build fails if a config not available is requested', () { + expect( + () => configureAndParse(['--config=host_debug_unopt_arm64']), + throwsA( + isA().having( + (e) => e.toString(), + 'toString()', + contains( + '"host_debug_unopt_arm64" is not an allowed value for option', + ), + ), ), - }, - help: true, - ); - - expect(parser.usage, contains('host_debug')); - expect(parser.usage, isNot(contains('ci/host_debug'))); + ); + }); }); - test('shows [ci/...] builds if verbose is true', () { - final testEnv = TestEnvironment.withTestEngine( - verbose: true, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'ci/host_debug', - dimension: TestDroneDimension.linux, - ); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final _ = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', + // These tests should strictly check the usage (--help) output. + group('shows instructions based on verbosity', () { + /// Creates a configured [ArgParser] based on then environemnt and builds. + /// + /// By default a non-verbose, non-RBE, Linux x64 configured [BuildPlan] + /// is created and used to configure the available options and instructions + /// for the returned [ArgParser]. + /// + /// - To use a different [TestEnvironment], provide [environment]. + /// - To use a different set of builds, provide [builds]. + ArgParser createArgParser({ + TestEnvironment? environment, + void Function(TestBuilderConfig)? builds, + }) { + final (_, parser, _) = _createTestFixture( + environment: environment, + builds: builds, + ); + return parser; + } + + test('show builds in help message as long as not a [ci/...] build', () { + final parser = createArgParser( + builds: (testConfig) { + testConfig.addBuild( + name: 'ci/host_debug', + dimension: TestDroneDimension.linux, + ); + testConfig.addBuild( + name: 'linux/host_debug', + dimension: TestDroneDimension.linux, + ); + }, + ); + + expect(parser.usage, contains('host_debug')); + expect(parser.usage, isNot(contains('ci/host_debug'))); + }); + + test('shows [ci/...] builds if verbose is true', () { + final parser = createArgParser( + environment: TestEnvironment.withTestEngine( + verbose: true, ), - }, - help: true, - ); - - expect(parser.usage, contains('host_debug')); - expect(parser.usage, contains('ci/host_debug')); - }); - - test('hides LTO instructions normally', () { - final testEnv = TestEnvironment.withTestEngine(); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final _ = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', + builds: (testConfig) { + testConfig.addBuild( + name: 'ci/host_debug', + dimension: TestDroneDimension.linux, + ); + testConfig.addBuild( + name: 'linux/host_debug', + dimension: TestDroneDimension.linux, + ); + }, + ); + + expect(parser.usage, contains('host_debug')); + expect(parser.usage, contains('ci/host_debug')); + }); + + test('hides LTO instructions normally', () { + final parser = createArgParser(); + + expect( + parser.usage, + isNot(contains('Whether LTO should be enabled for a build')), + ); + }); + + test('shows LTO instructions if verbose', () { + final parser = createArgParser( + environment: TestEnvironment.withTestEngine( + verbose: true, ), - }, - help: true, - ); - - expect( - parser.usage, - isNot(contains('Whether LTO should be enabled for a build')), - ); - }); - - test('shows LTO instructions if verbose', () { - final testEnv = TestEnvironment.withTestEngine( - verbose: true, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'ci/host_debug', - dimension: TestDroneDimension.linux, - ); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final _ = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', + ); + + expect( + parser.usage, + contains('Whether LTO should be enabled for a build'), + ); + }); + + test('shows RBE instructions if not configured', () { + final parser = createArgParser( + environment: TestEnvironment.withTestEngine( + // This is the default, but make it explicit for the test. + // ignore: avoid_redundant_argument_values + withRbe: false, ), - }, - help: true, - ); - - expect( - parser.usage, - contains('Whether LTO should be enabled for a build'), - ); - }); - - test('shows RBE instructions if not configured', () { - final testEnv = TestEnvironment.withTestEngine( - // This is the default, but make it explicit for the test. - // ignore: avoid_redundant_argument_values - withRbe: false, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final _ = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', + ); + + expect( + parser.usage, + stringContainsInOrder([ + 'Enable pre-configured remote build execution', + 'https://flutter.dev/to/engine-rbe', + ]), + ); + }); + + test('shows RBE instructions if verbose', () { + final parser = createArgParser( + environment: TestEnvironment.withTestEngine( + verbose: true, ), - }, - help: true, - ); - - expect( - parser.usage, - contains('Enable pre-configured remote build execution'), - ); - expect( - parser.usage, - contains('https://flutter.dev/to/engine-rbe'), - ); - }); - - test('shows RBE instructions if verbose', () { - final testEnv = TestEnvironment.withTestEngine( - withRbe: true, - verbose: true, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final _ = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', + ); + + expect( + parser.usage, + stringContainsInOrder([ + 'Enable pre-configured remote build execution', + 'https://flutter.dev/to/engine-rbe', + ]), + ); + }); + + test('hides RBE instructions if enabled', () { + final parser = createArgParser( + environment: TestEnvironment.withTestEngine( + withRbe: true, ), - }, - help: true, - ); - - expect( - parser.usage, - contains('Enable pre-configured remote build execution'), - ); - expect( - parser.usage, - contains('https://flutter.dev/to/engine-rbe'), - ); - }); - - test('hides RBE intsructions if enabled', () { - final testEnv = TestEnvironment.withTestEngine( - withRbe: true, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final _ = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', + ); + + expect( + parser.usage, + contains('Enable pre-configured remote build execution'), + ); + expect( + parser.usage, + isNot(contains('https://flutter.dev/to/engine-rbe')), + ); + }); + + test('hides --build-strategy if RBE not enabled', () { + final parser = createArgParser( + environment: TestEnvironment.withTestEngine( + // This is the default, but make it explicit for the test. + // ignore: avoid_redundant_argument_values + withRbe: false, ), - }, - help: true, - ); - - expect( - parser.usage, - contains('Enable pre-configured remote build execution'), - ); - expect( - parser.usage, - isNot(contains('https://flutter.dev/to/engine-rbe')), - ); - }); - - test('hides --build-strategy if RBE not enabled', () { - final testEnv = TestEnvironment.withTestEngine( - // This is the default, but make it explicit for the test. - // ignore: avoid_redundant_argument_values - withRbe: false, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final _ = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', + ); + + expect( + parser.usage, + isNot(contains('How to prefer remote or local builds')), + ); + }); + + test('shows --build-strategy if RBE enabled', () { + final parser = createArgParser( + environment: TestEnvironment.withTestEngine( + withRbe: true, ), - }, - help: true, - ); - - expect( - parser.usage, - isNot(contains('How to prefer remote or local builds')), - ); - }); - - test('shows --build-strategy if RBE enabled', () { - final testEnv = TestEnvironment.withTestEngine( - withRbe: true, - ); - addTearDown(testEnv.cleanup); - - final testConfig = TestBuilderConfig(); - testConfig.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final parser = ArgParser(); - final _ = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', + ); + + expect( + parser.usage, + contains('How to prefer remote or local builds'), + ); + }); + + test('shows --build-strategy if verbose', () { + final parser = createArgParser( + environment: TestEnvironment.withTestEngine( + verbose: true, ), - }, - help: true, - ); - - expect( - parser.usage, - contains('How to prefer remote or local builds'), - ); + ); + + expect( + parser.usage, + contains('How to prefer remote or local builds'), + ); + }); + + test('hides --gn-args if not verbose', () { + final parser = createArgParser(); + + expect( + parser.usage, + isNot(contains('Additional arguments to provide to "gn"')), + ); + }); + + test('shows --gn-args if verbose', () { + final parser = createArgParser( + environment: TestEnvironment.withTestEngine( + verbose: true, + ), + ); + + expect( + parser.usage, + contains('Additional arguments to provide to "gn"'), + ); + }); + + /// Returns an [ArgParser] pre-primed with both MacOS and Linux builds. + ArgParser createMultiPlatformArgParser({ + required bool verbose, + }) { + final testEnv = TestEnvironment.withTestEngine( + verbose: verbose, + ); + addTearDown(testEnv.cleanup); + + final linuxConfig = TestBuilderConfig(); + linuxConfig.addBuild( + name: 'linux/host_debug', + dimension: TestDroneDimension.linux, + description: 'A development build of the Linux host.', + ); + linuxConfig.addBuild( + name: 'ci/linux_host_debug', + dimension: TestDroneDimension.linux, + description: 'A CI-suitable development build of the Linux host.', + ); + + final macOSConfig = TestBuilderConfig(); + macOSConfig.addBuild( + name: 'macos/host_debug', + dimension: TestDroneDimension.mac, + description: 'A build we do not expect to see due to filtering.', + ); + + final parser = ArgParser(); + final _ = BuildPlan.configureArgParser( + parser, + testEnv.environment, + configs: { + 'linux_test_config': linuxConfig.buildConfig( + path: 'ci/builders/linux_test_config.json', + ), + 'macos_test_config': macOSConfig.buildConfig( + path: 'ci/builders/macos_test_config.json', + ), + }, + help: true, + ); + + return parser; + } + + test('shows only non-CI builds that can run locally', () { + final parser = createMultiPlatformArgParser(verbose: false); + + expect( + parser.usage, + contains('[host_debug]'), + ); + }); + + test('shows builds that can run locally, with details', () { + final parser = createMultiPlatformArgParser(verbose: true); + + expect( + parser.usage, + stringContainsInOrder([ + '[ci/linux_host_debug]', + 'A CI-suitable development build of the Linux host.', + '[host_debug]', + 'A development build of the Linux host.', + ]), + ); + }); }); +} - test('shows --build-strategy if verbose', () { - final testEnv = TestEnvironment.withTestEngine( - verbose: true, - ); - addTearDown(testEnv.cleanup); +(List, ArgParser, Environment) _createTestFixture({ + TestEnvironment? environment, + void Function(TestBuilderConfig)? builds, +}) { + final testEnv = environment ?? TestEnvironment.withTestEngine(); + addTearDown(testEnv.cleanup); - final testConfig = TestBuilderConfig(); + final testConfig = TestBuilderConfig(); + if (builds != null) { + builds(testConfig); + } else { testConfig.addBuild( name: 'linux/host_debug', dimension: TestDroneDimension.linux, ); + } - final parser = ArgParser(); - final _ = BuildPlan.configureArgParser( - parser, - testEnv.environment, - configs: { - 'linux_test_config': testConfig.buildConfig( - path: 'ci/builders/linux_test_config.json', - ), - }, - help: true, - ); + final parser = ArgParser(); + final builders = BuildPlan.configureArgParser( + parser, + testEnv.environment, + configs: { + 'linux_test_config': testConfig.buildConfig( + path: 'ci/builders/linux_test_config.json', + ), + }, + help: true, + ); - expect( - parser.usage, - contains('How to prefer remote or local builds'), - ); - }); + return (builders, parser, testEnv.environment); } diff --git a/tools/engine_tool/test/commands/build_command_test.dart b/tools/engine_tool/test/commands/build_command_test.dart index 3267ba8ce6781..e21cc9dabd021 100644 --- a/tools/engine_tool/test/commands/build_command_test.dart +++ b/tools/engine_tool/test/commands/build_command_test.dart @@ -16,39 +16,6 @@ import '../src/test_build_configs.dart'; import '../src/utils.dart'; void main() { - test('can find host runnable build', () async { - final testEnv = TestEnvironment.withTestEngine( - abi: Abi.macosArm64, - ); - addTearDown(testEnv.cleanup); - - final builder = TestBuilderConfig(); - builder.addBuild( - name: 'macos/host_debug', - dimension: TestDroneDimension.mac, - ); - builder.addBuild( - name: 'mac/host_profile', - dimension: TestDroneDimension.mac, - ); - builder.addBuild( - name: 'linux/host_debug', - dimension: TestDroneDimension.linux, - ); - - final configs = { - 'mac_test_config': builder.buildConfig( - path: 'ci/builders/mac_test_config.json', - ), - }; - - final result = runnableBuilds(testEnv.environment, configs, true); - expect( - result.map((r) => r.name), - unorderedEquals(['macos/host_debug', 'mac/host_profile']), - ); - }); - test('build command invokes gn', () async { final testEnv = TestEnvironment.withTestEngine( abi: Abi.macosArm64, @@ -475,9 +442,9 @@ void main() { CannedProcess( (command) => command.contains('desc'), stdout: convert.jsonEncode({ - '//flutter/fml:fml_arc_unittests': { + '//flutter/fml:fml_unittests': { 'outputs': [ - '//out/host_debug/fml_arc_unittests', + '//out/host_debug/fml_unittests', ], 'testonly': true, 'type': 'executable', @@ -506,7 +473,7 @@ void main() { 'build', '--config', 'ci/host_debug', - '//flutter/fml:fml_arc_unittests', + '//flutter/fml:fml_unittests', ]); printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n')); @@ -528,7 +495,7 @@ void main() { expect( ninjaCmd.command, contains( - contains('flutter/fml:fml_arc_unittests'), + contains('flutter/fml:fml_unittests'), ), ); }); @@ -554,9 +521,9 @@ void main() { 'testonly': true, 'type': 'executable', }, - '//flutter/fml:fml_arc_unittests': { + '//flutter/fml:fml_unittests': { 'outputs': [ - '//out/host_debug/fml_arc_unittests', + '//out/host_debug/fml_unittests', ], 'testonly': true, 'type': 'executable', @@ -609,7 +576,7 @@ void main() { containsAll([ 'flutter/display_list:display_list_unittests', 'flutter/flow:flow_unittests', - 'flutter/fml:fml_arc_unittests', + 'flutter/fml:fml_unittests', ]), ); }); diff --git a/tools/engine_tool/test/commands/cleanup_command_test.dart b/tools/engine_tool/test/commands/cleanup_command_test.dart new file mode 100644 index 0000000000000..5408b6f133533 --- /dev/null +++ b/tools/engine_tool/test/commands/cleanup_command_test.dart @@ -0,0 +1,153 @@ +// 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 'package:engine_tool/src/commands/command_runner.dart'; +import 'package:engine_tool/src/logger.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +import '../src/matchers.dart'; +import '../src/utils.dart'; + +void main() { + test('prints instead of deleting when --dry-run', () async { + final fakeNow = DateTime.now().add(const Duration(days: 100)); + final testEnv = TestEnvironment.withTestEngine(now: () => fakeNow); + addTearDown(testEnv.cleanup); + + final targetDir = Directory(p.join( + testEnv.environment.engine.outDir.path, + 'host_old', + )); + await targetDir.create(recursive: true); + + final runner = ToolCommandRunner( + environment: testEnv.environment, + configs: {}, + ); + + final result = await runner.run(['cleanup', '--dry-run']); + expect(result, 0); + + expect( + targetDir.existsSync(), + true, + reason: '--dry-run should not delete directories', + ); + + expect( + testEnv.testLogs, + containsAllInOrder([ + logRecord( + contains('Checking ${testEnv.environment.engine.outDir.path}'), + ), + logRecord( + contains('The following directories were accessed later than'), + ), + logRecord( + contains('host_old'), + ), + ]), + ); + }); + + test('uses 30 days if --untouched-since is omitted', () async { + final fakeNow = DateTime.now().add(const Duration(days: 32)); + final testEnv = TestEnvironment.withTestEngine(now: () => fakeNow); + addTearDown(testEnv.cleanup); + + final targetDir = Directory(p.join( + testEnv.environment.engine.outDir.path, + 'host_old', + )); + await targetDir.create(recursive: true); + + final runner = ToolCommandRunner( + environment: testEnv.environment, + configs: {}, + ); + + final result = await runner.run(['cleanup']); + expect(result, 0); + + expect( + targetDir.existsSync(), + false, + reason: 'Should be > 30 days since accessed', + ); + + expect( + testEnv.testLogs, + containsAllInOrder([ + logRecord( + contains('Checking ${testEnv.environment.engine.outDir.path}'), + ), + logRecord( + contains('Deleted 1 output directories'), + ), + ]), + ); + }); + + test('parses the provided YYYY-MM-DD', () async { + final fakeNow = DateTime.now(); + final testEnv = TestEnvironment.withTestEngine(now: () => fakeNow); + addTearDown(testEnv.cleanup); + + final targetDir = Directory(p.join( + testEnv.environment.engine.outDir.path, + 'host_old', + )); + await targetDir.create(recursive: true); + + final runner = ToolCommandRunner( + environment: testEnv.environment, + configs: {}, + ); + + final result = await runner.run([ + 'cleanup', + '--untouched-since=${fakeNow.year + 1}-01-01', + ]); + expect(result, 0); + + expect( + targetDir.existsSync(), + false, + reason: 'Due to --untouched-since being a year in the future', + ); + + expect( + testEnv.testLogs, + containsAllInOrder([ + logRecord( + contains('Checking ${testEnv.environment.engine.outDir.path}'), + ), + logRecord( + contains('Deleted 1 output directories'), + ), + ]), + ); + }); + + test('refuses an invalid YYYY-MM-DD date', () async { + final testEnv = TestEnvironment.withTestEngine(); + addTearDown(testEnv.cleanup); + + final runner = ToolCommandRunner( + environment: testEnv.environment, + configs: {}, + ); + + expect( + runner.run([ + 'cleanup', + '--untouched-since=02-14-2024', + ]), + throwsA(isA()), + ); + }); +} diff --git a/tools/engine_tool/test/commands/query_command_test.dart b/tools/engine_tool/test/commands/query_command_test.dart index 77edae6f36864..04b41c92996f8 100644 --- a/tools/engine_tool/test/commands/query_command_test.dart +++ b/tools/engine_tool/test/commands/query_command_test.dart @@ -320,9 +320,9 @@ void main() { 'testonly': true, 'type': 'executable', }, - '//flutter/fml:fml_arc_unittests': { + '//flutter/fml:fml_unittests': { 'outputs': [ - '//out/host_debug/fml_arc_unittests', + '//out/host_debug/fml_unittests', ], 'testonly': true, 'type': 'executable', @@ -374,7 +374,7 @@ void main() { final expected = [ '//flutter/display_list:display_list_unittests', '//flutter/flow:flow_unittest', - '//flutter/fml:fml_arc_unittests', + '//flutter/fml:fml_unittests', ]; final testLogs = stringsFromLogs(testEnvironment.testLogs); diff --git a/tools/engine_tool/test/src/utils.dart b/tools/engine_tool/test/src/utils.dart index 4ab0c4dd4e44b..55f33bf7977dc 100644 --- a/tools/engine_tool/test/src/utils.dart +++ b/tools/engine_tool/test/src/utils.dart @@ -61,6 +61,7 @@ class TestEnvironment { Logger? logger, ffi.Abi abi = ffi.Abi.macosArm64, bool verbose = false, + DateTime Function() now = DateTime.now, this.cannedProcesses = const [], }) { testLogs = []; @@ -100,6 +101,7 @@ class TestEnvironment { })), logger: logger, verbose: verbose, + now: now, ); } @@ -108,6 +110,7 @@ class TestEnvironment { ffi.Abi abi = ffi.Abi.linuxX64, List cannedProcesses = const [], bool verbose = false, + DateTime Function() now = DateTime.now, }) { final io.Directory rootDir = io.Directory.systemTemp.createTempSync('et'); final TestEngine engine = TestEngine.createTemp(rootDir: rootDir); @@ -135,6 +138,7 @@ class TestEnvironment { abi: abi, cannedProcesses: cannedProcesses + [cannedGn], verbose: verbose, + now: now, ); return testEnvironment; } diff --git a/tools/gen_web_locale_keymap/pubspec.yaml b/tools/gen_web_locale_keymap/pubspec.yaml index 9493b5bf16614..da2a9ae5f98d7 100644 --- a/tools/gen_web_locale_keymap/pubspec.yaml +++ b/tools/gen_web_locale_keymap/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/githooks/pubspec.yaml b/tools/githooks/pubspec.yaml index f0c37a7273cc4..a06b0645bb177 100644 --- a/tools/githooks/pubspec.yaml +++ b/tools/githooks/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/gn b/tools/gn index 0fa741985a008..325b5cbece147 100755 --- a/tools/gn +++ b/tools/gn @@ -434,6 +434,10 @@ def to_gn_args(args): gn_args['enable_unittests'] = False # Skia GN args. + gn_args['skia_use_libpng_decode'] = True + gn_args['skia_use_libpng_encode'] = True + gn_args['skia_use_rust_png_decode'] = False + gn_args['skia_use_rust_png_encode'] = False gn_args['skia_use_dng_sdk'] = False # RAW image handling. gn_args['skia_enable_pdf'] = False # PDF handling. gn_args['skia_use_x11'] = False # Never add the X11 dependency (only takes effect on Linux). @@ -569,10 +573,6 @@ def to_gn_args(args): else: gn_args['enable_backtrace'] = False - # 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 diff --git a/tools/golden_tests_harvester/pubspec.yaml b/tools/golden_tests_harvester/pubspec.yaml index c512a9c1c4ca3..576000ab657d8 100644 --- a/tools/golden_tests_harvester/pubspec.yaml +++ b/tools/golden_tests_harvester/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/header_guard_check/pubspec.yaml b/tools/header_guard_check/pubspec.yaml index 7401a16826bf4..be89d61e2d289 100644 --- a/tools/header_guard_check/pubspec.yaml +++ b/tools/header_guard_check/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/javadoc/gen_javadoc.py b/tools/javadoc/gen_javadoc.py index 1d1b53832b68f..270af576b25f8 100755 --- a/tools/javadoc/gen_javadoc.py +++ b/tools/javadoc/gen_javadoc.py @@ -66,7 +66,6 @@ def main(): classpath.append(args.build_config_path) packages = [ - 'io.flutter.app', 'io.flutter.embedding.android', 'io.flutter.embedding.engine', 'io.flutter.embedding.engine.dart', @@ -78,7 +77,6 @@ def main(): 'io.flutter.embedding.engine.plugins.contentprovider', 'io.flutter.embedding.engine.plugins.lifecycle', 'io.flutter.embedding.engine.plugins.service', - 'io.flutter.embedding.engine.plugins.shim', 'io.flutter.embedding.engine.renderer', 'io.flutter.embedding.engine.systemchannels', 'io.flutter.plugin.common', diff --git a/tools/licenses/lib/paths.dart b/tools/licenses/lib/paths.dart index 9684b58112414..1062b57a1b824 100644 --- a/tools/licenses/lib/paths.dart +++ b/tools/licenses/lib/paths.dart @@ -153,7 +153,6 @@ final Set skippedPaths = { r'flutter/third_party/pkg/flutter_packages', r'flutter/third_party/pkg/gcloud', r'flutter/third_party/pkg/googleapis', - r'flutter/third_party/pkg/platform', r'flutter/third_party/pkg/process', r'flutter/third_party/pkg/process_runner', r'flutter/third_party/pkg/vector_math', diff --git a/tools/licenses/pubspec.yaml b/tools/licenses/pubspec.yaml index caa66d7557619..1d215c599f489 100644 --- a/tools/licenses/pubspec.yaml +++ b/tools/licenses/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/path_ops/dart/pubspec.yaml b/tools/path_ops/dart/pubspec.yaml index 49e358c0d06e1..f6bec7bc1a196 100644 --- a/tools/path_ops/dart/pubspec.yaml +++ b/tools/path_ops/dart/pubspec.yaml @@ -6,7 +6,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/pkg/engine_build_configs/lib/src/build_config_runner.dart b/tools/pkg/engine_build_configs/lib/src/build_config_runner.dart index 20068571658e8..f96de99426012 100644 --- a/tools/pkg/engine_build_configs/lib/src/build_config_runner.dart +++ b/tools/pkg/engine_build_configs/lib/src/build_config_runner.dart @@ -8,11 +8,13 @@ import 'dart:ffi' as ffi; import 'dart:io' as io show Directory, File, Process, ProcessResult; import 'dart:math'; +import 'package:meta/meta.dart' show visibleForTesting; import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:process_runner/process_runner.dart'; import 'build_config.dart'; +import 'merge_gn_args.dart'; /// The base class for events generated by a command. sealed class RunnerEvent { @@ -365,29 +367,11 @@ final class BuildRunner extends Runner { return true; } - // GN arguments from the build config that can be overridden by extraGnArgs. - static const List<(String, String)> _overridableArgs = <(String, String)>[ - ('--lto', '--no-lto'), - ('--rbe', '--no-rbe'), - ]; - // extraGnArgs overrides the build config args. - late final Set _mergedGnArgs = () { - // Put the union of the build config args and extraGnArgs in gnArgs. - final Set gnArgs = Set.of(build.gn); - gnArgs.addAll(extraGnArgs); - - // If extraGnArgs contains an arg, remove its opposite from gnArgs. - for (final (String, String) arg in _overridableArgs) { - if (extraGnArgs.contains(arg.$1)) { - gnArgs.remove(arg.$2); - } - if (extraGnArgs.contains(arg.$2)) { - gnArgs.remove(arg.$1); - } - } - return gnArgs; - }(); + late final Set _mergedGnArgs = Set.of(mergeGnArgs( + buildArgs: build.gn, + extraArgs: extraGnArgs, + )); Future _runGn(RunnerEventHandler eventHandler) async { final String gnPath = p.join(engineSrcDir.path, 'flutter', 'tools', 'gn'); @@ -579,6 +563,28 @@ final class BuildRunner extends Runner { return min(multiplier * cores, 1000); }(); + static final RegExp _gccRegex = + RegExp(r'^(.+)(:\d+:\d+:\s+(?:error|note|warning):\s+.*)$'); + + /// Converts relative [path], who is relative to [dirPath] to a relative path + /// of the `CWD`. + static String _makeRelative(String path, String dirPath) { + final String abs = p.join(dirPath, path); + return './${p.relative(abs)}'; + } + + /// Takes a [line] from compilation and makes the path relative to `CWD` where + /// the paths are relative to [outDir]. + @visibleForTesting + static String fixGccPaths(String line, String outDir) { + final Match? match = _gccRegex.firstMatch(line); + if (match == null) { + return line; + } else { + return '${_makeRelative(match.group(1)!, outDir)}${match.group(2)}'; + } + } + Future _runNinja(RunnerEventHandler eventHandler) async { if (_isRbe) { if (!await _bootstrapRbe(eventHandler)) { @@ -637,6 +643,8 @@ final class BuildRunner extends Runner { if (_ninjaProgress(eventHandler, command, line)) { return; } + // TODO(157816): Forward errors to `et`. + line = fixGccPaths(line, outDir); final List bytes = utf8.encode('$line\n'); stdoutOutput.addAll(bytes); }, diff --git a/tools/pkg/engine_build_configs/lib/src/merge_gn_args.dart b/tools/pkg/engine_build_configs/lib/src/merge_gn_args.dart new file mode 100644 index 0000000000000..9558be5c1d887 --- /dev/null +++ b/tools/pkg/engine_build_configs/lib/src/merge_gn_args.dart @@ -0,0 +1,125 @@ +// 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. + +@internal +library; + +import 'package:meta/meta.dart'; + +/// Merges and returns the result of adding [extraArgs] to original [buildArgs]. +/// +/// A builder, for example `macos/ios_debug` might look like this (truncated): +/// ```json +/// { +/// "gn": [ +/// "--ios", +/// "--runtime-mode", +/// "debug", +/// "--no-stripped", +/// "--no-lte" +/// ] +/// } +/// ``` +/// +/// Before asking GN to generate a build from these arguments, [extraArgs] are +/// added by ways of merging; that is, by assuming that arguments provided by +/// [extraArgs] are strictly boolean flags in the format "--foo" or "--no-foo", +/// and either are appended or replace an existing argument specified in +/// [buildArgs]. +/// +/// [extraArgs] that are not boolean arguments are not supported. +/// +/// ## Example +/// +/// ```dart +/// print(mergeGnArgs( +/// buildArgs: [], +/// extraArgs: ["--foo"] +/// )); +/// ``` +/// +/// ... prints "[--foo]". +/// +/// ```dart +/// print(mergeGnArgs( +/// buildArgs: ["--foo"], +/// extraArgs: ["--bar"] +/// )); +/// ``` +/// +/// ... prints "[--foo, --bar]". +/// +/// ```dart +/// print(mergeGnArgs( +/// buildArgs: ["--foo", "--no-bar", "--baz"], +/// extraArgs: ["--no-foo", "--bar"] +/// )); +/// ``` +/// +/// ... prints "[--no-foo, --bar, --baz]". +List mergeGnArgs({ + required List buildArgs, + required List extraArgs, +}) { + // Make a copy of buildArgs so replacements can be made. + final newBuildArgs = List.of(buildArgs); + + // Index "extraArgs" as map of "flag-without-no" => true/false. + final indexedExtra = {}; + for (final extraArg in extraArgs) { + if (!_isFlag(extraArg)) { + throw ArgumentError.value( + extraArgs, + 'extraArgs', + 'Each argument must be in the form "--flag" or "--no-flag".', + ); + } + final (name, value) = _extractRawFlag(extraArg); + indexedExtra[name] = value; + } + + // Iterate over newBuildArgs and replace if applicable. + for (var i = 0; i < newBuildArgs.length; i++) { + // It is valid to have non-flags (i.e. --runtime-mode=debug) here. Skip. + final buildArg = newBuildArgs[i]; + if (!_isFlag(buildArg)) { + continue; + } + + // If there is no repalcement value, leave as-is. + final (name, value) = _extractRawFlag(buildArg); + final replaceWith = indexedExtra.remove(name); + if (replaceWith == null) { + continue; + } + + // Replace (i.e. --foo with --no-foo or --no-foo with --foo). + newBuildArgs[i] = _toFlag(name, replaceWith); + } + + // Append arguments that were not replaced above. + for (final MapEntry(key: name, value: value) in indexedExtra.entries) { + newBuildArgs.add(_toFlag(name, value)); + } + + return newBuildArgs; +} + +bool _isFlag(String arg) { + return arg.startsWith('--') && !arg.contains('=') && !arg.contains(' '); +} + +(String, bool) _extractRawFlag(String flagArgument) { + var rawFlag = flagArgument.substring(2); + var flagValue = true; + if (rawFlag.startsWith('no-')) { + rawFlag = rawFlag.substring(3); + flagValue = false; + } + return (rawFlag, flagValue); +} + +String _toFlag(String name, bool value) { + return "--${!value ? 'no-' : ''}$name"; +} diff --git a/tools/pkg/engine_build_configs/pubspec.yaml b/tools/pkg/engine_build_configs/pubspec.yaml index 369800c9d96dc..d3e29b99df4f8 100644 --- a/tools/pkg/engine_build_configs/pubspec.yaml +++ b/tools/pkg/engine_build_configs/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/pkg/engine_build_configs/test/build_config_runner_test.dart b/tools/pkg/engine_build_configs/test/build_config_runner_test.dart index b98474d91feab..a090a85fd254e 100644 --- a/tools/pkg/engine_build_configs/test/build_config_runner_test.dart +++ b/tools/pkg/engine_build_configs/test/build_config_runner_test.dart @@ -19,14 +19,7 @@ import 'fixtures.dart' as fixtures; void main() { // Find the engine repo. - final Engine engine; - try { - engine = Engine.findWithin(); - } catch (e) { - io.stderr.writeln(e); - io.exitCode = 1; - return; - } + final engine = Engine.findWithin(); final BuilderConfig buildConfig = BuilderConfig.fromJson( path: 'linux_test_config', @@ -249,7 +242,9 @@ void main() { expect((events[7] as RunnerResult).okMessage, equals('OK')); }); - test('GlobalBuildRunner passes the specified -j when explicitly provided in an RBE build', () async { + test( + 'GlobalBuildRunner passes the specified -j when explicitly provided in an RBE build', + () async { final Build targetBuild = buildConfig.builds[0]; final BuildRunner buildRunner = BuildRunner( platform: FakePlatform( @@ -329,7 +324,9 @@ void main() { ); }); - test('GlobalBuildRunner sets RBE_disable_remote when remote builds are disabled', () async { + test( + 'GlobalBuildRunner sets RBE_disable_remote when remote builds are disabled', + () async { final Build targetBuild = buildConfig.builds[0]; final BuildRunner buildRunner = BuildRunner( platform: FakePlatform( @@ -369,7 +366,9 @@ void main() { ); }); - test('GlobalBuildRunner sets RBE_exec_strategy when a non-default value is passed in the RbeConfig', () async { + test( + 'GlobalBuildRunner sets RBE_exec_strategy when a non-default value is passed in the RbeConfig', + () async { final Build targetBuild = buildConfig.builds[0]; final BuildRunner buildRunner = BuildRunner( platform: FakePlatform( @@ -412,7 +411,9 @@ void main() { ); }); - test('GlobalBuildRunner passes the specified -j when explicitly provided in a non-RBE build', () async { + test( + 'GlobalBuildRunner passes the specified -j when explicitly provided in a non-RBE build', + () async { final Build targetBuild = buildConfig.builds[0]; final BuildRunner buildRunner = BuildRunner( platform: FakePlatform( @@ -530,6 +531,14 @@ void main() { expect(events[3].name, equals('generator_task')); }); + test('fixes gcc paths', () { + final String outDir = path.join(io.Directory.current.path, 'foo', 'bar'); + const String error = + 'flutter/impeller/renderer/backend/metal/allocator_mtl.h:69:33: error: foobar'; + final String fixed = BuildRunner.fixGccPaths('../../$error', outDir); + expect(fixed, './$error'); + }); + test('GlobalBuildRunner skips generators when runGenerators is false', () async { final Build targetBuild = buildConfig.builds[0]; diff --git a/tools/pkg/engine_build_configs/test/merge_gn_args_test.dart b/tools/pkg/engine_build_configs/test/merge_gn_args_test.dart new file mode 100644 index 0000000000000..9762678513400 --- /dev/null +++ b/tools/pkg/engine_build_configs/test/merge_gn_args_test.dart @@ -0,0 +1,50 @@ +// 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 'package:engine_build_configs/src/merge_gn_args.dart'; +import 'package:test/test.dart'; + +void main() { + test('refuses to merge with arguments that do not start with --', () { + expect( + () => mergeGnArgs(buildArgs: [], extraArgs: ['foo']), + throwsArgumentError, + ); + }); + + test('refuses to merge with arguments that contain spaces', () { + expect( + () => mergeGnArgs(buildArgs: [], extraArgs: ['--foo bar']), + throwsArgumentError, + ); + }); + + test('refuses to merge with arguments that contain equals', () { + expect( + () => mergeGnArgs(buildArgs: [], extraArgs: ['--foo=bar']), + throwsArgumentError, + ); + }); + + test('appends if there are no matching arguments', () { + expect( + mergeGnArgs(buildArgs: ['--foo'], extraArgs: ['--bar']), + ['--foo', '--bar'], + ); + }); + + test('replaces --foo with --no-foo', () { + expect( + mergeGnArgs(buildArgs: ['--foo'], extraArgs: ['--no-foo']), + ['--no-foo'], + ); + }); + + test('replaces --no-foo with --foo', () { + expect( + mergeGnArgs(buildArgs: ['--no-foo'], extraArgs: ['--foo']), + ['--foo'], + ); + }); +} diff --git a/tools/pkg/engine_repo_tools/pubspec.yaml b/tools/pkg/engine_repo_tools/pubspec.yaml index 02c8262090b69..3b6bb295b457a 100644 --- a/tools/pkg/engine_repo_tools/pubspec.yaml +++ b/tools/pkg/engine_repo_tools/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/pkg/git_repo_tools/pubspec.yaml b/tools/pkg/git_repo_tools/pubspec.yaml index e01df70cf12bc..010a6c08569af 100644 --- a/tools/pkg/git_repo_tools/pubspec.yaml +++ b/tools/pkg/git_repo_tools/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/pkg/process_fakes/pubspec.yaml b/tools/pkg/process_fakes/pubspec.yaml index f17791397b963..5db0814324c0f 100644 --- a/tools/pkg/process_fakes/pubspec.yaml +++ b/tools/pkg/process_fakes/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Required for workspace support. environment: - sdk: ^3.5.0-294.0.dev + sdk: ^3.7.0-0 # This package is managed as part of the engine workspace. resolution: workspace diff --git a/tools/yapf.sh b/tools/yapf.sh index bab007132dbaf..ce651152971e8 100755 --- a/tools/yapf.sh +++ b/tools/yapf.sh @@ -36,14 +36,22 @@ function follow_links() ( SCRIPT_DIR=$(follow_links "$(dirname -- "${BASH_SOURCE[0]}")") SRC_DIR="$(cd "$SCRIPT_DIR/../.."; pwd -P)" YAPF_DIR="$(cd "$SRC_DIR/flutter/third_party/yapf"; pwd -P)" + +# TODO: https://github.com/flutter/flutter/issues/158384 +# Migrate to a supported Python formatter. if command -v python3.10 &> /dev/null; then PYTHON_EXEC="python3.10" +elif command -v python3.11 &> /dev/null; then + PYTHON_EXEC="python3.11" else python3 -c " import sys version = sys.version_info -if (version.major, version.minor) > (3, 10): - print(f'Error: python3 version {version.major}.{version.minor} is greater than 3.10.', file=sys.stderr) +if (version.major, version.minor) > (3, 11): + print(f'Error: The yapf Python formatter requires Python version 3.11 or ' + f'earlier. The installed python3 version is ' + f'{version.major}.{version.minor}.', + file=sys.stderr) sys.exit(1) else: print(f'Using python3 version {version.major}.{version.minor}.') diff --git a/vulkan/vulkan_surface.h b/vulkan/vulkan_surface.h index 3455425c27631..fafd90ab4be46 100644 --- a/vulkan/vulkan_surface.h +++ b/vulkan/vulkan_surface.h @@ -5,6 +5,8 @@ #ifndef FLUTTER_VULKAN_VULKAN_SURFACE_H_ #define FLUTTER_VULKAN_VULKAN_SURFACE_H_ +#include + #include "flutter/fml/macros.h" #include "flutter/vulkan/procs/vulkan_handle.h" #include "third_party/skia/include/core/SkSize.h" diff --git a/web_sdk/BUILD.gn b/web_sdk/BUILD.gn index 95e6bcd16bc78..704c1aa7e02a8 100644 --- a/web_sdk/BUILD.gn +++ b/web_sdk/BUILD.gn @@ -125,11 +125,12 @@ template("_dartdevc") { if (is_win) { ext = ".exe" } - dart = rebase_path("$host_prebuilt_dart_sdk/bin/dart$ext", root_out_dir) + dartaot = rebase_path("$host_prebuilt_dart_sdk/bin/dartaotruntime$ext", + root_out_dir) dartdevc = rebase_path( - "$host_prebuilt_dart_sdk/bin/snapshots/dartdevc.dart.snapshot") + "$host_prebuilt_dart_sdk/bin/snapshots/dartdevc_aot.dart.snapshot") args = [ - dart, + dartaot, dartdevc, ] + invoker.args } @@ -493,6 +494,7 @@ if (!is_fuchsia) { "//flutter/third_party/canvaskit:canvaskit_chromium_group", "//flutter/third_party/canvaskit:canvaskit_group", "//flutter/third_party/canvaskit:skwasm_group", + "//flutter/third_party/canvaskit:skwasm_st_group", ] } deps += [ "//flutter/lib/web_ui/flutter_js" ] @@ -525,7 +527,9 @@ if (!is_fuchsia) { "$root_out_dir/flutter_web_sdk/canvaskit/skwasm.js", "$root_out_dir/flutter_web_sdk/canvaskit/skwasm.js.symbols", "$root_out_dir/flutter_web_sdk/canvaskit/skwasm.wasm", - "$root_out_dir/flutter_web_sdk/canvaskit/skwasm.worker.js", + "$root_out_dir/flutter_web_sdk/canvaskit/skwasm_st.js", + "$root_out_dir/flutter_web_sdk/canvaskit/skwasm_st.js.symbols", + "$root_out_dir/flutter_web_sdk/canvaskit/skwasm_st.wasm", ] } diff --git a/web_sdk/pubspec.yaml b/web_sdk/pubspec.yaml index 3908fe252eeb8..2d5ef8e63cd42 100644 --- a/web_sdk/pubspec.yaml +++ b/web_sdk/pubspec.yaml @@ -2,10 +2,10 @@ name: web_sdk_tests # Keep the SDK version range in sync with lib/web_ui/pubspec.yaml environment: - sdk: '>=3.6.0-0 <4.0.0' + sdk: ^3.7.0-0 dependencies: - args: 2.3.1 + args: any # see dependency_overrides path: any # see dependency_overrides dev_dependencies: @@ -22,14 +22,16 @@ dependency_overrides: # Must include all transitive dependencies from the "any" path: ../third_party/dart/pkg/_macros analyzer: path: ../third_party/dart/pkg/analyzer + args: + path: ../third_party/dart/third_party/pkg/core/pkgs/args async: - path: ../third_party/dart/third_party/pkg/async + path: ../third_party/dart/third_party/pkg/core/pkgs/async collection: - path: ../third_party/dart/third_party/pkg/collection + path: ../third_party/dart/third_party/pkg/core/pkgs/collection convert: - path: ../third_party/dart/third_party/pkg/convert + path: ../third_party/dart/third_party/pkg/core/pkgs/convert crypto: - path: ../third_party/dart/third_party/pkg/crypto + path: ../third_party/dart/third_party/pkg/core/pkgs/crypto dart_internal: path: ../third_party/dart/pkg/dart_internal file: @@ -43,7 +45,7 @@ dependency_overrides: # Must include all transitive dependencies from the "any" package_config: path: ../third_party/dart/third_party/pkg/package_config path: - path: ../third_party/dart/third_party/pkg/path + path: ../third_party/dart/third_party/pkg/core/pkgs/path pub_semver: path: ../third_party/dart/third_party/pkg/pub_semver source_span: @@ -53,7 +55,7 @@ dependency_overrides: # Must include all transitive dependencies from the "any" term_glyph: path: ../third_party/dart/third_party/pkg/term_glyph typed_data: - path: ../third_party/dart/third_party/pkg/typed_data + path: ../third_party/dart/third_party/pkg/core/pkgs/typed_data watcher: path: ../third_party/dart/third_party/pkg/watcher yaml: diff --git a/web_sdk/web_engine_tester/pubspec.yaml b/web_sdk/web_engine_tester/pubspec.yaml index 875fe7984cb2e..7b86604a1fb34 100644 --- a/web_sdk/web_engine_tester/pubspec.yaml +++ b/web_sdk/web_engine_tester/pubspec.yaml @@ -2,7 +2,7 @@ name: web_engine_tester # Keep the SDK version range in sync with lib/web_ui/pubspec.yaml environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 dependencies: js: ^0.7.0 diff --git a/web_sdk/web_test_utils/pubspec.yaml b/web_sdk/web_test_utils/pubspec.yaml index 0cd10a68cb915..245582bc9db72 100644 --- a/web_sdk/web_test_utils/pubspec.yaml +++ b/web_sdk/web_test_utils/pubspec.yaml @@ -2,7 +2,7 @@ name: web_test_utils # Keep the SDK version range in sync with lib/web_ui/pubspec.yaml environment: - sdk: '>=3.2.0-0 <4.0.0' + sdk: ^3.7.0-0 dependencies: collection: 1.17.0